mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: implement authorization with roles
This commit is contained in:
parent
0595bfdb3f
commit
7738316200
19 changed files with 596 additions and 170 deletions
|
|
@ -46,7 +46,6 @@ class Activity(ActivityBase, table=True):
|
|||
|
||||
|
||||
class ActivityCreate(ActivityBase):
|
||||
order: int
|
||||
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||
course_id: int = Field(default=None, foreign_key="course.id")
|
||||
chapter_id: int
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class Collection(CollectionBase, table=True):
|
|||
|
||||
|
||||
class CollectionCreate(CollectionBase):
|
||||
courses: list
|
||||
courses: list[int]
|
||||
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import List, Optional
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
from src.db.trails import TrailRead
|
||||
from src.db.chapters import ChapterRead
|
||||
|
||||
|
||||
|
|
@ -39,6 +39,7 @@ class CourseUpdate(CourseBase):
|
|||
|
||||
class CourseRead(CourseBase):
|
||||
id: int
|
||||
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||
course_uuid: str
|
||||
creation_date: str
|
||||
update_date: str
|
||||
|
|
@ -53,3 +54,15 @@ class FullCourseRead(CourseBase):
|
|||
# Chapters, Activities
|
||||
chapters: List[ChapterRead]
|
||||
pass
|
||||
|
||||
|
||||
class FullCourseReadWithTrail(CourseBase):
|
||||
id: int
|
||||
course_uuid: str
|
||||
creation_date: str
|
||||
update_date: str
|
||||
# Chapters, Activities
|
||||
chapters: List[ChapterRead]
|
||||
# Trail
|
||||
trail: TrailRead
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -32,12 +32,14 @@ class UserUpdatePassword(SQLModel):
|
|||
|
||||
class UserRead(UserBase):
|
||||
id: int
|
||||
user_uuid: str
|
||||
|
||||
class PublicUser(UserRead):
|
||||
pass
|
||||
|
||||
class AnonymousUser(SQLModel):
|
||||
id: str = "anonymous"
|
||||
id: int = 0
|
||||
user_uuid: str = "user_anonymous"
|
||||
username: str = "anonymous"
|
||||
|
||||
class User(UserBase, table=True):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from src.core.events.database import get_db_session
|
|||
from src.db.users import PublicUser
|
||||
from src.db.courses import CourseCreate, CourseUpdate
|
||||
from src.security.auth import get_current_user
|
||||
|
||||
from src.services.courses.courses import (
|
||||
create_course,
|
||||
get_course,
|
||||
|
|
@ -46,9 +45,7 @@ async def api_create_course(
|
|||
learnings=learnings,
|
||||
tags=tags,
|
||||
)
|
||||
return await create_course(
|
||||
request, course, current_user, db_session, thumbnail
|
||||
)
|
||||
return await create_course(request, course, current_user, db_session, thumbnail)
|
||||
|
||||
|
||||
@router.put("/thumbnail/{course_id}")
|
||||
|
|
@ -85,7 +82,7 @@ async def api_get_course(
|
|||
@router.get("/meta/{course_id}")
|
||||
async def api_get_course_meta(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
course_id: int,
|
||||
db_session: Session = Depends(get_db_session),
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
):
|
||||
|
|
@ -109,7 +106,9 @@ async def api_get_course_by_orgslug(
|
|||
"""
|
||||
Get houses by page and limit
|
||||
"""
|
||||
return await get_courses_orgslug(request, current_user, page, limit, org_slug)
|
||||
return await get_courses_orgslug(
|
||||
request, current_user, org_slug, db_session, page, limit
|
||||
)
|
||||
|
||||
|
||||
@router.put("/")
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ async def api_get_org(
|
|||
"""
|
||||
Get single Org by ID
|
||||
"""
|
||||
return await get_organization(request, org_id, db_session)
|
||||
return await get_organization(request, org_id, db_session, current_user)
|
||||
|
||||
|
||||
@router.get("/slug/{org_slug}")
|
||||
|
|
@ -54,7 +54,7 @@ async def api_get_org_by_slug(
|
|||
"""
|
||||
Get single Org by Slug
|
||||
"""
|
||||
return await get_organization_by_slug(request, org_slug, db_session)
|
||||
return await get_organization_by_slug(request, org_slug, db_session, current_user)
|
||||
|
||||
|
||||
@router.put("/{org_id}/logo")
|
||||
|
|
@ -109,7 +109,7 @@ async def api_update_org(
|
|||
@router.delete("/{org_id}")
|
||||
async def api_delete_org(
|
||||
request: Request,
|
||||
org_id: str,
|
||||
org_id: int,
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
db_session: Session = Depends(get_db_session),
|
||||
):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from fastapi import APIRouter, Depends, Request
|
||||
from sqlmodel import Session
|
||||
from src.security.rbac.rbac import authorization_verify_based_on_roles, authorization_verify_if_element_is_public, authorization_verify_if_user_is_author
|
||||
from src.security.auth import get_current_user
|
||||
from src.core.events.database import get_db_session
|
||||
|
||||
from src.db.users import (
|
||||
PublicUser,
|
||||
User,
|
||||
UserCreate,
|
||||
UserRead,
|
||||
|
|
@ -37,13 +39,14 @@ async def api_create_user_with_orgid(
|
|||
*,
|
||||
request: Request,
|
||||
db_session: Session = Depends(get_db_session),
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
user_object: UserCreate,
|
||||
org_id: int,
|
||||
) -> UserRead:
|
||||
"""
|
||||
Create User with Org ID
|
||||
"""
|
||||
return await create_user(request, db_session, None, user_object, org_id)
|
||||
return await create_user(request, db_session, current_user, user_object, org_id)
|
||||
|
||||
|
||||
@router.post("/", response_model=UserRead, tags=["users"])
|
||||
|
|
@ -51,12 +54,13 @@ async def api_create_user_without_org(
|
|||
*,
|
||||
request: Request,
|
||||
db_session: Session = Depends(get_db_session),
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
user_object: UserCreate,
|
||||
) -> UserRead:
|
||||
"""
|
||||
Create User
|
||||
"""
|
||||
return await create_user_without_org(request, db_session, None, user_object)
|
||||
return await create_user_without_org(request, db_session, current_user, user_object)
|
||||
|
||||
|
||||
@router.get("/user_id/{user_id}", response_model=UserRead, tags=["users"])
|
||||
|
|
@ -64,12 +68,13 @@ async def api_get_user_by_id(
|
|||
*,
|
||||
request: Request,
|
||||
db_session: Session = Depends(get_db_session),
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
user_id: int,
|
||||
) -> UserRead:
|
||||
"""
|
||||
Get User by ID
|
||||
"""
|
||||
return await read_user_by_id(request, db_session, None, user_id)
|
||||
return await read_user_by_id(request, db_session, current_user, user_id)
|
||||
|
||||
|
||||
@router.get("/user_uuid/{user_uuid}", response_model=UserRead, tags=["users"])
|
||||
|
|
@ -77,12 +82,13 @@ async def api_get_user_by_uuid(
|
|||
*,
|
||||
request: Request,
|
||||
db_session: Session = Depends(get_db_session),
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
user_uuid: str,
|
||||
) -> UserRead:
|
||||
"""
|
||||
Get User by UUID
|
||||
"""
|
||||
return await read_user_by_uuid(request, db_session, None, user_uuid)
|
||||
return await read_user_by_uuid(request, db_session, current_user, user_uuid)
|
||||
|
||||
|
||||
@router.put("/", response_model=UserRead, tags=["users"])
|
||||
|
|
@ -90,12 +96,13 @@ async def api_update_user(
|
|||
*,
|
||||
request: Request,
|
||||
db_session: Session = Depends(get_db_session),
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
user_object: UserUpdate,
|
||||
) -> UserRead:
|
||||
"""
|
||||
Update User
|
||||
"""
|
||||
return await update_user(request, db_session, None, user_object)
|
||||
return await update_user(request, db_session, current_user, user_object)
|
||||
|
||||
|
||||
@router.put("/change_password/", response_model=UserRead, tags=["users"])
|
||||
|
|
@ -103,12 +110,13 @@ async def api_update_user_password(
|
|||
*,
|
||||
request: Request,
|
||||
db_session: Session = Depends(get_db_session),
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
form: UserUpdatePassword,
|
||||
) -> UserRead:
|
||||
"""
|
||||
Update User Password
|
||||
"""
|
||||
return await update_user_password(request, db_session, None, form)
|
||||
return await update_user_password(request, db_session, current_user, form)
|
||||
|
||||
|
||||
@router.delete("/user_id/{user_id}", tags=["users"])
|
||||
|
|
@ -116,9 +124,10 @@ async def api_delete_user(
|
|||
*,
|
||||
request: Request,
|
||||
db_session: Session = Depends(get_db_session),
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
user_id: int,
|
||||
):
|
||||
"""
|
||||
Delete User
|
||||
"""
|
||||
return await delete_user_by_id(request, db_session, None, user_id)
|
||||
return await delete_user_by_id(request, db_session, current_user, user_id)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from sqlmodel import Session
|
||||
from src.core.events.database import get_db_session
|
||||
from src.db.users import AnonymousUser, User, UserRead
|
||||
from src.db.users import AnonymousUser, PublicUser, User, UserRead
|
||||
from src.services.users.users import security_get_user
|
||||
from config.config import get_learnhouse_config
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -94,7 +94,7 @@ async def get_current_user(
|
|||
user = await security_get_user(request, db_session, email=token_data.username) # type: ignore # treated as an email
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return UserRead(**user.dict())
|
||||
return PublicUser(**user.dict())
|
||||
else:
|
||||
return AnonymousUser()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,27 +11,21 @@ from src.db.user_organizations import UserOrganization
|
|||
from src.security.rbac.utils import check_element_type
|
||||
|
||||
|
||||
# Tested and working
|
||||
async def authorization_verify_if_element_is_public(
|
||||
request,
|
||||
element_uuid: str,
|
||||
user_id: str,
|
||||
action: Literal["read"],
|
||||
db_session: Session,
|
||||
):
|
||||
element_nature = await check_element_type(element_uuid)
|
||||
|
||||
# Verifies if the element is public
|
||||
if (
|
||||
element_nature == ("courses" or "collections")
|
||||
and action == "read"
|
||||
and user_id == "anonymous"
|
||||
):
|
||||
if element_nature == ("courses" or "collections") and action == "read":
|
||||
if element_nature == "courses":
|
||||
statement = select(Course).where(
|
||||
Course.public == True, Course.course_uuid == element_uuid
|
||||
)
|
||||
course = db_session.exec(statement).first()
|
||||
|
||||
if course:
|
||||
return True
|
||||
else:
|
||||
|
|
@ -60,9 +54,10 @@ async def authorization_verify_if_element_is_public(
|
|||
)
|
||||
|
||||
|
||||
# Tested and working
|
||||
async def authorization_verify_if_user_is_author(
|
||||
request,
|
||||
user_id: str,
|
||||
user_id: int,
|
||||
action: Literal["read", "update", "delete", "create"],
|
||||
element_uuid: str,
|
||||
db_session: Session,
|
||||
|
|
@ -74,26 +69,23 @@ async def authorization_verify_if_user_is_author(
|
|||
resource_author = db_session.exec(statement).first()
|
||||
|
||||
if resource_author:
|
||||
if resource_author.user_id == user_id:
|
||||
if resource_author.user_id == int(user_id):
|
||||
if (resource_author.authorship == ResourceAuthorshipEnum.CREATOR) or (
|
||||
resource_author.authorship == ResourceAuthorshipEnum.MAINTAINER
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User rights (authorship) : You don't have the right to perform this action",
|
||||
)
|
||||
return False
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Wrong action (create)",
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
# Tested and working
|
||||
async def authorization_verify_based_on_roles(
|
||||
request: Request,
|
||||
user_id: str,
|
||||
user_id: int,
|
||||
action: Literal["read", "update", "delete", "create"],
|
||||
element_uuid: str,
|
||||
db_session: Session,
|
||||
|
|
@ -104,8 +96,8 @@ async def authorization_verify_based_on_roles(
|
|||
statement = (
|
||||
select(Role)
|
||||
.join(UserOrganization)
|
||||
.where((UserOrganization.org_id == Role.org_id) | (Role.org_id == null()))
|
||||
.where(UserOrganization.user_id == user_id)
|
||||
.where((UserOrganization.id == Role.org_id) | (UserOrganization.id == null))
|
||||
)
|
||||
|
||||
user_roles_in_organization_and_standard_roles = db_session.exec(statement).all()
|
||||
|
|
@ -120,15 +112,13 @@ async def authorization_verify_based_on_roles(
|
|||
else:
|
||||
return False
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User rights (roles) : You don't have the right to perform this action",
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
# Tested and working
|
||||
async def authorization_verify_based_on_roles_and_authorship(
|
||||
request: Request,
|
||||
user_id: str,
|
||||
user_id: int,
|
||||
action: Literal["read", "update", "delete", "create"],
|
||||
element_uuid: str,
|
||||
db_session: Session,
|
||||
|
|
@ -150,8 +140,8 @@ async def authorization_verify_based_on_roles_and_authorship(
|
|||
)
|
||||
|
||||
|
||||
async def authorization_verify_if_user_is_anon(user_id: str):
|
||||
if user_id == "anonymous":
|
||||
async def authorization_verify_if_user_is_anon(user_id: int):
|
||||
if user_id == 0:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You should be logged in to perform this action",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
from typing import Literal
|
||||
from sqlmodel import Session, select
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_user_is_anon,
|
||||
)
|
||||
from src.db.organizations import Organization
|
||||
from src.db.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate
|
||||
from src.db.chapter_activities import ChapterActivity
|
||||
from src.db.users import PublicUser
|
||||
from src.db.users import AnonymousUser, PublicUser
|
||||
from fastapi import HTTPException, Request
|
||||
from uuid import uuid4
|
||||
from datetime import datetime
|
||||
|
|
@ -16,7 +21,7 @@ from datetime import datetime
|
|||
async def create_activity(
|
||||
request: Request,
|
||||
activity_object: ActivityCreate,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
activity = Activity.from_orm(activity_object)
|
||||
|
|
@ -31,6 +36,9 @@ async def create_activity(
|
|||
detail="Organization not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, org.org_uuid, current_user, "create", db_session)
|
||||
|
||||
activity.activity_uuid = str(f"activity_{uuid4()}")
|
||||
activity.creation_date = str(datetime.now())
|
||||
activity.update_date = str(datetime.now())
|
||||
|
|
@ -85,13 +93,16 @@ async def get_activity(
|
|||
detail="Activity not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, activity.activity_uuid, current_user, "read", db_session)
|
||||
|
||||
return activity
|
||||
|
||||
|
||||
async def update_activity(
|
||||
request: Request,
|
||||
activity_object: ActivityUpdate,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Activity).where(Activity.id == activity_object.activity_id)
|
||||
|
|
@ -103,6 +114,11 @@ async def update_activity(
|
|||
detail="Activity not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(
|
||||
request, activity.activity_uuid, current_user, "update", db_session
|
||||
)
|
||||
|
||||
del activity_object.activity_id
|
||||
|
||||
# Update only the fields that were passed in
|
||||
|
|
@ -120,7 +136,7 @@ async def update_activity(
|
|||
async def delete_activity(
|
||||
request: Request,
|
||||
activity_id: str,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Activity).where(Activity.id == activity_id)
|
||||
|
|
@ -132,6 +148,11 @@ async def delete_activity(
|
|||
detail="Activity not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(
|
||||
request, activity.activity_uuid, current_user, "delete", db_session
|
||||
)
|
||||
|
||||
# Delete activity from chapter
|
||||
statement = select(ChapterActivity).where(
|
||||
ChapterActivity.activity_id == activity_id
|
||||
|
|
@ -159,7 +180,7 @@ async def delete_activity(
|
|||
async def get_activities(
|
||||
request: Request,
|
||||
coursechapter_id: str,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(ChapterActivity).where(
|
||||
|
|
@ -173,4 +194,31 @@ async def get_activities(
|
|||
detail="No activities found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, "activity_x", current_user, "read", db_session)
|
||||
|
||||
return activities
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
db_session: Session,
|
||||
):
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
current_user.id,
|
||||
action,
|
||||
course_id,
|
||||
db_session,
|
||||
)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
from typing import Literal
|
||||
from sqlmodel import Session, select
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_user_is_anon,
|
||||
)
|
||||
from src.db.chapters import Chapter
|
||||
from src.db.activities import (
|
||||
Activity,
|
||||
|
|
@ -8,7 +13,7 @@ from src.db.activities import (
|
|||
)
|
||||
from src.db.chapter_activities import ChapterActivity
|
||||
from src.db.course_chapters import CourseChapter
|
||||
from src.db.users import PublicUser
|
||||
from src.db.users import AnonymousUser, PublicUser
|
||||
from src.services.courses.activities.uploads.pdfs import upload_pdf
|
||||
from fastapi import HTTPException, status, UploadFile, Request
|
||||
from uuid import uuid4
|
||||
|
|
@ -19,10 +24,13 @@ async def create_documentpdf_activity(
|
|||
request: Request,
|
||||
name: str,
|
||||
chapter_id: str,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
pdf_file: UploadFile | None = None,
|
||||
):
|
||||
# RBAC check
|
||||
await rbac_check(request, "activity_x", current_user, "create", db_session)
|
||||
|
||||
# get chapter_id
|
||||
statement = select(Chapter).where(Chapter.id == chapter_id)
|
||||
chapter = db_session.exec(statement).first()
|
||||
|
|
@ -94,7 +102,7 @@ async def create_documentpdf_activity(
|
|||
# Add activity to chapter
|
||||
activity_chapter = ChapterActivity(
|
||||
chapter_id=(int(chapter_id)),
|
||||
activity_id=activity.id is not None,
|
||||
activity_id=activity.id, # type: ignore
|
||||
course_id=coursechapter.course_id,
|
||||
org_id=coursechapter.org_id,
|
||||
creation_date=str(datetime.now()),
|
||||
|
|
@ -113,3 +121,27 @@ async def create_documentpdf_activity(
|
|||
db_session.refresh(activity_chapter)
|
||||
|
||||
return ActivityRead.from_orm(activity)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
db_session: Session,
|
||||
):
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
current_user.id,
|
||||
action,
|
||||
course_id,
|
||||
db_session,
|
||||
)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
|
|
@ -2,11 +2,20 @@ from typing import Literal
|
|||
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import Session, select
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_user_is_anon,
|
||||
)
|
||||
from src.db.chapters import Chapter
|
||||
from src.db.activities import Activity, ActivityRead, ActivitySubTypeEnum, ActivityTypeEnum
|
||||
from src.db.activities import (
|
||||
Activity,
|
||||
ActivityRead,
|
||||
ActivitySubTypeEnum,
|
||||
ActivityTypeEnum,
|
||||
)
|
||||
from src.db.chapter_activities import ChapterActivity
|
||||
from src.db.course_chapters import CourseChapter
|
||||
from src.db.users import PublicUser
|
||||
from src.db.users import AnonymousUser, PublicUser
|
||||
from src.services.courses.activities.uploads.videos import upload_video
|
||||
from fastapi import HTTPException, status, UploadFile, Request
|
||||
from uuid import uuid4
|
||||
|
|
@ -21,6 +30,9 @@ async def create_video_activity(
|
|||
db_session: Session,
|
||||
video_file: UploadFile | None = None,
|
||||
):
|
||||
# RBAC check
|
||||
await rbac_check(request, "activity_x", current_user, "create", db_session)
|
||||
|
||||
# get chapter_id
|
||||
statement = select(Chapter).where(Chapter.id == chapter_id)
|
||||
chapter = db_session.exec(statement).first()
|
||||
|
|
@ -95,8 +107,8 @@ async def create_video_activity(
|
|||
|
||||
# update chapter
|
||||
chapter_activity_object = ChapterActivity(
|
||||
chapter_id=coursechapter.id is not None,
|
||||
activity_id=activity.id is not None,
|
||||
chapter_id=chapter.id, # type: ignore
|
||||
activity_id=activity.id, # type: ignore
|
||||
course_id=coursechapter.course_id,
|
||||
org_id=coursechapter.org_id,
|
||||
creation_date=str(datetime.now()),
|
||||
|
|
@ -111,6 +123,7 @@ async def create_video_activity(
|
|||
|
||||
return ActivityRead.from_orm(activity)
|
||||
|
||||
|
||||
class ExternalVideo(BaseModel):
|
||||
name: str
|
||||
uri: str
|
||||
|
|
@ -124,10 +137,13 @@ class ExternalVideoInDB(BaseModel):
|
|||
|
||||
async def create_external_video_activity(
|
||||
request: Request,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
data: ExternalVideo,
|
||||
db_session: Session,
|
||||
):
|
||||
# RBAC check
|
||||
await rbac_check(request, "activity_x", current_user, "create", db_session)
|
||||
|
||||
# get chapter_id
|
||||
statement = select(Chapter).where(Chapter.id == data.chapter_id)
|
||||
chapter = db_session.exec(statement).first()
|
||||
|
|
@ -174,8 +190,8 @@ async def create_external_video_activity(
|
|||
|
||||
# update chapter
|
||||
chapter_activity_object = ChapterActivity(
|
||||
chapter_id=coursechapter.id is not None,
|
||||
activity_id=activity.id is not None,
|
||||
chapter_id=coursechapter.id, # type: ignore
|
||||
activity_id=activity.id, # type: ignore
|
||||
creation_date=str(datetime.now()),
|
||||
update_date=str(datetime.now()),
|
||||
order=1,
|
||||
|
|
@ -186,3 +202,24 @@ async def create_external_video_activity(
|
|||
db_session.commit()
|
||||
|
||||
return ActivityRead.from_orm(activity)
|
||||
|
||||
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
db_session: Session,
|
||||
):
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
current_user.id,
|
||||
action,
|
||||
course_id,
|
||||
db_session,
|
||||
)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
from datetime import datetime
|
||||
from typing import List
|
||||
from typing import List, Literal
|
||||
from uuid import uuid4
|
||||
from sqlmodel import Session, select
|
||||
from src.db.users import AnonymousUser
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_user_is_anon,
|
||||
)
|
||||
from src.db.course_chapters import CourseChapter
|
||||
from src.db.activities import Activity, ActivityRead
|
||||
from src.db.chapter_activities import ChapterActivity
|
||||
|
|
@ -26,11 +31,14 @@ from fastapi import HTTPException, status, Request
|
|||
async def create_chapter(
|
||||
request: Request,
|
||||
chapter_object: ChapterCreate,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
) -> ChapterRead:
|
||||
chapter = Chapter.from_orm(chapter_object)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, "chapter_x", current_user, "create", db_session)
|
||||
|
||||
# complete chapter object
|
||||
chapter.course_id = chapter_object.course_id
|
||||
chapter.chapter_uuid = f"chapter_{uuid4()}"
|
||||
|
|
@ -87,7 +95,7 @@ async def create_chapter(
|
|||
async def get_chapter(
|
||||
request: Request,
|
||||
chapter_id: int,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
) -> ChapterRead:
|
||||
statement = select(Chapter).where(Chapter.id == chapter_id)
|
||||
|
|
@ -98,6 +106,9 @@ async def get_chapter(
|
|||
status_code=status.HTTP_409_CONFLICT, detail="Chapter does not exist"
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, chapter.chapter_uuid, current_user, "read", db_session)
|
||||
|
||||
# Get activities for this chapter
|
||||
statement = (
|
||||
select(Activity)
|
||||
|
|
@ -119,7 +130,7 @@ async def get_chapter(
|
|||
async def update_chapter(
|
||||
request: Request,
|
||||
chapter_object: ChapterUpdate,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
) -> ChapterRead:
|
||||
statement = select(Chapter).where(Chapter.id == chapter_object.chapter_id)
|
||||
|
|
@ -130,6 +141,9 @@ async def update_chapter(
|
|||
status_code=status.HTTP_409_CONFLICT, detail="Chapter does not exist"
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, chapter.chapter_uuid, current_user, "update", db_session)
|
||||
|
||||
# Update only the fields that were passed in
|
||||
for var, value in vars(chapter_object).items():
|
||||
if value is not None:
|
||||
|
|
@ -148,7 +162,7 @@ async def update_chapter(
|
|||
async def delete_chapter(
|
||||
request: Request,
|
||||
chapter_id: str,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Chapter).where(Chapter.id == chapter_id)
|
||||
|
|
@ -159,6 +173,9 @@ async def delete_chapter(
|
|||
status_code=status.HTTP_409_CONFLICT, detail="Chapter does not exist"
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, chapter.chapter_uuid, current_user, "delete", db_session)
|
||||
|
||||
db_session.delete(chapter)
|
||||
db_session.commit()
|
||||
|
||||
|
|
@ -173,15 +190,12 @@ async def delete_chapter(
|
|||
return {"detail": "chapter deleted"}
|
||||
|
||||
|
||||
####################################################
|
||||
# Misc
|
||||
####################################################
|
||||
|
||||
|
||||
async def get_course_chapters(
|
||||
request: Request,
|
||||
course_id: int,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
page: int = 1,
|
||||
limit: int = 10,
|
||||
) -> List[ChapterRead]:
|
||||
|
|
@ -195,6 +209,9 @@ async def get_course_chapters(
|
|||
|
||||
chapters = [ChapterRead(**chapter.dict(), activities=[]) for chapter in chapters]
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, "chapter_x", current_user, "read", db_session)
|
||||
|
||||
# Get activities for each chapter
|
||||
for chapter in chapters:
|
||||
statement = (
|
||||
|
|
@ -233,6 +250,9 @@ async def get_depreceated_course_chapters(
|
|||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||
|
||||
# Get chapters that are linked to his course and order them by order, using the order field in the CourseChapter table
|
||||
statement = (
|
||||
select(Chapter)
|
||||
|
|
@ -310,6 +330,9 @@ async def reorder_chapters_and_activities(
|
|||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
||||
|
||||
###########
|
||||
# Chapters
|
||||
###########
|
||||
|
|
@ -469,3 +492,27 @@ async def reorder_chapters_and_activities(
|
|||
db_session.commit()
|
||||
|
||||
return {"detail": "Chapters reordered"}
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
db_session: Session,
|
||||
):
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
current_user.id,
|
||||
action,
|
||||
course_id,
|
||||
db_session,
|
||||
)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
from datetime import datetime
|
||||
from typing import List
|
||||
from typing import List, Literal
|
||||
from uuid import uuid4
|
||||
from sqlmodel import Session, select
|
||||
from src.db.users import AnonymousUser
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_user_is_anon,
|
||||
)
|
||||
from src.db.collections import (
|
||||
Collection,
|
||||
CollectionCreate,
|
||||
|
|
@ -37,6 +42,11 @@ async def get_collection(
|
|||
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(
|
||||
request, collection.collection_uuid, current_user, "read", db_session
|
||||
)
|
||||
|
||||
# get courses in collection
|
||||
statement = (
|
||||
select(Course)
|
||||
|
|
@ -58,6 +68,9 @@ async def create_collection(
|
|||
) -> CollectionRead:
|
||||
collection = Collection.from_orm(collection_object)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, "collection_x", current_user, "create", db_session)
|
||||
|
||||
# Complete the collection object
|
||||
collection.collection_uuid = f"collection_{uuid4()}"
|
||||
collection.creation_date = str(datetime.now())
|
||||
|
|
@ -70,16 +83,17 @@ async def create_collection(
|
|||
db_session.refresh(collection)
|
||||
|
||||
# Link courses to collection
|
||||
for course in collection_object.courses:
|
||||
collection_course = CollectionCourse(
|
||||
collection_id=int(collection.id is not None),
|
||||
course_id=int(course),
|
||||
org_id=int(collection_object.org_id),
|
||||
creation_date=str(datetime.now()),
|
||||
update_date=str(datetime.now()),
|
||||
)
|
||||
# Add collection_course to database
|
||||
db_session.add(collection_course)
|
||||
if collection:
|
||||
for course_id in collection_object.courses:
|
||||
collection_course = CollectionCourse(
|
||||
collection_id=int(collection.id), # type: ignore
|
||||
course_id=course_id,
|
||||
org_id=int(collection_object.org_id),
|
||||
creation_date=str(datetime.now()),
|
||||
update_date=str(datetime.now()),
|
||||
)
|
||||
# Add collection_course to database
|
||||
db_session.add(collection_course)
|
||||
|
||||
db_session.commit()
|
||||
db_session.refresh(collection)
|
||||
|
|
@ -113,6 +127,11 @@ async def update_collection(
|
|||
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(
|
||||
request, collection.collection_uuid, current_user, "update", db_session
|
||||
)
|
||||
|
||||
courses = collection_object.courses
|
||||
|
||||
del collection_object.collection_id
|
||||
|
|
@ -142,7 +161,7 @@ async def update_collection(
|
|||
# Add new collection_courses
|
||||
for course in courses or []:
|
||||
collection_course = CollectionCourse(
|
||||
collection_id=int(collection.id is not None),
|
||||
collection_id=int(collection.id), # type: ignore
|
||||
course_id=int(course),
|
||||
org_id=int(collection.org_id),
|
||||
creation_date=str(datetime.now()),
|
||||
|
|
@ -180,6 +199,11 @@ async def delete_collection(
|
|||
detail="Collection not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(
|
||||
request, collection.collection_uuid, current_user, "delete", db_session
|
||||
)
|
||||
|
||||
# delete collection from database
|
||||
db_session.delete(collection)
|
||||
db_session.commit()
|
||||
|
|
@ -195,11 +219,14 @@ async def delete_collection(
|
|||
async def get_collections(
|
||||
request: Request,
|
||||
org_id: str,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
page: int = 1,
|
||||
limit: int = 10,
|
||||
) -> List[CollectionRead]:
|
||||
# RBAC check
|
||||
await rbac_check(request, "collection_x", current_user, "read", db_session)
|
||||
|
||||
statement = (
|
||||
select(Collection).where(Collection.org_id == org_id).distinct(Collection.id)
|
||||
)
|
||||
|
|
@ -223,3 +250,27 @@ async def get_collections(
|
|||
collections_with_courses.append(collection)
|
||||
|
||||
return collections_with_courses
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
db_session: Session,
|
||||
):
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
current_user.id,
|
||||
action,
|
||||
course_id,
|
||||
db_session,
|
||||
)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
|
|
@ -1,12 +1,28 @@
|
|||
from calendar import c
|
||||
import json
|
||||
from queue import Full
|
||||
import resource
|
||||
from typing import Literal
|
||||
from uuid import uuid4
|
||||
from sqlmodel import Session, select
|
||||
from src.db import chapters
|
||||
from src.db.activities import Activity, ActivityRead
|
||||
from src.db.chapter_activities import ChapterActivity
|
||||
from src.db.chapters import Chapter, ChapterRead
|
||||
from src.db.organizations import Organization
|
||||
from src.db.trails import TrailRead
|
||||
|
||||
from src.services.trail.trail import get_user_trail_with_orgid
|
||||
from src import db
|
||||
from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum
|
||||
from src.db.users import PublicUser, AnonymousUser
|
||||
from src.db.courses import Course, CourseCreate, CourseRead, CourseUpdate
|
||||
from src.db.courses import (
|
||||
Course,
|
||||
CourseCreate,
|
||||
CourseRead,
|
||||
CourseUpdate,
|
||||
FullCourseReadWithTrail,
|
||||
)
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_element_is_public,
|
||||
|
|
@ -18,7 +34,10 @@ from datetime import datetime
|
|||
|
||||
|
||||
async def get_course(
|
||||
request: Request, course_id: str, current_user: PublicUser, db_session: Session
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
|
@ -29,12 +48,21 @@ async def get_course(
|
|||
detail="Course not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||
|
||||
return course
|
||||
|
||||
|
||||
async def get_course_meta(
|
||||
request: Request, course_id: str, current_user: PublicUser, db_session: Session
|
||||
):
|
||||
request: Request,
|
||||
course_id: int,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
) -> FullCourseReadWithTrail:
|
||||
# Avoid circular import
|
||||
from src.services.courses.chapters import get_course_chapters
|
||||
|
||||
course_statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(course_statement).first()
|
||||
|
||||
|
|
@ -44,22 +72,40 @@ async def get_course_meta(
|
|||
detail="Course not found",
|
||||
)
|
||||
|
||||
# todo : get course chapters
|
||||
# todo : get course activities
|
||||
# todo : get trail
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||
|
||||
return course
|
||||
course = CourseRead.from_orm(course)
|
||||
|
||||
# Get course chapters
|
||||
chapters = await get_course_chapters(request, course.id, db_session, current_user)
|
||||
|
||||
# Trail
|
||||
trail = await get_user_trail_with_orgid(
|
||||
request, current_user, course.org_id, db_session
|
||||
)
|
||||
|
||||
trail = TrailRead.from_orm(trail)
|
||||
|
||||
return FullCourseReadWithTrail(
|
||||
**course.dict(),
|
||||
chapters=chapters,
|
||||
trail=trail,
|
||||
)
|
||||
|
||||
|
||||
async def create_course(
|
||||
request: Request,
|
||||
course_object: CourseCreate,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
thumbnail_file: UploadFile | None = None,
|
||||
):
|
||||
course = Course.from_orm(course_object)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, "course_x", current_user, "create", db_session)
|
||||
|
||||
# Complete course object
|
||||
course.org_id = course.org_id
|
||||
course.course_uuid = str(f"course_{uuid4()}")
|
||||
|
|
@ -69,7 +115,9 @@ async def create_course(
|
|||
# Upload thumbnail
|
||||
if thumbnail_file and thumbnail_file.filename:
|
||||
name_in_disk = f"{course.course_uuid}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
||||
await upload_thumbnail(thumbnail_file, name_in_disk, course_object.org_id, course.course_uuid)
|
||||
await upload_thumbnail(
|
||||
thumbnail_file, name_in_disk, course_object.org_id, course.course_uuid
|
||||
)
|
||||
course_object.thumbnail = name_in_disk
|
||||
|
||||
# Insert course
|
||||
|
|
@ -97,7 +145,7 @@ async def create_course(
|
|||
async def update_course_thumbnail(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
thumbnail_file: UploadFile | None = None,
|
||||
):
|
||||
|
|
@ -112,6 +160,9 @@ async def update_course_thumbnail(
|
|||
detail="Course not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
||||
|
||||
# Upload thumbnail
|
||||
if thumbnail_file and thumbnail_file.filename:
|
||||
name_in_disk = (
|
||||
|
|
@ -143,7 +194,7 @@ async def update_course_thumbnail(
|
|||
async def update_course(
|
||||
request: Request,
|
||||
course_object: CourseUpdate,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Course).where(Course.id == course_object.course_id)
|
||||
|
|
@ -154,7 +205,10 @@ async def update_course(
|
|||
status_code=404,
|
||||
detail="Course not found",
|
||||
)
|
||||
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
||||
|
||||
del course_object.course_id
|
||||
|
||||
# Update only the fields that were passed in
|
||||
|
|
@ -173,7 +227,10 @@ async def update_course(
|
|||
|
||||
|
||||
async def delete_course(
|
||||
request: Request, course_id: str, current_user: PublicUser, db_session: Session
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
|
@ -184,92 +241,74 @@ async def delete_course(
|
|||
detail="Course not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "delete", db_session)
|
||||
|
||||
db_session.delete(course)
|
||||
db_session.commit()
|
||||
|
||||
return {"detail": "Course deleted"}
|
||||
|
||||
|
||||
####################################################
|
||||
# Misc
|
||||
####################################################
|
||||
|
||||
|
||||
async def get_courses_orgslug(
|
||||
request: Request,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
org_slug: str,
|
||||
db_session: Session,
|
||||
page: int = 1,
|
||||
limit: int = 10,
|
||||
org_slug: str | None = None,
|
||||
):
|
||||
courses = request.app.db["courses"]
|
||||
orgs = request.app.db["organizations"]
|
||||
statement_public = (
|
||||
select(Course)
|
||||
.join(Organization)
|
||||
.where(Organization.slug == org_slug, Course.public == True)
|
||||
)
|
||||
statement_all = (
|
||||
select(Course).join(Organization).where(Organization.slug == org_slug)
|
||||
)
|
||||
|
||||
# get org_id from slug
|
||||
org = await orgs.find_one({"slug": org_slug})
|
||||
|
||||
if not org:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
|
||||
)
|
||||
|
||||
# show only public courses if user is not logged in
|
||||
if current_user.id == "anonymous":
|
||||
all_courses = (
|
||||
courses.find({"org_id": org["org_id"], "public": True})
|
||||
.sort("name", 1)
|
||||
.skip(10 * (page - 1))
|
||||
.limit(limit)
|
||||
)
|
||||
if current_user.id == 0:
|
||||
statement = statement_public
|
||||
else:
|
||||
all_courses = (
|
||||
courses.find({"org_id": org["org_id"]})
|
||||
.sort("name", 1)
|
||||
.skip(10 * (page - 1))
|
||||
.limit(limit)
|
||||
)
|
||||
# RBAC check
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
return [
|
||||
json.loads(json.dumps(course, default=str))
|
||||
for course in await all_courses.to_list(length=100)
|
||||
]
|
||||
statement = statement_all
|
||||
|
||||
courses = db_session.exec(statement)
|
||||
|
||||
return courses
|
||||
|
||||
|
||||
#### Security ####################################################
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
||||
async def verify_rights(
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
course_uuid: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
db_session: Session,
|
||||
):
|
||||
if action == "read":
|
||||
if current_user.id == "anonymous":
|
||||
if current_user.id == 0: # Anonymous user
|
||||
await authorization_verify_if_element_is_public(
|
||||
request, course_id, str(current_user.id), action, db_session
|
||||
request, course_uuid, action, db_session
|
||||
)
|
||||
else:
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
str(current_user.id),
|
||||
action,
|
||||
course_id,
|
||||
db_session,
|
||||
request, current_user.id, action, course_uuid, db_session
|
||||
)
|
||||
else:
|
||||
|
||||
await authorization_verify_if_user_is_anon(str(current_user.id))
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
str(current_user.id),
|
||||
current_user.id,
|
||||
action,
|
||||
course_id,
|
||||
course_uuid,
|
||||
db_session,
|
||||
)
|
||||
|
||||
|
||||
#### Security ####################################################
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
from datetime import datetime
|
||||
from typing import Literal
|
||||
from uuid import uuid4
|
||||
from sqlmodel import Session, select
|
||||
from src.db.users import PublicUser
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_user_is_anon,
|
||||
)
|
||||
from src.db.users import AnonymousUser, PublicUser
|
||||
from src.db.user_organizations import UserOrganization
|
||||
from src.db.organizations import (
|
||||
Organization,
|
||||
|
|
@ -13,7 +18,12 @@ from src.services.orgs.logos import upload_org_logo
|
|||
from fastapi import HTTPException, UploadFile, status, Request
|
||||
|
||||
|
||||
async def get_organization(request: Request, org_id: str, db_session: Session):
|
||||
async def get_organization(
|
||||
request: Request,
|
||||
org_id: str,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
):
|
||||
statement = select(Organization).where(Organization.id == org_id)
|
||||
result = db_session.exec(statement)
|
||||
|
||||
|
|
@ -25,11 +35,17 @@ async def get_organization(request: Request, org_id: str, db_session: Session):
|
|||
detail="Organization not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, org.org_uuid, current_user, "read", db_session)
|
||||
|
||||
return org
|
||||
|
||||
|
||||
async def get_organization_by_slug(
|
||||
request: Request, org_slug: str, db_session: Session
|
||||
request: Request,
|
||||
org_slug: str,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
):
|
||||
statement = select(Organization).where(Organization.slug == org_slug)
|
||||
result = db_session.exec(statement)
|
||||
|
|
@ -42,13 +58,16 @@ async def get_organization_by_slug(
|
|||
detail="Organization not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, org.org_uuid, current_user, "read", db_session)
|
||||
|
||||
return org
|
||||
|
||||
|
||||
async def create_org(
|
||||
request: Request,
|
||||
org_object: OrganizationCreate,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Organization).where(Organization.slug == org_object.slug)
|
||||
|
|
@ -64,6 +83,9 @@ async def create_org(
|
|||
|
||||
org = Organization.from_orm(org_object)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, org.org_uuid, current_user, "create", db_session)
|
||||
|
||||
# Complete the org object
|
||||
org.org_uuid = f"org_{uuid4()}"
|
||||
org.creation_date = str(datetime.now())
|
||||
|
|
@ -92,7 +114,7 @@ async def create_org(
|
|||
async def update_org(
|
||||
request: Request,
|
||||
org_object: OrganizationUpdate,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Organization).where(Organization.id == org_object.org_id)
|
||||
|
|
@ -106,6 +128,9 @@ async def update_org(
|
|||
detail="Organization slug not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, org.org_uuid, current_user, "update", db_session)
|
||||
|
||||
org = Organization.from_orm(org_object)
|
||||
|
||||
# Verify if the new slug is already in use
|
||||
|
|
@ -142,7 +167,7 @@ async def update_org_logo(
|
|||
request: Request,
|
||||
logo_file: UploadFile,
|
||||
org_id: str,
|
||||
current_user: PublicUser,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Organization).where(Organization.id == org_id)
|
||||
|
|
@ -156,6 +181,9 @@ async def update_org_logo(
|
|||
detail="Organization not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, org.org_uuid, current_user, "update", db_session)
|
||||
|
||||
# Upload logo
|
||||
name_in_disk = await upload_org_logo(logo_file, org_id)
|
||||
|
||||
|
|
@ -173,7 +201,10 @@ async def update_org_logo(
|
|||
|
||||
|
||||
async def delete_org(
|
||||
request: Request, org_id: str, current_user: PublicUser, db_session: Session
|
||||
request: Request,
|
||||
org_id: int,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Organization).where(Organization.id == org_id)
|
||||
result = db_session.exec(statement)
|
||||
|
|
@ -186,6 +217,9 @@ async def delete_org(
|
|||
detail="Organization not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, org.org_uuid, current_user, "delete", db_session)
|
||||
|
||||
db_session.delete(org)
|
||||
db_session.commit()
|
||||
|
||||
|
|
@ -224,3 +258,28 @@ async def get_orgs_by_user(
|
|||
orgs = result.all()
|
||||
|
||||
return orgs
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
org_id: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
db_session: Session,
|
||||
):
|
||||
# Organizations are readable by anyone
|
||||
if action == "read":
|
||||
return True
|
||||
|
||||
else:
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request, current_user.id, action, org_id, db_session
|
||||
)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
from typing import Literal
|
||||
from uuid import uuid4
|
||||
from sqlmodel import Session, select
|
||||
from src.db.users import PublicUser
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_user_is_anon,
|
||||
authorization_verify_if_user_is_author,
|
||||
)
|
||||
from src.db.users import AnonymousUser, PublicUser
|
||||
from src.db.roles import Role, RoleCreate, RoleUpdate
|
||||
from fastapi import HTTPException, Request
|
||||
from datetime import datetime
|
||||
|
|
@ -14,6 +20,9 @@ async def create_role(
|
|||
):
|
||||
role = Role.from_orm(role_object)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "create", "role_xxx", db_session)
|
||||
|
||||
# Complete the role object
|
||||
role.role_uuid = f"role_{uuid4()}"
|
||||
role.creation_date = str(datetime.now())
|
||||
|
|
@ -40,6 +49,9 @@ async def read_role(
|
|||
detail="Role not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "read", role.role_uuid, db_session)
|
||||
|
||||
return role
|
||||
|
||||
|
||||
|
|
@ -60,6 +72,9 @@ async def update_role(
|
|||
detail="Role not found",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "update", role.role_uuid, db_session)
|
||||
|
||||
# Complete the role object
|
||||
role.update_date = str(datetime.now())
|
||||
|
||||
|
|
@ -81,6 +96,9 @@ async def update_role(
|
|||
async def delete_role(
|
||||
request: Request, db_session: Session, role_id: str, current_user: PublicUser
|
||||
):
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "delete", role_id, db_session)
|
||||
|
||||
statement = select(Role).where(Role.id == role_id)
|
||||
result = db_session.exec(statement)
|
||||
|
||||
|
|
@ -96,3 +114,23 @@ async def delete_role(
|
|||
db_session.commit()
|
||||
|
||||
return "Role deleted"
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
role_uuid: str,
|
||||
db_session: Session,
|
||||
):
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request, current_user.id, action, role_uuid, db_session
|
||||
)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from src.db.courses import Course
|
|||
from src.db.trail_runs import TrailRun, TrailRunRead
|
||||
from src.db.trail_steps import TrailStep
|
||||
from src.db.trails import Trail, TrailCreate, TrailRead
|
||||
from src.db.users import PublicUser
|
||||
from src.db.users import AnonymousUser, PublicUser
|
||||
|
||||
|
||||
async def create_user_trail(
|
||||
|
|
@ -80,7 +80,7 @@ async def get_user_trails(
|
|||
|
||||
|
||||
async def get_user_trail_with_orgid(
|
||||
request: Request, user: PublicUser, org_id: int, db_session: Session
|
||||
request: Request, user: PublicUser | AnonymousUser, org_id: int, db_session: Session
|
||||
) -> TrailRead:
|
||||
statement = select(Trail).where(Trail.org_id == org_id, Trail.user_id == user.id)
|
||||
trail = db_session.exec(statement).first()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
from datetime import datetime
|
||||
from typing import Literal
|
||||
from uuid import uuid4
|
||||
from fastapi import HTTPException, Request, status
|
||||
from sqlmodel import Session, select
|
||||
from src import db
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles,
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_user_is_anon,
|
||||
)
|
||||
from src.db.organizations import Organization
|
||||
from src.db.users import (
|
||||
AnonymousUser,
|
||||
PublicUser,
|
||||
User,
|
||||
UserCreate,
|
||||
|
|
@ -18,12 +26,15 @@ from src.security.security import security_hash_password, security_verify_passwo
|
|||
async def create_user(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | None,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
user_object: UserCreate,
|
||||
org_id: int,
|
||||
):
|
||||
user = User.from_orm(user_object)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "create", "user_x", db_session)
|
||||
|
||||
# Complete the user object
|
||||
user.user_uuid = f"user_{uuid4()}"
|
||||
user.password = await security_hash_password(user_object.password)
|
||||
|
|
@ -94,11 +105,14 @@ async def create_user(
|
|||
async def create_user_without_org(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | None,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
user_object: UserCreate,
|
||||
):
|
||||
user = User.from_orm(user_object)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "create", "user_x", db_session)
|
||||
|
||||
# Complete the user object
|
||||
user.user_uuid = f"user_{uuid4()}"
|
||||
user.password = await security_hash_password(user_object.password)
|
||||
|
|
@ -146,7 +160,7 @@ async def create_user_without_org(
|
|||
async def update_user(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | None,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
user_object: UserUpdate,
|
||||
):
|
||||
# Get user
|
||||
|
|
@ -158,6 +172,9 @@ async def update_user(
|
|||
status_code=400,
|
||||
detail="User does not exist",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "update", user.user_uuid, db_session)
|
||||
|
||||
# Update user
|
||||
user_data = user_object.dict(exclude_unset=True)
|
||||
|
|
@ -179,7 +196,7 @@ async def update_user(
|
|||
async def update_user_password(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | None,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
form: UserUpdatePassword,
|
||||
):
|
||||
# Get user
|
||||
|
|
@ -191,6 +208,9 @@ async def update_user_password(
|
|||
status_code=400,
|
||||
detail="User does not exist",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "update", user.user_uuid, db_session)
|
||||
|
||||
if not await security_verify_password(form.old_password, user.password):
|
||||
raise HTTPException(
|
||||
|
|
@ -214,7 +234,7 @@ async def update_user_password(
|
|||
async def read_user_by_id(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | None,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
user_id: int,
|
||||
):
|
||||
# Get user
|
||||
|
|
@ -227,6 +247,9 @@ async def read_user_by_id(
|
|||
detail="User does not exist",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "read", user.user_uuid, db_session)
|
||||
|
||||
user = UserRead.from_orm(user)
|
||||
|
||||
return user
|
||||
|
|
@ -235,11 +258,11 @@ async def read_user_by_id(
|
|||
async def read_user_by_uuid(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | None,
|
||||
uuid: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
user_uuid: str,
|
||||
):
|
||||
# Get user
|
||||
statement = select(User).where(User.user_uuid == uuid)
|
||||
statement = select(User).where(User.user_uuid == user_uuid)
|
||||
user = db_session.exec(statement).first()
|
||||
|
||||
if not user:
|
||||
|
|
@ -248,6 +271,9 @@ async def read_user_by_uuid(
|
|||
detail="User does not exist",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "read", user.user_uuid, db_session)
|
||||
|
||||
user = UserRead.from_orm(user)
|
||||
|
||||
return user
|
||||
|
|
@ -256,7 +282,7 @@ async def read_user_by_uuid(
|
|||
async def delete_user_by_id(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | None,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
user_id: int,
|
||||
):
|
||||
# Get user
|
||||
|
|
@ -269,6 +295,9 @@ async def delete_user_by_id(
|
|||
detail="User does not exist",
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, current_user, "delete", user.user_uuid, db_session)
|
||||
|
||||
# Delete user
|
||||
db_session.delete(user)
|
||||
db_session.commit()
|
||||
|
|
@ -293,3 +322,37 @@ async def security_get_user(request: Request, db_session: Session, email: str) -
|
|||
user = User(**user.dict())
|
||||
|
||||
return user
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
||||
async def rbac_check(
|
||||
request: Request,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
user_uuid: str,
|
||||
db_session: Session,
|
||||
):
|
||||
if action == "create":
|
||||
if current_user.id == 0: # if user is anonymous
|
||||
return True
|
||||
else:
|
||||
res = await authorization_verify_based_on_roles_and_authorship(
|
||||
request, current_user.id, "create", "user_x", db_session
|
||||
)
|
||||
|
||||
|
||||
else:
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
# if user is the same as the one being read
|
||||
if current_user.user_uuid == user_uuid:
|
||||
return True
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request, current_user.id, "read", action, db_session
|
||||
)
|
||||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue