small conflict solved

This commit is contained in:
RubenCGomes 2024-12-17 10:02:26 +00:00
commit 6c1de74a12
No known key found for this signature in database
20 changed files with 207 additions and 114 deletions

View File

@ -1,10 +1,14 @@
#!/bin/python3 #!/bin/python3
import base64
import os import os
import sys import sys
import argparse import argparse
import logging import logging
import json import json
import requests import requests
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from subject import main from subject import main
@ -36,15 +40,15 @@ def createSession(args):
args = parser.parse_args() args = parser.parse_args()
if not args.org or not args.username or not args.password or not args.credentials or not args.session: if not args.org or not args.username or not args.credentials or not args.session:
logger.error("Need organization, username, password, credentials and session file") logger.error("Need organization, username, credentials and session file")
sys.exit(1) sys.exit(1)
if (not os.path.isfile(BASE_DIR + args.credentials)): if not os.path.isfile(BASE_DIR + args.credentials):
logger.error("File '" + args.credentials + "' not found.") logger.error("File '" + args.credentials + "' not found.")
sys.exit(1) sys.exit(1)
session = {'org' : args.org, 'username' : args.username, 'password' : args.password, 'credentials_file' : args.credentials} session = {'org' : args.org, 'username' : args.username}
#print( type(json.dumps(session))) #print( type(json.dumps(session)))
@ -55,6 +59,29 @@ def createSession(args):
logger.error("Failed to obtain response from server") logger.error("Failed to obtain response from server")
sys.exit(-1) sys.exit(-1)
response = req.json()
challenge = response['challenge']
with open(BASE_DIR + args.credentials, 'rb') as f:
try:
key = load_pem_private_key(f.read(), password=args.password.encode("utf-8") if args.password else None)
except ValueError:
logger.error("Invalid password")
sys.exit(-1)
signature = key.sign(
challenge.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
try:
req = requests.post(f'http://{state['REP_ADDRESS']}/user/login', json=json.dumps({'signature' : base64.b64encode(signature).decode('utf-8')}), headers={'Authorization': response['token']})
req.raise_for_status()
except requests.exceptions.RequestException as errex:
logger.error("Failed to obtain response from server")
sys.exit(-1)
with open(BASE_DIR + args.session, 'w') as f: with open(BASE_DIR + args.session, 'w') as f:
json.dump(req.json(), f) json.dump(req.json(), f)

View File

@ -30,7 +30,7 @@ def decryptFile(args):
sys.exit(1) sys.exit(1)
# If first argument is not a file or not found # If first argument is not a file or not found
if (not os.path.isfile(BASE_DIR + args.encrypted)): if not os.path.isfile(BASE_DIR + args.encrypted):
logger.error("File '" + args.encrypted + "' not found.") logger.error("File '" + args.encrypted + "' not found.")
sys.exit(1) sys.exit(1)
@ -42,7 +42,7 @@ def decryptFile(args):
print(args.metadata) print(args.metadata)
metadata = json.loads(args.metadata) metadata = json.loads(args.metadata)
content = symmetric_encryption.decrypt_file(metadata['key'].encode(), BASE_DIR + args.encrypted, 'file.txt') content = symmetric_encryption.decrypt_file(metadata['key'].encode(), BASE_DIR + args.encrypted)
# Send decrypted content to stdout # Send decrypted content to stdout
sys.stdout.write(content) sys.stdout.write(content)

View File

@ -63,7 +63,7 @@ def getDoc(args):
#Get file with file_handle provided by metadata #Get file with file_handle provided by metadata
try: try:
file = requests.get(f'http://{state['REP_ADDRESS']}/get/' + metadata['file_handle'] + '/content') file = requests.get(f'http://{state['REP_ADDRESS']}/file/get/' + metadata['file_handle'] + '/content')
file.raise_for_status() file.raise_for_status()
except requests.exceptions.RequestException as errex: except requests.exceptions.RequestException as errex:
logger.error("Failed to obtain response from server.") logger.error("Failed to obtain response from server.")

View File

@ -13,6 +13,9 @@ logger.setLevel(logging.INFO)
BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/') BASE_DIR = os.path.join(os.path.expanduser('~'), '.sio/')
if not os.path.exists(BASE_DIR):
os.makedirs(BASE_DIR)
# Generate a key pair for a subject # Generate a key pair for a subject
# password - file for public key, file for private key # password - file for public key, file for private key
def generateKeyPair(args): def generateKeyPair(args):

View File

@ -23,7 +23,7 @@ def reset(args):
parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository") parser.add_argument("-r", '--repo', nargs=1, help="Address:Port of the repository")
parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true") parser.add_argument("-v", '--verbose', help="Increase verbosity", action="store_true")
parser.add_argument('password', nargs='?', default=None) parser.add_argument('password', nargs='?', default="123")
args = parser.parse_args() args = parser.parse_args()

View File

@ -40,7 +40,7 @@ def test_rep_list_orgs():
def test_rep_create_session(): def test_rep_create_session():
# Test the rep_create_session command # Test the rep_create_session command
process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_create_session org1 username password pub.pem session.json", shell=True) process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_create_session org1 username password priv.pem session.json", shell=True)
process.wait() process.wait()
assert process.returncode == 0 assert process.returncode == 0

View File

@ -34,7 +34,7 @@ def encrypt_file(input_file, output_file=None):
# Function to decrypt a file # Function to decrypt a file
def decrypt_file(key, input_file, output_file=None): def decrypt_file(key, input_file, output_file=None) -> str:
plaintext_content = b"" plaintext_content = b""
with open(input_file, 'rb') as infile: with open(input_file, 'rb') as infile:
@ -62,5 +62,5 @@ def decrypt_file(key, input_file, output_file=None):
# Finalize decryption # Finalize decryption
plaintext_content += decryptor.finalize() plaintext_content += decryptor.finalize()
return plaintext_content return plaintext_content.decode('utf-8', errors='ignore')

View File

@ -32,7 +32,7 @@ def index():
@app.route("/reset", methods=["POST"]) @app.route("/reset", methods=["POST"])
def reset(): def reset():
password = request.json["password"] password = request.json.get("password")
if password != "123": if password != "123":
return jsonify({"error": "Invalid password"}), 403 return jsonify({"error": "Invalid password"}), 403
try: try:

View File

@ -26,9 +26,9 @@ class File(db_connection.Model):
"name": self.name, "name": self.name,
"created_at": self.created_at, "created_at": self.created_at,
"acl": self.acl, "acl": self.acl,
"deleter_id": self.deleter_id,
"key": self.key, "key": self.key,
"alg": self.alg, "alg": self.alg,
"org": {"id": self.org.id, "name": self.org.name}, "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
} }

View File

@ -10,6 +10,8 @@ class Session(db_connection.Model):
roles = db_connection.Column(db_connection.JSON, default=list) roles = db_connection.Column(db_connection.JSON, default=list)
created_at = db_connection.Column(db_connection.DateTime, server_default=db_connection.func.now()) created_at = db_connection.Column(db_connection.DateTime, server_default=db_connection.func.now())
updated_at = db_connection.Column(db_connection.DateTime, server_default=db_connection.func.now(), server_onupdate=db_connection.func.now()) updated_at = db_connection.Column(db_connection.DateTime, server_default=db_connection.func.now(), server_onupdate=db_connection.func.now())
challenge = db_connection.Column(db_connection.String(255), unique=True)
verified = db_connection.Column(db_connection.Boolean, default=False)
def to_dict(self): def to_dict(self):
return { return {
@ -18,5 +20,7 @@ class Session(db_connection.Model):
"org_id": self.org_id, "org_id": self.org_id,
"token": self.token, "token": self.token,
"created_at": self.created_at, "created_at": self.created_at,
"updated_at": self.updated_at "updated_at": self.updated_at,
"challenge": self.challenge,
"verified": self.verified
} }

View File

