diff --git a/src/main.py b/src/main.py index ef4d5405..ebcdca20 100644 --- a/src/main.py +++ b/src/main.py @@ -1,5 +1,5 @@ from fastapi import APIRouter -from src.routers import users, auth, houses, orgs +from src.routers import users, auth, houses, orgs, roles from starlette.responses import FileResponse @@ -11,3 +11,4 @@ global_router.include_router(users.router, prefix="/users", tags=["users"]) global_router.include_router(auth.router, prefix="/auth", tags=["auth"]) global_router.include_router(houses.router, prefix="/houses", tags=["houses"]) global_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"]) +global_router.include_router(roles.router, prefix="/roles", tags=["roles"]) diff --git a/src/routers/roles.py b/src/routers/roles.py new file mode 100644 index 00000000..eeaa6f79 --- /dev/null +++ b/src/routers/roles.py @@ -0,0 +1,49 @@ +from fastapi import APIRouter, Depends +from src.services.auth import get_current_user + +from src.services.roles import Role, create_role, delete_role, get_role, update_role +from src.services.users import User + + +router = APIRouter() + + +@router.post("/") +async def api_create_role(role_object: Role, current_user: User = Depends(get_current_user)): + """ + Create new role + """ + return await create_role(role_object, current_user) + + +@router.get("/{role_id}") +async def api_get_role(role_id: str): + """ + Get single role by role_id + """ + return await get_role(role_id) + + +@router.get("/page/{page}/limit/{limit}") +async def api_get_role_by(page: int, limit: int): + """ + Get roles by page and limit + """ + return await get_role(page, limit) + + +@router.put("/{role_id}") +async def api_update_role(role_object: Role, role_id: str, current_user: User = Depends(get_current_user)): + """ + Update role by role_id + """ + return await update_role(role_object, role_id, current_user) + + +@router.delete("/{role_id}") +async def api_delete_role(role_id: str, current_user: User = Depends(get_current_user)): + """ + Delete role by ID + """ + + return await delete_role(role_id, current_user) diff --git a/src/services/roles.py b/src/services/roles.py new file mode 100644 index 00000000..ddfda711 --- /dev/null +++ b/src/services/roles.py @@ -0,0 +1,168 @@ +import json +from typing import List +from uuid import uuid4 +from pydantic import BaseModel +from src.services.users import User +from ..services.database import check_database, learnhouseDB, learnhouseDB +from ..services.security import * +from ..services.houses import House +from fastapi import HTTPException, status +from datetime import datetime + +#### Classes #################################################### + + +class Permission(BaseModel): + create: bool + read: bool + update: bool + delete: bool + + +class Elements(BaseModel): + course: List[str] + users: List[str] + houses: List[str] + paths: List[str] + coursegroups: List[str] + + +class Role(BaseModel): + name: str + description: str + permissions: Permission + elements: Elements + + +class RoleInDB(Role): + role_id: str + creationDate: str + updateDate: str + +#### Classes #################################################### + + +async def get_role(role_id: str): + await check_database() + roles = learnhouseDB["roles"] + + role = roles.find_one({"role_id": role_id}) + + if not role: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Role does not exist") + + role = Role(**role) + return role + + +async def create_role(role_object: Role, current_user: User): + await check_database() + roles = learnhouseDB["roles"] + + # find if house already exists using name + isRoleAvailable = roles.find_one({"name": role_object.name}) + + if isRoleAvailable: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Role name already exists") + + await verify_user_permissions("create", current_user) + + # generate house_id with uuid4 + role_id = str(f"role_{uuid4()}") + + role = RoleInDB(role_id=role_id, creationDate=str(datetime.now()), + updateDate=str(datetime.now()), **role_object.dict()) + + role_in_db = roles.insert_one(role.dict()) + + if not role_in_db: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") + + return role.dict() + + +async def update_role(role_object: House, role_id: str, current_user: User): + await check_database() + + # verify house rights + await verify_user_permissions("update", current_user) + + roles = learnhouseDB["roles"] + + role = roles.find_one({"role_id": role_id}) + + if not role: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="House does not exist") + + updated_role = RoleInDB( + role_id=role_id, updateDate=str(datetime.now()), creationDate=role["creationDate"], **role_object.dict()) + + roles.update_one({"role_id": role_id}, {"$set": updated_role.dict()}) + + return RoleInDB(**updated_role.dict()) + + +async def delete_role(role_id: str, current_user: User): + await check_database() + + # verify house rights + await verify_user_permissions("delete", current_user) + + roles = learnhouseDB["roles"] + + role = roles.find_one({"role_id": role_id}) + + if not role: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Role does not exist") + + isDeleted = roles.delete_one({"role_id": role_id}) + + if isDeleted: + return {"detail": "Role deleted"} + else: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") + + +async def get_roles(page: int = 1, limit: int = 10): + await check_database() + roles = learnhouseDB["roles"] + + # get all roles from database + all_roles = roles.find().sort("name", 1).skip(10 * (page - 1)).limit(limit) + + return [json.loads(json.dumps(role, default=str)) for role in all_roles] + + +#### Security #################################################### + +async def verify_user_permissions(action: str, current_user: User): + await check_database() + users = learnhouseDB["users"] + + user = users.find_one({"username": current_user.username}) + + if not user: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User does not exist") + + isOwner = "owner" in user["user_type"] + isEditor = "editor" in user["user_type"] + + if action == "delete": + if isEditor: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this Role") + + if not isOwner and not isEditor: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this Role") + + return True + +#### Security #################################################### diff --git a/src/services/users.py b/src/services/users.py index 7aa298c6..c707aeff 100644 --- a/src/services/users.py +++ b/src/services/users.py @@ -1,7 +1,7 @@ from pydantic import BaseModel -from ..services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB +from ..services.database import check_database, learnhouseDB, learnhouseDB from ..services.security import * -from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks +from fastapi import HTTPException, status from datetime import datetime #### Classes #################################################### @@ -15,7 +15,8 @@ class User(BaseModel): avatar_url: str | None = None verified: bool created_date: str - bio : str | None = None + user_type: str + bio: str | None = None class UserInDB(User): @@ -61,7 +62,7 @@ async def update_user(user_object: UserInDB): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="User does not exist") - user_object.password = security_hash_password(user_object.password) + user_object.password = await security_hash_password(user_object.password) updated_user = {"$set": user_object.dict()} users.update_one({"username": user_object.username}, updated_user) @@ -96,7 +97,7 @@ async def create_user(user_object: UserInDB): # lowercase username user_object.username = user_object.username.lower() - + user_object.created_date = str(datetime.now()) user_object.password = await security_hash_password(user_object.password)