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:
Gergely Hegedus 2023-03-27 19:07:29 +03:00
parent 0a71a6c840
commit 1f06c40c4c
31 changed files with 516 additions and 762 deletions

View file

@ -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

View file

@ -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)
})

View file

@ -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,

View file

@ -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

View 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])

View file

@ -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,

View file

@ -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__":

View 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

View file

@ -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)

View file

@ -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,

View file

@ -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'))