@ -19,6 +19,6 @@ class User(db_connection.Model):
"username": self.username, "username": self.username,
"full_name": self.full_name, "full_name": self.full_name,
"email": self.email, "email": self.email,
"role": self.role, "roles": self.roles,
"orgs": [{"id": org_id, "name": org_data["name"], "status": org_data["status"]} for org_id, org_data in self.orgs.items()], "orgs": [{"id": org_id, "name": org_data["name"], "status": org_data["status"]} for org_id, org_data in self.orgs.items()],
} }

View File

@ -40,7 +40,6 @@ def file_get_metadata(document_handle: str):
@file_bp.route("/upload/metadata", methods=["POST"]) @file_bp.route("/upload/metadata", methods=["POST"])
def file_upload_metadata(): def file_upload_metadata():
session_token = request.headers.get("Authorization") session_token = request.headers.get("Authorization")
print(session_token)
session = SessionService.validate_session(session_token, required_perms=[Perm.DOC_NEW]) session = SessionService.validate_session(session_token, required_perms=[Perm.DOC_NEW])
if isinstance(session, tuple): if isinstance(session, tuple):
return session return session
@ -196,10 +195,9 @@ def file_acl():
if role not in org.roles: if role not in org.roles:
return jsonify({"error": "Role not found"}), 404 return jsonify({"error": "Role not found"}), 404
try: acl = RoleService.change_perm_on_role_in_file(file, role, perm, operation)
RoleService.change_perm_on_role_in_file(file, role, perm, operation) if isinstance(acl, tuple):
except ValueError as e: return acl
return jsonify({"error": str(e)}), 400
return jsonify(file.to_dict()), 200 return jsonify(file.to_dict()), 200

View File

