refactor: update role permissions and enhance RBAC logic to include own permissions for courses and dashboard access

This commit is contained in:
swve 2025-08-06 14:12:43 +02:00
parent a1976c5423
commit 9f13884c08
8 changed files with 201 additions and 31 deletions

View file

@ -11,8 +11,6 @@ from fastapi_jwt_auth.exceptions import AuthJWTException
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
# from src.services.mocks.initial import create_initial_data
######################## ########################
# Pre-Alpha Version 0.1.0 # Pre-Alpha Version 0.1.0
# Author: @swve # Author: @swve

View file

@ -49,6 +49,8 @@ def install(
email="", email="",
logo_image="", logo_image="",
thumbnail_image="", thumbnail_image="",
about="",
label="",
) )
install_create_organization(org, db_session) install_create_organization(org, db_session)
print("Default organization created ✅") print("Default organization created ✅")
@ -91,6 +93,8 @@ def install(
email="", email="",
logo_image="", logo_image="",
thumbnail_image="", thumbnail_image="",
about="",
label="",
) )
install_create_organization(org, db_session) install_create_organization(org, db_session)
print(orgname + " Organization created ✅") print(orgname + " Organization created ✅")

View file

@ -16,14 +16,36 @@ class Permission(BaseModel):
return getattr(self, item) return getattr(self, item)
class PermissionsWithOwn(BaseModel):
action_create: bool
action_read: bool
action_read_own: bool
action_update: bool
action_update_own: bool
action_delete: bool
action_delete_own: bool
def __getitem__(self, item):
return getattr(self, item)
class DashboardPermission(BaseModel):
action_access: bool
def __getitem__(self, item):
return getattr(self, item)
class Rights(BaseModel): class Rights(BaseModel):
courses: Permission courses: PermissionsWithOwn
users: Permission users: Permission
usergroups : Permission usergroups : Permission
collections: Permission collections: Permission
organizations: Permission organizations: Permission
coursechapters: Permission coursechapters: Permission
activities: Permission activities: Permission
roles: Permission
dashboard: DashboardPermission
def __getitem__(self, item): def __getitem__(self, item):
return getattr(self, item) return getattr(self, item)

View file

@ -7,7 +7,7 @@ from src.db.courses.courses import Course
from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum, ResourceAuthorshipStatusEnum from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum, ResourceAuthorshipStatusEnum
from src.db.roles import Role from src.db.roles import Role
from src.db.user_organizations import UserOrganization from src.db.user_organizations import UserOrganization
from src.security.rbac.utils import check_element_type from src.security.rbac.utils import check_element_type, check_course_permissions_with_own
# Tested and working # Tested and working
@ -106,13 +106,29 @@ async def authorization_verify_based_on_roles(
user_roles_in_organization_and_standard_roles = db_session.exec(statement).all() user_roles_in_organization_and_standard_roles = db_session.exec(statement).all()
# Check if user is the author of the resource for "own" permissions
is_author = False
if action in ["update", "delete", "read"]:
is_author = await authorization_verify_if_user_is_author(
request, user_id, action, element_uuid, db_session
)
# Check all roles until we find one that grants the permission # Check all roles until we find one that grants the permission
for role in user_roles_in_organization_and_standard_roles: for role in user_roles_in_organization_and_standard_roles:
role = Role.model_validate(role) role = Role.model_validate(role)
if role.rights: if role.rights:
rights = role.rights rights = role.rights
element_rights = getattr(rights, element_type, None) element_rights = getattr(rights, element_type, None)
if element_rights and getattr(element_rights, f"action_{action}", False): if element_rights:
# Special handling for courses with PermissionsWithOwn
if element_type == "courses":
if await check_course_permissions_with_own(element_rights, action, is_author):
return True
else:
# For non-course resources, only check general permissions
# (regular Permission class no longer has "own" permissions)
if getattr(element_rights, f"action_{action}", False):
return True return True
# If we get here, no role granted the permission # If we get here, no role granted the permission

View file

@ -30,6 +30,38 @@ async def check_element_type(element_uuid):
) )
async def check_course_permissions_with_own(
element_rights,
action: str,
is_author: bool = False
) -> bool:
"""
Check course-specific permissions including "own" permissions.
Args:
element_rights: The rights object for courses (PermissionsWithOwn)
action: The action to check ("read", "update", "delete", "create")
is_author: Whether the user is the author of the course
Returns:
bool: True if permission is granted, False otherwise
"""
if not element_rights:
return False
# Check for general permission first
if getattr(element_rights, f"action_{action}", False):
return True
# Check for "own" permission if user is the author
if is_author:
own_action = f"action_{action}_own"
if getattr(element_rights, own_action, False):
return True
return False
async def get_singular_form_of_element(element_uuid): async def get_singular_form_of_element(element_uuid):
element_type = await check_element_type(element_uuid) element_type = await check_element_type(element_uuid)

View file

@ -31,7 +31,7 @@ async def create_documentpdf_activity(
pdf_file: UploadFile | None = None, pdf_file: UploadFile | None = None,
): ):
# RBAC check # RBAC check
await rbac_check(request, "activity_x", current_user, "create", db_session) await rbac_check(request, "course_uuid", current_user, "create", db_session)
# get chapter_id # get chapter_id
statement = select(Chapter).where(Chapter.id == chapter_id) statement = select(Chapter).where(Chapter.id == chapter_id)
@ -95,8 +95,6 @@ async def create_documentpdf_activity(
"filename": "documentpdf." + pdf_format, "filename": "documentpdf." + pdf_format,
"activity_uuid": activity_uuid, "activity_uuid": activity_uuid,
}, },
published_version=1,
version=1,
org_id=org_id if org_id else 0, org_id=org_id if org_id else 0,
course_id=coursechapter.course_id, course_id=coursechapter.course_id,
activity_uuid=activity_uuid, activity_uuid=activity_uuid,

View file

@ -99,13 +99,11 @@ async def create_video_activity(
activity_uuid=activity_uuid, activity_uuid=activity_uuid,
org_id=coursechapter.org_id, org_id=coursechapter.org_id,
course_id=coursechapter.course_id, course_id=coursechapter.course_id,
published_version=1,
content={ content={
"filename": "video." + video_format, "filename": "video." + video_format,
"activity_uuid": activity_uuid, "activity_uuid": activity_uuid,
}, },
details=details, details=details,
version=1,
creation_date=str(datetime.now()), creation_date=str(datetime.now()),
update_date=str(datetime.now()), update_date=str(datetime.now()),
) )
@ -198,14 +196,12 @@ async def create_external_video_activity(
activity_uuid=activity_uuid, activity_uuid=activity_uuid,
course_id=coursechapter.course_id, course_id=coursechapter.course_id,
org_id=coursechapter.org_id, org_id=coursechapter.org_id,
published_version=1,
content={ content={
"uri": data.uri, "uri": data.uri,
"type": data.type, "type": data.type,
"activity_uuid": activity_uuid, "activity_uuid": activity_uuid,
}, },
details=details, details=details,
version=1,
creation_date=str(datetime.now()), creation_date=str(datetime.now()),
update_date=str(datetime.now()), update_date=str(datetime.now()),
) )

View file

@ -24,7 +24,7 @@ from src.db.organization_config import (
UserGroupOrgConfig, UserGroupOrgConfig,
) )
from src.db.organizations import Organization, OrganizationCreate from src.db.organizations import Organization, OrganizationCreate
from src.db.roles import Permission, Rights, Role, RoleTypeEnum from src.db.roles import DashboardPermission, Permission, PermissionsWithOwn, Rights, Role, RoleTypeEnum
from src.db.user_organizations import UserOrganization from src.db.user_organizations import UserOrganization
from src.db.users import User, UserCreate, UserRead from src.db.users import User, UserCreate, UserRead
from config.config import get_learnhouse_config from config.config import get_learnhouse_config
@ -127,7 +127,7 @@ def install_default_elements(db_session: Session):
statement = select(Role).where(Role.role_type == RoleTypeEnum.TYPE_GLOBAL) statement = select(Role).where(Role.role_type == RoleTypeEnum.TYPE_GLOBAL)
roles = db_session.exec(statement).all() roles = db_session.exec(statement).all()
if roles and len(roles) == 3: if roles and len(roles) == 4:
raise HTTPException( raise HTTPException(
status_code=409, status_code=409,
detail="Default roles already exist", detail="Default roles already exist",
@ -136,16 +136,19 @@ def install_default_elements(db_session: Session):
# Create default roles # Create default roles
role_global_admin = Role( role_global_admin = Role(
name="Admin", name="Admin",
description="Standard Admin Role", description="Full platform control",
id=1, id=1,
role_type=RoleTypeEnum.TYPE_GLOBAL, role_type=RoleTypeEnum.TYPE_GLOBAL,
role_uuid="role_global_admin", role_uuid="role_global_admin",
rights=Rights( rights=Rights(
courses=Permission( courses=PermissionsWithOwn(
action_create=True, action_create=True,
action_read=True, action_read=True,
action_read_own=True,
action_update=True, action_update=True,
action_update_own=True,
action_delete=True, action_delete=True,
action_delete_own=True,
), ),
users=Permission( users=Permission(
action_create=True, action_create=True,
@ -183,6 +186,15 @@ def install_default_elements(db_session: Session):
action_update=True, action_update=True,
action_delete=True, action_delete=True,
), ),
roles=Permission(
action_create=True,
action_read=True,
action_update=True,
action_delete=True,
),
dashboard=DashboardPermission(
action_access=True,
),
), ),
creation_date=str(datetime.now()), creation_date=str(datetime.now()),
update_date=str(datetime.now()), update_date=str(datetime.now()),
@ -190,22 +202,25 @@ def install_default_elements(db_session: Session):
role_global_maintainer = Role( role_global_maintainer = Role(
name="Maintainer", name="Maintainer",
description="Standard Maintainer Role", description="Mid-level manager, wide permissions but no platform control",
id=2, id=2,
role_type=RoleTypeEnum.TYPE_GLOBAL, role_type=RoleTypeEnum.TYPE_GLOBAL,
role_uuid="role_global_maintainer", role_uuid="role_global_maintainer",
rights=Rights( rights=Rights(
courses=Permission( courses=PermissionsWithOwn(
action_create=True, action_create=True,
action_read=True, action_read=True,
action_read_own=True,
action_update=True, action_update=True,
action_update_own=True,
action_delete=True, action_delete=True,
action_delete_own=True,
), ),
users=Permission( users=Permission(
action_create=True, action_create=True,
action_read=True, action_read=True,
action_update=True, action_update=True,
action_delete=True, action_delete=False,
), ),
usergroups=Permission( usergroups=Permission(
action_create=True, action_create=True,
@ -220,10 +235,10 @@ def install_default_elements(db_session: Session):
action_delete=True, action_delete=True,
), ),
organizations=Permission( organizations=Permission(
action_create=True, action_create=False,
action_read=True, action_read=True,
action_update=True, action_update=False,
action_delete=True, action_delete=False,
), ),
coursechapters=Permission( coursechapters=Permission(
action_create=True, action_create=True,
@ -237,6 +252,81 @@ def install_default_elements(db_session: Session):
action_update=True, action_update=True,
action_delete=True, action_delete=True,
), ),
roles=Permission(
action_create=False,
action_read=True,
action_update=False,
action_delete=False,
),
dashboard=DashboardPermission(
action_access=True,
),
),
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
)
role_global_instructor = Role(
name="Instructor",
description="Can manage their own content",
id=3,
role_type=RoleTypeEnum.TYPE_GLOBAL,
role_uuid="role_global_instructor",
rights=Rights(
courses=PermissionsWithOwn(
action_create=True,
action_read=True,
action_read_own=True,
action_update=False,
action_update_own=True,
action_delete=False,
action_delete_own=True,
),
users=Permission(
action_create=False,
action_read=False,
action_update=False,
action_delete=False,
),
usergroups=Permission(
action_create=False,
action_read=True,
action_update=False,
action_delete=False,
),
collections=Permission(
action_create=True,
action_read=True,
action_update=False,
action_delete=False,
),
organizations=Permission(
action_create=False,
action_read=False,
action_update=False,
action_delete=False,
),
coursechapters=Permission(
action_create=True,
action_read=True,
action_update=False,
action_delete=False,
),
activities=Permission(
action_create=True,
action_read=True,
action_update=False,
action_delete=False,
),
roles=Permission(
action_create=False,
action_read=False,
action_update=False,
action_delete=False,
),
dashboard=DashboardPermission(
action_access=True,
),
), ),
creation_date=str(datetime.now()), creation_date=str(datetime.now()),
update_date=str(datetime.now()), update_date=str(datetime.now()),
@ -244,20 +334,23 @@ def install_default_elements(db_session: Session):
role_global_user = Role( role_global_user = Role(
name="User", name="User",
description="Standard User Role", description="Read-Only Learner",
role_type=RoleTypeEnum.TYPE_GLOBAL, role_type=RoleTypeEnum.TYPE_GLOBAL,
role_uuid="role_global_user", role_uuid="role_global_user",
id=3, id=4,
rights=Rights( rights=Rights(
courses=Permission( courses=PermissionsWithOwn(
action_create=False, action_create=False,
action_read=True, action_read=True,
action_read_own=True,
action_update=False, action_update=False,
action_delete=False, action_update_own=False,
action_delete=True,
action_delete_own=True,
), ),
users=Permission( users=Permission(
action_create=True, action_create=False,
action_read=True, action_read=False,
action_update=False, action_update=False,
action_delete=False, action_delete=False,
), ),
@ -275,7 +368,7 @@ def install_default_elements(db_session: Session):
), ),
organizations=Permission( organizations=Permission(
action_create=False, action_create=False,
action_read=True, action_read=False,
action_update=False, action_update=False,
action_delete=False, action_delete=False,
), ),
@ -291,6 +384,15 @@ def install_default_elements(db_session: Session):
action_update=False, action_update=False,
action_delete=False, action_delete=False,
), ),
roles=Permission(
action_create=False,
action_read=False,
action_update=False,
action_delete=False,
),
dashboard=DashboardPermission(
action_access=False,
),
), ),
creation_date=str(datetime.now()), creation_date=str(datetime.now()),
update_date=str(datetime.now()), update_date=str(datetime.now()),
@ -299,11 +401,13 @@ def install_default_elements(db_session: Session):
# Serialize rights to JSON # Serialize rights to JSON
role_global_admin.rights = role_global_admin.rights.dict() # type: ignore role_global_admin.rights = role_global_admin.rights.dict() # type: ignore
role_global_maintainer.rights = role_global_maintainer.rights.dict() # type: ignore role_global_maintainer.rights = role_global_maintainer.rights.dict() # type: ignore
role_global_instructor.rights = role_global_instructor.rights.dict() # type: ignore
role_global_user.rights = role_global_user.rights.dict() # type: ignore role_global_user.rights = role_global_user.rights.dict() # type: ignore
# Insert roles in DB # Insert roles in DB
db_session.add(role_global_admin) db_session.add(role_global_admin)
db_session.add(role_global_maintainer) db_session.add(role_global_maintainer)
db_session.add(role_global_instructor)
db_session.add(role_global_user) db_session.add(role_global_user)
# commit changes # commit changes