feat: revamp authorization mechanism across app

This commit is contained in:
swve 2023-07-20 01:10:54 +02:00
parent 72c5d13028
commit 3c2f6b3a98
14 changed files with 648 additions and 371 deletions

40
app.py
View file

@ -8,6 +8,9 @@ from fastapi.staticfiles import StaticFiles
from fastapi_jwt_auth.exceptions import AuthJWTException from fastapi_jwt_auth.exceptions import AuthJWTException
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
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.services.users.schemas.users import UserRolesInOrganization
# from src.services.mocks.initial import create_initial_data # from src.services.mocks.initial import create_initial_data
@ -66,3 +69,40 @@ app.include_router(v1_router)
@app.get("/") @app.get("/")
async def root(): async def root():
return {"Message": "Welcome to LearnHouse ✨"} return {"Message": "Welcome to LearnHouse ✨"}
@app.get("/test")
async def rootd(request: Request):
res = await authorization_verify_based_on_roles(
request=request,
user_id="user_c441e47e-5c04-4b03-9886-b0f5cb333c06",
action="read",
roles_list=[
UserRolesInOrganization(
org_id="org_e7085838-2efc-48f3-b414-77318572d9f5", role_id="role_admin"
),
],
element_id="collection_1c277b46-5a4b-440a-ac29-94b874ef7cf4",
)
return res
@app.get("/test2")
async def rootds(request: Request):
res = await authorization_verify_if_user_is_author(
request=request,
user_id="user_c441e47e-5c04-4b03-9886-b0f5cb333c06",
action="read",
element_id="course_1c277b46-5a4b-440a-ac29-94b874ef7cf4",
)
return res
@app.get("/test3")
async def rootdsc(request: Request):
res = await authorization_verify_if_element_is_public(
request=request,
user_id="anonymous",
action="read",
element_id="course_1c277b46-5a4b-440a-ac29-94b874ef7cf4",
)
return res

View file

@ -40,6 +40,7 @@ function NewCollection(params: any) {
name: name, name: name,
description: description, description: description,
courses: selectedCourses, courses: selectedCourses,
public: true,
org_id: org.org_id, org_id: org.org_id,
}; };
await createCollection(collection); await createCollection(collection);

127
src/security/rbac/rbac.py Normal file
View file

@ -0,0 +1,127 @@
from typing import Literal
from fastapi import HTTPException, status, Request
from src.security.rbac.utils import check_element_type, get_id_identifier_of_element
from src.services.roles.schemas.roles import RoleInDB
from src.services.users.schemas.users import UserRolesInOrganization
async def authorization_verify_if_element_is_public(
request,
element_id: str,
user_id: str,
action: Literal["read"],
):
element_nature = await check_element_type(element_id)
# Verifies if the element is public
if (
element_nature == ("courses" or "collections")
and action == "read"
and user_id == "anonymous"
):
if element_nature == "courses":
courses = request.app.db["courses"]
course = await courses.find_one({"course_id": element_id})
if course["public"]:
return True
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User rights (public content) : You don't have the right to perform this action",
)
if element_nature == "collections":
collections = request.app.db["collections"]
collection = await collections.find_one({"collection_id": element_id})
if collection["public"]:
return True
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User rights (public content) : You don't have the right to perform this action",
)
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User rights (public content) : You don't have the right to perform this action",
)
async def authorization_verify_if_user_is_author(
request,
user_id: str,
action: Literal["read", "update", "delete", "create"],
element_id: str,
):
if action == "update" or "delete" or "read":
element_nature = await check_element_type(element_id)
elements = request.app.db[element_nature]
element_identifier = await get_id_identifier_of_element(element_id)
element = await elements.find_one({element_identifier: element_id})
if user_id in element["authors"]:
return True
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User rights (author) : You don't have the right to perform this action",
)
else:
return False
async def authorization_verify_based_on_roles(
request: Request,
user_id: str,
action: Literal["read", "update", "delete", "create"],
roles_list: list[UserRolesInOrganization],
element_id: str,
):
element_type = await check_element_type(element_id)
print(element_type)
element = request.app.db[element_type]
roles = request.app.db["roles"]
# Get the element
element_identifier = await get_id_identifier_of_element(element_id)
element = await element.find_one({element_identifier: element_id})
# Get the roles of the user
roles_id_list = [role["role_id"] for role in roles_list]
roles = await roles.find({"role_id": {"$in": roles_id_list}}).to_list(length=100)
# Get the rights of the roles
for role in roles:
role = RoleInDB(**role)
if role.elements[element_type][f"action_{action}"] is True:
return True
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User rights (roles) : You don't have the right to perform this action",
)
async def authorization_verify_based_on_roles_and_authorship(
request: Request,
user_id: str,
action: Literal["read", "update", "delete", "create"],
roles_list: list[UserRolesInOrganization],
element_id: str,
):
isAuthor = await authorization_verify_if_user_is_author(
request, user_id, action, element_id
)
isRole = await authorization_verify_based_on_roles(
request, user_id, action, roles_list, element_id
)
if isAuthor or isRole:
return True
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User rights (roles & authorship) : You don't have the right to perform this action",
)

View file

@ -0,0 +1,45 @@
from fastapi import HTTPException, status
async def check_element_type(element_id):
"""
Check if the element is a course, a user, a house or a collection, by checking its prefix
"""
if element_id.startswith("course_"):
return "courses"
elif element_id.startswith("user_"):
return "users"
elif element_id.startswith("house_"):
return "houses"
elif element_id.startswith("org_"):
return "organizations"
elif element_id.startswith("coursechapter_"):
return "coursechapters"
elif element_id.startswith("collection_"):
return "collections"
elif element_id.startswith("activity_"):
return "activities"
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User rights : Issue verifying element nature",
)
async def get_singular_form_of_element(element_id):
element_type = await check_element_type(element_id)
if element_type == "activities":
return "activity"
else:
singular_form_element = element_type[:-1]
return singular_form_element
async def get_id_identifier_of_element(element_id):
singular_form_element = await get_singular_form_of_element(element_id)
if singular_form_element == "ogranizations":
return "org_id"
else:
return str(singular_form_element) + "_id"

View file

@ -1,10 +1,7 @@
from fastapi import HTTPException, status, Request
from passlib.context import CryptContext from passlib.context import CryptContext
from passlib.hash import pbkdf2_sha256 from passlib.hash import pbkdf2_sha256
from config.config import get_learnhouse_config from config.config import get_learnhouse_config
from src.services.roles.schemas.roles import RoleInDB
from src.services.users.schemas.users import UserInDB, UserRolesInOrganization
### 🔒 JWT ############################################################## ### 🔒 JWT ##############################################################
@ -30,122 +27,4 @@ async def security_verify_password(plain_password: str, hashed_password: str):
### 🔒 Passwords Hashing ############################################################## ### 🔒 Passwords Hashing ##############################################################
### 🔒 Roles checking ##############################################################
async def verify_user_rights_with_roles(
request: Request, action: str, user_id: str, element_id: str, element_org_id: str
):
"""
Check if the user has the right to perform the action on the element
"""
request.app.db["roles"]
users = request.app.db["users"]
user = await users.find_one({"user_id": user_id})
#########
# Users existence verification
#########
if not user and user_id != "anonymous":
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="User rights : User not found"
)
# Check if user is anonymous
if user_id == "anonymous":
return False
# Get User
user: UserInDB = UserInDB(**await users.find_one({"user_id": user_id}))
#########
# Organization Roles verification
#########
for org in user.orgs:
if org.org_id == element_org_id:
# Check if user is owner or reader of the organization
if org.org_role == ("owner" or "editor"):
return True
#########
# Roles verification
#########
user_roles = user.roles
if action != "create":
return await check_user_role_org_with_element_org(
request, element_id, user_roles, action
)
# If no role is found, raise an error
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User rights : You don't have the right to perform this action",
)
async def check_element_type(element_id):
"""
Check if the element is a course, a user, a house or a collection, by checking its prefix
"""
if element_id.startswith("course_"):
return "courses"
elif element_id.startswith("user_"):
return "users"
elif element_id.startswith("house_"):
return "houses"
elif element_id.startswith("org_"):
return "organizations"
elif element_id.startswith("coursechapter_"):
return "coursechapters"
elif element_id.startswith("collection_"):
return "collections"
elif element_id.startswith("activity_"):
return "activities"
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User rights : Issue verifying element nature",
)
async def check_user_role_org_with_element_org(
request: Request,
element_id: str,
roles_list: list[UserRolesInOrganization],
action: str,
):
element_type = await check_element_type(element_id)
element = request.app.db[element_type]
roles = request.app.db["roles"]
# get singular element type
singular_form_element = element_type[:-1]
element_type_id = singular_form_element + "_id"
element_org = await element.find_one({element_type_id: element_id})
for role in roles_list:
# Check if The role belongs to the same organization as the element
role_db = await roles.find_one({"role_id": role.role_id})
role = RoleInDB(**role_db)
if (role.org_id == element_org["org_id"]) or role.org_id == "*":
# Check if user has the right role
for role in roles_list:
role_db = await roles.find_one({"role_id": role.role_id})
role = RoleInDB(**role_db)
if role.elements[element_type][f"action_{action}"]:
return True
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User rights (roles) : You don't have the right to perform this action",
)
### 🔒 Roles checking ##############################################################

View file

