diff --git a/src/models/pydantic_models/model.py b/src/models/pydantic_models/model.py index fa92849..1f050c4 100644 --- a/src/models/pydantic_models/model.py +++ b/src/models/pydantic_models/model.py @@ -42,6 +42,7 @@ class PermissionsGroupsOut(Base): id:Annotated[int, Field(..., description="id of the permission group")] group:Annotated[str, Field(..., max_length=255, description="group name for the permissions")] + permissions: Annotated[list[PermissionsOut], Field(..., description="permissions granted by this group")] class UserCreate(Base): diff --git a/src/service/auth/auth.py b/src/service/auth/auth.py index 25e4019..746e454 100644 --- a/src/service/auth/auth.py +++ b/src/service/auth/auth.py @@ -49,7 +49,7 @@ class CurrentUserService: ) - async def get_current_user(self, token:str)->UserOut: + async def get_current_user(self, token:str, *permissions: str)->UserOut: payload= await self.jwt_service.jwt_decode(token) sub=payload.get("sub") @@ -69,6 +69,12 @@ class CurrentUserService: if user.status is False: raise self.error.credentials_error(detail="This user is deactivated") + effective = {p.permission for p in user.direct_permissions} | {p.permission for group in user.group for p in group.permissions} + missing = set(permissions) - effective + + if missing: + raise self.error.forbidden_error(detail=f"Missing: {missing}") + return UserOut.model_validate(user) diff --git a/src/web/protected_routes/auth_routes.py b/src/web/protected_routes/auth_routes.py index 87e5b5d..5f19f7d 100644 --- a/src/web/protected_routes/auth_routes.py +++ b/src/web/protected_routes/auth_routes.py @@ -8,6 +8,16 @@ from src.service.auth.auth import CurrentUserService, auth_service router=APIRouter(prefix="/protected") oauth2_schema=OAuth2PasswordBearer(tokenUrl="/protected/token", refreshUrl="/protected/refresh") + +def require_permissions(*permissions: str): #permissions check dependency + async def checker( + token: str = Depends(oauth2_schema), + auth: CurrentUserService = Depends(auth_service), #noqa: B008 + ) -> UserOut: + return UserOut.model_validate(await auth.get_current_user(token, *permissions)) + return checker + + @router.post("/token") async def get_access_token(request: Request,response:Response,auth:CurrentUserService=Depends(auth_service), form_data:OAuth2PasswordRequestForm=Depends())->dict: # noqa: B008 @@ -40,16 +50,11 @@ async def get_refresh_token(request:Request,response:Response, refresh_token: st return {"access_token":access_token, "token_type": "bearer"} - -async def get_current_user(token:str = Depends(oauth2_schema), auth:CurrentUserService=Depends(auth_service)) -> UserOut: # noqa: B008 - return UserOut.model_validate(await auth.get_current_user(token)) - - @router.get("/logout") -async def logout(response:Response,refresh_token: str = Cookie(),auth:CurrentUserService=Depends(auth_service),current_user:UserOut=Depends(get_current_user))->bool: # noqa: B008 +async def logout(response:Response,refresh_token: str = Cookie(),auth:CurrentUserService=Depends(auth_service),current_user:UserOut=Depends(require_permissions()))->bool: # noqa: B008 response.delete_cookie("refresh_token") return await auth.logout(refresh_token) @router.get("") -async def protected(current_user:UserOut=Depends(get_current_user))->dict: # noqa: B008 +async def protected(current_user:UserOut=Depends(require_permissions()))->dict: # noqa: B008 return {"protected router": "Hello, this is a protected router"} diff --git a/src/web/protected_routes/protected_user_action_routes.py b/src/web/protected_routes/protected_user_action_routes.py index 440852f..d25d3f9 100644 --- a/src/web/protected_routes/protected_user_action_routes.py +++ b/src/web/protected_routes/protected_user_action_routes.py @@ -2,27 +2,27 @@ from fastapi import APIRouter, Depends from src.models.pydantic_models.model import UserCreate, UserOut, UserUpdate from src.service.users_crud.users_crud import CrudService, crud_service -from src.web.protected_routes.auth_routes import get_current_user +from src.web.protected_routes.auth_routes import require_permissions router=APIRouter(prefix="/user") @router.get("/get_by_email") -async def get_current_user_by_email(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(get_current_user))->UserOut: # noqa: B008 +async def get_current_user_by_email(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut: # noqa: B008 return await crud.get_user_by_email(email) @router.post("/create_user") -async def create_user(data:UserCreate, crud:CrudService=Depends(crud_service), current_user=Depends(get_current_user))->UserOut: #noqa: B008 +async def create_user(data:UserCreate, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut: #noqa: B008 return await crud.create_user(data) @router.post("/delete_user_soft") -async def delete_user_soft(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(get_current_user))->bool: #noqa: B008 +async def delete_user_soft(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->bool: #noqa: B008 return await crud.delete_user_soft(email) @router.post("/delete_user_hard") -async def delete_user_hard(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(get_current_user))->bool: #noqa: B008 +async def delete_user_hard(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->bool: #noqa: B008 return await crud.delete_user_hard(email, current_user) @router.patch("/patch_user") -async def patch_user(email:str, data:UserUpdate, crud:CrudService=Depends(crud_service), current_user=Depends(get_current_user))->UserOut: #noqa: B008 +async def patch_user(email:str, data:UserUpdate, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut: #noqa: B008 return await crud.update_user(email, data) \ No newline at end of file