diff --git a/delivery1/server/app.py b/delivery1/server/app.py index cb701d8..fc669fb 100644 --- a/delivery1/server/app.py +++ b/delivery1/server/app.py @@ -1,3 +1,4 @@ +import os import sqlalchemy.exc from flask import Flask, request, jsonify from routes import org_bp, user_bp, file_bp @@ -37,6 +38,12 @@ def reset(): with app.app_context(): db_connection.drop_all() db_connection.create_all() + repos = os.path.join(os.path.dirname(os.path.abspath(__file__)), "repository") + for repo in os.listdir(repos): + if os.path.isdir(os.path.join(repos, repo)): + for file in os.listdir(os.path.join(repos, repo)): + os.remove(os.path.join(repos, repo, file)) + os.rmdir(os.path.join(repos, repo)) except sqlalchemy.exc.OperationalError: return jsonify({"error": "Database error"}), 500 return jsonify({"message": "Database reset"}), 200 diff --git a/delivery1/server/models/file.py b/delivery1/server/models/file.py index b99847e..8288403 100644 --- a/delivery1/server/models/file.py +++ b/delivery1/server/models/file.py @@ -5,16 +5,16 @@ class File(db_connection.Model): __tablename__ = 'files' id = db_connection.Column(db_connection.Integer, primary_key=True, index=True) - file_handle = db_connection.Column(db_connection.String, unique=True, nullable=False) - document_handle = db_connection.Column(db_connection.String, unique=True, nullable=True) + file_handle = db_connection.Column(db_connection.String, unique=True, nullable=True) + document_handle = db_connection.Column(db_connection.String, unique=True, nullable=False) name = db_connection.Column(db_connection.String, nullable=False) created_at = db_connection.Column(db_connection.Integer, nullable=False) - # alg: db_connection.Column(db_connection.String, nullable=False) - # key: db_connection.Column(db_connection.String, nullable=False) + key = db_connection.Column(db_connection.String, nullable=False) + alg = db_connection.Column(db_connection.String, nullable=False) org_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('organizations.id'), nullable=False) creator_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'), nullable=False) - org = db_connection.relationship('Organization', back_populates='files') - creator = db_connection.relationship('User', back_populates='files') + org = db_connection.relationship('Organization', backref=db_connection.backref('org_files', uselist=False)) + creator = db_connection.relationship('User', backref=db_connection.backref('created_files', uselist=False)) def to_dict(self): return { @@ -23,6 +23,8 @@ class File(db_connection.Model): "document_handle": self.document_handle, "name": self.name, "created_at": self.created_at, + "key": self.key, + "alg": self.alg, "org": {"id": self.org.id, "name": self.org.name}, "creator": {"id": self.creator.id, "username": self.creator.username} } \ No newline at end of file diff --git a/delivery1/server/models/org.py b/delivery1/server/models/org.py index 53c1adb..1a9b356 100644 --- a/delivery1/server/models/org.py +++ b/delivery1/server/models/org.py @@ -6,20 +6,19 @@ class Organization(db_connection.Model): id = db_connection.Column(db_connection.Integer, primary_key=True, index=True) name = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False) users = db_connection.Column(db_connection.JSON, nullable=False, default=dict) - owner_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id')) - owner = db_connection.relationship('User', backref=db_connection.backref('owned_organization', uselist=False)) - files = db_connection.relationship('File', back_populates='org') + users_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id')) + manager = db_connection.relationship('User', backref=db_connection.backref('owned_organization', uselist=False)) def to_dict(self): return { "id": self.id, "name": self.name, - "owner": self.owner.to_dict(), + "manager": self.manager.to_dict(), "users": [{"id": user_id, "user_data": { "username": user_data["username"], "full_name": user_data["full_name"], "email": user_data["email"], "status": user_data["status"] }} for user_id, user_data in self.users.items()], - "files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files] + # "files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files] } \ No newline at end of file diff --git a/delivery1/server/models/user.py b/delivery1/server/models/user.py index df03020..0e019eb 100644 --- a/delivery1/server/models/user.py +++ b/delivery1/server/models/user.py @@ -12,8 +12,6 @@ class User(db_connection.Model): public_keys = db_connection.Column(db_connection.JSON, nullable=False, default=dict) orgs = db_connection.Column(db_connection.JSON, nullable=False, default=dict) - files = db_connection.relationship('File', back_populates='creator') - def to_dict(self): return { "id": self.id, @@ -21,5 +19,5 @@ class User(db_connection.Model): "full_name": self.full_name, "email": self.email, "orgs": [{"id": org_id, "name": org_data["name"], "status": org_data["status"]} for org_id, org_data in self.orgs.items()], - "files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files] + # "files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files] } \ No newline at end of file diff --git a/delivery1/server/routes/file.py b/delivery1/server/routes/file.py index 7373c46..b3ab3bc 100644 --- a/delivery1/server/routes/file.py +++ b/delivery1/server/routes/file.py @@ -1,14 +1,175 @@ -from flask import Blueprint, request, jsonify -from services import FileService +from flask import Blueprint, request, jsonify, send_file, Response + +import utils +from services import FileService, OrganizationService, UserService, SessionService file_bp = Blueprint("file", __name__) +upload_service = FileService() -@file_bp.route("/get", methods=["GET"]) -def file_get(): - data = request.json - file_handle = data["file_handle"] + +@file_bp.route("/get//content", methods=["GET"]) +def file_get_content(file_handle: str): file = FileService.get_file_by_file_handle(file_handle) if not file: return jsonify({"error": "File not found"}), 404 - return jsonify(file.to_dict()), 200 + file_content = FileService.get_file_content(file) + return send_file(file_content, as_attachment=True, download_name=file.name) + + +@file_bp.route("/get//metadata", methods=["GET"]) +def file_get_metadata(document_handle: str): + session_token = request.headers.get("Authorization") + session = SessionService.validate_session(session_token) + if isinstance(session, tuple): + return session + + org = OrganizationService.get_organization(session.org_id) + if not org: + return jsonify({"error": "Organization not found"}), 404 + + file = FileService.get_file_by_document_handle(document_handle) + if not file: + return jsonify({"error": "File not found"}), 404 + + return jsonify(file.to_dict()) + + +@file_bp.route("/upload/metadata", methods=["POST"]) +def file_upload_metadata(): + session_token = request.headers.get("Authorization") + print(session_token) + session = SessionService.validate_session(session_token) + if isinstance(session, tuple): + return session + + data = request.json + if "document_name" not in data or "key" not in data or "alg" not in data: + return jsonify({"error": "Missing required fields"}), 400 + + org = OrganizationService.get_organization(session.org_id) + if not org: + return jsonify({"error": "Organization not found"}), 404 + + user = UserService.get_user(session.user_id) + if not user: + return jsonify({"error": "User not found"}), 404 + + file = upload_service.create_file(session.token, org, user, data["document_name"], data["key"], data["alg"]) + return jsonify(file.to_dict()), 201 + + +@file_bp.route("/upload/content", methods=["POST"]) +def file_upload_content(): + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 + + session = SessionService.validate_session(session_token) + if isinstance(session, tuple): + return session + + if "file" not in request.files: + return jsonify({"error": "No file data"}), 400 + + file = request.files.get("file") + if file.filename == "": + return jsonify({"error": "No file selected for uploading"}), 400 + + if not file: + return jsonify({"error": "Invalid file data"}), 400 + + file = upload_service.write_file(session_token, file.stream) + if isinstance(file, tuple): + return file + + return jsonify(file.to_dict()), 201 + + +@file_bp.route("/list", methods=["GET"]) +def file_list(): + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 + + session = SessionService.validate_session(session_token) + if isinstance(session, tuple): + return session + + + data = request.json + + org = OrganizationService.get_organization(session.org_id) + if not org: + return jsonify({"error": "Organization not found"}), 404 + + if "datetime" in data: + try: + datetime_value = int(data["datetime"]["value"]) + datetime_relation = data["datetime"]["relation"] + except ValueError: + return jsonify({"error": "Invalid datetime value"}), 400 + + if "username" in data: + user = UserService.get_user_by_username(data["username"]) + if not user: + return jsonify({"error": "User not found"}), 404 + files = FileService.list_files_in_org(org) + return jsonify([file.to_dict() for file in files if file.creator_id == user.id and ( + utils.check_valid_time(file.created_at, datetime_value, datetime_relation) + if "datetime" in data else True + )]) + + files = FileService.list_files_in_org(org) + return jsonify([file.to_dict() for file in files if (utils.check_valid_time(file.created_at, datetime_value, datetime_relation) if "datetime" in data else True)]) + + +@file_bp.route("/delete/", methods=["POST"]) +def file_delete(document_handle: str): + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 + + session = SessionService.validate_session(session_token) + if isinstance(session, tuple): + return session + + org = OrganizationService.get_organization(session.org_id) + if not org: + return jsonify({"error": "Organization not found"}), 404 + + file = FileService.get_file_by_document_handle(document_handle) + if not file: + return jsonify({"error": "File not found"}), 404 + + if file.creator_id != session.user_id: + return jsonify({"error": "Not authorized to delete file"}), 403 + + file = FileService.delete_file(file) + return jsonify(file.to_dict()) + + +################################################ + + +@file_bp.route("/create_dummy", methods=["POST"]) +def file_create_dummy(): + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 + + session = SessionService.validate_session(session_token) + + if isinstance(session, tuple): + return session + + org = OrganizationService.get_organization(session.org_id) + if not org: + return jsonify({"error": "Organization not found"}), 404 + + user = UserService.get_user(session.user_id) + if not user: + return jsonify({"error": "User not found"}), 404 + + file = FileService.create_dummy_file(org, user) + return jsonify(file.to_dict()), 201 \ No newline at end of file diff --git a/delivery1/server/routes/user.py b/delivery1/server/routes/user.py index 5c1c670..7ac7a13 100644 --- a/delivery1/server/routes/user.py +++ b/delivery1/server/routes/user.py @@ -1,13 +1,17 @@ import json +import utils from flask import Blueprint, request, jsonify from services import UserService, SessionService, OrganizationService -from utils import data_checks user_bp = Blueprint("user", __name__) @user_bp.route("/login", methods=["POST"]) def user_login(): data = request.json + + if "username" not in data or "org" not in data: + return jsonify({"error": "Missing required fields"}), 400 + user = UserService.get_user_by_username(data["username"]) if not user: return jsonify({"error": "User not found"}), 404 @@ -16,25 +20,17 @@ def user_login(): if not org: return jsonify({"error": "Organization not found"}), 404 - id_str = str(org.id) - if id_str not in user.public_keys: - return jsonify({"error": "User not associated with organization"}), 403 - - if user.public_keys[id_str] != data["public_key"]: - return jsonify({"error": "Invalid public key"}), 403 - session = SessionService.create_session(user, org) return jsonify(session.to_dict()), 201 @user_bp.route("/logout", methods=["POST"]) def user_logout(): - data = request.json - session_file = data["session_file"] - session_data = json.loads(session_file) - session_token = session_data["token"] - session = SessionService.get_session(session_token) + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 + session = SessionService.get_session(session_token) if not session: return jsonify({"error": "Not authenticated"}), 401 @@ -44,17 +40,16 @@ def user_logout(): @user_bp.route("/list", methods=["GET"]) def user_list(): - data = request.json - if "session_file" not in data: - return jsonify({"error": "No session file"}), 400 + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 - session_file = data["session_file"] - session_data = json.loads(session_file) - - session = data_checks.validate_session_file(session_data) + session = SessionService.validate_session(session_token) if isinstance(session, tuple): return session + data = request.json + org = OrganizationService.get_organization(session.org_id) if not org: return jsonify({"error": "Organization not found"}), 404 @@ -71,22 +66,23 @@ def user_list(): @user_bp.route("/create", methods=["POST"]) def user_create(): - data = request.json - if "session_file" not in data or "username" not in data or "full_name" not in data or "email" not in data or "public_key" not in data: - return jsonify({"error": "Missing required fields"}), 400 + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 - session_file = data["session_file"] - session_data = json.loads(session_file) - - session = data_checks.validate_session_file(session_data) + session = SessionService.validate_session(session_token) if isinstance(session, tuple): return session + data = request.json + if "username" not in data or "full_name" not in data or "email" not in data or "public_key" not in data: + return jsonify({"error": "Missing required fields"}), 400 + org = OrganizationService.get_organization(session.org_id) if not org: return jsonify({"error": "Organization not found"}), 404 - if org.owner.id != session.user_id: + if org.manager.id != session.user_id: return jsonify({"error": "Not authorized to create users"}), 403 user = UserService.get_user_by_username(data["username"]) @@ -102,52 +98,46 @@ def user_create(): return jsonify(user.to_dict()), 201 -@user_bp.route("/suspend", methods=["POST"]) -def user_suspend(): - data = request.json - if "session_file" not in data or "username" not in data: - return jsonify({"error": "Missing required fields"}), 400 +@user_bp.route("//suspend", methods=["POST"]) +def user_suspend(username): + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 - session_file = data["session_file"] - session_data = json.loads(session_file) - - session = data_checks.validate_session_file(session_data) + session = SessionService.validate_session(session_token) if isinstance(session, tuple): return session org = OrganizationService.get_organization(session.org_id) if not org: return jsonify({"error": "Organization not found"}), 404 - if org.owner.id != session.user_id: + if org.manager.id != session.user_id: return jsonify({"error": "Not authorized to suspend users"}), 403 - user = UserService.get_user_by_username(data["username"]) + user = UserService.get_user_by_username(username) if not user: return jsonify({"error": "User not found"}), 404 return OrganizationService.suspend_user(org, user) -@user_bp.route("/activate", methods=["POST"]) -def user_unsuspend(): - data = request.json - if "session_file" not in data or "username" not in data: - return jsonify({"error": "Missing required fields"}), 400 +@user_bp.route("//activate", methods=["POST"]) +def user_unsuspend(username): + session_token = request.headers.get("Authorization") + if not session_token: + return jsonify({"error": "No session token"}), 400 - session_file = data["session_file"] - session_data = json.loads(session_file) - - session = data_checks.validate_session_file(session_data) + session = SessionService.validate_session(session_token) if isinstance(session, tuple): return session org = OrganizationService.get_organization(session.org_id) if not org: return jsonify({"error": "Organization not found"}), 404 - if org.owner.id != session.user_id: + if org.manager.id != session.user_id: return jsonify({"error": "Not authorized to unsuspend users"}), 403 - user = UserService.get_user_by_username(data["username"]) + user = UserService.get_user_by_username(username) if not user: return jsonify({"error": "User not found"}), 404 diff --git a/delivery1/server/services/files.py b/delivery1/server/services/files.py index 83c465b..28498a1 100644 --- a/delivery1/server/services/files.py +++ b/delivery1/server/services/files.py @@ -1,12 +1,57 @@ import os +import io from datetime import datetime from typing import List, Type +from flask import jsonify from database import db from models import File, Organization, User +from utils import get_hash class FileService: + def __init__(self): + self.current_requests = {} + + def create_file(self, session_token: str, org: Organization, user: User, file_name: str, key: str, alg: str) -> File: + file = File( + file_handle = None, + document_handle = get_hash(file_name), + name = file_name, + created_at = int(datetime.now().timestamp()), + key = key, + alg = alg, + org_id = org.id, + creator_id = user.id, + org = org, + creator = user + ) + + self.current_requests[session_token] = file + + db.add(file) + db.commit() + db.refresh(file) + return file + + + def write_file(self, session_token: str, file_data: bytes) -> File | tuple: + if session_token not in self.current_requests: + return jsonify({"error": "No file upload request found"}), 400 + + file = self.current_requests[session_token] + file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.org.name, file.document_handle) + with open(file_path, "wb") as f: + f.write(file_data) + + file.file_handle = get_hash(file_data) + db.commit() + db.refresh(file) + + del self.current_requests[session_token] + return file + + @staticmethod def create_dummy_file(org: Organization, user: User) -> File: file = File( @@ -41,6 +86,12 @@ class FileService: def get_file_by_file_handle(file_handle: str) -> File | None: return db.query(File).filter(File.file_handle == file_handle).first() + @staticmethod + def get_file_content(file: File) -> io.BytesIO: + file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.org.name, file.document_handle) + with open(file_path, "rb") as f: + return io.BytesIO(f.read()) + @staticmethod def list_files() -> list[Type[File]]: return db.query(File).all() @@ -51,9 +102,9 @@ class FileService: @staticmethod def delete_file(file: File) -> File: - file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.document_handle) + file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.org.name, file.document_handle) os.remove(file_path) - file.document_handle = None + file.file_handle = None db.commit() db.refresh(file) return file \ No newline at end of file diff --git a/delivery1/server/services/orgs.py b/delivery1/server/services/orgs.py index 746b4e2..a5e7388 100644 --- a/delivery1/server/services/orgs.py +++ b/delivery1/server/services/orgs.py @@ -20,7 +20,7 @@ class OrganizationService: organization = Organization( name=name, - owner=user, + manager=user, users={user.id: { "username": user.username, "full_name": user.full_name, @@ -76,8 +76,8 @@ class OrganizationService: if OrganizationService.get_user_status(org, user.id) != "active": return {"error": "User already suspended"}, 400 - if org.owner.id == user.id: - return {"error": "Cannot suspend owner"}, 400 + if org.manager.id == user.id: + return {"error": "Cannot suspend manager"}, 400 org.users[str(user.id)]["status"] = "suspended" flag_modified(org, "users") diff --git a/delivery1/server/services/sessions.py b/delivery1/server/services/sessions.py index 9885e92..c322a1f 100644 --- a/delivery1/server/services/sessions.py +++ b/delivery1/server/services/sessions.py @@ -1,6 +1,7 @@ import secrets from database import db from models import Session, User, Organization +from flask import jsonify class SessionService: @@ -23,4 +24,25 @@ class SessionService: @staticmethod def delete_session(session: Session) -> None: db.delete(session) - db.commit() \ No newline at end of file + db.commit() + + @staticmethod + def validate_session(token: str) -> tuple | Session: + from services import OrganizationService + + if "Bearer" in token: + token = token.split(" ")[1] + + session = SessionService.get_session(token) + if not session: + return jsonify({"error": "Not authenticated"}), 401 + + org = OrganizationService.get_organization(session.org_id) + if not org: + return jsonify({"error": "Organization not found"}), 404 + + status = OrganizationService.get_user_status(org, session.user_id) + if status != "active": + return jsonify({"error": "User is not active"}), 403 + + return session diff --git a/delivery1/server/tests/file_management.http b/delivery1/server/tests/file_management.http new file mode 100644 index 0000000..167b431 --- /dev/null +++ b/delivery1/server/tests/file_management.http @@ -0,0 +1,106 @@ +### Reset database +POST http://localhost:5000/reset +Content-Type: application/json + +{ + "password": "123" +} + +### Create a new organization +POST http://localhost:5000/org/create +Content-Type: application/json + +{ + "name": "org", + "username": "username", + "full_name": "Full Name", + "email": "user@mail.com", + "public_key": "null" +} + +### Login +POST http://localhost:5000/user/login +Content-Type: application/json + +{ + "username": "username", + "org": "org" +} + +> {% client.global.set("token", response.body["token"]) %} + +### Upload dummy file metadata +POST http://localhost:5000/file/upload/metadata +Content-Type: application/json +Authorization: {{token}} + +{ + "document_name": "dummy_file.txt", + "key": "arfarf", + "alg": "ftgtrg" +} + +> {% client.global.set("document_handle", response.body["document_handle"]) %} + + +#### Upload dummy file content, through send file +#POST http://localhost:5000/file/upload/content +#Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW +#Authorization: {{token}} +# +#----WebKitFormBoundary7MA4YWxkTrZu0gW +#Content-Disposition: form-data; name="file"; filename="dummy_file.txt" +#Content-Type: text/plain +# +#This is a dummy file +#----WebKitFormBoundary7MA4YWxkTrZu0gW + +### List files (with no data) +GET http://localhost:5000/file/list +Content-Type: application/json +Authorization: {{token}} + +{} + +### List files by user +GET http://localhost:5000/file/list +Content-Type: application/json +Authorization: {{token}} + +{ + "username": "username" +} + +### List files by datetime +GET http://localhost:5000/file/list +Content-Type: application/json +Authorization: {{token}} + +{ + "datetime": { + "relation": "ot", + "value": "1731863876" + } +} + +### Get file metadata +GET http://localhost:5000/file/get/{{document_handle}}/metadata +Content-Type: application/json +Authorization: {{token}} + +> {% client.global.set("file_handle", response.body["file_handle"]) %} + +### Get file content +GET http://localhost:5000/file/get/{{file_handle}}/content +Content-Type: application/json +Authorization: {{token}} + +### Delete dummy file +POST http://localhost:5000/file/delete/{{document_handle}} +Content-Type: application/json +Authorization: {{token}} + +### Logout +POST http://localhost:5000/user/logout +Content-Type: application/json +Authorization: {{token}} diff --git a/delivery1/server/utils/__init__.py b/delivery1/server/utils/__init__.py index 5edd78f..cfb2387 100644 --- a/delivery1/server/utils/__init__.py +++ b/delivery1/server/utils/__init__.py @@ -1 +1,2 @@ -from .data_checks import validate_session_file \ No newline at end of file +from .checks import check_valid_time +from .hashing import get_hash \ No newline at end of file diff --git a/delivery1/server/utils/checks.py b/delivery1/server/utils/checks.py new file mode 100644 index 0000000..2d8755c --- /dev/null +++ b/delivery1/server/utils/checks.py @@ -0,0 +1,9 @@ +def check_valid_time(time: int, relation_time: int, relation: str) -> bool: + if relation == 'ot': + return time < relation_time + elif relation == 'nt': + return time > relation_time + elif relation == 'eq': + return time == relation_time + else: + raise ValueError('Invalid relation: {}'.format(relation)) diff --git a/delivery1/server/utils/data_checks.py b/delivery1/server/utils/data_checks.py deleted file mode 100644 index 7f31068..0000000 --- a/delivery1/server/utils/data_checks.py +++ /dev/null @@ -1,28 +0,0 @@ -import json -from flask import jsonify -from services import SessionService, OrganizationService -from models import Session - -def validate_session_file(data) -> tuple | Session: - """ - Check if the session file is valid, and return the session object if it is - :param data: session file data (json) - :return: Session object or error response - """ - if "token" not in data: - return jsonify({"error": "No session token"}), 400 - session_token = data["token"] - - session = SessionService.get_session(session_token) - if not session: - return jsonify({"error": "Not authenticated"}), 401 - - org = OrganizationService.get_organization(session.org_id) - if not org: - return jsonify({"error": "Organization not found"}), 404 - - status = OrganizationService.get_user_status(org, session.user_id) - if status != "active": - return jsonify({"error": "User is not active"}), 403 - - return session \ No newline at end of file diff --git a/delivery1/server/utils/hashing.py b/delivery1/server/utils/hashing.py new file mode 100644 index 0000000..f03531c --- /dev/null +++ b/delivery1/server/utils/hashing.py @@ -0,0 +1,8 @@ +import cryptography.hazmat.primitives.hashes + +def get_hash(data): + if isinstance(data, str): + data = data.encode('utf-8') + digest = cryptography.hazmat.primitives.hashes.Hash(cryptography.hazmat.primitives.hashes.SHA256()) + digest.update(data) + return digest.finalize().hex() \ No newline at end of file