Add Media-Token beside access token
Media-Token can be used only to access the content, but unable to modify user data
This commit is contained in:
parent
0a71a6c840
commit
1f06c40c4c
31 changed files with 516 additions and 762 deletions
|
|
@ -1,7 +1,6 @@
|
|||
from flask import request, jsonify
|
||||
from .require_decorators import get_cropped_username
|
||||
from .require_decorators import get_cropped_otp
|
||||
from .require_decorators import get_cropped_token
|
||||
from . import token_generator_util
|
||||
from .data import dao_registration_tokens
|
||||
from .data import dao_reset_password_tokens
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from .data.data_models import DataError
|
|||
from .data.data_models import RegisteringUser
|
||||
from .data.data_models import User
|
||||
from .data.data_models import ResponseCode
|
||||
from .data.data_models import Session
|
||||
from math import trunc
|
||||
|
||||
def handle_register(username, password):
|
||||
one_time_password = get_cropped_otp(request.form.get('otp') or '')
|
||||
|
|
@ -42,12 +44,7 @@ def handle_otp_verification(user: User):
|
|||
dao_users.update_user_otp_verification(user.id, True)
|
||||
session = token_generator_util.generate_session(user.id)
|
||||
dao_session.insert_user_session(session)
|
||||
sessionResponse = jsonify({
|
||||
'access_token': session.access_token,
|
||||
'refresh_token': session.refresh_token,
|
||||
'expires_at': session.access_expires_at
|
||||
})
|
||||
return sessionResponse, 200
|
||||
return _jsonify_session(session), 200
|
||||
else:
|
||||
errorResponse = jsonify({'message':'Invalid Token!','code':ResponseCode.INVALID_OTP})
|
||||
return errorResponse, 400
|
||||
|
|
@ -74,9 +71,12 @@ def handle_refresh_token():
|
|||
new_session = token_generator_util.generate_session(user_id)
|
||||
dao_session.swap_refresh_session(refresh_token = refresh_token, session = new_session)
|
||||
|
||||
sessionResponse = jsonify({
|
||||
'access_token': new_session.access_token,
|
||||
'refresh_token': new_session.refresh_token,
|
||||
'expires_at': new_session.access_expires_at
|
||||
})
|
||||
return sessionResponse, 200
|
||||
return _jsonify_session(new_session), 200
|
||||
|
||||
def _jsonify_session(session: Session):
|
||||
return jsonify({
|
||||
'access_token': session.access_token,
|
||||
'media_token': session.media_token,
|
||||
'refresh_token': session.refresh_token,
|
||||
'expires_at': trunc(session.access_expires_at)
|
||||
})
|
||||
|
|
@ -20,13 +20,26 @@ def get_user_for_token(access_token: str):
|
|||
return rows[0][0]
|
||||
return None
|
||||
|
||||
_INSER_SESSION_SQL = "INSERT INTO session(user_id, access_token, refresh_token, access_expires_at, refresh_expires_at)"\
|
||||
"VALUES(:user_id, :access_token, :refresh_token, :access_expires_at, :refresh_expires_at)"
|
||||
_GET_USER_FOR_MEDIA_TOKEN_SQL = "SELECT user_id FROM session where media_token = :token and access_expires_at >= :time"
|
||||
def get_user_for_media_token(media_token: str):
|
||||
db = get_db()
|
||||
_delete_expired_tokens(db)
|
||||
db_cursor = db.cursor()
|
||||
db_cursor.execute(_GET_USER_FOR_MEDIA_TOKEN_SQL, {"token": media_token, "time": time.time()})
|
||||
rows = db_cursor.fetchall()
|
||||
|
||||
if (len(rows) == 1):
|
||||
return rows[0][0]
|
||||
return None
|
||||
|
||||
_INSER_SESSION_SQL = "INSERT INTO session(user_id, access_token, media_token, refresh_token, access_expires_at, refresh_expires_at)"\
|
||||
"VALUES(:user_id, :access_token, :media_token, :refresh_token, :access_expires_at, :refresh_expires_at)"
|
||||
|
||||
def _session_insert(db_cursor, session: Session):
|
||||
params = {
|
||||
"user_id": session.user_id,
|
||||
"access_token": session.access_token,
|
||||
"media_token": session.media_token,
|
||||
"refresh_token": session.refresh_token,
|
||||
"access_expires_at": session.access_expires_at,
|
||||
"refresh_expires_at": session.refresh_expires_at,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ from enum import Enum
|
|||
from enum import IntEnum
|
||||
|
||||
class Session:
|
||||
def __init__(self, user_id, access_token, refresh_token, access_expires_at, refresh_expires_at):
|
||||
def __init__(self, user_id, access_token, media_token, refresh_token, access_expires_at, refresh_expires_at):
|
||||
self.user_id = user_id
|
||||
self.access_token = access_token
|
||||
self.media_token = media_token
|
||||
self.refresh_token = refresh_token
|
||||
self.access_expires_at = access_expires_at
|
||||
self.refresh_expires_at = refresh_expires_at
|
||||
|
|
@ -14,12 +15,13 @@ class Session:
|
|||
return False
|
||||
return self.user_id == other.user_id \
|
||||
and self.access_token == other.access_token \
|
||||
and self.media_token == other.media_token \
|
||||
and self.refresh_token == other.refresh_token \
|
||||
and self.access_expires_at == other.access_expires_at \
|
||||
and self.refresh_expires_at == other.refresh_expires_at \
|
||||
|
||||
def __str__(self):
|
||||
return 'Session(user_id={},access_token={},refresh_token={},access_expires_at={},refresh_expires_at={})'.format(self.user_id, self.access_token, self.refresh_token, self.access_expires_at, self.refresh_expires_at)
|
||||
return 'Session(user_id={},access_token={},media_token={},refresh_token={},access_expires_at={},refresh_expires_at={})'.format(self.user_id, self.access_token, self.media_token, self.refresh_token, self.access_expires_at, self.refresh_expires_at)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
|
@ -88,18 +90,25 @@ class ResponseCode(IntEnum):
|
|||
SUCCESS_RESET_OTP_VERIFICATION = 207
|
||||
SUCCESS_SAVED_RESET_PASSWORD_TOKEN = 208
|
||||
SUCCESS_DELETED_TOKEN = 209
|
||||
SUCCESS_MEDIA_ACCESS = 220
|
||||
|
||||
|
||||
EMPTY_USERNAME = 410
|
||||
ALREADY_TAKEN_USERNAME = 411
|
||||
NOT_FOUND_USER = 412
|
||||
INVALID_USERNAME_TO_EDIT = 413
|
||||
CANT_SAVE_USER_FILE_METADATA = 414
|
||||
CANT_SAVE_FILE_METADATA = 415
|
||||
INVALID_FILE_KEY = 416
|
||||
EMPTY_PASSWORD = 420
|
||||
INVALID_PASSWORD = 421
|
||||
INVALID_NEW_PASSWORD = 422
|
||||
UNKNOWN_REGISTRATION_TOKEN = 430
|
||||
INVALID_OTP = 431
|
||||
MISSING_AUTHORIZATION = 440
|
||||
INVALID_AUTHORIZATION = 441
|
||||
UNASSOCIATED_AUTHORIZATION = 442
|
||||
MISSING_MEDIA_AUTHORIZATION = 443
|
||||
INVALID_MEDIA_AUTHORIZATION = 444
|
||||
INVALID_REFRESH_TOKEN = 450
|
||||
INVALID_RESET_PASSWORD_TOKEN = 459
|
||||
INVALID_REGISTRATION_TOKEN = 460
|
||||
|
|
|
|||
13
server/flask/application/backend/data/migration.py
Normal file
13
server/flask/application/backend/data/migration.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import sqlite3
|
||||
import sys
|
||||
|
||||
def migration_0_to_1(db_path: str):
|
||||
db = sqlite3.connect(db_path, detect_types=sqlite3.PARSE_DECLTYPES)
|
||||
db_cursor = db.cursor()
|
||||
db_cursor.execute("ALTER TABLE session ADD media_token TEXT NOT NULL;")
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(sys.argv[1])
|
||||
#migration_0_to_1(sys.argv[1])
|
||||
|
|
@ -27,6 +27,7 @@ CREATE TABLE reset_password_token (
|
|||
CREATE TABLE session (
|
||||
user_id INTEGER NOT NULL,
|
||||
access_token TEXT NOT NULL,
|
||||
media_token TEXT NOT NULL,
|
||||
refresh_token TEXT NOT NULL,
|
||||
access_expires_at INTEGER NOT NULL,
|
||||
refresh_expires_at INTEGER NOT NULL,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from .require_decorators import require_user_priviliged_after_session
|
|||
from . import auth_requests as auth_requests_handler
|
||||
from . import admin_requests as admin_requests_handler
|
||||
from . import user_action_requests as user_action_requests_handler
|
||||
from . import media_access_requests as media_access_requests_handler
|
||||
|
||||
# for chrome to accept the certificate run in console `endCommand(SecurityInterstitialCommandId.CMD_PROCEED)`
|
||||
# to restart = `uwsgi --ini home-vod-server.ini` like in Dockerimage
|
||||
|
|
@ -137,6 +138,12 @@ def create_app(test_config=None):
|
|||
return admin_requests_handler.handle_delete_registration_token(user = user)
|
||||
# endregion
|
||||
|
||||
# region media access
|
||||
@app.route("/has_media_access", methods=['GET'])
|
||||
def has_media_access():
|
||||
return media_access_requests_handler.handle_has_media_access()
|
||||
# endregion
|
||||
|
||||
return app
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
16
server/flask/application/backend/media_access_requests.py
Normal file
16
server/flask/application/backend/media_access_requests.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from flask import request, jsonify
|
||||
from .require_decorators import get_cropped_token
|
||||
from .data.data_models import ResponseCode
|
||||
from .data import dao_session
|
||||
from .data import dao_users
|
||||
|
||||
def handle_has_media_access():
|
||||
media_token = get_cropped_token(request.headers.get('Media-Authorization'))
|
||||
if (media_token is None):
|
||||
errorResponse = jsonify({'message':'Missing Authorization!','code':ResponseCode.MISSING_MEDIA_AUTHORIZATION})
|
||||
return errorResponse, 401
|
||||
user_id = dao_session.get_user_for_media_token(media_token=media_token)
|
||||
if (user_id is None):
|
||||
errorResponse = jsonify({'message':'Invalid Authorization!','code':ResponseCode.INVALID_MEDIA_AUTHORIZATION})
|
||||
return errorResponse, 401
|
||||
return jsonify({'message':'Access Granted','code': ResponseCode.SUCCESS_MEDIA_ACCESS}), 200
|
||||
|
|
@ -4,6 +4,7 @@ from . import token_generator_util
|
|||
from .data import dao_users
|
||||
from .data.data_models import User
|
||||
from .data import dao_session
|
||||
from .data.data_models import ResponseCode
|
||||
|
||||
def get_cropped_username(username: str):
|
||||
max_length = current_app.config['MAX_USERNAME_LENGTH']
|
||||
|
|
@ -45,12 +46,12 @@ def require_username_and_password(request_processor):
|
|||
def do_require_username_and_password():
|
||||
username = request.form.get('username')
|
||||
if username is None:
|
||||
errorResponse = jsonify({'message':'Username cannot be empty!','code':410})
|
||||
errorResponse = jsonify({'message':'Username cannot be empty!','code':ResponseCode.EMPTY_USERNAME})
|
||||
return errorResponse, 400
|
||||
|
||||
password = request.form.get('password')
|
||||
if password is None:
|
||||
errorResponse = jsonify({'message':'Password cannot be empty!','code':420})
|
||||
errorResponse = jsonify({'message':'Password cannot be empty!','code':ResponseCode.EMPTY_PASSWORD})
|
||||
return errorResponse, 400
|
||||
return request_processor(get_cropped_username(username), get_cropped_password(password))
|
||||
return do_require_username_and_password
|
||||
|
|
@ -60,7 +61,7 @@ def require_user_exists_by_username_and_password(request_processor):
|
|||
def do_require_user(username, password):
|
||||
user = dao_users.get_user_by_name_and_password(user_name = username, password = password)
|
||||
if user is None:
|
||||
errorResponse = jsonify({'message':'User cannot be found!','code':412})
|
||||
errorResponse = jsonify({'message':'User cannot be found!','code':ResponseCode.NOT_FOUND_USER})
|
||||
return errorResponse, 400
|
||||
return request_processor(user)
|
||||
return do_require_user
|
||||
|
|
@ -70,17 +71,17 @@ def requires_session(request_processor):
|
|||
def do_require_session():
|
||||
access_token = get_cropped_token(request.headers.get('Authorization'))
|
||||
if (access_token is None):
|
||||
errorResponse = jsonify({'message':'Missing Authorization!','code':440})
|
||||
errorResponse = jsonify({'message':'Missing Authorization!','code':ResponseCode.MISSING_AUTHORIZATION})
|
||||
return errorResponse, 401
|
||||
|
||||
user_id = dao_session.get_user_for_token(access_token)
|
||||
if (user_id is None):
|
||||
errorResponse = jsonify({'message':'Invalid Authorization!','code':441})
|
||||
errorResponse = jsonify({'message':'Invalid Authorization!','code':ResponseCode.INVALID_AUTHORIZATION})
|
||||
return errorResponse, 401
|
||||
|
||||
user = dao_users.get_user_by_id(user_id)
|
||||
if user is None:
|
||||
errorResponse = jsonify({'message':'Invalid Authorization!','code':442})
|
||||
errorResponse = jsonify({'message':'Invalid Authorization!','code':ResponseCode.UNASSOCIATED_AUTHORIZATION})
|
||||
return errorResponse, 400
|
||||
return request_processor(user)
|
||||
return do_require_session
|
||||
|
|
@ -91,7 +92,7 @@ def require_otp_verification_after_session(request_processor):
|
|||
one_time_password = get_cropped_otp(request.form.get('otp') or '')
|
||||
is_otp_ok = token_generator_util.verify_otp(user.otp_secret, one_time_password)
|
||||
if not is_otp_ok:
|
||||
errorResponse = jsonify({'message':'Invalid Token!','code':431})
|
||||
errorResponse = jsonify({'message':'Invalid Token!','code':ResponseCode.INVALID_OTP})
|
||||
return errorResponse, 400
|
||||
|
||||
return request_processor(user)
|
||||
|
|
@ -101,7 +102,7 @@ def require_user_priviliged_after_session(request_processor):
|
|||
@functools.wraps(request_processor)
|
||||
def do_require_user_priviliged(user: User):
|
||||
if not user.privileged:
|
||||
errorResponse = jsonify({'message':'Not Authorized!','code':460})
|
||||
errorResponse = jsonify({'message':'Not Authorized!','code':ResponseCode.INVALID_REGISTRATION_TOKEN})
|
||||
return errorResponse, 400
|
||||
return request_processor(user)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ def generate_session(user_id, byte_count = None, access_expires_in = None, refre
|
|||
return Session(
|
||||
user_id = user_id,
|
||||
access_token = token_urlsafe(byte_count),
|
||||
media_token = token_urlsafe(byte_count),
|
||||
refresh_token = token_urlsafe(byte_count),
|
||||
access_expires_at = access_expires_in + current_time,
|
||||
refresh_expires_at = refresh_expires_in + current_time,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from flask import request, jsonify
|
||||
import json
|
||||
from .require_decorators import get_cropped_password
|
||||
from .require_decorators import get_cropped_otp
|
||||
from .require_decorators import get_cropped_key
|
||||
|
|
@ -11,6 +10,7 @@ from .data import dao_file_metadata_of_user
|
|||
from .data import dao_file_metadata
|
||||
from .data.data_models import User
|
||||
from .data.data_models import ResponseCode
|
||||
from .auth_requests import _jsonify_session as jsonify_session
|
||||
|
||||
def handle_change_password(user: User):
|
||||
password = get_cropped_password(request.form.get('password'))
|
||||
|
|
@ -31,12 +31,7 @@ def handle_change_password(user: User):
|
|||
session = token_generator_util.generate_session(user.id)
|
||||
dao_users.update_user_password(user_id = user.id, new_password = new_password)
|
||||
dao_session.create_new_single_session(session = session)
|
||||
sessionResponse = jsonify({
|
||||
'access_token': session.access_token,
|
||||
'refresh_token': session.refresh_token,
|
||||
'expires_at': session.access_expires_at
|
||||
})
|
||||
return sessionResponse, 200
|
||||
return jsonify_session(session), 200
|
||||
|
||||
def handle_reset_password(username: str, password: str):
|
||||
reset_password_token = get_cropped_otp(request.form.get('reset_password_token'))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue