"""Tenant validator helper functions.""" from typing import Any, Collection, List from ddtrace.trace import tracer from fastapi import Depends from fastapi.exceptions import HTTPException from fastapi.requests import Request from pydantic import TypeAdapter, ValidationError from pdp.connectors.ows_account import OwsAccountClient from pdp.connectors.ows_participant import OwsParticipantClient from pdp.connectors.redis_client import RedisConnector from pdp.constants.constants import TenantType from pdp.fastapi.datasources import ( get_ows_account_client, get_ows_participant_client, get_redis_connector, ) from pdp.fastapi.schemas.identity import AttachDetachRolesRequest from pdp.fastapi.schemas.tenant import Tenant from pdp.proxies.multi_tenant_proxy import MultiTenantProxy TenantList = TypeAdapter(List[Tenant]) class InvalidAttachDetachRolesRequest(HTTPException): """400 status code error for invalid AttachDetachRolesRequest.""" pass class InvalidTenantsError(HTTPException): """400 status code error for invalid tenants in a request.""" def __init__(self, invalid_tenants: List[Tenant], *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) self.invalid_tenants = invalid_tenants DEFAULT_TENANT_TYPES_FOR_VALIDATION_CHECKS = ( TenantType.TENANT_TYPE_ACCOUNT, TenantType.TENANT_TYPE_SUBACCOUNT, TenantType.TENANT_TYPE_COMPANY_BRAND, TenantType.TENANT_TYPE_PARENT_COMPANY, TenantType.TENANT_TYPE_LABEL_PARTICIPANT, ) @tracer.wrap() async def assert_valid_attach_detach_request( request: Request, redis_connector: RedisConnector = Depends(get_redis_connector), ows_account_client: OwsAccountClient = Depends(get_ows_account_client), ows_participant_client: OwsParticipantClient = Depends(get_ows_participant_client), ) -> None: """Method to validate a AttachDetachRolesRequest before calling the handler for `attach-detach-roles`. Usage: @router.put( "/{identity_uuid}/tenant/{tenant_uuid}/attach-and-detach/roles/", dependencies=[Depends(assert_valid_attach_detach_request)], ): ... """ data = await request.json() try: attach_detach_request = AttachDetachRolesRequest.model_validate(data) except ValidationError as e: raise InvalidAttachDetachRolesRequest( status_code=400, detail=e.errors(), ) if ( not attach_detach_request.roles_to_attach and not attach_detach_request.roles_to_detach ): # Skip the validation because there are no roles to attach or detach. return await assert_valid_tenants( tenants=[ Tenant( tenant_type=attach_detach_request.tenant_type, tenant_uuid=attach_detach_request.tenant_uuid, ) ], redis_connector=redis_connector, ows_account_client=ows_account_client, ows_participant_client=ows_participant_client, ) @tracer.wrap() async def assert_valid_tenants( tenants: List[Tenant], redis_connector: RedisConnector, ows_account_client: OwsAccountClient, ows_participant_client: OwsParticipantClient, tenant_types_to_validate: Collection[ TenantType ] = DEFAULT_TENANT_TYPES_FOR_VALIDATION_CHECKS, ) -> bool: """Use MultiTenantProxy to validate if the tenants are valid. Raise an exception if any tenant in the list is invalid.""" tenants_to_validate = [ t for t in tenants if t.tenant_type in tenant_types_to_validate ] if not tenants_to_validate: return True # We can change the proxy to a UuidToIdExchangeTenantProxy # when it supports LPs and company brands. proxy = MultiTenantProxy( tenants=tenants_to_validate, redis_client=redis_connector, ows_account_client=ows_account_client, ows_participant_client=ows_participant_client, ) hierarchies = await proxy.gather_tenant_hierarchies() invalid_tenants = [ t for t in tenants_to_validate if hierarchies.get(t.tenant_uuid) is None ] if invalid_tenants: # Raise a 400 when we find invalid tenants raise InvalidTenantsError( status_code=400, detail=f"Found invalid tenants: {str(invalid_tenants)}", invalid_tenants=invalid_tenants, ) return True