From 61bed37f0bbd0cb081aaba1a65057b70e28940b3 Mon Sep 17 00:00:00 2001 From: Lucas ONeil Date: Tue, 27 Jun 2023 21:42:06 -0700 Subject: [PATCH 1/4] user routes Signed-off-by: Lucas ONeil --- .../v1_0/innkeeper/models.py | 108 ++++++++++++++++++ .../v1_0/innkeeper/routes.py | 77 +++++++++++++ 2 files changed, 185 insertions(+) diff --git a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py index 42f40b66d..1e6ee2e8d 100644 --- a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py +++ b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py @@ -370,3 +370,111 @@ class Meta: fields.Str(description="Ledger id"), required=False, ) + + +class TenantAuthenticationUserRecord(BaseRecord): + """Innkeeper Tenant Authentication - User Record Schema""" + + class Meta: + """TenantAuthenticationUserRecord Meta.""" + + schema_class = "TenantAuthenticationUserSchema" + + RECORD_TYPE = "tenant_authentication_user" + RECORD_ID_NAME = "tenant_authentication_user_id" + TAG_NAMES = { + "tenant_id", + "user_id", + } + + def __init__( + self, + *, + tenant_authentication_user_id: str = None, + tenant_id: str = None, + user_id: str = None, + user_name: str = None, + user_email: str = None, + **kwargs, + ): + """Construct record.""" + super().__init__(tenant_authentication_user_id, **kwargs) + self.tenant_id = tenant_id + self.user_id = user_id + self.user_name = user_name + self.user_email = user_email + self.user_id = user_id + + @property + def tenant_authentication_user_id(self) -> Optional[str]: + """Return record id.""" + return uuid.UUID(self._id).hex + + @classmethod + async def query_by_tenant_id( + cls, + session: ProfileSession, + tenant_id: str, + ) -> "TenantAuthenticationUserRecord": + """Retrieve TenantAuthenticationUserRecord by tenant_id. + Args: + session: the profile session to use + tenant_id: the tenant_id by which to filter + """ + tag_filter = { + **{"tenant_id": tenant_id for _ in [""] if tenant_id}, + } + + result = await cls.query(session, tag_filter) + return result + + @property + def record_value(self) -> dict: + """Return record value.""" + return { + prop: getattr(self, prop) + for prop in ( + "tenant_authentication_user_id", + "tenant_id", + "user_id", + "user_name", + "user_email", + ) + } + +class TenantAuthenticationUserRecordSchema(BaseRecordSchema): + """Innkeeper Tenant Authentication - User Record Schema.""" + + class Meta: + """TenantAuthenticationUserSchema Meta.""" + + model_class = "TenantAuthenticationUser" + unknown = EXCLUDE + + tenant_authentication_user_id = fields.Str( + required=True, + description="Tenant Authentication User Record identifier", + example=UUIDFour.EXAMPLE, + ) + + tenant_id = fields.Str( + required=False, + description="Tenant Record identifier", + example=UUIDFour.EXAMPLE, + ) + + user_id = fields.Str( + required=True, + description="Externally associable user identifier", + example="000000-1111111-aaaaa-bbbbb", + ) + + user_name = fields.Str( + required=True, + description="Contact name for this external user", + ) + + user_email = fields.Str( + required=True, + description="Contact email for this tenant request", + ) \ No newline at end of file diff --git a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py index e8b6bf733..d794bddcd 100644 --- a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py +++ b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py @@ -31,6 +31,8 @@ ReservationRecordSchema, TenantRecord, TenantRecordSchema, + TenantAuthenticationUserRecord, + TenantAuthenticationUserRecordSchema ) LOGGER = logging.getLogger(__name__) @@ -215,6 +217,20 @@ class TenantListSchema(OpenAPISchema): description="List of tenants", ) +class TenantAuthenticationUsersListSchema(OpenAPISchema): + """Response schema for authentications - users list.""" + + results = fields.List( + fields.Nested(TenantAuthenticationUserRecordSchema()), + description="List of reservations", + ) + +class TenantAuthenticationUsersIdMatchInfoSchema(OpenAPISchema): + """Schema for finding a tenant auth user by the record ID.""" + tenant_authentication_user_id = fields.Str( + description="Tenant authentication user identifier", required=True, example=UUIDFour.EXAMPLE + ) + @docs( tags=["multitenancy"], @@ -592,6 +608,57 @@ async def innkeeper_tenant_get(request: web.BaseRequest): return web.json_response(rec.serialize()) +@docs( + tags=[SWAGGER_CATEGORY], +) +@response_schema(TenantAuthenticationUsersListSchema(), 200, description="") +@innkeeper_only +@error_handler +async def innkeeper_authentications_user_list(request: web.BaseRequest): + context: AdminRequestContext = request["context"] + + # records are under base/root profile, use Tenant Manager profile + mgr = context.inject(TenantManager) + profile = mgr.profile + + tag_filter = {} + post_filter = {} + async with profile.session() as session: + # innkeeper can access all reservation records + records = await TenantAuthenticationUserRecord.query( + session=session, + tag_filter=tag_filter, + post_filter_positive=post_filter, + alt=True, + ) + results = [record.serialize() for record in records] + + return web.json_response({"results": results}) + + +@docs( + tags=[SWAGGER_CATEGORY], +) +@match_info_schema(TenantAuthenticationUsersIdMatchInfoSchema()) +@response_schema(TenantAuthenticationUserRecordSchema(), 200, description="") +@innkeeper_only +@error_handler +async def innkeeper_authentications_user_get(request: web.BaseRequest): + context: AdminRequestContext = request["context"] + tenant_id = request.match_info["tenant_id"] + + # records are under base/root profile, use Tenant Manager profile + mgr = context.inject(TenantManager) + profile = mgr.profile + + async with profile.session() as session: + # innkeeper can access all tenants.. + rec = await TenantRecord.retrieve_by_id(session, tenant_id) + LOGGER.info(rec) + + return web.json_response(rec.serialize()) + + async def register(app: web.Application): """Register routes.""" LOGGER.info("> registering routes") @@ -637,6 +704,16 @@ async def register(app: web.Application): "/innkeeper/tenants/{tenant_id}", innkeeper_tenant_get, allow_head=False ), web.put("/innkeeper/tenants/{tenant_id}/config", tenant_config_update), + web.get( + "/innkeeper/authentications/users/", + innkeeper_authentications_user_list, + allow_head=False, + ), + web.get( + "/innkeeper/authentications/users/{tenant_authentication_user_id}", + innkeeper_authentications_user_get, + allow_head=False, + ), ] ) LOGGER.info("< registering routes") From 5db88025f38a3f97eb21a21059bb4069e24ad92f Mon Sep 17 00:00:00 2001 From: Lucas ONeil Date: Thu, 27 Jul 2023 22:12:32 -0700 Subject: [PATCH 2/4] api routes Signed-off-by: Lucas ONeil --- .../v1_0/innkeeper/models.py | 64 +++++++-------- .../v1_0/innkeeper/routes.py | 79 +++++++++++++++---- 2 files changed, 91 insertions(+), 52 deletions(-) diff --git a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py index 1e6ee2e8d..17963eeb8 100644 --- a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py +++ b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py @@ -372,41 +372,37 @@ class Meta: ) -class TenantAuthenticationUserRecord(BaseRecord): - """Innkeeper Tenant Authentication - User Record Schema""" +class TenantAuthenticationApiRecord(BaseRecord): + """Innkeeper Tenant Authentication - API Record Schema""" class Meta: - """TenantAuthenticationUserRecord Meta.""" + """TenantAuthenticationApiRecord Meta.""" - schema_class = "TenantAuthenticationUserSchema" + schema_class = "TenantAuthenticationApiSchema" - RECORD_TYPE = "tenant_authentication_user" - RECORD_ID_NAME = "tenant_authentication_user_id" + RECORD_TYPE = "tenant_authentication_api" + RECORD_ID_NAME = "tenant_authentication_api_id" TAG_NAMES = { "tenant_id", - "user_id", } def __init__( self, *, - tenant_authentication_user_id: str = None, + tenant_authentication_api_id: str = None, tenant_id: str = None, - user_id: str = None, - user_name: str = None, - user_email: str = None, + api_key: str = None, + alias: str = None, **kwargs, ): """Construct record.""" - super().__init__(tenant_authentication_user_id, **kwargs) + super().__init__(tenant_authentication_api_id, **kwargs) self.tenant_id = tenant_id - self.user_id = user_id - self.user_name = user_name - self.user_email = user_email - self.user_id = user_id + self.api_key = api_key + self.alias = alias @property - def tenant_authentication_user_id(self) -> Optional[str]: + def tenant_authentication_api_id(self) -> Optional[str]: """Return record id.""" return uuid.UUID(self._id).hex @@ -415,8 +411,8 @@ async def query_by_tenant_id( cls, session: ProfileSession, tenant_id: str, - ) -> "TenantAuthenticationUserRecord": - """Retrieve TenantAuthenticationUserRecord by tenant_id. + ) -> "TenantAuthenticationApiRecord": + """Retrieve TenantAuthenticationApiRecord by tenant_id. Args: session: the profile session to use tenant_id: the tenant_id by which to filter @@ -436,24 +432,23 @@ def record_value(self) -> dict: for prop in ( "tenant_authentication_user_id", "tenant_id", - "user_id", - "user_name", - "user_email", + "api_key", + "alias", ) } -class TenantAuthenticationUserRecordSchema(BaseRecordSchema): - """Innkeeper Tenant Authentication - User Record Schema.""" +class TenantAuthenticationApiRecordSchema(BaseRecordSchema): + """Innkeeper Tenant Authentication - API Record Schema.""" class Meta: - """TenantAuthenticationUserSchema Meta.""" + """TenantAuthenticationApiRecordSchema Meta.""" - model_class = "TenantAuthenticationUser" + model_class = "TenantAuthenticationApi" unknown = EXCLUDE - tenant_authentication_user_id = fields.Str( + tenant_authentication_api_id = fields.Str( required=True, - description="Tenant Authentication User Record identifier", + description="Tenant Authentication API Record identifier", example=UUIDFour.EXAMPLE, ) @@ -463,18 +458,13 @@ class Meta: example=UUIDFour.EXAMPLE, ) - user_id = fields.Str( + api_key = fields.Str( required=True, description="Externally associable user identifier", example="000000-1111111-aaaaa-bbbbb", ) - user_name = fields.Str( - required=True, - description="Contact name for this external user", + alias = fields.Str( + required=False, + description="Alias description for this API key", ) - - user_email = fields.Str( - required=True, - description="Contact email for this tenant request", - ) \ No newline at end of file diff --git a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py index d794bddcd..f53ebcc78 100644 --- a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py +++ b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py @@ -31,8 +31,8 @@ ReservationRecordSchema, TenantRecord, TenantRecordSchema, - TenantAuthenticationUserRecord, - TenantAuthenticationUserRecordSchema + TenantAuthenticationApiRecord, + TenantAuthenticationApiRecordSchema ) LOGGER = logging.getLogger(__name__) @@ -209,6 +209,31 @@ class TenantIdMatchInfoSchema(OpenAPISchema): ) +class TenantAuthenticationsApiRequestSchema(OpenAPISchema): + """Request schema for api auth record.""" + + tenant_id = fields.Str( + required=True, + description="Tenant ID", + example="000000-000000-00000-00000000", + ) + + alias = fields.Str( + required=False, + description="Optional alias/label", + example="API key for sample line of buisness", + ) + + +class TenantAuthenticationsApiResponseSchema(OpenAPISchema): + """Response schema for api auth record.""" + + reservation_id = fields.Str( + required=True, + description="The reservation record identifier", + example=UUIDFour.EXAMPLE, + ) + class TenantListSchema(OpenAPISchema): """Response schema for tenants list.""" @@ -217,15 +242,15 @@ class TenantListSchema(OpenAPISchema): description="List of tenants", ) -class TenantAuthenticationUsersListSchema(OpenAPISchema): +class TenantAuthenticationApiListSchema(OpenAPISchema): """Response schema for authentications - users list.""" results = fields.List( - fields.Nested(TenantAuthenticationUserRecordSchema()), + fields.Nested(TenantAuthenticationApiRecordSchema()), description="List of reservations", ) -class TenantAuthenticationUsersIdMatchInfoSchema(OpenAPISchema): +class TenantAuthenticationApiIdMatchInfoSchema(OpenAPISchema): """Schema for finding a tenant auth user by the record ID.""" tenant_authentication_user_id = fields.Str( description="Tenant authentication user identifier", required=True, example=UUIDFour.EXAMPLE @@ -611,10 +636,33 @@ async def innkeeper_tenant_get(request: web.BaseRequest): @docs( tags=[SWAGGER_CATEGORY], ) -@response_schema(TenantAuthenticationUsersListSchema(), 200, description="") +@request_schema(TenantAuthenticationsApiRequestSchema()) +@response_schema(TenantAuthenticationsApiResponseSchema(), 200, description="") +@error_handler +async def innkeeper_authentications_api(request: web.BaseRequest): + context: AdminRequestContext = request["context"] + + body = await request.json() + rec: TenantAuthenticationApiRecord = TenantAuthenticationApiRecord(**body) + + # reservations are under base/root profile, use Tenant Manager profile + mgr = context.inject(TenantManager) + profile = mgr.profile + + async with profile.session() as session: + await rec.save(session, reason="New API Key") + LOGGER.info(rec) + + return web.json_response({"api_key": rec.api_key}) + + +@docs( + tags=[SWAGGER_CATEGORY], +) +@response_schema(TenantAuthenticationApiListSchema(), 200, description="") @innkeeper_only @error_handler -async def innkeeper_authentications_user_list(request: web.BaseRequest): +async def innkeeper_authentications_api_list(request: web.BaseRequest): context: AdminRequestContext = request["context"] # records are under base/root profile, use Tenant Manager profile @@ -625,7 +673,7 @@ async def innkeeper_authentications_user_list(request: web.BaseRequest): post_filter = {} async with profile.session() as session: # innkeeper can access all reservation records - records = await TenantAuthenticationUserRecord.query( + records = await TenantAuthenticationApiRecord.query( session=session, tag_filter=tag_filter, post_filter_positive=post_filter, @@ -639,11 +687,11 @@ async def innkeeper_authentications_user_list(request: web.BaseRequest): @docs( tags=[SWAGGER_CATEGORY], ) -@match_info_schema(TenantAuthenticationUsersIdMatchInfoSchema()) -@response_schema(TenantAuthenticationUserRecordSchema(), 200, description="") +@match_info_schema(TenantAuthenticationApiIdMatchInfoSchema()) +@response_schema(TenantAuthenticationApiRecordSchema(), 200, description="") @innkeeper_only @error_handler -async def innkeeper_authentications_user_get(request: web.BaseRequest): +async def innkeeper_authentications_api_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] tenant_id = request.match_info["tenant_id"] @@ -704,14 +752,15 @@ async def register(app: web.Application): "/innkeeper/tenants/{tenant_id}", innkeeper_tenant_get, allow_head=False ), web.put("/innkeeper/tenants/{tenant_id}/config", tenant_config_update), + web.post("/innkeeper/authentications/api", innkeeper_authentications_api), web.get( - "/innkeeper/authentications/users/", - innkeeper_authentications_user_list, + "/innkeeper/authentications/api/", + innkeeper_authentications_api_list, allow_head=False, ), web.get( - "/innkeeper/authentications/users/{tenant_authentication_user_id}", - innkeeper_authentications_user_get, + "/innkeeper/authentications/api/{tenant_authentication_api_id}", + innkeeper_authentications_api_get, allow_head=False, ), ] From d51aa42cb70e2b3245c5c0bd0c29e3506e1e55a8 Mon Sep 17 00:00:00 2001 From: Lucas ONeil Date: Sat, 29 Jul 2023 10:05:52 -0700 Subject: [PATCH 3/4] Fixes to api auth classes Signed-off-by: Lucas ONeil --- .../v1_0/innkeeper/models.py | 36 ++++++++++++------ .../v1_0/innkeeper/routes.py | 21 ++++++----- .../v1_0/innkeeper/utils.py | 37 ++++++++++++++++++- 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py index 17963eeb8..7fdbe322d 100644 --- a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py +++ b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/models.py @@ -378,7 +378,7 @@ class TenantAuthenticationApiRecord(BaseRecord): class Meta: """TenantAuthenticationApiRecord Meta.""" - schema_class = "TenantAuthenticationApiSchema" + schema_class = "TenantAuthenticationApiRecordSchema" RECORD_TYPE = "tenant_authentication_api" RECORD_ID_NAME = "tenant_authentication_api_id" @@ -391,20 +391,40 @@ def __init__( *, tenant_authentication_api_id: str = None, tenant_id: str = None, - api_key: str = None, + api_key_token_salt: str = None, + api_key_token_hash: str = None, alias: str = None, **kwargs, ): """Construct record.""" super().__init__(tenant_authentication_api_id, **kwargs) self.tenant_id = tenant_id - self.api_key = api_key + self.api_key_token_salt = api_key_token_salt + self.api_key_token_hash = api_key_token_hash self.alias = alias @property def tenant_authentication_api_id(self) -> Optional[str]: """Return record id.""" return uuid.UUID(self._id).hex + + @classmethod + async def retrieve_by_auth_api_id( + cls, + session: ProfileSession, + tenant_authentication_api_id: str, + *, + for_update=False, + ) -> "TenantAuthenticationApiRecord": + """Retrieve TenantAuthenticationApiRecord by tenant_authentication_api_id. + Args: + session: the profile session to use + tenant_authentication_api_id: the tenant_authentication_api_id by which to filter + """ + record = await cls.retrieve_by_id( + session, tenant_authentication_api_id, for_update=for_update + ) + return record @classmethod async def query_by_tenant_id( @@ -430,9 +450,9 @@ def record_value(self) -> dict: return { prop: getattr(self, prop) for prop in ( - "tenant_authentication_user_id", "tenant_id", - "api_key", + "api_key_token_salt", + "api_key_token_hash", "alias", ) } @@ -458,12 +478,6 @@ class Meta: example=UUIDFour.EXAMPLE, ) - api_key = fields.Str( - required=True, - description="Externally associable user identifier", - example="000000-1111111-aaaaa-bbbbb", - ) - alias = fields.Str( required=False, description="Alias description for this API key", diff --git a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py index f53ebcc78..eb02dbb41 100644 --- a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py +++ b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py @@ -22,8 +22,9 @@ from . import TenantManager from .utils import ( approve_reservation, - generate_reservation_token_data, + create_api_key, ReservationException, + TenantApiKeyException, TenantConfigSchema, ) from .models import ( @@ -252,8 +253,8 @@ class TenantAuthenticationApiListSchema(OpenAPISchema): class TenantAuthenticationApiIdMatchInfoSchema(OpenAPISchema): """Schema for finding a tenant auth user by the record ID.""" - tenant_authentication_user_id = fields.Str( - description="Tenant authentication user identifier", required=True, example=UUIDFour.EXAMPLE + tenant_authentication_api_id = fields.Str( + description="Tenant authentication api key identifier", required=True, example=UUIDFour.EXAMPLE ) @@ -638,6 +639,7 @@ async def innkeeper_tenant_get(request: web.BaseRequest): ) @request_schema(TenantAuthenticationsApiRequestSchema()) @response_schema(TenantAuthenticationsApiResponseSchema(), 200, description="") +@innkeeper_only @error_handler async def innkeeper_authentications_api(request: web.BaseRequest): context: AdminRequestContext = request["context"] @@ -649,11 +651,12 @@ async def innkeeper_authentications_api(request: web.BaseRequest): mgr = context.inject(TenantManager) profile = mgr.profile - async with profile.session() as session: - await rec.save(session, reason="New API Key") - LOGGER.info(rec) + try: + api_key, tenant_authentication_api_id = await create_api_key(rec, mgr) + except TenantApiKeyException as err: + raise web.HTTPConflict(reason=str(err)) - return web.json_response({"api_key": rec.api_key}) + return web.json_response({"tenant_authentication_api_id": tenant_authentication_api_id, "api_key": api_key}) @docs( @@ -693,7 +696,7 @@ async def innkeeper_authentications_api_list(request: web.BaseRequest): @error_handler async def innkeeper_authentications_api_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] - tenant_id = request.match_info["tenant_id"] + tenant_authentication_api_id = request.match_info["tenant_authentication_api_id"] # records are under base/root profile, use Tenant Manager profile mgr = context.inject(TenantManager) @@ -701,7 +704,7 @@ async def innkeeper_authentications_api_get(request: web.BaseRequest): async with profile.session() as session: # innkeeper can access all tenants.. - rec = await TenantRecord.retrieve_by_id(session, tenant_id) + rec = await TenantAuthenticationApiRecord.retrieve_by_auth_api_id(session, tenant_authentication_api_id) LOGGER.info(rec) return web.json_response(rec.serialize()) diff --git a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/utils.py b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/utils.py index 0f5448df0..583745af0 100644 --- a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/utils.py +++ b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/utils.py @@ -7,7 +7,11 @@ from aries_cloudagent.messaging.models.openapi import OpenAPISchema from marshmallow import fields -from .models import ReservationRecord +from .models import ( + ReservationRecord, + TenantAuthenticationApiRecord +) + from . import TenantManager @@ -87,5 +91,36 @@ async def approve_reservation( return _pwd +def generate_api_key_data(): + _key = str(uuid.uuid4().hex) + LOGGER.info(f"_key = {_key}") + + _salt = bcrypt.gensalt() + LOGGER.info(f"_salt = {_salt}") + + _hash = bcrypt.hashpw(_key.encode("utf-8"), _salt) + LOGGER.info(f"_hash = {_hash}") + + return _key, _salt, _hash + + +async def create_api_key( + rec: TenantAuthenticationApiRecord, manager: TenantManager +): + async with manager.profile.session() as session: + _key, _salt, _hash = generate_api_key_data() + rec.api_key_token_salt = _salt.decode("utf-8") + rec.api_key_token_hash = _hash.decode("utf-8") + await rec.save(session) + LOGGER.info(rec) + + # return the generated key and the created record id + return _key, rec.tenant_authentication_api_id + + class ReservationException(Exception): pass + + +class TenantApiKeyException(Exception): + pass From e156cdd01a9dc9c0a1725d3dbac347ca53efae6d Mon Sep 17 00:00:00 2001 From: Lucas ONeil Date: Sat, 29 Jul 2023 21:59:19 -0700 Subject: [PATCH 4/4] delete key Signed-off-by: Lucas ONeil --- .../v1_0/innkeeper/routes.py | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py index eb02dbb41..5b8cb1736 100644 --- a/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py +++ b/plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/routes.py @@ -258,6 +258,14 @@ class TenantAuthenticationApiIdMatchInfoSchema(OpenAPISchema): ) +class TenantAuthenticationApiOperationResponseSchema(OpenAPISchema): + """Response schema for simple operations.""" + + success = fields.Bool( + required=True, + description="True if operation successful, false if otherwise", + ) + @docs( tags=["multitenancy"], ) @@ -634,9 +642,7 @@ async def innkeeper_tenant_get(request: web.BaseRequest): return web.json_response(rec.serialize()) -@docs( - tags=[SWAGGER_CATEGORY], -) +@docs(tags=[SWAGGER_CATEGORY], summary="Create API Key Record") @request_schema(TenantAuthenticationsApiRequestSchema()) @response_schema(TenantAuthenticationsApiResponseSchema(), 200, description="") @innkeeper_only @@ -659,9 +665,7 @@ async def innkeeper_authentications_api(request: web.BaseRequest): return web.json_response({"tenant_authentication_api_id": tenant_authentication_api_id, "api_key": api_key}) -@docs( - tags=[SWAGGER_CATEGORY], -) +@docs(tags=[SWAGGER_CATEGORY], summary="List all API Key Records") @response_schema(TenantAuthenticationApiListSchema(), 200, description="") @innkeeper_only @error_handler @@ -687,9 +691,7 @@ async def innkeeper_authentications_api_list(request: web.BaseRequest): return web.json_response({"results": results}) -@docs( - tags=[SWAGGER_CATEGORY], -) +@docs(tags=[SWAGGER_CATEGORY], summary="Read API Key Record") @match_info_schema(TenantAuthenticationApiIdMatchInfoSchema()) @response_schema(TenantAuthenticationApiRecordSchema(), 200, description="") @innkeeper_only @@ -710,6 +712,35 @@ async def innkeeper_authentications_api_get(request: web.BaseRequest): return web.json_response(rec.serialize()) +@docs(tags=[SWAGGER_CATEGORY], summary="Delete API Key") +@match_info_schema(TenantAuthenticationApiIdMatchInfoSchema) +@response_schema(TenantAuthenticationApiOperationResponseSchema, 200, description="") +@innkeeper_only +@error_handler +async def innkeeper_authentications_api_delete(request: web.BaseRequest): + context: AdminRequestContext = request["context"] + tenant_authentication_api_id = request.match_info["tenant_authentication_api_id"] + + # records are under base/root profile, use Tenant Manager profile + mgr = context.inject(TenantManager) + profile = mgr.profile + + result = False + async with profile.session() as session: + rec = await TenantAuthenticationApiRecord.retrieve_by_auth_api_id(session, tenant_authentication_api_id) + + await rec.delete_record(session) + + try: + await TenantAuthenticationApiRecord.retrieve_by_auth_api_id(session, tenant_authentication_api_id) + except StorageNotFoundError: + # this is to be expected... do nothing, do not log + result = True + + return web.json_response({"success": result}) + + + async def register(app: web.Application): """Register routes.""" LOGGER.info("> registering routes") @@ -766,6 +797,9 @@ async def register(app: web.Application): innkeeper_authentications_api_get, allow_head=False, ), + web.delete("/innkeeper/authentications/api/{tenant_authentication_api_id}", + innkeeper_authentications_api_delete + ), ] ) LOGGER.info("< registering routes")