@ -49,10 +49,9 @@ def role_list_users(role):
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
try:
users = RoleService.get_users_in_role(org, role) users = RoleService.get_users_in_role(org, role)
except ValueError as e: if isinstance(users, tuple):
return jsonify({"error": str(e)}), 400 return users
return jsonify(users), 200 return jsonify(users), 200
@ -70,10 +69,9 @@ def role_list_perms(role):
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
try: perms = RoleService.get_perms_for_role(org, role, return_str=True)
perms = RoleService.get_perms_for_role(org, role) if isinstance(perms, tuple):
except ValueError as e: return perms
return jsonify({"error": str(e)}), 400
return jsonify(perms), 200 return jsonify(perms), 200
@ -98,10 +96,9 @@ def role_suspend(role):
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
try: status = RoleService.change_role_status(org, role, "suspended")
RoleService.change_role_status(org, role, "suspended") if isinstance(status, tuple):
except ValueError as e: return status
return jsonify({"error": str(e)}), 400
return jsonify({"message": "Role suspended"}), 200 return jsonify({"message": "Role suspended"}), 200
@ -127,10 +124,9 @@ def role_activate(role):
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
try: status = RoleService.change_role_status(org, role, "active")
RoleService.change_role_status(org, role, "active") if isinstance(status, tuple):
except ValueError as e: return status
return jsonify({"error": str(e)}), 400
return jsonify({"message": "Role activated"}), 200 return jsonify({"message": "Role activated"}), 200
@ -160,10 +156,9 @@ def role_user_add(role, username):
if not user: if not user:
return jsonify({"error": "User not found"}), 404 return jsonify({"error": "User not found"}), 404
try: role = RoleService.add_user_to_role(role, org, user)
RoleService.add_user_to_role(role, org, user) if isinstance(role, tuple):
except ValueError as e: return role
return jsonify({"error": str(e)}), 400
return jsonify({"message": "User added to role"}), 200 return jsonify({"message": "User added to role"}), 200
@ -193,10 +188,9 @@ def role_user_remove(role, username):
if not user: if not user:
return jsonify({"error": "User not found"}), 404 return jsonify({"error": "User not found"}), 404
try: role = RoleService.remove_user_from_role(role, org, user)
RoleService.remove_user_from_role(role, org, user) if isinstance(role, tuple):
except ValueError as e: return role
return jsonify({"error": str(e)}), 400
return jsonify({"message": "User removed from role"}), 200 return jsonify({"message": "User removed from role"}), 200
@ -222,10 +216,9 @@ def role_perm_add(role, perm):
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
try: role = RoleService.change_perm_on_role(org, role, Perm.from_str(perm), PermOperation.ADD)
RoleService.change_perm_on_role(org, role, Perm.from_str(perm), PermOperation.ADD) if isinstance(role, tuple):
except ValueError as e: return role
return jsonify({"error": str(e)}), 400
return jsonify({"message": "Permission added to role"}), 200 return jsonify({"message": "Permission added to role"}), 200
@ -251,10 +244,9 @@ def role_perm_remove(role, perm):
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
try: role = RoleService.change_perm_on_role(org, role, Perm.from_str(perm), PermOperation.REMOVE)
RoleService.change_perm_on_role(org, role, Perm.from_str(perm), PermOperation.REMOVE) if isinstance(role, tuple):
except ValueError as e: return role
return jsonify({"error": str(e)}), 400
return jsonify({"message": "Permission removed from role"}), 200 return jsonify({"message": "Permission removed from role"}), 200
@ -272,10 +264,9 @@ def role_session_assume(role):
if not RoleService.get_role(session.org_id, role): if not RoleService.get_role(session.org_id, role):
return jsonify({"error": "Role not found"}), 404 return jsonify({"error": "Role not found"}), 404
try: session = SessionService.change_role(session, role, "add")
SessionService.change_role(session, role, "add") if isinstance(session, tuple):
except ValueError as e: return session
return jsonify({"error": str(e)}), 400
return jsonify(session.to_dict()), 200 return jsonify(session.to_dict()), 200
@ -293,10 +284,9 @@ def role_session_drop(role):
if not RoleService.get_role(session.org_id, role): if not RoleService.get_role(session.org_id, role):
return jsonify({"error": "Role not found"}), 404 return jsonify({"error": "Role not found"}), 404
try: session = SessionService.change_role(session, role, "drop")
SessionService.change_role(session, role, "drop") if isinstance(session, tuple):
except ValueError as e: return session
return jsonify({"error": str(e)}), 400
return jsonify(session.to_dict()), 200 return jsonify(session.to_dict()), 200
@ -328,8 +318,7 @@ def perm_list_roles(perm):
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
try:
roles = RoleService.get_roles_for_perm(org, Perm(perm)) roles = RoleService.get_roles_for_perm(org, Perm(perm))
except ValueError as e: if isinstance(roles, tuple):
return jsonify({"error": str(e)}), 400 return roles
return jsonify(roles), 200 return jsonify(roles), 200

View File

@ -1,4 +1,7 @@
import base64
import json import json
from cryptography.exceptions import InvalidSignature
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from services import UserService, SessionService, OrganizationService, RoleService from services import UserService, SessionService, OrganizationService, RoleService
from utils import Perm from utils import Perm
@ -11,9 +14,7 @@ def user_login():
if type(data) is str: if type(data) is str:
data = json.loads(data) data = json.loads(data)
if "username" not in data or "org" not in data: if "username" in data and "org" in data:
return jsonify({"error": "Missing required fields"}), 400
user = UserService.get_user_by_username(data["username"]) user = UserService.get_user_by_username(data["username"])
if not user: if not user:
return jsonify({"error": "User not found"}), 404 return jsonify({"error": "User not found"}), 404
@ -25,6 +26,27 @@ def user_login():
session = SessionService.create_session(user, org) session = SessionService.create_session(user, org)
return jsonify(session.to_dict()), 201 return jsonify(session.to_dict()), 201
elif session_token := request.headers.get("Authorization"):
session = SessionService.get_session(session_token)
if not session:
return jsonify({"error": "Not authenticated"}), 401
if session.verified:
return jsonify(session.to_dict()), 200
if not "signature" in data:
return jsonify({"error": "Missing required fields"}), 400
signature = data["signature"]
signature = base64.b64decode(signature)
session = SessionService.verify_session(session_token, signature)
if isinstance(session, tuple):
return session
return jsonify(session.to_dict()), 200
return jsonify({"error": "Missing required fields"}), 400
@user_bp.route("/logout", methods=["POST"]) @user_bp.route("/logout", methods=["POST"])
def user_logout(): def user_logout():