@ -1,6 +1,10 @@
from typing import Literal
from pydantic import BaseModel from pydantic import BaseModel
from src.security.security import verify_user_rights_with_roles from src.security.rbac.rbac import (
from src.services.users.schemas.users import PublicUser authorization_verify_based_on_roles,
authorization_verify_if_element_is_public,
)
from src.services.users.schemas.users import AnonymousUser, PublicUser
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
from uuid import uuid4 from uuid import uuid4
from datetime import datetime from datetime import datetime
@ -40,23 +44,26 @@ async def create_activity(
): ):
activities = request.app.db["activities"] activities = request.app.db["activities"]
courses = request.app.db["courses"] courses = request.app.db["courses"]
users = request.app.db["users"]
# get user
user = await users.find_one({"user_id": current_user.user_id})
# generate activity_id # generate activity_id
activity_id = str(f"activity_{uuid4()}") activity_id = str(f"activity_{uuid4()}")
hasRoleRights = await verify_user_rights_with_roles( # verify activity rights
request, "create", current_user.user_id, activity_id, org_id await authorization_verify_based_on_roles(
request,
current_user.user_id,
"create",
user["roles"],
activity_id,
) )
# get course_id from activity # get course_id from activity
course = await courses.find_one({"chapters": coursechapter_id}) course = await courses.find_one({"chapters": coursechapter_id})
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Roles : Insufficient rights to perform this action",
)
# create activity # create activity
activity = ActivityInDB( activity = ActivityInDB(
**activity_object.dict(), **activity_object.dict(),
@ -86,29 +93,10 @@ async def get_activity(request: Request, activity_id: str, current_user: PublicU
# get course_id from activity # get course_id from activity
coursechapter_id = activity["coursechapter_id"] coursechapter_id = activity["coursechapter_id"]
course = await courses.find_one({"chapters": coursechapter_id}) await courses.find_one({"chapters": coursechapter_id})
isCoursePublic = course["public"]
isAuthor = current_user.user_id in course["authors"]
if isAuthor:
activity = ActivityInDB(**activity)
return activity
# verify course rights # verify course rights
hasRoleRights = await verify_user_rights_with_roles( await verify_rights(request, activity["course_id"], current_user, "read")
request,
"read",
current_user.user_id,
activity_id,
element_org_id=activity["org_id"],
)
if not hasRoleRights and not isCoursePublic:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Roles : Insufficient rights to perform this action",
)
if not activity: if not activity:
raise HTTPException( raise HTTPException(
@ -128,14 +116,9 @@ async def update_activity(
activities = request.app.db["activities"] activities = request.app.db["activities"]
activity = await activities.find_one({"activity_id": activity_id}) activity = await activities.find_one({"activity_id": activity_id})
# verify course rights # verify course rights
await verify_user_rights_with_roles( await verify_rights(request, activity_id, current_user, "update")
request,
"update",
current_user.user_id,
activity_id,
element_org_id=activity["org_id"],
)
if activity: if activity:
creationDate = activity["creationDate"] creationDate = activity["creationDate"]
@ -171,13 +154,7 @@ async def delete_activity(request: Request, activity_id: str, current_user: Publ
activity = await activities.find_one({"activity_id": activity_id}) activity = await activities.find_one({"activity_id": activity_id})
# verify course rights # verify course rights
await verify_user_rights_with_roles( await verify_rights(request, activity_id, current_user, "delete")
request,
"delete",
current_user.user_id,
activity_id,
element_org_id=activity["org_id"],
)
if not activity: if not activity:
raise HTTPException( raise HTTPException(
@ -217,3 +194,44 @@ async def get_activities(
] ]
return activities return activities
#### Security ####################################################
async def verify_rights(
request: Request,
activity_id: str, # course_id in case of read
current_user: PublicUser | AnonymousUser,
action: Literal["create", "read", "update", "delete"],
):
if action == "read":
if current_user.user_id == "anonymous":
await authorization_verify_if_element_is_public(
request, activity_id, current_user.user_id, action
)
else:
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
await authorization_verify_based_on_roles(
request,
current_user.user_id,
action,
user["roles"],
activity_id,
)
else:
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
await authorization_verify_based_on_roles(
request,
current_user.user_id,
action,
user["roles"],
activity_id,
)
#### Security ####################################################

View file

@ -1,4 +1,4 @@
from src.security.security import verify_user_rights_with_roles from src.security.rbac.rbac import authorization_verify_based_on_roles
from src.services.courses.activities.uploads.pdfs import upload_pdf from src.services.courses.activities.uploads.pdfs import upload_pdf
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.services.courses.activities.activities import ActivityInDB from src.services.courses.activities.activities import ActivityInDB
@ -16,6 +16,10 @@ async def create_documentpdf_activity(
): ):
activities = request.app.db["activities"] activities = request.app.db["activities"]
courses = request.app.db["courses"] courses = request.app.db["courses"]
users = request.app.db["users"]
# get user
user = await users.find_one({"user_id": current_user.user_id})
# generate activity_id # generate activity_id
activity_id = str(f"activity_{uuid4()}") activity_id = str(f"activity_{uuid4()}")
@ -64,16 +68,14 @@ async def create_documentpdf_activity(
updateDate=str(datetime.now()), updateDate=str(datetime.now()),
) )
hasRoleRights = await verify_user_rights_with_roles( await authorization_verify_based_on_roles(
request, "create", current_user.user_id, activity_id, element_org_id=org_id request,
current_user.user_id,
"create",
user["roles"],
activity_id,
) )
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Roles : Insufficient rights to perform this action",
)
# create activity # create activity
activity = ActivityInDB(**activity_object.dict()) activity = ActivityInDB(**activity_object.dict())
await activities.insert_one(activity.dict()) await activities.insert_one(activity.dict())

View file

@ -1,7 +1,9 @@
from typing import Literal from typing import Literal
from pydantic import BaseModel from pydantic import BaseModel
from src.security.security import verify_user_rights_with_roles from src.security.rbac.rbac import (
authorization_verify_based_on_roles,
)
from src.services.courses.activities.uploads.videos import upload_video from src.services.courses.activities.uploads.videos import upload_video
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.services.courses.activities.activities import ActivityInDB from src.services.courses.activities.activities import ActivityInDB
@ -19,6 +21,10 @@ async def create_video_activity(
): ):
activities = request.app.db["activities"] activities = request.app.db["activities"]
courses = request.app.db["courses"] courses = request.app.db["courses"]
users = request.app.db["users"]
# get user
user = await users.find_one({"user_id": current_user.user_id})
# generate activity_id # generate activity_id
activity_id = str(f"activity_{uuid4()}") activity_id = str(f"activity_{uuid4()}")
@ -75,16 +81,14 @@ async def create_video_activity(
updateDate=str(datetime.now()), updateDate=str(datetime.now()),
) )
hasRoleRights = await verify_user_rights_with_roles( await authorization_verify_based_on_roles(
request, "create", current_user.user_id, activity_id, element_org_id=org_id request,
current_user.user_id,
"create",
user["roles"],
activity_id,
) )
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Roles : Insufficient rights to perform this action",
)
# create activity # create activity
activity = ActivityInDB(**activity_object.dict()) activity = ActivityInDB(**activity_object.dict())
await activities.insert_one(activity.dict()) await activities.insert_one(activity.dict())
@ -122,6 +126,10 @@ async def create_external_video_activity(
): ):
activities = request.app.db["activities"] activities = request.app.db["activities"]
courses = request.app.db["courses"] courses = request.app.db["courses"]
users = request.app.db["users"]
# get user
user = await users.find_one({"user_id": current_user.user_id})
# generate activity_id # generate activity_id
activity_id = str(f"activity_{uuid4()}") activity_id = str(f"activity_{uuid4()}")
@ -157,16 +165,14 @@ async def create_external_video_activity(
updateDate=str(datetime.now()), updateDate=str(datetime.now()),
) )
hasRoleRights = await verify_user_rights_with_roles( await authorization_verify_based_on_roles(
request, "create", current_user.user_id, activity_id, element_org_id=org_id request,
current_user.user_id,
"create",
user["roles"],
activity_id,
) )
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Roles : Insufficient rights to perform this action",
)
# create activity # create activity
activity = ActivityInDB(**activity_object.dict()) activity = ActivityInDB(**activity_object.dict())
await activities.insert_one(activity.dict()) await activities.insert_one(activity.dict())

View file

