flask server

This commit is contained in:
Gergely Hegedus 2023-02-01 23:15:18 +02:00
parent 5acb2992ce
commit 0a71a6c840
54 changed files with 5876 additions and 0 deletions

View file

@ -0,0 +1,33 @@
from .db import get_db
from .data_models import DataError
from sqlite3 import IntegrityError
_INSER_METADATA_SQL = "INSERT INTO file_metadata(file_key,metadata) "\
"VALUES(:file_key, :metadata)"
_DELETE_METADATA_SQL = "DELETE FROM file_metadata WHERE file_key=:file_key"
def insert_metadata(metadata: dict):
db = get_db()
db_cursor = db.cursor()
delete_params = map(lambda key: {'file_key':key}, metadata.keys())
delete_params = list(delete_params)
db_cursor.executemany(_DELETE_METADATA_SQL, delete_params)
insert_params = map(lambda item: {'file_key':item[0], 'metadata':item[1]}, metadata.items())
insert_params = list(insert_params)
db_cursor.executemany(_INSER_METADATA_SQL, insert_params)
db.commit()
def get_metadata(file_key: str):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("SELECT metadata FROM file_metadata WHERE file_key=:file_key",{"file_key":file_key})
rows = db_cursor.fetchone()
if (rows is None):
return dict()
converted_tuple_array = map(lambda metadata: (file_key, metadata), rows)
return dict(converted_tuple_array)

View file

@ -0,0 +1,30 @@
from .db import get_db
from .data_models import DataError
from sqlite3 import IntegrityError
_INSER_METADATA_SQL = "INSERT INTO file_metadata_of_user(user_id,file_key,metadata) "\
"VALUES(:user_id, :file_key, :metadata)"
_DELETE_METADATA_SQL = "DELETE FROM file_metadata_of_user WHERE user_id=:user_id AND file_key=:file_key"
def insert_metadata(user_id: str, metadata: dict):
db = get_db()
db_cursor = db.cursor()
delete_params = map(lambda key: {'user_id':user_id, 'file_key':key}, metadata.keys())
delete_params = list(delete_params)
db_cursor.executemany(_DELETE_METADATA_SQL, delete_params)
insert_params = map(lambda item: {'user_id':user_id, 'file_key':item[0], 'metadata':item[1]}, metadata.items())
insert_params = list(insert_params)
db_cursor.executemany(_INSER_METADATA_SQL, insert_params)
db.commit()
def get_metadata(user_id: str):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("SELECT file_key, metadata FROM file_metadata_of_user WHERE user_id=:user_id",{"user_id":user_id})
rows = db_cursor.fetchall()
converted_tuple_array = map(lambda row: (row['file_key'], row['metadata']), rows)
return dict(converted_tuple_array)

View file

@ -0,0 +1,34 @@
from .db import get_db
from .data_models import DataError
from sqlite3 import IntegrityError
def is_valid_token(token):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("SELECT COUNT(*) FROM registration_token where token = :token", {"token": token})
rows = db_cursor.fetchone()
return rows[0] == 1
def insert_token(token):
db = get_db()
db_cursor = db.cursor()
try:
db_cursor.execute("INSERT INTO registration_token(token) VALUES(:token)", {"token": token})
except IntegrityError as e:
return DataError.REGISTRATION_CODE_ALREADY_EXISTS
db.commit()
def delete_token(token):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("DELETE FROM registration_token WHERE token=:token", {"token": token})
db.commit()
def get_tokens():
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("SELECT * FROM registration_token")
rows = db_cursor.fetchall()
return list(map(lambda row: row['token'],rows))

View file

@ -0,0 +1,34 @@
from .db import get_db
import time
_DELETE_EXPIRED_SESSION_SQL = "DELETE FROM reset_password_token where expires_at <= :time"
def _delete_expired_tokens(db):
db_cursor = db.cursor()
db_cursor.execute(_DELETE_EXPIRED_SESSION_SQL, {"time": time.time()})
db.commit()
def is_valid_token(token, username):
db = get_db()
_delete_expired_tokens(db)
db_cursor = db.cursor()
db_cursor.execute("SELECT COUNT(*) FROM reset_password_token where token = :token AND username = :username", {"token": token, "username": username})
rows = db_cursor.fetchone()
return rows[0] == 1
def insert_token(token, username, expires_at):
db = get_db()
db_cursor = db.cursor()
params = {
"token": token,
"username": username,
"expires_at": expires_at
}
db_cursor.execute("INSERT INTO reset_password_token(token, username, expires_at) VALUES(:token, :username, :expires_at)", params)
db.commit()
def delete_tokens(username):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("DELETE FROM reset_password_token WHERE username=:username", {"username": username})
db.commit()