View File

@ -34,7 +34,8 @@ class OrganizationService:
Perm.ROLE_UP, Perm.ROLE_UP,
Perm.ROLE_MOD Perm.ROLE_MOD
]), ]),
"users": [user.id] "users": [],
"status": "active"
} }
} }
@ -54,7 +55,7 @@ class OrganizationService:
db.refresh(organization) db.refresh(organization)
UserService().add_org_to_user(user, organization) UserService().add_org_to_user(user, organization)
RoleService().add_user_to_role(user, organization, "manager") RoleService().add_user_to_role("manager", organization, user)
UserService().add_public_key_to_user(user, organization, public_key) UserService().add_public_key_to_user(user, organization, public_key)
return organization return organization
@ -97,7 +98,7 @@ class OrganizationService:
if OrganizationService.get_user_status(org, user.id) != "active": if OrganizationService.get_user_status(org, user.id) != "active":
return {"error": "User already suspended"}, 400 return {"error": "User already suspended"}, 400
if user.roles[org.id] == "manager": if user.id in RoleService.get_users_in_role(org, "manager"):
return {"error": "Cannot suspend manager"}, 400 return {"error": "Cannot suspend manager"}, 400
org.users[str(user.id)]["status"] = "suspended" org.users[str(user.id)]["status"] = "suspended"

View File

