diff --git a/api/kiwi_vpn_api/db/schemas.py b/api/kiwi_vpn_api/db/schemas.py index 3f7e5b5..4f17168 100644 --- a/api/kiwi_vpn_api/db/schemas.py +++ b/api/kiwi_vpn_api/db/schemas.py @@ -201,4 +201,8 @@ class User(UserBase): models.UserCapability(capability=capability.value) ) + for capability in old_dbuser.capabilities: + if UserCapability.from_value(capability) not in self.capabilities: + db.delete(capability) + db.commit() diff --git a/api/kiwi_vpn_api/routers/_common.py b/api/kiwi_vpn_api/routers/_common.py index e98c60e..0c08753 100644 --- a/api/kiwi_vpn_api/routers/_common.py +++ b/api/kiwi_vpn_api/routers/_common.py @@ -3,13 +3,13 @@ Common dependencies for routers. """ -from fastapi import Depends +from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from sqlalchemy.orm import Session from ..config import Config from ..db import Connection -from ..db.schemas import User +from ..db.schemas import User, UserCapability oauth2_scheme = OAuth2PasswordBearer(tokenUrl="user/authenticate") @@ -50,7 +50,7 @@ async def get_current_user( token: str = Depends(oauth2_scheme), db: Session | None = Depends(Connection.get), current_config: Config | None = Depends(Config.load), -): +) -> User | None: """ Get the currently logged-in user from the database. """ @@ -63,3 +63,21 @@ async def get_current_user( user = User.from_db(db, username) return user + + +async def get_current_admin_user( + current_config: Config | None = Depends(Config.load), + current_user: User | None = Depends(get_current_user), +) -> User: + """ + Check if the currently logged-in user is an admin. + """ + + # fail if not installed + if current_config is None: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) + + # fail if not requested by an admin + if (current_user is None + or UserCapability.admin not in current_user.capabilities): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) diff --git a/api/kiwi_vpn_api/routers/user.py b/api/kiwi_vpn_api/routers/user.py index 10b9f00..62838a2 100644 --- a/api/kiwi_vpn_api/routers/user.py +++ b/api/kiwi_vpn_api/routers/user.py @@ -10,7 +10,7 @@ from sqlalchemy.orm import Session from ..config import Config from ..db import Connection from ..db.schemas import User, UserCapability, UserCreate -from ._common import Responses, get_current_user +from ._common import Responses, get_current_admin_user, get_current_user router = APIRouter(prefix="/user") @@ -82,22 +82,13 @@ async def get_current_user( async def add_user( user: UserCreate, current_config: Config | None = Depends(Config.load), - current_user: User | None = Depends(get_current_user), + _: User = Depends(get_current_admin_user), db: Session | None = Depends(Connection.get), ): """ POST ./new: Create a new user in the database. """ - # fail if not installed - if current_config is None: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) - - # fail if not requested by an admin - if (current_user is None - or UserCapability.admin not in current_user.capabilities): - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - # actually create the new user new_user = User.create( db=db, @@ -111,3 +102,71 @@ async def add_user( # return the created user on success return new_user + + +@router.post( + "/{user_name}/capabilities", + responses={ + status.HTTP_200_OK: Responses.OK, + status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED, + status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_ADMIN, + }, + response_model=User, +) +async def extend_capabilities( + user_name: str, + capabilities: list[UserCapability], + _: User = Depends(get_current_admin_user), + db: Session | None = Depends(Connection.get), +): + """ + POST ./{user_name}/capabilities: Add capabilities to a user. + """ + + # get and change the user + user = User.from_db( + db=db, + name=user_name, + ) + + user.capabilities.extend(capabilities) + user.update(db) + + # return the modified user + return user + + +@router.delete( + "/{user_name}/capabilities", + responses={ + status.HTTP_200_OK: Responses.OK, + status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED, + status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_ADMIN, + }, + response_model=User, +) +async def remove_capabilities( + user_name: str, + capabilities: list[UserCapability], + _: User | None = Depends(get_current_admin_user), + db: Session | None = Depends(Connection.get), +): + """ + DELETE ./{user_name}/capabilities: Add capabilities to a user. + """ + + # get and change the user + user = User.from_db( + db=db, + name=user_name, + ) + + for capability in capabilities: + user.capabilities.remove(capability) + + user.update(db) + + # return the modified user + return user