Implement perms system

Signed-off-by: Tiago Garcia <tiago.rgarcia@ua.pt>
This commit is contained in:
Tiago Garcia 2024-12-12 01:24:20 +00:00
parent ee825dcf39
commit 0ce6d25359
Signed by: TiagoRG
GPG Key ID: DFCD48E3F420DB42
6 changed files with 130 additions and 11 deletions

View File

@ -5,15 +5,15 @@ class Organization(db_connection.Model):
id = db_connection.Column(db_connection.Integer, primary_key=True, index=True) 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) name = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
roles = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
users = db_connection.Column(db_connection.JSON, nullable=False, default=dict) users = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
users_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id')) 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): def to_dict(self):
return { return {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
"manager": self.manager.to_dict(), "roles": [{"role": role, "permissions": permissions} for role, permissions in self.roles.items()],
"users": [{"id": user_id, "user_data": { "users": [{"id": user_id, "user_data": {
"username": user_data["username"], "username": user_data["username"],
"full_name": user_data["full_name"], "full_name": user_data["full_name"],

View File

@ -9,6 +9,7 @@ class User(db_connection.Model):
username = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False) username = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
full_name = db_connection.Column(db_connection.String, nullable=False) full_name = db_connection.Column(db_connection.String, nullable=False)
email = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False) email = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
roles = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
public_keys = db_connection.Column(db_connection.JSON, nullable=False, default=dict) public_keys = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
orgs = db_connection.Column(db_connection.JSON, nullable=False, default=dict) orgs = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
@ -18,5 +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,
"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

@ -89,9 +89,6 @@ def user_create():
if not org: if not org:
return jsonify({"error": "Organization not found"}), 404 return jsonify({"error": "Organization not found"}), 404
if org.manager.id != session.user_id:
return jsonify({"error": "Not authorized to create users"}), 403
user = UserService.get_user_by_username(data["username"]) user = UserService.get_user_by_username(data["username"])
if not user: if not user:
user = UserService.create_user( user = UserService.create_user(
@ -118,8 +115,6 @@ def user_suspend(username):
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
if org.manager.id != session.user_id:
return jsonify({"error": "Not authorized to suspend users"}), 403
user = UserService.get_user_by_username(username) user = UserService.get_user_by_username(username)
if not user: if not user:
@ -141,8 +136,6 @@ def user_unsuspend(username):
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
if org.manager.id != session.user_id:
return jsonify({"error": "Not authorized to unsuspend users"}), 403
user = UserService.get_user_by_username(username) user = UserService.get_user_by_username(username)
if not user: if not user:

View File

@ -3,6 +3,7 @@ import os.path
from database import db from database import db
from models import Organization, User from models import Organization, User
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
from utils.perms import Perm
class OrganizationService: class OrganizationService:
@ -18,9 +19,33 @@ class OrganizationService:
if not os.path.exists(os.path.join(repos, name)): if not os.path.exists(os.path.join(repos, name)):
os.mkdir(os.path.join(repos, name)) os.mkdir(os.path.join(repos, name))
roles = {
"manager": {
"permissions": Perm.get_int([
Perm.DOC_ACL,
Perm.DOC_READ,
Perm.DOC_DELETE,
Perm.ROLE_ACL,
Perm.SUBJECT_NEW,
Perm.SUBJECT_DOWN,
Perm.SUBJECT_UP,
Perm.DOC_NEW,
Perm.ROLE_NEW,
Perm.ROLE_DOWN,
Perm.ROLE_UP,
Perm.ROLE_MOD
]),
"users": [user.id]
},
"default": {
"permissions": Perm.get_int([]),
"users": []
}
}
organization = Organization( organization = Organization(
name=name, name=name,
manager=user, roles=roles,
users={user.id: { users={user.id: {
"username": user.username, "username": user.username,
"full_name": user.full_name, "full_name": user.full_name,
@ -34,6 +59,7 @@ class OrganizationService:
db.refresh(organization) db.refresh(organization)
UserService().add_org_to_user(user, organization) UserService().add_org_to_user(user, organization)
UserService().add_role_to_user(user, organization, "manager")
UserService().add_public_key_to_user(user, organization, public_key) UserService().add_public_key_to_user(user, organization, public_key)
return organization return organization
@ -71,12 +97,40 @@ class OrganizationService:
db.refresh(org) db.refresh(org)
return org return org
@staticmethod
def create_role(org: Organization, role: str, perms: list[Perm]) -> Organization:
roles = org.roles.copy()
roles[role] = {
"permissions": Perm.get_int(perms),
"users": []
}
org.roles = roles
flag_modified(org, "roles")
db.commit()
db.refresh(org)
return org
@staticmethod
def delete_role(org: Organization, role: str) -> Organization:
roles = org.roles.copy()
del roles[role]
org.roles = roles
flag_modified(org, "roles")
db.commit()
db.refresh(org)
return org
@staticmethod
def check_role_permission(org: Organization, role: str, perm: Perm) -> bool:
role_perms = org.roles[role]["permissions"]
return Perm.check_perm(role_perms, perm.value)
@staticmethod @staticmethod
def suspend_user(org: Organization, user: User) -> tuple: def suspend_user(org: Organization, user: User) -> tuple:
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 org.manager.id == user.id: if user.roles[org.id] == "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,3 +1,5 @@
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
@ -10,6 +12,7 @@ class UserService:
username=username, username=username,
full_name=full_name, full_name=full_name,
email=email, email=email,
roles={},
public_keys={org.id: public_key} if org else {}, public_keys={org.id: public_key} if org else {},
orgs={org.id: { orgs={org.id: {
"name": org.name, "name": org.name,
@ -43,6 +46,40 @@ class UserService:
db.refresh(user) db.refresh(user)
return user return user
@staticmethod
def add_role_to_user(user: User, org: Organization, role: str) -> User:
roles = user.roles.copy()
roles[org.id] = role
user.roles = roles
flag_modified(user, "roles")
db.commit()
db.refresh(user)
roles = org.roles.copy()
roles[role]["users"].append(user.id)
org.roles = roles
flag_modified(org, "roles")
db.commit()
db.refresh(org)
return user
@staticmethod
def remove_role_from_user(user: User, org: Organization, role: str) -> User:
roles = user.roles.copy()
roles.pop(org.id)
user.roles = roles
flag_modified(user, "roles")
db.commit()
db.refresh(user)
roles = org.roles.copy()
roles[role]["users"].remove(user.id)
org.roles = roles
flag_modified(org, "roles")
db.commit()
db.refresh(org)
return user
@staticmethod @staticmethod
def add_public_key_to_user(user: User, org: Organization, public_key: str) -> User: def add_public_key_to_user(user: User, org: Organization, public_key: str) -> User:
public_keys = user.public_keys.copy() public_keys = user.public_keys.copy()

View File

@ -0,0 +1,33 @@
from enum import Enum
class Perm(Enum):
DOC_ACL = 0b000000000001
DOC_READ = 0b000000000010
DOC_DELETE = 0b000000000100
ROLE_ACL = 0b000000001000
SUBJECT_NEW = 0b000000010000
SUBJECT_DOWN = 0b000000100000
SUBJECT_UP = 0b000001000000
DOC_NEW = 0b000010000000
ROLE_NEW = 0b000100000000
ROLE_DOWN = 0b001000000000
ROLE_UP = 0b010000000000
ROLE_MOD = 0b100000000000
@staticmethod
def get_perm(bit_array: int):
for perm in Perm:
if bit_array == perm.value:
return perm
return None
@staticmethod
def get_int(perms):
if isinstance(perms, list):
return sum([p.value for p in perms if isinstance(p, Perm)])
return perms.value if isinstance(perms, Perm) else 0
@staticmethod
def check_perm(perm, bit_array: int):
return perm.value & bit_array == perm.value