@ -1,14 +1,18 @@
from flask import jsonify
from database import db from database import db
from models import Organization, User, File from models import Organization, User, File
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
from services import FileService, OrganizationService
from utils import Perm, PermOperation from utils import Perm, PermOperation
class RoleService: class RoleService:
@staticmethod @staticmethod
def create_role(org: Organization, role: str, perms: list[Perm]) -> dict: def create_role(org: Organization, role: str, perms: list[Perm]) -> dict | tuple:
if role in org.roles: if role in org.roles:
raise ValueError(f"Role {role} already exists in organization {org.name}") return jsonify({"error": f"Role {role} already exists in organization {org.name}"}), 400
roles = org.roles.copy() roles = org.roles.copy()
roles[role] = { roles[role] = {
@ -33,12 +37,12 @@ class RoleService:
return roles return roles
@staticmethod @staticmethod
def change_role_status(org: Organization, role: str, status: str) -> Organization: def change_role_status(org: Organization, role: str, status: str) -> Organization | tuple:
if role not in org.roles: if role not in org.roles:
raise ValueError(f"Role {role} does not exist in organization {org.name}") return jsonify({"error": f"Role {role} does not exist in organization {org.name}"}), 400
if org.roles[role]["status"] == status: if org.roles[role]["status"] == status:
raise ValueError(f"Role {role} is already {status} in organization {org.name}") return jsonify({"error": f"Role {role} is already {status} in organization {org.name}"}), 400
roles = org.roles.copy() roles = org.roles.copy()
roles[role]["status"] = status roles[role]["status"] = status
@ -55,13 +59,13 @@ class RoleService:
return org.roles[role] return org.roles[role]
@staticmethod @staticmethod
def check_role_permission(org: Organization, role: str, perm: Perm, doc_handle=None) -> bool: def check_role_permission(org: Organization, role: str, perm: Perm, doc_handle=None) -> bool | tuple:
from services import FileService from services import FileService
if doc_handle: if doc_handle:
file = FileService.get_file_by_document_handle(doc_handle) file = FileService.get_file_by_document_handle(doc_handle)
if not file: if not file:
raise ValueError(f"Document {doc_handle} not found") return jsonify({"error": "File not found"}), 404
if not Perm.check_perm(file.acl[role], perm.value): if not Perm.check_perm(file.acl[role], perm.value):
return False return False
@ -69,7 +73,7 @@ class RoleService:
return True return True
if role not in org.roles: if role not in org.roles:
raise ValueError(f"Role {role} does not exist in organization {org.name}") return jsonify({"error": f"Role {role} does not exist in organization {org.name}"}), 400
role_perms = org.roles[role]["permissions"] role_perms = org.roles[role]["permissions"]
return Perm.check_perm(role_perms, perm.value) return Perm.check_perm(role_perms, perm.value)
@ -86,9 +90,9 @@ class RoleService:
return org.roles return org.roles
@staticmethod @staticmethod
def get_users_in_role(org: Organization, role: str) -> list: def get_users_in_role(org: Organization, role: str) -> list | tuple:
if role not in org.roles: if role not in org.roles:
raise ValueError(f"Role {role} does not exist in organization {org.name}") return jsonify({"error": f"Role {role} does not exist in organization {org.name}"}), 400
return org.roles[role]["users"] return org.roles[role]["users"]
@staticmethod @staticmethod
@ -97,7 +101,11 @@ class RoleService:
@staticmethod @staticmethod
def get_perms_for_role(org: Organization, role: str, return_str=False) -> list[Perm | str]: def get_perms_for_role(org: Organization, role: str, return_str=False) -> list[Perm | str]:
return Perm.get_perms(org.roles[role]["permissions"], return_str) perms_list = Perm.get_perms(org.roles[role]["permissions"], return_str)
for f in FileService.list_files_in_org(org):
perms_list.append({f.document_handle: Perm.get_perms(f.acl[role], return_str)})
return perms_list
@staticmethod @staticmethod
def get_roles_for_perm(org: Organization, perm: Perm) -> list: def get_roles_for_perm(org: Organization, perm: Perm) -> list:
@ -108,15 +116,15 @@ class RoleService:
return roles return roles
@staticmethod @staticmethod
def add_user_to_role(role: str, org: Organization, user: User) -> User: def add_user_to_role(role: str, org: Organization, user: User) -> User | tuple:
if role not in org.roles: if role not in org.roles:
raise ValueError(f"Role {role} does not exist in organization {org.name}") return jsonify({"error": f"Role {role} does not exist in organization {org.name}"}), 400
if user.id in org.roles[role]["users"]: if user.id in org.roles[role]["users"]:
raise ValueError(f"User {user.username} already has role {role} in organization {org.name}") return jsonify({"error": f"User {user.username} already has role {role} in organization {org.name}"}), 400
if org.roles[role]["status"] != "active": if org.roles[role]["status"] != "active":
raise ValueError(f"Role {role} is not active in organization {org.name}") return jsonify({"error": f"Role {role} is not active in organization {org.name}"}), 400
roles = user.roles.copy() roles = user.roles.copy()
roles[org.id] = role roles[org.id] = role
@ -134,15 +142,15 @@ class RoleService:
return user return user
@staticmethod @staticmethod
def remove_user_from_role(role: str, org: Organization, user: User) -> User: def remove_user_from_role(role: str, org: Organization, user: User) -> User | tuple:
if role not in org.roles: if role not in org.roles:
raise ValueError(f"Role {role} does not exist in organization {org.name}") return jsonify({"error": f"Role {role} does not exist in organization {org.name}"}), 400
if user.id not in org.roles[role]["users"]: if user.id not in org.roles[role]["users"]:
raise ValueError(f"User {user.username} does not have role {role} in organization {org.name}") return jsonify({"error": f"User {user.username} does not have role {role} in organization {org.name}"}), 400
if org.roles[role]["status"] != "active": if org.roles[role]["status"] != "active":
raise ValueError(f"Role {role} is not active in organization {org.name}") return jsonify({"error": f"Role {role} is not active in organization {org.name}"}), 400
roles = user.roles.copy() roles = user.roles.copy()
roles.pop(org.id) roles.pop(org.id)
@ -160,15 +168,15 @@ class RoleService:
return user return user
@staticmethod @staticmethod
def change_perm_on_role(org: Organization, role: str, perm: Perm, operation: PermOperation) -> dict: def change_perm_on_role(org: Organization, role: str, perm: Perm, operation: PermOperation) -> dict | tuple:
if Perm.get_int([perm]) <= 0b111: if Perm.get_int([perm]) <= 0b111:
raise ValueError(f"Permission {perm} is not allowed for organization's roles") return jsonify({"error": f"Permission {perm} is not allowed for organization's roles"}), 400
if role not in org.roles: if role not in org.roles:
raise ValueError(f"Role {role} does not exist in organization {org.name}") return jsonify({"error": f"Role {role} does not exist in organization {org.name}"}), 400
if org.roles[role]["status"] != "active": if org.roles[role]["status"] != "active":
raise ValueError(f"Role {role} is not active in organization {org.name}") return jsonify({"error": f"Role {role} is not active in organization {org.name}"}), 400
roles = org.roles.copy() roles = org.roles.copy()
roles[role]["permissions"] = PermOperation.calc(roles[role]["permissions"], perm, operation) roles[role]["permissions"] = PermOperation.calc(roles[role]["permissions"], perm, operation)
@ -179,15 +187,15 @@ class RoleService:
return org.roles[role] return org.roles[role]
@staticmethod @staticmethod
def change_perm_on_role_in_file(file: File, role: str, perm: Perm, operation: PermOperation) -> dict: def change_perm_on_role_in_file(file: File, role: str, perm: Perm, operation: PermOperation) -> dict | tuple:
if Perm.get_int([perm]) > 0b111: if Perm.get_int([perm]) > 0b111:
raise ValueError(f"Permission {perm} is not allowed for files' roles") return jsonify({"error": f"Permission {perm} is not allowed for files' roles"}), 400
if role not in file.acl: if role not in file.acl:
file.acl[role] = 0 file.acl[role] = 0
if file.acl[role] & perm.value != 0: if file.acl[role] & perm.value != 0:
raise ValueError(f"Role {role} already has permission {perm} in file {file.document_handle}") return jsonify({"error": f"Role {role} already has permission {perm} in file {file.document_handle}"}), 400
file.acl[role] = PermOperation.calc(file.acl[role], perm, operation) file.acl[role] = PermOperation.calc(file.acl[role], perm, operation)
flag_modified(file, "acl") flag_modified(file, "acl")

View File

@ -1,5 +1,8 @@
import secrets import secrets
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
from database import db from database import db
@ -16,13 +19,35 @@ class SessionService:
user_id=user.id, user_id=user.id,
org_id=org.id, org_id=org.id,
token=secrets.token_hex(128), token=secrets.token_hex(128),
roles=[] roles=[],
challenge=secrets.token_hex(128),
verified=False
) )
db.add(session) db.add(session)
db.commit() db.commit()
db.refresh(session) db.refresh(session)
return session return session
@staticmethod
def verify_session(token: str, signature: bytes):
session = SessionService.get_session(token)
if not session:
return jsonify({"error": "Session not found"}), 401
public_key_pem = User.query.get(session.user_id).public_keys.get(str(session.org_id))
if not public_key_pem:
return jsonify({"error": "Public key not found"}), 404
public_key = load_pem_public_key(public_key_pem.encode())
public_key.verify(
signature,
session.challenge.encode(),
padding.PKCS1v15(),
hashes.SHA256()
)
session.challenge = None
session.verified = True
db.commit()
db.refresh(session)
@staticmethod @staticmethod
def get_session(token: str) -> Session | None: def get_session(token: str) -> Session | None:
return db.query(Session).filter(Session.token == token).first() return db.query(Session).filter(Session.token == token).first()
@ -43,6 +68,9 @@ class SessionService:
if not session: if not session:
return jsonify({"error": "Not authenticated"}), 401 return jsonify({"error": "Not authenticated"}), 401
if not session.verified:
return jsonify({"error": "Session has not yet been verified"}), 403
org = OrganizationService.get_organization(session.org_id) org = OrganizationService.get_organization(session.org_id)
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
@ -64,37 +92,38 @@ class SessionService:
org = OrganizationService.get_organization(session.org_id) org = OrganizationService.get_organization(session.org_id)
if not org: if not org:
raise ValueError(f"Organization {session.org_id} not found") return jsonify({"error": "Organization not found"}), 404
user = User.query.get(session.user_id) user = User.query.get(session.user_id)
if not user: if not user:
raise ValueError(f"User {session.user_id} not found") return jsonify({"error": "User not found"}), 404
if role not in org.roles: if role not in org.roles:
raise ValueError(f"Role {role} does not exist in organization {org.name}") return jsonify({"error": f"Role {role} does not exist in organization {org.name}"}), 404
if operation == "add": if operation == "add":
if role not in user.roles[org.id]: if role not in user.roles[org.id]:
raise ValueError(f"User {user.username} does not have role {role}") return jsonify({"error": f"User {user.username} does not have role {role}"}), 400
if role in session.roles: if role in session.roles:
raise ValueError(f"User {user.username} already has role {role} in current session") return jsonify({"error": f"User {user.username} already has role {role} in current session"}), 400
session.roles.append(role) session.roles.append(role)
elif operation == "drop": elif operation == "drop":
if role not in user.roles[org.id]: if role not in user.roles[org.id]:
raise ValueError(f"User {user.username} does not have role {role}") return jsonify({"error": f"User {user.username} does not have role {role}"}), 400
if role not in session.roles: if role not in session.roles:
raise ValueError(f"User {user.username} does not have role {role} in current session") return jsonify({"error": f"User {user.username} does not have role {role} in current session"}), 400
session.roles.remove(role) session.roles.remove(role)
else: else:
raise ValueError(f"Invalid operation {operation}") return jsonify({"error": "Invalid operation"}), 400
flag_modified(session, "roles") flag_modified(session, "roles")
db.commit() db.commit()
db.refresh(session) db.refresh(session)
return session
@staticmethod @staticmethod
def list_roles(session: Session) -> list: def list_roles(session: Session) -> list:

View File

@ -2,6 +2,7 @@ from sqlalchemy.orm.attributes import flag_modified
from database import db from database import db
from models import User, Organization from models import User, Organization
from utils import encode_public_key
class UserService: class UserService:
@ -13,7 +14,7 @@ class UserService:
full_name=full_name, full_name=full_name,
email=email, email=email,
roles={}, roles={},
public_keys={org.id: public_key} if org else {}, public_keys={org.id: encode_public_key(public_key)} if org else {},
orgs={org.id: { orgs={org.id: {
"name": org.name, "name": org.name,
"status": "active" "status": "active"

View File

@ -1,3 +1,3 @@
from .checks import check_valid_time from .checks import check_valid_time
from .hashing import get_hash, get_hex_from_temp_file from .hashing import get_hash, get_hex_from_temp_file, encode_public_key
from .perms import Perm, PermOperation from .perms import Perm, PermOperation

View File

@ -1,5 +1,6 @@
from tempfile import SpooledTemporaryFile from tempfile import SpooledTemporaryFile
import cryptography.hazmat.primitives.hashes import cryptography.hazmat.primitives.hashes
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, load_pem_public_key
def get_hash(data): def get_hash(data):
@ -14,3 +15,13 @@ def get_hex_from_temp_file(temp_file: SpooledTemporaryFile) -> bytes:
temp_file.seek(0) temp_file.seek(0)
file_data = temp_file.read() file_data = temp_file.read()
return file_data return file_data
def encode_public_key(public_key):
if isinstance(public_key, str):
public_key = load_pem_public_key(public_key.encode('utf-8'))
return public_key.public_bytes(
encoding=cryptography.hazmat.primitives.serialization.Encoding.PEM,
format=cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8')