From 3a36ecc5983eec15d1b1015197b4908417fd39b4 Mon Sep 17 00:00:00 2001 From: Vadim Melnik Date: Tue, 19 Sep 2023 11:42:33 +0300 Subject: [PATCH] Get all API keys as list via admin get_all_apikeys REST API. --- repromon_app/model.py | 9 +++++++++ repromon_app/router/api_v1.py | 32 +++++++++++++++++++++++--------- repromon_app/security.py | 22 ++++++++++++++++------ repromon_app/service.py | 33 ++++++++++++++++++++++++++++----- 4 files changed, 76 insertions(+), 20 deletions(-) diff --git a/repromon_app/model.py b/repromon_app/model.py index 1dc256f..4b4ea45 100644 --- a/repromon_app/model.py +++ b/repromon_app/model.py @@ -87,6 +87,15 @@ class BasePydantic(BaseModel, BaseDTO): pass +class ApiKeyInfoDTO(BasePydantic): + """API key info + """ + + username: str = None + apikey: str = None + issued_on: datetime.datetime = None + + class LoginInfoDTO(BasePydantic): """Logged user info """ diff --git a/repromon_app/router/api_v1.py b/repromon_app/router/api_v1.py index ad260f3..61e18e1 100755 --- a/repromon_app/router/api_v1.py +++ b/repromon_app/router/api_v1.py @@ -5,11 +5,11 @@ from fastapi import (APIRouter, Depends, Query, Request, WebSocket, WebSocketDisconnect, WebSocketException) -from repromon_app.model import (DataProviderId, DeviceEntity, LoginInfoDTO, - MessageCategoryId, MessageLevelId, - MessageLogEntity, MessageLogInfoDTO, - PushMessageDTO, RoleEntity, Rolename, - StudyInfoDTO, UserEntity) +from repromon_app.model import (ApiKeyInfoDTO, DataProviderId, DeviceEntity, + LoginInfoDTO, MessageCategoryId, + MessageLevelId, MessageLogEntity, + MessageLogInfoDTO, PushMessageDTO, RoleEntity, + Rolename, StudyInfoDTO, UserEntity) from repromon_app.security import (ApiKey, SecurityContext, SecurityManager, Token, security_check, security_context, web_oauth2_apikey_context, @@ -428,6 +428,21 @@ def secsys_create_apikey(request: Request, svc: SecSysService = SecSysService() return svc.create_apikey() + # @security: admin + @api_v1_router.get("/secsys/get_all_apikeys", + response_model=list[ApiKeyInfoDTO], + tags=["SecSysService"], + summary="get_all_apikeys", + description="Get all API keys as list") + def secsys_get_all_apikeys(request: Request, + sec_ctx: + Annotated[SecurityContext, Depends(web_oauth2_context)], + ) -> list[ApiKeyInfoDTO]: + logger.debug("secsys_get_all_apikeys()") + security_check(rolename=Rolename.ADMIN) + svc: SecSysService = SecSysService() + return svc.get_all_apikeys() + # @security: admin @api_v1_router.get("/secsys/get_apikey_hash", response_model=object, @@ -468,7 +483,7 @@ def secsys_get_password_hash(request: Request, # @security: admin @api_v1_router.get("/secsys/get_user_apikey", - response_model=object, + response_model=ApiKeyInfoDTO, tags=["SecSysService"], summary="get_user_apikey", description="Get user current API key if any") @@ -478,12 +493,11 @@ def secsys_get_user_apikey(request: Request, username: str = Query(..., description="Specify username"), - ) -> UserEntity: + ) -> ApiKeyInfoDTO: logger.debug(f"secsys_get_user_apikey(username={username})") security_check(rolename=Rolename.ADMIN) svc: SecSysService = SecSysService() - key: ApiKey = svc.get_user_apikey(username) - return {"username": username, "apikey": key.key} + return svc.get_user_apikey(username) # @security: admin @api_v1_router.get("/secsys/get_user_devices", diff --git a/repromon_app/security.py b/repromon_app/security.py index 8d484ac..bbd5fa2 100644 --- a/repromon_app/security.py +++ b/repromon_app/security.py @@ -31,6 +31,7 @@ class ApiKey(BaseModel): prefix: str body: str data: str + issued_on: datetime = None # class representing current security context @@ -146,7 +147,8 @@ def calculate_apikey(self, apikey_data: str) -> ApiKey: # create key as prefix.body key = f"{prefix}.{body}" # logger.debug(f"key={key}") - return ApiKey(key=key, prefix=prefix, body=body, data=apikey_data) + return ApiKey(key=key, prefix=prefix, body=body, + data=apikey_data, issued_on=None) def create_apikey(self) -> ApiKey: logger.debug("create_apikey()") @@ -201,11 +203,9 @@ def create_context_by_username(self, username: str) -> SecurityContext: self.__context_cache[username] = ctx return ctx - def get_apikey_by_user(self, username: str) -> ApiKey: - u: UserEntity = self._get_cached_user(username) - + def get_apikey_by_entity(self, u: UserEntity) -> ApiKey: if not u: - raise Exception(f"User not found: {username}") + raise Exception("User is null") if not u.apikey_data: raise Exception("User doesn't have API key") @@ -213,7 +213,17 @@ def get_apikey_by_user(self, username: str) -> ApiKey: if len(u.apikey_data) < 16: raise Exception("User doesn't have valid API key") - return self.calculate_apikey(u.apikey_data) + key: ApiKey = self.calculate_apikey(u.apikey_data) + key.issued_on = u.apikey_issued_on + return key + + def get_apikey_by_user(self, username: str) -> ApiKey: + u: UserEntity = self._get_cached_user(username) + + if not u: + raise Exception(f"User not found: {username}") + + return self.get_apikey_by_entity(u) def get_apikey_hash(self, apikey: str) -> str: if not apikey: diff --git a/repromon_app/service.py b/repromon_app/service.py index 98d8acb..ab38390 100644 --- a/repromon_app/service.py +++ b/repromon_app/service.py @@ -4,9 +4,9 @@ from repromon_app.config import app_settings from repromon_app.dao import DAO -from repromon_app.model import (DeviceEntity, LoginInfoDTO, MessageLevelId, - MessageLogEntity, MessageLogInfoDTO, - PushMessageDTO, RoleEntity, +from repromon_app.model import (ApiKeyInfoDTO, DeviceEntity, LoginInfoDTO, + MessageLevelId, MessageLogEntity, + MessageLogInfoDTO, PushMessageDTO, RoleEntity, SecUserDeviceEntity, SecUserRoleEntity, StudyDataEntity, StudyInfoDTO, UserEntity) from repromon_app.security import (ApiKey, SecurityManager, Token, @@ -242,6 +242,12 @@ class SecSysService(BaseService): def __init__(self): super().__init__() + def _apikey_info(self, username: str, key: ApiKey) -> ApiKeyInfoDTO: + o: ApiKeyInfoDTO = ApiKeyInfoDTO(username=username, + apikey=key.key, + issued_on=key.issued_on) + return o + def calculate_apikey(self, apikey_data: str) -> str: logger.debug(f"calculate_apikey(apikey_data={apikey_data})") apikey: ApiKey = SecurityManager.instance().calculate_apikey(apikey_data) @@ -263,6 +269,17 @@ def create_apikey(self) -> ApiKey: logger.debug("create_apikey()") return SecurityManager.instance().create_apikey() + def get_all_apikeys(self) -> list[ApiKeyInfoDTO]: + lst: list[UserEntity] = DAO.account.get_users() + lst = [u for u in lst if u.apikey_data and len(u.apikey_data) > 1] + lst2 = [] + for u in lst: + try: + lst2.append(self.get_user_entity_apikey(u)) + except BaseException as e: + logger.error(f"Failed calculate apikey for u={u}. {str(e)}") + return lst2 + def get_apikey_hash(self, apikey: str) -> str: logger.debug("get_apikey_hash(...)") return SecurityManager.instance().get_apikey_hash(apikey) @@ -271,9 +288,15 @@ def get_password_hash(self, pwd: str) -> str: logger.debug("get_password_hash(...)") return SecurityManager.instance().get_password_hash(pwd) - def get_user_apikey(self, username: str) -> ApiKey: + def get_user_apikey(self, username: str) -> ApiKeyInfoDTO: logger.debug(f"get_user_apikey(username={username})") - return SecurityManager.instance().get_apikey_by_user(username) + key: ApiKey = SecurityManager.instance().get_apikey_by_user(username) + return self._apikey_info(username, key) + + def get_user_entity_apikey(self, u: UserEntity) -> ApiKeyInfoDTO: + logger.debug("get_user_entity_apikey(...)") + key: ApiKey = SecurityManager.instance().get_apikey_by_entity(u) + return self._apikey_info(u.username, key) def get_user_devices(self, username: str) -> list[str]: logger.debug(f"get_user_devices(username={username})")