@ -1,11 +1,15 @@
from datetime import datetime from datetime import datetime
from typing import List from typing import List, Literal
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.security.auth import non_public_endpoint from src.security.auth import non_public_endpoint
from src.security.rbac.rbac import (
authorization_verify_based_on_roles,
authorization_verify_based_on_roles_and_authorship,
authorization_verify_if_element_is_public,
)
from src.services.courses.courses import Course from src.services.courses.courses import Course
from src.services.courses.activities.activities import ActivityInDB from src.services.courses.activities.activities import ActivityInDB
from src.security.security import verify_user_rights_with_roles
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
@ -29,6 +33,7 @@ class CourseChapterMetaData(BaseModel):
chapters: dict chapters: dict
activities: object activities: object
#### Classes #################################################### #### Classes ####################################################
#################################################### ####################################################
@ -36,35 +41,60 @@ class CourseChapterMetaData(BaseModel):
#################################################### ####################################################
async def create_coursechapter(request: Request, coursechapter_object: CourseChapter, course_id: str, current_user: PublicUser): async def create_coursechapter(
request: Request,
coursechapter_object: CourseChapter,
course_id: str,
current_user: PublicUser,
):
courses = request.app.db["courses"] courses = request.app.db["courses"]
print(course_id) users = request.app.db["users"]
# get course org_id and verify rights # get course org_id and verify rights
course = await courses.find_one({"course_id": course_id}) await courses.find_one({"course_id": course_id})
user = await users.find_one({"user_id": current_user.user_id})
# generate coursechapter_id with uuid4 # generate coursechapter_id with uuid4
coursechapter_id = str(f"coursechapter_{uuid4()}") coursechapter_id = str(f"coursechapter_{uuid4()}")
hasRoleRights = await verify_user_rights_with_roles(request, "create", current_user.user_id, coursechapter_id, course["org_id"]) hasRoleRights = await authorization_verify_based_on_roles(
request, current_user.user_id, "create", user["roles"], course_id
)
if not hasRoleRights: if not hasRoleRights:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Roles : Insufficient rights to perform this action") status_code=status.HTTP_409_CONFLICT,
detail="Roles : Insufficient rights to perform this action",
)
coursechapter = CourseChapterInDB(coursechapter_id=coursechapter_id, creationDate=str( coursechapter = CourseChapterInDB(
datetime.now()), updateDate=str(datetime.now()), course_id=course_id, **coursechapter_object.dict()) coursechapter_id=coursechapter_id,
creationDate=str(datetime.now()),
updateDate=str(datetime.now()),
course_id=course_id,
**coursechapter_object.dict(),
)
courses.update_one({"course_id": course_id}, { courses.update_one(
"$addToSet": {"chapters": coursechapter_id, "chapters_content": coursechapter.dict()}}) {"course_id": course_id},
{
"$addToSet": {
"chapters": coursechapter_id,
"chapters_content": coursechapter.dict(),
}
},
)
return coursechapter.dict() return coursechapter.dict()
async def get_coursechapter(request: Request, coursechapter_id: str, current_user: PublicUser): async def get_coursechapter(
request: Request, coursechapter_id: str, current_user: PublicUser
):
courses = request.app.db["courses"] courses = request.app.db["courses"]
coursechapter = await courses.find_one( coursechapter = await courses.find_one(
{"chapters_content.coursechapter_id": coursechapter_id}) {"chapters_content.coursechapter_id": coursechapter_id}
)
if coursechapter: if coursechapter:
# verify course rights # verify course rights
@ -75,64 +105,87 @@ async def get_coursechapter(request: Request, coursechapter_id: str, current_use
else: else:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="CourseChapter does not exist") status_code=status.HTTP_409_CONFLICT, detail="CourseChapter does not exist"
)
async def update_coursechapter(request: Request, coursechapter_object: CourseChapter, coursechapter_id: str, current_user: PublicUser): async def update_coursechapter(
request: Request,
coursechapter_object: CourseChapter,
coursechapter_id: str,
current_user: PublicUser,
):
courses = request.app.db["courses"] courses = request.app.db["courses"]
coursechapter = await courses.find_one( coursechapter = await courses.find_one(
{"chapters_content.coursechapter_id": coursechapter_id}) {"chapters_content.coursechapter_id": coursechapter_id}
)
if coursechapter: if coursechapter:
# verify course rights # verify course rights
await verify_rights(request, coursechapter["course_id"], current_user, "update") await verify_rights(request, coursechapter["course_id"], current_user, "update")
coursechapter = CourseChapterInDB(coursechapter_id=coursechapter_id, creationDate=str( coursechapter = CourseChapterInDB(
datetime.now()), updateDate=str(datetime.now()), course_id=coursechapter["course_id"], **coursechapter_object.dict()) coursechapter_id=coursechapter_id,
creationDate=str(datetime.now()),
updateDate=str(datetime.now()),
course_id=coursechapter["course_id"],
**coursechapter_object.dict(),
)
courses.update_one({"chapters_content.coursechapter_id": coursechapter_id}, { courses.update_one(
"$set": {"chapters_content.$": coursechapter.dict()}}) {"chapters_content.coursechapter_id": coursechapter_id},
{"$set": {"chapters_content.$": coursechapter.dict()}},
)
return coursechapter return coursechapter
else: else:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Coursechapter does not exist") status_code=status.HTTP_409_CONFLICT, detail="Coursechapter does not exist"
)
async def delete_coursechapter(request: Request, coursechapter_id: str, current_user: PublicUser): async def delete_coursechapter(
request: Request, coursechapter_id: str, current_user: PublicUser
):
courses = request.app.db["courses"] courses = request.app.db["courses"]
course = await courses.find_one( course = await courses.find_one(
{"chapters_content.coursechapter_id": coursechapter_id}) {"chapters_content.coursechapter_id": coursechapter_id}
)
if course: if course:
# verify course rights # verify course rights
await verify_rights(request, course["course_id"], current_user, "delete") await verify_rights(request, course["course_id"], current_user, "delete")
# Remove coursechapter from course # Remove coursechapter from course
await courses.update_one({"course_id": course["course_id"]}, { await courses.update_one(
"$pull": {"chapters": coursechapter_id}}) {"course_id": course["course_id"]},
{"$pull": {"chapters": coursechapter_id}},
await courses.update_one({"chapters_content.coursechapter_id": coursechapter_id}, { )
"$pull": {"chapters_content": {"coursechapter_id": coursechapter_id}}})
await courses.update_one(
{"chapters_content.coursechapter_id": coursechapter_id},
{"$pull": {"chapters_content": {"coursechapter_id": coursechapter_id}}},
)
return {"message": "Coursechapter deleted"} return {"message": "Coursechapter deleted"}
else: else:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist") status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
)
#################################################### ####################################################
# Misc # Misc
#################################################### ####################################################
async def get_coursechapters(request: Request, course_id: str, page: int = 1, limit: int = 10): async def get_coursechapters(
request: Request, course_id: str, page: int = 1, limit: int = 10
):
courses = request.app.db["courses"] courses = request.app.db["courses"]
course = await courses.find_one({"course_id": course_id}) course = await courses.find_one({"course_id": course_id})
@ -144,19 +197,26 @@ async def get_coursechapters(request: Request, course_id: str, page: int = 1, li
return coursechapters return coursechapters
async def get_coursechapters_meta(request: Request, course_id: str, current_user: PublicUser): async def get_coursechapters_meta(
request: Request, course_id: str, current_user: PublicUser
):
courses = request.app.db["courses"] courses = request.app.db["courses"]
activities = request.app.db["activities"] activities = request.app.db["activities"]
await non_public_endpoint(current_user) await non_public_endpoint(current_user)
coursechapters = await courses.find_one({"course_id": course_id}, {"chapters": 1, "chapters_content": 1, "_id": 0}) await verify_rights(request, course_id, current_user, "read")
coursechapters = await courses.find_one(
{"course_id": course_id}, {"chapters": 1, "chapters_content": 1, "_id": 0}
)
coursechapters = coursechapters coursechapters = coursechapters
if not coursechapters: if not coursechapters:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist") status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
)
# activities # activities
coursechapter_activityIds_global = [] coursechapter_activityIds_global = []
@ -165,7 +225,6 @@ async def get_coursechapters_meta(request: Request, course_id: str, current_user
chapters = {} chapters = {}
if coursechapters["chapters_content"]: if coursechapters["chapters_content"]:
for coursechapter in coursechapters["chapters_content"]: for coursechapter in coursechapters["chapters_content"]:
coursechapter = CourseChapterInDB(**coursechapter) coursechapter = CourseChapterInDB(**coursechapter)
coursechapter_activityIds = [] coursechapter_activityIds = []
@ -174,37 +233,55 @@ async def get_coursechapters_meta(request: Request, course_id: str, current_user
coursechapter_activityIds_global.append(activity) coursechapter_activityIds_global.append(activity)
chapters[coursechapter.coursechapter_id] = { chapters[coursechapter.coursechapter_id] = {
"id": coursechapter.coursechapter_id, "name": coursechapter.name, "activityIds": coursechapter_activityIds "id": coursechapter.coursechapter_id,
"name": coursechapter.name,
"activityIds": coursechapter_activityIds,
} }
# activities # activities
activities_list = {} activities_list = {}
for activity in await activities.find({"activity_id": {"$in": coursechapter_activityIds_global}}).to_list(length=100): for activity in await activities.find(
{"activity_id": {"$in": coursechapter_activityIds_global}}
).to_list(length=100):
activity = ActivityInDB(**activity) activity = ActivityInDB(**activity)
activities_list[activity.activity_id] = { activities_list[activity.activity_id] = {
"id": activity.activity_id, "name": activity.name, "type": activity.type, "content": activity.content "id": activity.activity_id,
"name": activity.name,
"type": activity.type,
"content": activity.content,
} }
final = { final = {
"chapters": chapters, "chapters": chapters,
"chapterOrder": coursechapters["chapters"], "chapterOrder": coursechapters["chapters"],
"activities": activities_list "activities": activities_list,
} }
return final return final
async def update_coursechapters_meta(request: Request, course_id: str, coursechapters_metadata: CourseChapterMetaData, current_user: PublicUser): async def update_coursechapters_meta(
request: Request,
course_id: str,
coursechapters_metadata: CourseChapterMetaData,
current_user: PublicUser,
):
courses = request.app.db["courses"] courses = request.app.db["courses"]
await verify_rights(request, course_id, current_user, "update")
# update chapters in course # update chapters in course
await courses.update_one({"course_id": course_id}, { await courses.update_one(
"$set": {"chapters": coursechapters_metadata.chapterOrder}}) {"course_id": course_id},
{"$set": {"chapters": coursechapters_metadata.chapterOrder}},
)
if coursechapters_metadata.chapters is not None: if coursechapters_metadata.chapters is not None:
for coursechapter_id, chapter_metadata in coursechapters_metadata.chapters.items(): for (
filter_query = { coursechapter_id,
"chapters_content.coursechapter_id": coursechapter_id} chapter_metadata,
) in coursechapters_metadata.chapters.items():
filter_query = {"chapters_content.coursechapter_id": coursechapter_id}
update_query = { update_query = {
"$set": { "$set": {
"chapters_content.$.activities": chapter_metadata["activityIds"] "chapters_content.$.activities": chapter_metadata["activityIds"]
@ -213,30 +290,57 @@ async def update_coursechapters_meta(request: Request, course_id: str, coursecha
result = await courses.update_one(filter_query, update_query) result = await courses.update_one(filter_query, update_query)
if result.matched_count == 0: if result.matched_count == 0:
# handle error when no documents are matched by the filter query # handle error when no documents are matched by the filter query
print( print(f"No documents found for course chapter ID {coursechapter_id}")
f"No documents found for course chapter ID {coursechapter_id}")
return {"detail": "coursechapters metadata updated"} return {"detail": "coursechapters metadata updated"}
#### Security #################################################### #### Security ####################################################
async def verify_rights(request: Request, course_id: str, current_user: PublicUser, action: str): async def verify_rights(
request: Request,
course_id: str,
current_user: PublicUser,
action: Literal["read", "update", "delete"],
):
courses = request.app.db["courses"] courses = request.app.db["courses"]
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
course = await courses.find_one({"course_id": course_id}) course = await courses.find_one({"course_id": course_id})
if not course: if not course:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist") status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
)
hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, course_id, course["org_id"]) if action == "read":
isAuthor = current_user.user_id in course["authors"] if current_user.user_id == "anonymous":
await authorization_verify_if_element_is_public(
request, course_id, current_user.user_id, action
)
else:
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
if not hasRoleRights and not isAuthor: await authorization_verify_based_on_roles_and_authorship(
raise HTTPException( request,
status_code=status.HTTP_403_FORBIDDEN, detail="Roles/Ownership : Insufficient rights to perform this action") current_user.user_id,
action,
user["roles"],
course_id,
)
else:
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
await authorization_verify_based_on_roles_and_authorship(
request,
current_user.user_id,
action,
user["roles"],
course_id,
)
return True
#### Security #################################################### #### Security ####################################################

View file

@ -1,8 +1,8 @@
from typing import List from typing import List, Literal
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.security.rbac.rbac import authorization_verify_based_on_roles_and_authorship
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.security.security import verify_user_rights_with_roles
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
#### Classes #################################################### #### Classes ####################################################
@ -12,11 +12,13 @@ class Collection(BaseModel):
name: str name: str
description: str description: str
courses: List[str] # course_id courses: List[str] # course_id
public: bool
org_id: str # org_id org_id: str # org_id
class CollectionInDB(Collection): class CollectionInDB(Collection):
collection_id: str collection_id: str
authors: List[str] # user_id
#### Classes #################################################### #### Classes ####################################################
@ -81,7 +83,11 @@ async def create_collection(
# generate collection_id with uuid4 # generate collection_id with uuid4
collection_id = str(f"collection_{uuid4()}") collection_id = str(f"collection_{uuid4()}")
collection = CollectionInDB(collection_id=collection_id, **collection_object.dict()) collection = CollectionInDB(
collection_id=collection_id,
authors=[current_user.user_id],
**collection_object.dict(),
)
collection_in_db = await collections.insert_one(collection.dict()) collection_in_db = await collections.insert_one(collection.dict())
@ -169,15 +175,18 @@ async def get_collections(
print(org_id) print(org_id)
# get all collections from database without ObjectId if current_user.user_id == "anonymous":
all_collections = ( all_collections = collections.find(
collections.find({"org_id": org_id}) {"org_id": org_id, "public": True}, {"_id": 0}
.sort("name", 1) )
.skip(10 * (page - 1)) else:
.limit(limit) # get all collections from database without ObjectId
) all_collections = (
collections.find({"org_id": org_id})
await verify_collection_rights(request, "*", current_user, "read", org_id) .sort("name", 1)
.skip(10 * (page - 1))
.limit(limit)
)
# create list of collections and include courses in each collection # create list of collections and include courses in each collection
collections_list = [] collections_list = []
@ -207,11 +216,12 @@ async def verify_collection_rights(
request: Request, request: Request,
collection_id: str, collection_id: str,
current_user: PublicUser, current_user: PublicUser,
action: str, action: Literal["create", "read", "update", "delete"],
org_id: str, org_id: str,
): ):
collections = request.app.db["collections"] collections = request.app.db["collections"]
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
collection = await collections.find_one({"collection_id": collection_id}) collection = await collections.find_one({"collection_id": collection_id})
if not collection and action != "create" and collection_id != "*": if not collection and action != "create" and collection_id != "*":
@ -223,17 +233,9 @@ async def verify_collection_rights(
if current_user.user_id == "anonymous" and action == "read": if current_user.user_id == "anonymous" and action == "read":
return True return True
hasRoleRights = await verify_user_rights_with_roles( await authorization_verify_based_on_roles_and_authorship(
request, action, current_user.user_id, collection_id, org_id request, current_user.user_id, action, user["roles"], collection_id
) )
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have rights to this Collection",
)
return True
#### Security #################################################### #### Security ####################################################

View file

@ -1,12 +1,16 @@
import json import json
from typing import List, Optional from typing import List, Literal, Optional
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.security.rbac.rbac import (
authorization_verify_based_on_roles,
authorization_verify_based_on_roles_and_authorship,
authorization_verify_if_element_is_public,
)
from src.services.courses.activities.activities import ActivityInDB from src.services.courses.activities.activities import ActivityInDB
from src.services.courses.thumbnails import upload_thumbnail from src.services.courses.thumbnails import upload_thumbnail
from src.services.users.schemas.users import AnonymousUser from src.services.users.schemas.users import AnonymousUser
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.security.security import verify_user_rights_with_roles
from fastapi import HTTPException, Request, status, UploadFile from fastapi import HTTPException, Request, status, UploadFile
from datetime import datetime from datetime import datetime
@ -144,7 +148,6 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public
trail = await trails.find_one( trail = await trails.find_one(
{"courses.course_id": course_id, "user_id": current_user.user_id} {"courses.course_id": course_id, "user_id": current_user.user_id}
) )
print(trail)
if trail: if trail:
# get only the course where course_id == course_id # get only the course where course_id == course_id
trail_course = next( trail_course = next(
@ -169,6 +172,8 @@ async def create_course(
thumbnail_file: UploadFile | None = None, thumbnail_file: UploadFile | None = None,
): ):
courses = request.app.db["courses"] courses = request.app.db["courses"]
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
# generate course_id with uuid4 # generate course_id with uuid4
course_id = str(f"course_{uuid4()}") course_id = str(f"course_{uuid4()}")
@ -176,10 +181,16 @@ async def create_course(
# TODO(fix) : the implementation here is clearly not the best one (this entire function) # TODO(fix) : the implementation here is clearly not the best one (this entire function)
course_object.org_id = org_id course_object.org_id = org_id
course_object.chapters_content = [] course_object.chapters_content = []
await verify_user_rights_with_roles(
request, "create", current_user.user_id, course_id, org_id await authorization_verify_based_on_roles(
request,
current_user.user_id,
"create",
user["roles"],
course_id,
) )
if thumbnail_file and thumbnail_file.filename: if thumbnail_file and thumbnail_file.filename:
name_in_disk = ( name_in_disk = (
f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}" f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
@ -214,12 +225,13 @@ async def update_course_thumbnail(
current_user: PublicUser, current_user: PublicUser,
thumbnail_file: UploadFile | None = None, thumbnail_file: UploadFile | None = None,
): ):
# verify course rights
await verify_rights(request, course_id, current_user, "update")
courses = request.app.db["courses"] courses = request.app.db["courses"]
course = await courses.find_one({"course_id": course_id}) course = await courses.find_one({"course_id": course_id})
# verify course rights
await verify_rights(request, course_id, current_user, "update")
# TODO(fix) : the implementation here is clearly not the best one # TODO(fix) : the implementation here is clearly not the best one
if course: if course:
creationDate = course["creationDate"] creationDate = course["creationDate"]
@ -254,13 +266,13 @@ async def update_course_thumbnail(
async def update_course( async def update_course(
request: Request, course_object: Course, course_id: str, current_user: PublicUser request: Request, course_object: Course, course_id: str, current_user: PublicUser
): ):
# verify course rights
await verify_rights(request, course_id, current_user, "update")
courses = request.app.db["courses"] courses = request.app.db["courses"]
course = await courses.find_one({"course_id": course_id}) course = await courses.find_one({"course_id": course_id})
# verify course rights
await verify_rights(request, course_id, current_user, "update")
if course: if course:
creationDate = course["creationDate"] creationDate = course["creationDate"]
authors = course["authors"] authors = course["authors"]
@ -289,13 +301,13 @@ async def update_course(
async def delete_course(request: Request, course_id: str, current_user: PublicUser): async def delete_course(request: Request, course_id: str, current_user: PublicUser):
# verify course rights
await verify_rights(request, course_id, current_user, "delete")
courses = request.app.db["courses"] courses = request.app.db["courses"]
course = await courses.find_one({"course_id": course_id}) course = await courses.find_one({"course_id": course_id})
# verify course rights
await verify_rights(request, course_id, current_user, "delete")
if not course: if not course:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist" status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
@ -364,41 +376,35 @@ async def verify_rights(
request: Request, request: Request,
course_id: str, course_id: str,
current_user: PublicUser | AnonymousUser, current_user: PublicUser | AnonymousUser,
action: str, action: Literal["create", "read", "update", "delete"],
): ):
courses = request.app.db["courses"] if action == "read":
if current_user.user_id == "anonymous":
await authorization_verify_if_element_is_public(
request, course_id, current_user.user_id, action
)
else:
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
course = await courses.find_one({"course_id": course_id}) await authorization_verify_based_on_roles_and_authorship(
request,
current_user.user_id,
action,
user["roles"],
course_id,
)
else:
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
isAuthor = current_user.user_id in course["authors"] await authorization_verify_based_on_roles_and_authorship(
request,
if isAuthor: current_user.user_id,
return True action,
user["roles"],
if ( course_id,
current_user.user_id == "anonymous"
and course["public"] is True
and action == "read"
):
return True
if not course:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Course/CourseChapter does not exist",
) )
hasRoleRights = await verify_user_rights_with_roles(
request, action, current_user.user_id, course_id, course["org_id"]
)
if not hasRoleRights and not isAuthor:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Roles/Ownership : Insufficient rights to perform this action",
)
return True
#### Security #################################################### #### Security ####################################################

View file

@ -1,5 +1,7 @@
import json import json
from typing import Literal
from uuid import uuid4 from uuid import uuid4
from src.security.rbac.rbac import authorization_verify_based_on_roles
from src.services.orgs.logos import upload_org_logo from src.services.orgs.logos import upload_org_logo
from src.services.orgs.schemas.orgs import ( from src.services.orgs.schemas.orgs import (
Organization, Organization,
@ -8,7 +10,6 @@ from src.services.orgs.schemas.orgs import (
) )
from src.services.users.schemas.users import UserOrganization from src.services.users.schemas.users import UserOrganization
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.security.security import verify_user_rights_with_roles
from fastapi import HTTPException, UploadFile, status, Request from fastapi import HTTPException, UploadFile, status, Request
@ -197,9 +198,12 @@ async def verify_org_rights(
request: Request, request: Request,
org_id: str, org_id: str,
current_user: PublicUser, current_user: PublicUser,
action: str, action: Literal["create", "read", "update", "delete"],
): ):
orgs = request.app.db["organizations"] orgs = request.app.db["organizations"]
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
org = await orgs.find_one({"org_id": org_id}) org = await orgs.find_one({"org_id": org_id})
@ -208,17 +212,9 @@ async def verify_org_rights(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist" status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
) )
hasRoleRights = await verify_user_rights_with_roles( await authorization_verify_based_on_roles(
request, action, current_user.user_id, org_id, org_id request, current_user.user_id, action, user["roles"], org_id
) )
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have rights to this organization",
)
return True
#### Security #################################################### #### Security ####################################################

View file

@ -41,6 +41,9 @@ class UserInDB(User):
creation_date: str creation_date: str
update_date: str update_date: str
def __getitem__(self, item):
return getattr(self, item)

View file

@ -2,24 +2,39 @@ from datetime import datetime
from typing import Literal from typing import Literal
from uuid import uuid4 from uuid import uuid4
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from src.security.rbac.rbac import authorization_verify_based_on_roles
from src.security.security import security_hash_password, security_verify_password from src.security.security import security_hash_password, security_verify_password
from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserOrganization, UserRolesInOrganization, UserWithPassword, UserInDB from src.services.users.schemas.users import (
PasswordChangeForm,
PublicUser,
User,
UserOrganization,
UserRolesInOrganization,
UserWithPassword,
UserInDB,
)
async def create_user(request: Request, current_user: PublicUser | None, user_object: UserWithPassword, org_slug: str): async def create_user(
request: Request,
current_user: PublicUser | None,
user_object: UserWithPassword,
org_slug: str,
):
users = request.app.db["users"] users = request.app.db["users"]
isUsernameAvailable = await users.find_one({"username": user_object.username}) isUsernameAvailable = await users.find_one({"username": user_object.username})
isEmailAvailable = await users.find_one({"email": user_object.email}) isEmailAvailable = await users.find_one({"email": user_object.email})
if isUsernameAvailable: if isUsernameAvailable:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Username already exists") status_code=status.HTTP_409_CONFLICT, detail="Username already exists"
)
if isEmailAvailable: if isEmailAvailable:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Email already exists") status_code=status.HTTP_409_CONFLICT, detail="Email already exists"
)
# Generate user_id with uuid4 # Generate user_id with uuid4
user_id = str(f"user_{uuid4()}") user_id = str(f"user_{uuid4()}")
@ -42,11 +57,12 @@ async def create_user(request: Request, current_user: PublicUser | None, user_o
# If the org does not exist, raise an error # If the org does not exist, raise an error
if not isOrgExists: if not isOrgExists:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="You are trying to create a user in an organization that does not exist") status_code=status.HTTP_409_CONFLICT,
detail="You are trying to create a user in an organization that does not exist",
)
org_id = isOrgExists["org_id"] org_id = isOrgExists["org_id"]
# Create initial orgs list with the org_id passed in # Create initial orgs list with the org_id passed in
orgs = [UserOrganization(org_id=org_id, org_role="member")] orgs = [UserOrganization(org_id=org_id, org_role="member")]
@ -54,8 +70,14 @@ async def create_user(request: Request, current_user: PublicUser | None, user_o
roles = [UserRolesInOrganization(role_id="role_member", org_id=org_id)] roles = [UserRolesInOrganization(role_id="role_member", org_id=org_id)]
# Create the user # Create the user
user = UserInDB(user_id=user_id, creation_date=str(datetime.now()), user = UserInDB(
update_date=str(datetime.now()), orgs=orgs, roles=roles, **user_object.dict()) user_id=user_id,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
orgs=orgs,
roles=roles,
**user_object.dict(),
)
# Insert the user into the database # Insert the user into the database
await users.insert_one(user.dict()) await users.insert_one(user.dict())
@ -75,12 +97,15 @@ async def read_user(request: Request, current_user: PublicUser, user_id: str):
# If the user does not exist, raise an error # If the user does not exist, raise an error
if not isUserExists: if not isUserExists:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist") status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
)
return User(**isUserExists) return User(**isUserExists)
async def update_user(request: Request, user_id: str, user_object: User,current_user: PublicUser): async def update_user(
request: Request, user_id: str, user_object: User, current_user: PublicUser
):
users = request.app.db["users"] users = request.app.db["users"]
# Verify rights # Verify rights
@ -92,7 +117,8 @@ async def update_user(request: Request, user_id: str, user_object: User,current
if not isUserExists: if not isUserExists:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist") status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
)
# okay if username is not changed # okay if username is not changed
if isUserExists["username"] == user_object.username: if isUserExists["username"] == user_object.username:
@ -101,11 +127,13 @@ async def update_user(request: Request, user_id: str, user_object: User,current
else: else:
if isUsernameAvailable: if isUsernameAvailable:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Username already used") status_code=status.HTTP_409_CONFLICT, detail="Username already used"
)
if isEmailAvailable: if isEmailAvailable:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Email already used") status_code=status.HTTP_409_CONFLICT, detail="Email already used"
)
updated_user = {"$set": user_object.dict()} updated_user = {"$set": user_object.dict()}
users.update_one({"user_id": user_id}, updated_user) users.update_one({"user_id": user_id}, updated_user)
@ -113,8 +141,12 @@ async def update_user(request: Request, user_id: str, user_object: User,current
return User(**user_object.dict()) return User(**user_object.dict())
async def update_user_password(
async def update_user_password(request: Request, current_user: PublicUser, user_id: str, password_change_form: PasswordChangeForm): request: Request,
current_user: PublicUser,
user_id: str,
password_change_form: PasswordChangeForm,
):
users = request.app.db["users"] users = request.app.db["users"]
isUserExists = await users.find_one({"user_id": user_id}) isUserExists = await users.find_one({"user_id": user_id})
@ -124,11 +156,15 @@ async def update_user_password(request: Request, current_user: PublicUser, user_
if not isUserExists: if not isUserExists:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist") status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
)
if not await security_verify_password(password_change_form.old_password, isUserExists["password"]): if not await security_verify_password(
password_change_form.old_password, isUserExists["password"]
):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password") status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password"
)
new_password = await security_hash_password(password_change_form.new_password) new_password = await security_hash_password(password_change_form.new_password)
@ -148,7 +184,8 @@ async def delete_user(request: Request, current_user: PublicUser, user_id: str):
if not isUserExists: if not isUserExists:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist") status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
)
await users.delete_one({"user_id": user_id}) await users.delete_one({"user_id": user_id})
@ -157,18 +194,21 @@ async def delete_user(request: Request, current_user: PublicUser, user_id: str):
# Utils & Security functions # Utils & Security functions
async def security_get_user(request: Request, email: str): async def security_get_user(request: Request, email: str):
users = request.app.db["users"] users = request.app.db["users"]
user = await users.find_one({"email": email}) user = await users.find_one({"email": email})
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User with Email does not exist") status_code=status.HTTP_409_CONFLICT,
detail="User with Email does not exist",
)
return UserInDB(**user) return UserInDB(**user)
async def get_userid_by_username(request: Request, username: str): async def get_userid_by_username(request: Request, username: str):
users = request.app.db["users"] users = request.app.db["users"]
@ -176,10 +216,12 @@ async def get_userid_by_username(request: Request, username: str):
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist") status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
)
return user["user_id"] return user["user_id"]
async def get_user_by_userid(request: Request, user_id: str): async def get_user_by_userid(request: Request, user_id: str):
users = request.app.db["users"] users = request.app.db["users"]
@ -187,32 +229,36 @@ async def get_user_by_userid(request: Request, user_id: str):
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist") status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
)
user = User(**user) user = User(**user)
return user return user
async def get_profile_metadata(request: Request, user): async def get_profile_metadata(request: Request, user):
users = request.app.db["users"] users = request.app.db["users"]
request.app.db["roles"] request.app.db["roles"]
user = await users.find_one({"user_id": user['user_id']}) user = await users.find_one({"user_id": user["user_id"]})
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist") status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
)
return {"user_object": PublicUser(**user), "roles": "random"}
return {
"user_object": PublicUser(**user),
"roles": "random"
}
# Verification of the user's permissions on the roles # Verification of the user's permissions on the roles
async def verify_user_rights_on_user(request: Request, current_user: PublicUser, action: Literal["create", "read", "update", "delete"], user_id: str):
async def verify_user_rights_on_user(
request: Request,
current_user: PublicUser,
action: Literal["create", "read", "update", "delete"],
user_id: str,
):
users = request.app.db["users"] users = request.app.db["users"]
user = UserInDB(**await users.find_one({"user_id": user_id})) user = UserInDB(**await users.find_one({"user_id": user_id}))
@ -235,11 +281,12 @@ async def verify_user_rights_on_user(request: Request, current_user: PublicUser,
for org in current_user.orgs: for org in current_user.orgs:
if org.org_id in [org.org_id for org in user.orgs]: if org.org_id in [org.org_id for org in user.orgs]:
if org.org_role == "owner": if org.org_role == "owner":
return True return True
# TODO: Verify user roles on the org await authorization_verify_based_on_roles(
request, current_user.user_id, "update", user["roles"], user_id
)
return False return False
@ -249,8 +296,9 @@ async def verify_user_rights_on_user(request: Request, current_user: PublicUser,
for org in current_user.orgs: for org in current_user.orgs:
if org.org_id in [org.org_id for org in user.orgs]: if org.org_id in [org.org_id for org in user.orgs]:
if org.org_role == "owner": if org.org_role == "owner":
return True return True
# TODO: Verify user roles on the org await authorization_verify_based_on_roles(
request, current_user.user_id, "update", user["roles"], user_id
)