diff --git a/delivery2/server/models/file.py b/delivery2/server/models/file.py index 8288403..6f4a817 100644 --- a/delivery2/server/models/file.py +++ b/delivery2/server/models/file.py @@ -9,6 +9,8 @@ class File(db_connection.Model): 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) + acl = db_connection.Column(db_connection.JSON, nullable=False, default=dict) + deleter_id = db_connection.Column(db_connection.Integer, nullable=True) 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) @@ -23,8 +25,10 @@ class File(db_connection.Model): "document_handle": self.document_handle, "name": self.name, "created_at": self.created_at, + "acl": self.acl, "key": self.key, "alg": self.alg, "org": {"id": self.org.id, "name": self.org.name}, - "creator": {"id": self.creator.id, "username": self.creator.username} + "creator": {"id": self.creator.id, "username": self.creator.username}, + "deleter": {"id": self.deleter.id, "username": self.deleter.username} if self.deleter else None } \ No newline at end of file diff --git a/delivery2/server/routes/file.py b/delivery2/server/routes/file.py index 3ccbbd4..8e9393f 100644 --- a/delivery2/server/routes/file.py +++ b/delivery2/server/routes/file.py @@ -22,7 +22,7 @@ def file_get_content(file_handle: str): @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, required_perms=[Perm.DOC_READ]) + session = SessionService.validate_session(session_token, required_perms=[Perm.DOC_READ], doc_handle=document_handle) if isinstance(session, tuple): return session @@ -145,7 +145,7 @@ def file_delete(document_handle: str): if not session_token: return jsonify({"error": "No session token"}), 400 - session = SessionService.validate_session(session_token, required_perms=[Perm.DOC_DELETE]) + session = SessionService.validate_session(session_token, required_perms=[Perm.DOC_DELETE], doc_handle=document_handle) if isinstance(session, tuple): return session @@ -160,7 +160,7 @@ def file_delete(document_handle: str): if file.creator_id != session.user_id: return jsonify({"error": "Not authorized to delete file"}), 403 - file = FileService.delete_file(file) + file = FileService.delete_file(file, session.user_id) return jsonify(file.to_dict()) diff --git a/delivery2/server/services/files.py b/delivery2/server/services/files.py index 316409d..1d2c04f 100644 --- a/delivery2/server/services/files.py +++ b/delivery2/server/services/files.py @@ -6,7 +6,7 @@ from flask import jsonify from database import db from models import File, Organization, User -from utils import get_hash +from utils import get_hash, Perm class FileService: @@ -19,10 +19,18 @@ class FileService: document_handle = get_hash(file_name), name = file_name, created_at = int(datetime.now().timestamp()), + acl = { + "manager": Perm.get_int([ + Perm.DOC_ACL, + Perm.DOC_READ, + Perm.DOC_DELETE, + ]) + }, key = key, alg = alg, org_id = org.id, creator_id = user.id, + deleter_id = None, org = org, creator = user ) @@ -101,10 +109,11 @@ class FileService: return db.query(File).filter(File.org_id == org.id).all() @staticmethod - def delete_file(file: File) -> File: + def delete_file(file: File, deleter_id: int) -> File: 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.file_handle = None + file.deleter_id = deleter_id db.commit() db.refresh(file) return file \ No newline at end of file diff --git a/delivery2/server/services/orgs.py b/delivery2/server/services/orgs.py index 74a28a9..3d70394 100644 --- a/delivery2/server/services/orgs.py +++ b/delivery2/server/services/orgs.py @@ -24,9 +24,6 @@ class OrganizationService: roles = { "manager": { "permissions": Perm.get_int([ - Perm.DOC_ACL, - Perm.DOC_READ, - Perm.DOC_DELETE, Perm.ROLE_ACL, Perm.SUBJECT_NEW, Perm.SUBJECT_DOWN, diff --git a/delivery2/server/services/roles.py b/delivery2/server/services/roles.py index 925d275..bfca6d2 100644 --- a/delivery2/server/services/roles.py +++ b/delivery2/server/services/roles.py @@ -1,5 +1,5 @@ from database import db -from models import Organization, User +from models import Organization, User, File from sqlalchemy.orm.attributes import flag_modified from utils import Perm @@ -74,7 +74,22 @@ class RoleService: return org.roles[role] @staticmethod - def check_role_permission(org: Organization, role: str, perm: Perm) -> bool: + def check_role_permission(org: Organization, role: str, perm: Perm, doc_handle=None) -> bool: + from services import FileService + + if doc_handle: + file = FileService.get_file_by_document_handle(doc_handle) + if not file: + raise ValueError(f"Document {doc_handle} not found") + + if not Perm.check_perm(file.acl[role], perm.value): + return False + + return True + + if role not in org.roles: + raise ValueError(f"Role {role} does not exist in organization {org.name}") + role_perms = org.roles[role]["permissions"] return Perm.check_perm(role_perms, perm.value) @@ -163,6 +178,9 @@ class RoleService: @staticmethod def add_perm_to_role(org: Organization, role: str, perm: Perm) -> dict: + if perm in [Perm.DOC_ACL, Perm.DOC_READ, Perm.DOC_DELETE]: + raise ValueError(f"Permission {perm} is not allowed for organization's roles") + if role not in org.roles: raise ValueError(f"Role {role} does not exist in organization {org.name}") @@ -179,6 +197,9 @@ class RoleService: @staticmethod def remove_perm_from_role(org: Organization, role: str, perm: Perm) -> dict: + if perm in [Perm.DOC_ACL, Perm.DOC_READ, Perm.DOC_DELETE]: + raise ValueError(f"Permission {perm} is not allowed for organization's roles") + if role not in org.roles: raise ValueError(f"Role {role} does not exist in organization {org.name}") @@ -192,3 +213,37 @@ class RoleService: db.commit() db.refresh(org) return org.roles[role] + + @staticmethod + def add_perm_to_role_in_file(file: File, role: str, perm: Perm) -> dict: + if perm not in [Perm.DOC_ACL, Perm.DOC_READ, Perm.DOC_DELETE]: + raise ValueError(f"Permission {perm} is not allowed for files' roles") + + if role not in file.acl: + file.acl[role] = 0 + + if file.acl[role] & perm.value != 0: + raise ValueError(f"Role {role} already has permission {perm} in file {file.document_handle}") + + file.acl[role] |= perm.value + flag_modified(file, "acl") + db.commit() + db.refresh(file) + return file.acl + + @staticmethod + def remove_perm_from_role_in_file(file: File, role: str, perm: Perm) -> dict: + if perm not in [Perm.DOC_ACL, Perm.DOC_READ, Perm.DOC_DELETE]: + raise ValueError(f"Permission {perm} is not allowed for files' roles") + + if role not in file.acl: + file.acl[role] = 0 + + if file.acl[role] & perm.value == 0: + raise ValueError(f"Role {role} does not have permission {perm} in file {file.document_handle}") + + file.acl[role] &= ~perm.value + flag_modified(file, "acl") + db.commit() + db.refresh(file) + return file.acl \ No newline at end of file diff --git a/delivery2/server/services/sessions.py b/delivery2/server/services/sessions.py index b7411ee..527fc7c 100644 --- a/delivery2/server/services/sessions.py +++ b/delivery2/server/services/sessions.py @@ -33,7 +33,7 @@ class SessionService: db.commit() @staticmethod - def validate_session(token: str, required_perms: list[Perm] = None) -> tuple | Session: + def validate_session(token: str, required_perms: list[Perm] = None, doc_handle=None) -> tuple | Session: from services import OrganizationService if "Bearer" in token: @@ -53,7 +53,7 @@ class SessionService: if required_perms: for perm in required_perms: - if not SessionService.check_permission(session, perm): + if not SessionService.check_permission(session, perm, doc_handle): return jsonify({"error": f"Permission denied, missing required permission: {perm}"}), 403 return session @@ -111,7 +111,7 @@ class SessionService: return session.roles @staticmethod - def check_permission(session: Session, perm: Perm) -> bool: + def check_permission(session: Session, perm: Perm, doc_handle=None) -> bool: from services import OrganizationService, RoleService org = OrganizationService.get_organization(session.org_id) @@ -123,7 +123,7 @@ class SessionService: return False for role in session.roles: - if RoleService.check_role_permission(org, role, perm): + if RoleService.check_role_permission(org, role, perm, doc_handle): return True return False \ No newline at end of file