View file

@ -0,0 +1,78 @@
from .db import get_db
from .data_models import Session
import time
_DELETE_EXPIRED_SESSION_SQL = "DELETE FROM session where refresh_expires_at <= :time"
def _delete_expired_tokens(db):
db_cursor = db.cursor()
db_cursor.execute(_DELETE_EXPIRED_SESSION_SQL, {"time": time.time()})
db.commit()
_GET_USER_FOR_ACCESS_TOKEN_SQL = "SELECT user_id FROM session where access_token = :token and access_expires_at >= :time"
def get_user_for_token(access_token: str):
db = get_db()
_delete_expired_tokens(db)
db_cursor = db.cursor()
db_cursor.execute(_GET_USER_FOR_ACCESS_TOKEN_SQL, {"token": access_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, refresh_token, access_expires_at, refresh_expires_at)"\
"VALUES(:user_id, :access_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,
"refresh_token": session.refresh_token,
"access_expires_at": session.access_expires_at,
"refresh_expires_at": session.refresh_expires_at,
}
db_cursor.execute(_INSER_SESSION_SQL, params)
def insert_user_session(session: Session):
db = get_db()
db_cursor = db.cursor()
_session_insert(db_cursor, session)
db.commit()
def delete_user_session(access_token: str):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("DELETE FROM session WHERE access_token = :token", {"token": access_token})
db.commit()
def delete_all_user_session_by_user_id(user_id: int):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("DELETE FROM session WHERE user_id = :id", {"id": user_id})
db.commit()
def create_new_single_session(session: Session):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("DELETE FROM session where user_id = :id", {"id": session.user_id})
_session_insert(db_cursor, session)
db.commit()
def get_user_for_refresh_token(refresh_token: str):
db = get_db()
_delete_expired_tokens(db)
db_cursor = db.cursor()
db_cursor.execute("SELECT user_id FROM session where refresh_token = :token", {"token": refresh_token})
rows = db_cursor.fetchall()
if (len(rows) == 1):
return rows[0][0]
return None
def swap_refresh_session(refresh_token: str, session: Session):
db = get_db()
_delete_expired_tokens(db)
db_cursor = db.cursor()
db_cursor.execute("DELETE FROM session WHERE refresh_token = :token", {"token": refresh_token})
_session_insert(db_cursor, session)
db.commit()

View file

@ -0,0 +1,107 @@
from .data_models import User
from .data_models import RegisteringUser
from .data_models import DataError
from .db import get_db
from sqlite3 import IntegrityError
from passlib.hash import sha256_crypt
def _user_from_row(row):
return User(
id = row['id'],
name = row['username'],
otp_secret = row['otp_secret'],
was_otp_verified = row['was_otp_verified'] != 0,
privileged = row['privileged'] != 0,
)
def get_user_by_id(user_id: int):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("SELECT * FROM user where id = :user_id", {"user_id": user_id})
row = db_cursor.fetchone()
if (row is None):
return None
return _user_from_row(row)
def get_user_by_name(username: str):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("SELECT * FROM user where username = :name", {"name": username})
row = db_cursor.fetchone()
if (row is None):
return None
return _user_from_row(row)
def get_user_by_name_and_password(user_name: str, password: str):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("SELECT * FROM user where username = :user_name", {"user_name": user_name})
row = db_cursor.fetchone()
if (row is None):
sha256_crypt.hash('') # do hashing even if no user is found
return None
is_password_wrong = not sha256_crypt.verify(password, row['password'] or '')
if is_password_wrong:
return None
return _user_from_row(row)
def get_users():
db = get_db()
db_cursor = db.cursor()
db_cursor.execute("SELECT * FROM user")
rows = db_cursor.fetchall()
return map(_user_from_row, rows)
_INSER_USER_SQL = "INSERT INTO user(username, password, otp_secret, privileged, was_otp_verified)"\
"VALUES(:name, :pass, :otp, :privileged, :otp_verified)"
def insert_user(user: RegisteringUser):
db = get_db()
db_cursor = db.cursor()
hashed_password = sha256_crypt.hash(user.password)
params = {
"name": user.name,
"pass": hashed_password,
"otp": user.otp_secret,
"privileged": 1 if user.privileged else 0,
"otp_verified": 1 if user.was_otp_verified else 0,
}
try:
db_cursor.execute(_INSER_USER_SQL, params)
except IntegrityError as e:
return DataError.USER_NAME_NOT_VALID
db.commit()
return db_cursor.lastrowid
def update_user_privilige(user_id: int, privileged: bool):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute('UPDATE user SET privileged = :privileged WHERE id=:id',{'id':user_id, 'privileged': privileged})
db.commit()
def update_user_otp_verification(user_id: int, was_otp_verified: bool):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute('UPDATE user SET was_otp_verified = :otp_verified WHERE id=:id',{'id':user_id, 'otp_verified': was_otp_verified})
db.commit()
def update_user_password(user_id: int, new_password: str):
hashed_password = sha256_crypt.hash(new_password)
db = get_db()
db_cursor = db.cursor()
db_cursor.execute('UPDATE user SET password = :pass WHERE id=:id',{'id':user_id, 'pass': hashed_password})
db.commit()
def delete_user_by_id(user_id: int):
db = get_db()
db_cursor = db.cursor()
db_cursor.execute('DELETE FROM user WHERE id=:id',{'id':user_id})
db.commit()

View file

@ -0,0 +1,106 @@
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):
self.user_id = user_id
self.access_token = access_token
self.refresh_token = refresh_token
self.access_expires_at = access_expires_at
self.refresh_expires_at = refresh_expires_at
def __eq__(self, other):
if not isinstance(other, Session):
return False
return self.user_id == other.user_id \
and self.access_token == other.access_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)
def __repr__(self):
return self.__str__()
class RegisteringUser:
def __init__(self, name, password, otp_secret, privileged = False, was_otp_verified = False):
self.name = name
self.password = password
self.otp_secret = otp_secret
self.privileged = privileged
self.was_otp_verified = was_otp_verified
def __eq__(self, other):
if not isinstance(other, User):
return False
return self.name == other.name \
and self.password == other.password \
and self.otp_secret == other.otp_secret \
and self.privileged == other.privileged \
and self.was_otp_verified == other.was_otp_verified \
def __str__(self):
return 'User(name={},pass={},otp={},privileged={},otp_active={})'\
.format(self.name, self.password, self.otp_secret, self.privileged, self.was_otp_verified)
def __repr__(self):
return self.__str__()
class User:
def __init__(self, id, name, otp_secret, privileged, was_otp_verified):
self.id = id
self.name = name
self.otp_secret = otp_secret
self.privileged = privileged
self.was_otp_verified = was_otp_verified
def __eq__(self, other):
if not isinstance(other, User):
return False
return self.id == other.id \
and self.name == other.name \
and self.otp_secret == other.otp_secret \
and self.privileged == other.privileged \
and self.was_otp_verified == other.was_otp_verified \
def __str__(self):
return 'User(id={},name={},otp={},privileged={},otp_active={})'\
.format(self.id, self.name, self.otp_secret, self.privileged, self.was_otp_verified)
def __repr__(self):
return self.__str__()
class DataError(Enum):
USER_NAME_NOT_VALID = 1
REGISTRATION_CODE_ALREADY_EXISTS = 2
class ResponseCode(IntEnum):
SUCCESS_FOUND_USER = 200
SUCCESS_SAVED_PASSWORD = 201
SUCCESS_SAVED_USER_FILE_METADATA = 202
SUCCESS_SAVED_FILE_METADATA = 203
SUCCESS_SAVED_REGISTRATION_TOKEN = 205
SUCCESS_DELETED_USER = 206
SUCCESS_RESET_OTP_VERIFICATION = 207
SUCCESS_SAVED_RESET_PASSWORD_TOKEN = 208
SUCCESS_DELETED_TOKEN = 209
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
INVALID_PASSWORD = 421
INVALID_NEW_PASSWORD = 422
UNKNOWN_REGISTRATION_TOKEN = 430
INVALID_OTP = 431
INVALID_REFRESH_TOKEN = 450
INVALID_RESET_PASSWORD_TOKEN = 459
INVALID_REGISTRATION_TOKEN = 460
UNKNOWN_RESET_PASSWORD_TOKEN = 461

