From e11f96b0afb3b9005edfc370821cadc77c0ebab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Tue, 29 Mar 2022 16:35:41 +0000 Subject: [PATCH 1/9] dirty commit --- api/kiwi_vpn_api/db/user.py | 86 +++++++++++++++++++++++------ api/kiwi_vpn_api/main.py | 4 +- api/kiwi_vpn_api/routers/_common.py | 4 +- api/kiwi_vpn_api/routers/device.py | 5 +- api/kiwi_vpn_api/routers/user.py | 2 +- 5 files changed, 78 insertions(+), 23 deletions(-) diff --git a/api/kiwi_vpn_api/db/user.py b/api/kiwi_vpn_api/db/user.py index 8e6e5f7..eac3c03 100644 --- a/api/kiwi_vpn_api/db/user.py +++ b/api/kiwi_vpn_api/db/user.py @@ -174,20 +174,6 @@ class User(UserBase, table=True): for capability in self.capabilities ) - def can( - self, - capability: UserCapabilityType, - ) -> bool: - """ - Check if this user has a capability. - """ - - return ( - capability in self.get_capabilities() - # admin can do everything - or UserCapabilityType.admin in self.get_capabilities() - ) - def set_capabilities( self, capabilities: Sequence[UserCapabilityType], @@ -203,6 +189,20 @@ class User(UserBase, table=True): ) for capability in capabilities ] + def _can( + self, + capability: UserCapabilityType, + ) -> bool: + """ + Check if this user has a capability. + """ + + return ( + capability in self.get_capabilities() + # admin can do everything + or UserCapabilityType.admin in self.get_capabilities() + ) + def can_edit( self, user: User, @@ -214,7 +214,61 @@ class User(UserBase, table=True): return ( user.name == self.name # admin can edit everything - or self.can(UserCapabilityType.admin) + or self._can(UserCapabilityType.admin) + ) + + def is_admin( + self, + ) -> bool: + """ + Check if this user is an admin. + """ + + # is admin with "admin" capability + return self._can(UserCapabilityType.admin) + + def can_login( + self, + ) -> bool: + """ + Check if this user can log in. + """ + + return ( + # can login with "login" capability + self._can(UserCapabilityType.login) + # admins can always login + or self.is_admin() + ) + + def can_be_edited_by( + self, + user: User, + ) -> bool: + """ + Check if this user can be edited by another user. + """ + + return ( + # user can edit itself + self.name == user.name + # admin can edit every user + or user._can(UserCapabilityType.admin) + ) + + def can_be_deleted_by( + self, + user: User, + ) -> bool: + """ + Check if this user can be deleted by another user. + """ + + return ( + # only admin can delete users + user._can(UserCapabilityType.admin) + # even admin cannot delete itself + and self.name != user.name ) def owns( @@ -228,5 +282,5 @@ class User(UserBase, table=True): return ( device.owner_name == self.name # admin owns everything - or self.can(UserCapabilityType.admin) + or self._can(UserCapabilityType.admin) ) diff --git a/api/kiwi_vpn_api/main.py b/api/kiwi_vpn_api/main.py index 86ae4f2..eb69de2 100755 --- a/api/kiwi_vpn_api/main.py +++ b/api/kiwi_vpn_api/main.py @@ -13,7 +13,7 @@ import uvicorn from fastapi import FastAPI from .config import Config, Settings -from .db import Connection, User +from .db import Connection, User, UserRead from .routers import main_router app = FastAPI( @@ -43,7 +43,7 @@ async def on_startup() -> None: Connection.connect(current_config.db.uri) # some testing - print(User.get("admin")) + print(UserRead.from_orm(User.get("admin"))) print(User.get("nonexistent")) diff --git a/api/kiwi_vpn_api/routers/_common.py b/api/kiwi_vpn_api/routers/_common.py index 092bb7e..b017952 100644 --- a/api/kiwi_vpn_api/routers/_common.py +++ b/api/kiwi_vpn_api/routers/_common.py @@ -7,7 +7,7 @@ from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from ..config import Config, Settings -from ..db import Device, User, UserCapabilityType +from ..db import Device, User oauth2_scheme = OAuth2PasswordBearer( tokenUrl=f"{Settings._.api_v1_prefix}/user/authenticate" @@ -97,7 +97,7 @@ async def get_current_user_if_admin( Fail if the currently logged-in user is not an admin. """ - if not current_user.can(UserCapabilityType.admin): + if not current_user.is_admin(): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return current_user diff --git a/api/kiwi_vpn_api/routers/device.py b/api/kiwi_vpn_api/routers/device.py index 7b1e58c..21f774f 100644 --- a/api/kiwi_vpn_api/routers/device.py +++ b/api/kiwi_vpn_api/routers/device.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, HTTPException, status from ..db import Device, DeviceCreate, DeviceRead, User -from ._common import Responses, get_device_by_id_if_editable, get_user_by_name +from ._common import (Responses, get_device_by_id_if_editable, + get_user_by_name_if_editable) router = APIRouter(prefix="/device", tags=["device"]) @@ -24,7 +25,7 @@ router = APIRouter(prefix="/device", tags=["device"]) ) async def add_device( device: DeviceCreate, - user: User = Depends(get_user_by_name), + user: User = Depends(get_user_by_name_if_editable), ) -> Device: """ POST ./: Create a new device in the database. diff --git a/api/kiwi_vpn_api/routers/user.py b/api/kiwi_vpn_api/routers/user.py index 8bd9b74..5772d5b 100644 --- a/api/kiwi_vpn_api/routers/user.py +++ b/api/kiwi_vpn_api/routers/user.py @@ -48,7 +48,7 @@ async def login( headers={"WWW-Authenticate": "Bearer"}, ) - if not user.can(UserCapabilityType.login): + if not user.can_login(): # user cannot login raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) From bb53bab0c02f842d9adb34b4d97821ae7a0e8dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Tue, 29 Mar 2022 19:57:33 +0000 Subject: [PATCH 2/9] rename "capability" -> "tag" --- api/kiwi_vpn_api/db/__init__.py | 4 +- api/kiwi_vpn_api/db/tag.py | 56 ++++++++++++++++++++++++++ api/kiwi_vpn_api/db/user.py | 50 +++++++++++------------ api/kiwi_vpn_api/db/user_capability.py | 56 -------------------------- api/kiwi_vpn_api/routers/admin.py | 4 +- api/kiwi_vpn_api/routers/user.py | 24 +++++------ 6 files changed, 97 insertions(+), 97 deletions(-) create mode 100644 api/kiwi_vpn_api/db/tag.py delete mode 100644 api/kiwi_vpn_api/db/user_capability.py diff --git a/api/kiwi_vpn_api/db/__init__.py b/api/kiwi_vpn_api/db/__init__.py index ae088cd..14006e1 100644 --- a/api/kiwi_vpn_api/db/__init__.py +++ b/api/kiwi_vpn_api/db/__init__.py @@ -4,8 +4,8 @@ Package `db`: ORM and schemas for database content. from .connection import Connection from .device import Device, DeviceBase, DeviceCreate, DeviceRead +from .tag import TagValue from .user import User, UserBase, UserCreate, UserRead -from .user_capability import UserCapabilityType __all__ = [ "Connection", @@ -17,5 +17,5 @@ __all__ = [ "UserBase", "UserCreate", "UserRead", - "UserCapabilityType", + "TagValue", ] diff --git a/api/kiwi_vpn_api/db/tag.py b/api/kiwi_vpn_api/db/tag.py new file mode 100644 index 0000000..b29622b --- /dev/null +++ b/api/kiwi_vpn_api/db/tag.py @@ -0,0 +1,56 @@ +""" +Python representation of `tag` table. +""" + +from enum import Enum +from typing import TYPE_CHECKING + +from sqlmodel import Field, Relationship, SQLModel + +if TYPE_CHECKING: + from .user import User + + +class TagValue(Enum): + """ + Allowed values for tags + """ + + admin = "admin" + login = "login" + issue = "issue" + renew = "renew" + + def __repr__(self) -> str: + return self.value + + +class TagBase(SQLModel): + """ + Common to all representations of tags + """ + + tag_value: str = Field(primary_key=True) + + @property + def _(self) -> TagValue: + """ + Transform into a `TagValue`. + """ + + return TagValue(self.tag_value) + + def __repr__(self) -> str: + return self.tag_value + + +class Tag(TagBase, table=True): + """ + Representation of `tag` table + """ + + user_name: str = Field(primary_key=True, foreign_key="user.name") + + user: "User" = Relationship( + back_populates="tags", + ) diff --git a/api/kiwi_vpn_api/db/user.py b/api/kiwi_vpn_api/db/user.py index eac3c03..997dab0 100644 --- a/api/kiwi_vpn_api/db/user.py +++ b/api/kiwi_vpn_api/db/user.py @@ -13,7 +13,7 @@ from sqlmodel import Field, Relationship, SQLModel from ..config import Config from .connection import Connection from .device import Device -from .user_capability import UserCapability, UserCapabilityType +from .tag import Tag, TagValue class UserBase(SQLModel): @@ -77,7 +77,7 @@ class User(UserBase, table=True): password: str - capabilities: list[UserCapability] = Relationship( + tags: list[Tag] = Relationship( back_populates="user", sa_relationship_kwargs={ "lazy": "joined", @@ -164,43 +164,43 @@ class User(UserBase, table=True): db.delete(self) db.commit() - def get_capabilities(self) -> set[UserCapabilityType]: + def get_tags(self) -> set[TagValue]: """ - Return the capabilities of this user. + Return the tags of this user. """ return set( - capability._ - for capability in self.capabilities + tag._ + for tag in self.tags ) - def set_capabilities( + def set_tags( self, - capabilities: Sequence[UserCapabilityType], + tags: Sequence[TagValue], ) -> None: """ - Change the capabilities of this user. + Change the tags of this user. """ - self.capabilities = [ - UserCapability( + self.tags = [ + Tag( user_name=self.name, - capability_name=capability.value, - ) for capability in capabilities + tag_value=tag.value, + ) for tag in tags ] def _can( self, - capability: UserCapabilityType, + tag: TagValue, ) -> bool: """ - Check if this user has a capability. + Check if this user has a tag. """ return ( - capability in self.get_capabilities() + tag in self.get_tags() # admin can do everything - or UserCapabilityType.admin in self.get_capabilities() + or TagValue.admin in self.get_tags() ) def can_edit( @@ -214,7 +214,7 @@ class User(UserBase, table=True): return ( user.name == self.name # admin can edit everything - or self._can(UserCapabilityType.admin) + or self._can(TagValue.admin) ) def is_admin( @@ -224,8 +224,8 @@ class User(UserBase, table=True): Check if this user is an admin. """ - # is admin with "admin" capability - return self._can(UserCapabilityType.admin) + # is admin with "admin" tag + return self._can(TagValue.admin) def can_login( self, @@ -235,8 +235,8 @@ class User(UserBase, table=True): """ return ( - # can login with "login" capability - self._can(UserCapabilityType.login) + # can login with "login" tag + self._can(TagValue.login) # admins can always login or self.is_admin() ) @@ -253,7 +253,7 @@ class User(UserBase, table=True): # user can edit itself self.name == user.name # admin can edit every user - or user._can(UserCapabilityType.admin) + or user._can(TagValue.admin) ) def can_be_deleted_by( @@ -266,7 +266,7 @@ class User(UserBase, table=True): return ( # only admin can delete users - user._can(UserCapabilityType.admin) + user._can(TagValue.admin) # even admin cannot delete itself and self.name != user.name ) @@ -282,5 +282,5 @@ class User(UserBase, table=True): return ( device.owner_name == self.name # admin owns everything - or self._can(UserCapabilityType.admin) + or self._can(TagValue.admin) ) diff --git a/api/kiwi_vpn_api/db/user_capability.py b/api/kiwi_vpn_api/db/user_capability.py deleted file mode 100644 index 479fec4..0000000 --- a/api/kiwi_vpn_api/db/user_capability.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Python representation of `user_capability` table. -""" - -from enum import Enum -from typing import TYPE_CHECKING - -from sqlmodel import Field, Relationship, SQLModel - -if TYPE_CHECKING: - from .user import User - - -class UserCapabilityType(Enum): - """ - Allowed values for capabilities - """ - - admin = "admin" - login = "login" - issue = "issue" - renew = "renew" - - def __repr__(self) -> str: - return self.value - - -class UserCapabilityBase(SQLModel): - """ - Common to all representations of capabilities - """ - - capability_name: str = Field(primary_key=True) - - @property - def _(self) -> UserCapabilityType: - """ - Transform into a `Capability`. - """ - - return UserCapabilityType(self.capability_name) - - def __repr__(self) -> str: - return self.capability_name - - -class UserCapability(UserCapabilityBase, table=True): - """ - Representation of `user_capability` table - """ - - user_name: str = Field(primary_key=True, foreign_key="user.name") - - user: "User" = Relationship( - back_populates="capabilities", - ) diff --git a/api/kiwi_vpn_api/routers/admin.py b/api/kiwi_vpn_api/routers/admin.py index 29c442f..e5e3941 100644 --- a/api/kiwi_vpn_api/routers/admin.py +++ b/api/kiwi_vpn_api/routers/admin.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlmodel import select from ..config import Config -from ..db import Connection, User, UserCapabilityType, UserCreate +from ..db import Connection, TagValue, User, UserCreate from ._common import Responses, get_current_user_if_admin router = APIRouter(prefix="/admin", tags=["admin"]) @@ -64,7 +64,7 @@ async def create_initial_admin( # create an administrative user new_user = User.create(user=admin_user) - new_user.set_capabilities([UserCapabilityType.admin]) + new_user.set_tags([TagValue.admin]) new_user.update() diff --git a/api/kiwi_vpn_api/routers/user.py b/api/kiwi_vpn_api/routers/user.py index 5772d5b..35346c6 100644 --- a/api/kiwi_vpn_api/routers/user.py +++ b/api/kiwi_vpn_api/routers/user.py @@ -7,7 +7,7 @@ from fastapi.security import OAuth2PasswordRequestForm from pydantic import BaseModel from ..config import Config -from ..db import User, UserCapabilityType, UserCreate, UserRead +from ..db import TagValue, User, UserCreate, UserRead from ._common import (Responses, get_current_user_if_admin, get_current_user_if_exists, get_user_by_name) @@ -94,7 +94,7 @@ async def add_user( if new_user is None: raise HTTPException(status_code=status.HTTP_409_CONFLICT) - new_user.set_capabilities([UserCapabilityType.login]) + new_user.set_tags([TagValue.login]) new_user.update() # return the created user on success @@ -130,7 +130,7 @@ async def remove_user( @router.post( - "/{user_name}/capabilities", + "/{user_name}/tags", responses={ status.HTTP_200_OK: Responses.OK, status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED, @@ -138,22 +138,22 @@ async def remove_user( status.HTTP_403_FORBIDDEN: Responses.NEEDS_ADMIN, }, ) -async def extend_capabilities( - capabilities: list[UserCapabilityType], +async def extend_tags( + tags: list[TagValue], _: User = Depends(get_current_user_if_admin), user: User = Depends(get_user_by_name), ): """ - POST ./{user_name}/capabilities: Add capabilities to a user. + POST ./{user_name}/tags: Add tags to a user. """ - user.set_capabilities(user.get_capabilities() | set(capabilities)) + user.set_tags(user.get_tags() | set(tags)) user.update() @router.delete( - "/{user_name}/capabilities", + "/{user_name}/tags", responses={ status.HTTP_200_OK: Responses.OK, status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED, @@ -161,15 +161,15 @@ async def extend_capabilities( status.HTTP_403_FORBIDDEN: Responses.NEEDS_ADMIN, }, ) -async def remove_capabilities( - capabilities: list[UserCapabilityType], +async def remove_tags( + tags: list[TagValue], _: User = Depends(get_current_user_if_admin), user: User = Depends(get_user_by_name), ): """ - DELETE ./{user_name}/capabilities: Remove capabilities from a user. + DELETE ./{user_name}/tags: Remove tags from a user. """ - user.set_capabilities(user.get_capabilities() - set(capabilities)) + user.set_tags(user.get_tags() - set(tags)) user.update() From 0d02c24b64e726da44797d5d770fc47b60b07633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Tue, 29 Mar 2022 20:46:40 +0000 Subject: [PATCH 3/9] start "permission" implementation --- api/kiwi_vpn_api/main.py | 7 +++++-- api/kiwi_vpn_api/permission.py | 35 ++++++++++++++++++++++++++++++++++ api/plan.md | 2 +- 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 api/kiwi_vpn_api/permission.py diff --git a/api/kiwi_vpn_api/main.py b/api/kiwi_vpn_api/main.py index eb69de2..444871d 100755 --- a/api/kiwi_vpn_api/main.py +++ b/api/kiwi_vpn_api/main.py @@ -13,7 +13,8 @@ import uvicorn from fastapi import FastAPI from .config import Config, Settings -from .db import Connection, User, UserRead +from .db import Connection, User +from .permission import Permission from .routers import main_router app = FastAPI( @@ -43,9 +44,11 @@ async def on_startup() -> None: Connection.connect(current_config.db.uri) # some testing - print(UserRead.from_orm(User.get("admin"))) + print(admin := User.get("admin")) print(User.get("nonexistent")) + print(Permission._(admin, admin)) + def main() -> None: uvicorn.run( diff --git a/api/kiwi_vpn_api/permission.py b/api/kiwi_vpn_api/permission.py new file mode 100644 index 0000000..4f9b83a --- /dev/null +++ b/api/kiwi_vpn_api/permission.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from enum import Enum, auto + +from .db import User + + +class Permission(Enum): + tag = auto() + untag = auto() + edit = auto() + delete = auto() + + def __repr__(self) -> str: + return self.name + + @classmethod + def _( + cls, + actor: User | None, + target: User, + ) -> set[Permission]: + result = set() + + if actor is None: + return result + + if isinstance(target, User): + if actor.is_admin(): + if target != actor: + result |= set([cls.tag, cls.untag, cls.delete]) + + result.add(cls.edit) + + return result diff --git a/api/plan.md b/api/plan.md index 0822af0..fdeaae1 100644 --- a/api/plan.md +++ b/api/plan.md @@ -13,7 +13,7 @@ - custom DN parts: country, state, city, org, OU - email -## User caps +## User tags - admin: administrator - login: can log into the web interface - issue: can certify own devices without approval From 03d3a86668b7f6e65019e0c54a4dde209790d373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Tue, 29 Mar 2022 23:36:23 +0000 Subject: [PATCH 4/9] basic permissions system --- api/kiwi_vpn_api/db/user.py | 120 +++++++++++----------------- api/kiwi_vpn_api/main.py | 5 +- api/kiwi_vpn_api/permission.py | 35 -------- api/kiwi_vpn_api/routers/_common.py | 60 +++----------- api/kiwi_vpn_api/routers/device.py | 24 ++++-- api/kiwi_vpn_api/routers/user.py | 12 +-- api/plan.md | 28 +++++-- 7 files changed, 99 insertions(+), 185 deletions(-) delete mode 100644 api/kiwi_vpn_api/permission.py diff --git a/api/kiwi_vpn_api/db/user.py b/api/kiwi_vpn_api/db/user.py index 997dab0..7e0164d 100644 --- a/api/kiwi_vpn_api/db/user.py +++ b/api/kiwi_vpn_api/db/user.py @@ -143,6 +143,10 @@ class User(UserBase, table=True): # password hash mismatch return None + if TagValue.login in user.get_tags(): + # no login permission + return None + return user def update(self) -> None: @@ -189,98 +193,64 @@ class User(UserBase, table=True): ) for tag in tags ] - def _can( + def may_edit( self, - tag: TagValue, + target: User | Device, ) -> bool: """ - Check if this user has a tag. + Check if this user can edit another user or a device. """ - return ( - tag in self.get_tags() - # admin can do everything - or TagValue.admin in self.get_tags() - ) + # admin can "edit" everything + if TagValue.admin in self.get_tags(): + return True - def can_edit( + # user can "edit" itself + if isinstance(target, User) and target != self: + return False + + # user can edit its owned devices + return target.owner == self + + def may_admin( self, - user: User, + target: User | Device, ) -> bool: """ - Check if this user can edit another user. + Check if this user can administer another user or a device. """ - return ( - user.name == self.name - # admin can edit everything - or self._can(TagValue.admin) - ) + # only admin can "admin" anything + if TagValue.admin not in self.get_tags(): + return False - def is_admin( + # admin canot "admin itself"! + if isinstance(target, User) and target == self: + return False + + # admin can "admin" everything else + return True + + def may_create( self, + target: type, + owner: User | None = None, ) -> bool: """ - Check if this user is an admin. + Check if this user can create another user or a device. """ - # is admin with "admin" tag - return self._can(TagValue.admin) + # can never create anything but users or devices + if not issubclass(target, (User, Device)): + return False - def can_login( - self, - ) -> bool: - """ - Check if this user can log in. - """ + # admin can "create" everything + if TagValue.admin in self.get_tags(): + return True - return ( - # can login with "login" tag - self._can(TagValue.login) - # admins can always login - or self.is_admin() - ) + # user can only create devices for itself + if target is Device and owner == self: + return True - def can_be_edited_by( - self, - user: User, - ) -> bool: - """ - Check if this user can be edited by another user. - """ - - return ( - # user can edit itself - self.name == user.name - # admin can edit every user - or user._can(TagValue.admin) - ) - - def can_be_deleted_by( - self, - user: User, - ) -> bool: - """ - Check if this user can be deleted by another user. - """ - - return ( - # only admin can delete users - user._can(TagValue.admin) - # even admin cannot delete itself - and self.name != user.name - ) - - def owns( - self, - device: Device, - ) -> bool: - """ - Check if this user owns a device. - """ - - return ( - device.owner_name == self.name - # admin owns everything - or self._can(TagValue.admin) - ) + # deny be default + return False diff --git a/api/kiwi_vpn_api/main.py b/api/kiwi_vpn_api/main.py index 444871d..86ae4f2 100755 --- a/api/kiwi_vpn_api/main.py +++ b/api/kiwi_vpn_api/main.py @@ -14,7 +14,6 @@ from fastapi import FastAPI from .config import Config, Settings from .db import Connection, User -from .permission import Permission from .routers import main_router app = FastAPI( @@ -44,11 +43,9 @@ async def on_startup() -> None: Connection.connect(current_config.db.uri) # some testing - print(admin := User.get("admin")) + print(User.get("admin")) print(User.get("nonexistent")) - print(Permission._(admin, admin)) - def main() -> None: uvicorn.run( diff --git a/api/kiwi_vpn_api/permission.py b/api/kiwi_vpn_api/permission.py deleted file mode 100644 index 4f9b83a..0000000 --- a/api/kiwi_vpn_api/permission.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from enum import Enum, auto - -from .db import User - - -class Permission(Enum): - tag = auto() - untag = auto() - edit = auto() - delete = auto() - - def __repr__(self) -> str: - return self.name - - @classmethod - def _( - cls, - actor: User | None, - target: User, - ) -> set[Permission]: - result = set() - - if actor is None: - return result - - if isinstance(target, User): - if actor.is_admin(): - if target != actor: - result |= set([cls.tag, cls.untag, cls.delete]) - - result.add(cls.edit) - - return result diff --git a/api/kiwi_vpn_api/routers/_common.py b/api/kiwi_vpn_api/routers/_common.py index b017952..0732135 100644 --- a/api/kiwi_vpn_api/routers/_common.py +++ b/api/kiwi_vpn_api/routers/_common.py @@ -5,6 +5,7 @@ Common dependencies for routers. from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer +from kiwi_vpn_api.db.tag import TagValue from ..config import Config, Settings from ..db import Device, User @@ -40,8 +41,8 @@ class Responses: "description": "Must be admin", "content": None, } - NEEDS_REQUESTED_USER = { - "description": "Must be the requested user", + PERMISSION_ERROR = { + "description": "You're not allowed that action", "content": None, } ENTRY_EXISTS = { @@ -61,9 +62,9 @@ class Responses: async def get_current_user( token: str = Depends(oauth2_scheme), current_config: Config | None = Depends(Config.load), -) -> User | None: +) -> User: """ - Get the currently logged-in user from the database. + Get the currently logged-in user if it exists. """ # can't connect to an unconfigured database @@ -72,32 +73,22 @@ async def get_current_user( username = await current_config.jwt.decode_token(token) - return User.get(username) - - -async def get_current_user_if_exists( - current_user: User | None = Depends(get_current_user), -) -> User: - """ - Get the currently logged-in user if it exists. - """ - # fail if not requested by a user - if current_user is None: + if (user := User.get(username)) is None: # don't use error 404 here: possible user enumeration raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - return current_user + return user async def get_current_user_if_admin( - current_user: User = Depends(get_current_user_if_exists), + current_user: User = Depends(get_current_user), ) -> User: """ Fail if the currently logged-in user is not an admin. """ - if not current_user.is_admin(): + if TagValue.admin not in current_user.get_tags(): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return current_user @@ -118,25 +109,6 @@ async def get_user_by_name( return User.get(user_name) -async def get_user_by_name_if_editable( - user: User | None = Depends(get_user_by_name), - current_user: User = Depends(get_current_user_if_exists), -) -> User: - """ - Get a user by name if it can be edited by the current user. - """ - - # fail if user doesn't exist - if user is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - - # fail if user isn't editable by the current user - if not current_user.can_edit(user): - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - - return user - - async def get_device_by_id( device_id: int, current_config: Config | None = Depends(Config.load), @@ -146,20 +118,8 @@ async def get_device_by_id( if current_config is None: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) - return Device.get(device_id) - - -async def get_device_by_id_if_editable( - device: Device | None = Depends(get_device_by_id), - current_user: User = Depends(get_current_user_if_exists), -) -> Device: - # fail if device doesn't exist - if device is None: + if (device := Device.get(device_id)) is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - # fail if device is not owned by current user - if not current_user.owns(device): - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - return device diff --git a/api/kiwi_vpn_api/routers/device.py b/api/kiwi_vpn_api/routers/device.py index 21f774f..957e31d 100644 --- a/api/kiwi_vpn_api/routers/device.py +++ b/api/kiwi_vpn_api/routers/device.py @@ -5,19 +5,19 @@ from fastapi import APIRouter, Depends, HTTPException, status from ..db import Device, DeviceCreate, DeviceRead, User -from ._common import (Responses, get_device_by_id_if_editable, - get_user_by_name_if_editable) +from ._common import (Responses, get_current_user, get_device_by_id, + get_user_by_name) router = APIRouter(prefix="/device", tags=["device"]) @router.post( - "", + "/{user_name}", 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_REQUESTED_USER, + status.HTTP_403_FORBIDDEN: Responses.PERMISSION_ERROR, status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST, status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS, }, @@ -25,15 +25,20 @@ router = APIRouter(prefix="/device", tags=["device"]) ) async def add_device( device: DeviceCreate, - user: User = Depends(get_user_by_name_if_editable), + current_user: User = Depends(get_current_user), + owner: User = Depends(get_user_by_name), ) -> Device: """ POST ./: Create a new device in the database. """ + # check permission + if not current_user.may_create(Device, owner): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + # create the new device new_device = Device.create( - owner=user, + owner=current_user, device=device, ) @@ -57,11 +62,16 @@ async def add_device( response_model=User, ) async def remove_device( - device: Device = Depends(get_device_by_id_if_editable), + current_user: User = Depends(get_current_user), + device: Device = Depends(get_device_by_id), ): """ DELETE ./{device_id}: Remove a device from the database. """ + # check permission + if not current_user.may_edit(device): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + # delete device device.delete() diff --git a/api/kiwi_vpn_api/routers/user.py b/api/kiwi_vpn_api/routers/user.py index 35346c6..2572d40 100644 --- a/api/kiwi_vpn_api/routers/user.py +++ b/api/kiwi_vpn_api/routers/user.py @@ -9,7 +9,7 @@ from pydantic import BaseModel from ..config import Config from ..db import TagValue, User, UserCreate, UserRead from ._common import (Responses, get_current_user_if_admin, - get_current_user_if_exists, get_user_by_name) + get_current_user, get_user_by_name) router = APIRouter(prefix="/user", tags=["user"]) @@ -37,10 +37,10 @@ async def login( raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) # try logging in - if not (user := User.authenticate( + if (user := User.authenticate( name=form_data.username, password=form_data.password, - )): + )) is None: # authentication failed raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -48,10 +48,6 @@ async def login( headers={"WWW-Authenticate": "Bearer"}, ) - if not user.can_login(): - # user cannot login - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - # authentication succeeded access_token = await current_config.jwt.create_token(user.name) return {"access_token": access_token, "token_type": "bearer"} @@ -59,7 +55,7 @@ async def login( @router.get("/current", response_model=UserRead) async def get_current_user( - current_user: User = Depends(get_current_user_if_exists), + current_user: User = Depends(get_current_user), ): """ GET ./current: Respond with the currently logged-in user. diff --git a/api/plan.md b/api/plan.md index fdeaae1..09120c9 100644 --- a/api/plan.md +++ b/api/plan.md @@ -4,22 +4,38 @@ - flag: use client-to-client - force cipher, tls-cipher, auth params - server name -- default certification length +- default certification duration - default certificate algo ## User props -- username +- username (CN part) - password - custom DN parts: country, state, city, org, OU -- email +- email (DN part) +- tags ## User tags - admin: administrator - login: can log into the web interface -- issue: can certify own devices without approval -- renew: can renew certificates for own devices +- issue: can certify own devices (without approval) +- renew: can renew certificates for own devices (without approval) ## Device props -- name +- name (CN part) - type (icon) +- approved: bool - expiry + +## Permissions +- admin cannot "admin" itself (to prevent self decapitation) +- admin can "edit", "admin" and "create" everything else +- user can "edit" itself and its devices +- user can "create" devices for itself + +### User +- edit: change DN parts, password +- admin: add or remove tag, delete, generate password + +### Device +- edit: change type, delete +- admin: approve From 9b5a98e0c07d755fec26a82af8f1bf6a8c31af5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 30 Mar 2022 01:51:43 +0000 Subject: [PATCH 5/9] rework User methods --- api/kiwi_vpn_api/db/tag.py | 14 +++++++- api/kiwi_vpn_api/db/user.py | 50 ++++++++++++++++++++--------- api/kiwi_vpn_api/routers/_common.py | 2 +- api/kiwi_vpn_api/routers/admin.py | 2 +- api/kiwi_vpn_api/routers/device.py | 4 +-- api/kiwi_vpn_api/routers/user.py | 10 +++--- 6 files changed, 56 insertions(+), 26 deletions(-) diff --git a/api/kiwi_vpn_api/db/tag.py b/api/kiwi_vpn_api/db/tag.py index b29622b..403f124 100644 --- a/api/kiwi_vpn_api/db/tag.py +++ b/api/kiwi_vpn_api/db/tag.py @@ -2,6 +2,8 @@ Python representation of `tag` table. """ +from __future__ import annotations + from enum import Enum from typing import TYPE_CHECKING @@ -24,6 +26,16 @@ class TagValue(Enum): def __repr__(self) -> str: return self.value + def _(self, user: User) -> Tag: + """ + Transform into a `Tag`. + """ + + return Tag( + user=user, + tag_value=self.value, + ) + class TagBase(SQLModel): """ @@ -51,6 +63,6 @@ class Tag(TagBase, table=True): user_name: str = Field(primary_key=True, foreign_key="user.name") - user: "User" = Relationship( + user: User = Relationship( back_populates="tags", ) diff --git a/api/kiwi_vpn_api/db/user.py b/api/kiwi_vpn_api/db/user.py index 7e0164d..ad1d51f 100644 --- a/api/kiwi_vpn_api/db/user.py +++ b/api/kiwi_vpn_api/db/user.py @@ -4,7 +4,7 @@ Python representation of `user` table. from __future__ import annotations -from typing import Any, Sequence +from typing import Any, Iterable, Sequence from pydantic import root_validator from sqlalchemy.exc import IntegrityError @@ -143,7 +143,7 @@ class User(UserBase, table=True): # password hash mismatch return None - if TagValue.login in user.get_tags(): + if user.has_tag(TagValue.login): # no login permission return None @@ -168,32 +168,50 @@ class User(UserBase, table=True): db.delete(self) db.commit() - def get_tags(self) -> set[TagValue]: + def _get_tags(self) -> Iterable[TagValue]: """ Return the tags of this user. """ - return set( + return ( tag._ for tag in self.tags ) - def set_tags( + def has_tag(self, tag: TagValue) -> bool: + """ + Check if this user has a tag. + """ + + return tag in self._get_tags + + def add_tags( self, tags: Sequence[TagValue], ) -> None: """ - Change the tags of this user. + Add tags to this user. """ self.tags = [ - Tag( - user_name=self.name, - tag_value=tag.value, - ) for tag in tags + tag._(self) + for tag in (set(self._get_tags()) | set(tags)) ] - def may_edit( + def remove_tags( + self, + tags: Sequence[TagValue], + ) -> None: + """ + remove tags from this user. + """ + + self.tags = [ + tag._(self) + for tag in (set(self._get_tags()) - set(tags)) + ] + + def can_edit( self, target: User | Device, ) -> bool: @@ -202,7 +220,7 @@ class User(UserBase, table=True): """ # admin can "edit" everything - if TagValue.admin in self.get_tags(): + if self.has_tag(TagValue.admin): return True # user can "edit" itself @@ -212,7 +230,7 @@ class User(UserBase, table=True): # user can edit its owned devices return target.owner == self - def may_admin( + def can_admin( self, target: User | Device, ) -> bool: @@ -221,7 +239,7 @@ class User(UserBase, table=True): """ # only admin can "admin" anything - if TagValue.admin not in self.get_tags(): + if not self.has_tag(TagValue.admin): return False # admin canot "admin itself"! @@ -231,7 +249,7 @@ class User(UserBase, table=True): # admin can "admin" everything else return True - def may_create( + def can_create( self, target: type, owner: User | None = None, @@ -245,7 +263,7 @@ class User(UserBase, table=True): return False # admin can "create" everything - if TagValue.admin in self.get_tags(): + if self.has_tag(TagValue.admin): return True # user can only create devices for itself diff --git a/api/kiwi_vpn_api/routers/_common.py b/api/kiwi_vpn_api/routers/_common.py index 0732135..1d7bff7 100644 --- a/api/kiwi_vpn_api/routers/_common.py +++ b/api/kiwi_vpn_api/routers/_common.py @@ -88,7 +88,7 @@ async def get_current_user_if_admin( Fail if the currently logged-in user is not an admin. """ - if TagValue.admin not in current_user.get_tags(): + if current_user.has_tag(TagValue.admin): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return current_user diff --git a/api/kiwi_vpn_api/routers/admin.py b/api/kiwi_vpn_api/routers/admin.py index e5e3941..7727bad 100644 --- a/api/kiwi_vpn_api/routers/admin.py +++ b/api/kiwi_vpn_api/routers/admin.py @@ -64,7 +64,7 @@ async def create_initial_admin( # create an administrative user new_user = User.create(user=admin_user) - new_user.set_tags([TagValue.admin]) + new_user.add_tags([TagValue.admin]) new_user.update() diff --git a/api/kiwi_vpn_api/routers/device.py b/api/kiwi_vpn_api/routers/device.py index 957e31d..6611cbf 100644 --- a/api/kiwi_vpn_api/routers/device.py +++ b/api/kiwi_vpn_api/routers/device.py @@ -33,7 +33,7 @@ async def add_device( """ # check permission - if not current_user.may_create(Device, owner): + if not current_user.can_create(Device, owner): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) # create the new device @@ -70,7 +70,7 @@ async def remove_device( """ # check permission - if not current_user.may_edit(device): + if not current_user.can_edit(device): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) # delete device diff --git a/api/kiwi_vpn_api/routers/user.py b/api/kiwi_vpn_api/routers/user.py index 2572d40..0529b16 100644 --- a/api/kiwi_vpn_api/routers/user.py +++ b/api/kiwi_vpn_api/routers/user.py @@ -8,8 +8,8 @@ from pydantic import BaseModel from ..config import Config from ..db import TagValue, User, UserCreate, UserRead -from ._common import (Responses, get_current_user_if_admin, - get_current_user, get_user_by_name) +from ._common import (Responses, get_current_user, get_current_user_if_admin, + get_user_by_name) router = APIRouter(prefix="/user", tags=["user"]) @@ -90,7 +90,7 @@ async def add_user( if new_user is None: raise HTTPException(status_code=status.HTTP_409_CONFLICT) - new_user.set_tags([TagValue.login]) + new_user.add_tags([TagValue.login]) new_user.update() # return the created user on success @@ -143,7 +143,7 @@ async def extend_tags( POST ./{user_name}/tags: Add tags to a user. """ - user.set_tags(user.get_tags() | set(tags)) + user.add_tags(tags) user.update() @@ -166,6 +166,6 @@ async def remove_tags( DELETE ./{user_name}/tags: Remove tags from a user. """ - user.set_tags(user.get_tags() - set(tags)) + user.remove_tags(tags) user.update() From 3b66565481c2917560bc2f3e6d6e8c33bf184d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 30 Mar 2022 02:02:45 +0000 Subject: [PATCH 6/9] remove get_current_user_if_admin --- api/kiwi_vpn_api/routers/_common.py | 22 ++-------------- api/kiwi_vpn_api/routers/admin.py | 8 ++++-- api/kiwi_vpn_api/routers/device.py | 2 +- api/kiwi_vpn_api/routers/user.py | 40 ++++++++++++++++++----------- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/api/kiwi_vpn_api/routers/_common.py b/api/kiwi_vpn_api/routers/_common.py index 1d7bff7..875b15e 100644 --- a/api/kiwi_vpn_api/routers/_common.py +++ b/api/kiwi_vpn_api/routers/_common.py @@ -5,7 +5,6 @@ Common dependencies for routers. from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer -from kiwi_vpn_api.db.tag import TagValue from ..config import Config, Settings from ..db import Device, User @@ -41,8 +40,8 @@ class Responses: "description": "Must be admin", "content": None, } - PERMISSION_ERROR = { - "description": "You're not allowed that action", + NEEDS_PERMISSION = { + "description": "You're not allowed that operation", "content": None, } ENTRY_EXISTS = { @@ -53,10 +52,6 @@ class Responses: "description": "Entry does not exist in database", "content": None, } - CANT_TARGET_SELF = { - "description": "Operation can't target yourself", - "content": None, - } async def get_current_user( @@ -81,19 +76,6 @@ async def get_current_user( return user -async def get_current_user_if_admin( - current_user: User = Depends(get_current_user), -) -> User: - """ - Fail if the currently logged-in user is not an admin. - """ - - if current_user.has_tag(TagValue.admin): - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) - - return current_user - - async def get_user_by_name( user_name: str, current_config: Config | None = Depends(Config.load), diff --git a/api/kiwi_vpn_api/routers/admin.py b/api/kiwi_vpn_api/routers/admin.py index 7727bad..b337a9a 100644 --- a/api/kiwi_vpn_api/routers/admin.py +++ b/api/kiwi_vpn_api/routers/admin.py @@ -8,7 +8,7 @@ from sqlmodel import select from ..config import Config from ..db import Connection, TagValue, User, UserCreate -from ._common import Responses, get_current_user_if_admin +from ._common import Responses, get_current_user router = APIRouter(prefix="/admin", tags=["admin"]) @@ -79,12 +79,16 @@ async def create_initial_admin( ) async def set_config( config: Config, - _: User = Depends(get_current_user_if_admin), + current_user: User = Depends(get_current_user), ): """ PUT ./config: Edit `kiwi-vpn` main config. """ + # check permissions + if not current_user.has_tag(TagValue.admin): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + # update config file, reconnect to database config.save() Connection.connect(config.db.uri) diff --git a/api/kiwi_vpn_api/routers/device.py b/api/kiwi_vpn_api/routers/device.py index 6611cbf..4e2bd16 100644 --- a/api/kiwi_vpn_api/routers/device.py +++ b/api/kiwi_vpn_api/routers/device.py @@ -17,7 +17,7 @@ router = APIRouter(prefix="/device", tags=["device"]) 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.PERMISSION_ERROR, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST, status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS, }, diff --git a/api/kiwi_vpn_api/routers/user.py b/api/kiwi_vpn_api/routers/user.py index 0529b16..56a0a35 100644 --- a/api/kiwi_vpn_api/routers/user.py +++ b/api/kiwi_vpn_api/routers/user.py @@ -8,8 +8,7 @@ from pydantic import BaseModel from ..config import Config from ..db import TagValue, User, UserCreate, UserRead -from ._common import (Responses, get_current_user, get_current_user_if_admin, - get_user_by_name) +from ._common import Responses, get_current_user, get_user_by_name router = APIRouter(prefix="/user", tags=["user"]) @@ -54,7 +53,7 @@ async def login( @router.get("/current", response_model=UserRead) -async def get_current_user( +async def get_current_user_route( current_user: User = Depends(get_current_user), ): """ @@ -77,13 +76,17 @@ async def get_current_user( ) async def add_user( user: UserCreate, - _: User = Depends(get_current_user_if_admin), + current_user: User = Depends(get_current_user), ) -> User: """ POST ./: Create a new user in the database. """ - # actually create the new user + # check permissions + if not current_user.can_create(User): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + + # create the new user new_user = User.create(user=user) # fail if creation was unsuccessful @@ -103,23 +106,22 @@ async def add_user( 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, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST, - status.HTTP_406_NOT_ACCEPTABLE: Responses.CANT_TARGET_SELF, }, response_model=User, ) async def remove_user( - current_user: User = Depends(get_current_user_if_admin), + current_user: User = Depends(get_current_user), user: User = Depends(get_user_by_name), ): """ DELETE ./{user_name}: Remove a user from the database. """ - # stop inting - if current_user.name == user.name: - raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE) + # check permissions + if not current_user.can_admin(user): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) # delete user user.delete() @@ -136,15 +138,19 @@ async def remove_user( ) async def extend_tags( tags: list[TagValue], - _: User = Depends(get_current_user_if_admin), + current_user: User = Depends(get_current_user), user: User = Depends(get_user_by_name), ): """ POST ./{user_name}/tags: Add tags to a user. """ - user.add_tags(tags) + # check permissions + if not current_user.can_admin(user): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + # change user + user.add_tags(tags) user.update() @@ -159,13 +165,17 @@ async def extend_tags( ) async def remove_tags( tags: list[TagValue], - _: User = Depends(get_current_user_if_admin), + current_user: User = Depends(get_current_user), user: User = Depends(get_user_by_name), ): """ DELETE ./{user_name}/tags: Remove tags from a user. """ - user.remove_tags(tags) + # check permissions + if not current_user.can_admin(user): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + # change user + user.remove_tags(tags) user.update() From 598b0ca2cb718468b7ebf01302f6cd0cbfd2f843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 30 Mar 2022 02:07:22 +0000 Subject: [PATCH 7/9] remove NEEDS_ADMIN --- api/kiwi_vpn_api/routers/_common.py | 4 ---- api/kiwi_vpn_api/routers/admin.py | 2 +- api/kiwi_vpn_api/routers/device.py | 2 +- api/kiwi_vpn_api/routers/user.py | 6 +++--- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/api/kiwi_vpn_api/routers/_common.py b/api/kiwi_vpn_api/routers/_common.py index 875b15e..885c6c4 100644 --- a/api/kiwi_vpn_api/routers/_common.py +++ b/api/kiwi_vpn_api/routers/_common.py @@ -36,10 +36,6 @@ class Responses: "description": "Must be logged in", "content": None, } - NEEDS_ADMIN = { - "description": "Must be admin", - "content": None, - } NEEDS_PERMISSION = { "description": "You're not allowed that operation", "content": None, diff --git a/api/kiwi_vpn_api/routers/admin.py b/api/kiwi_vpn_api/routers/admin.py index b337a9a..2230d7c 100644 --- a/api/kiwi_vpn_api/routers/admin.py +++ b/api/kiwi_vpn_api/routers/admin.py @@ -74,7 +74,7 @@ async def create_initial_admin( 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, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, }, ) async def set_config( diff --git a/api/kiwi_vpn_api/routers/device.py b/api/kiwi_vpn_api/routers/device.py index 4e2bd16..1067fe3 100644 --- a/api/kiwi_vpn_api/routers/device.py +++ b/api/kiwi_vpn_api/routers/device.py @@ -56,7 +56,7 @@ async def add_device( 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, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST, }, response_model=User, diff --git a/api/kiwi_vpn_api/routers/user.py b/api/kiwi_vpn_api/routers/user.py index 56a0a35..46425d2 100644 --- a/api/kiwi_vpn_api/routers/user.py +++ b/api/kiwi_vpn_api/routers/user.py @@ -69,7 +69,7 @@ async def get_current_user_route( 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, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS, }, response_model=UserRead, @@ -133,7 +133,7 @@ async def remove_user( 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, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, }, ) async def extend_tags( @@ -160,7 +160,7 @@ async def extend_tags( 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, + status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, }, ) async def remove_tags( From cb3a3fca692de0daebc28818d849bce70d9f93ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 30 Mar 2022 02:11:04 +0000 Subject: [PATCH 8/9] typo --- api/kiwi_vpn_api/db/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/kiwi_vpn_api/db/user.py b/api/kiwi_vpn_api/db/user.py index ad1d51f..e8f41fc 100644 --- a/api/kiwi_vpn_api/db/user.py +++ b/api/kiwi_vpn_api/db/user.py @@ -183,7 +183,7 @@ class User(UserBase, table=True): Check if this user has a tag. """ - return tag in self._get_tags + return tag in self._get_tags() def add_tags( self, From d02239816a99b2b6ee1c43e2dc70aff4e6052b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 30 Mar 2022 02:16:06 +0000 Subject: [PATCH 9/9] admin can login --- api/kiwi_vpn_api/db/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/kiwi_vpn_api/db/user.py b/api/kiwi_vpn_api/db/user.py index e8f41fc..54e0d2b 100644 --- a/api/kiwi_vpn_api/db/user.py +++ b/api/kiwi_vpn_api/db/user.py @@ -143,7 +143,8 @@ class User(UserBase, table=True): # password hash mismatch return None - if user.has_tag(TagValue.login): + if not (user.has_tag(TagValue.login) + or user.has_tag(TagValue.admin)): # no login permission return None