View file

@ -0,0 +1,82 @@
import sqlite3
from os import path
import argparse
from flask import current_app, g
from passlib.hash import sha256_crypt
default_database_name = "sqlitedb"
def get_db():
current_app.config.get('DATABASE_PATH')
if 'db' not in g:
db_path = current_app.config.get('DATABASE_PATH')
if (db_path is None):
db_path = path.join(current_app.instance_path, current_app.config['DATABASE_NAME'])
g.db = sqlite3.connect(db_path, detect_types=sqlite3.PARSE_DECLTYPES)
g.db.row_factory = sqlite3.Row
return g.db
def close_db(e=None):
db = g.pop('db', None)
if db is not None:
db.close()
def init_db(db_path = None, schema_path = None):
if db_path is None:
db = get_db()
else:
db = sqlite3.connect(db_path, detect_types=sqlite3.PARSE_DECLTYPES)
if schema_path is None:
with current_app.open_resource('data/schema.sql') as f:
script = f.read().decode('UTF-8')
else:
with open(schema_path, "r") as f:
script = f.read()
db.executescript(script)
db.commit()
db.close()
def init_app(app):
app.teardown_appcontext(close_db)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="DB Init ArgumentParser", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-u", "--username", type=str, help="username of adming user", required=True)
parser.add_argument("-p", "--password", type=str, help="password of adming user", required=True)
parser.add_argument("-s", "--otp-secret", type=str, help="otp secret of admin user, python pyotp.random_base32()", required=False)
args = parser.parse_args()
config = vars(args)
username = config['username']
password = config['password']
otp_secret = config['otp_secret']
if (otp_secret is None):
import pyotp
otp_secret = pyotp.random_base32()
db_path = path.join('/server/instance/', default_database_name)
if path.exists(db_path):
print('Database already exists at {}. Will NOT override, if necessary first delete first then restart initialization!'.format(db_path))
exit(1)
schema_path = path.join(path.dirname(__file__), 'schema.sql')
init_db(db_path = db_path, schema_path = schema_path)
db = sqlite3.connect(db_path, detect_types=sqlite3.PARSE_DECLTYPES)
db.row_factory = sqlite3.Row
db_cursor = db.cursor()
hashed_password = sha256_crypt.hash(password)
sql = "INSERT INTO user(username, password, otp_secret, privileged, was_otp_verified)"\
"VALUES(:name, :pass, :otp, :privileged, :otp_verified)"
params = {
"name": username,
"pass": hashed_password,
"otp": otp_secret,
"privileged": True,
"otp_verified": False,
}
db_cursor.execute(sql, params)
db.commit()
db.close()

View file

@ -0,0 +1,47 @@
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS registration_token;
DROP TABLE IF EXISTS reset_password_token;
DROP TABLE IF EXISTS session;
DROP TABLE IF EXISTS file_metadata_of_user;
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
otp_secret TEXT NOT NULL,
privileged INTEGER NOT NULL,
was_otp_verified INTEGER NOT NULL
);
CREATE TABLE registration_token (
token TEXT PRIMARY KEY NOT NULL
);
CREATE TABLE reset_password_token (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT NOT NULL,
username TEXT NOT NULL,
expires_at INTEGER NOT NULL
);
CREATE TABLE session (
user_id INTEGER NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT NOT NULL,
access_expires_at INTEGER NOT NULL,
refresh_expires_at INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES user (id)
);
CREATE TABLE file_metadata_of_user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
file_key TEXT NOT NULL,
metadata TEXT NOT NULL
);
CREATE TABLE file_metadata (
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_key TEXT NOT NULL,
metadata TEXT NOT NULL
);