"""Models used to serialize tenant-related requests/responses.""" from typing import Any, Dict, List, Optional, Tuple from uuid import UUID from pydantic import BaseModel, ConfigDict from pdp.constants.constants import TenantType from pdp.utils.cache import make_complicated_cache_key from pdp.utils.tenant_cache import TENANT_HIERARCHY_KEY_PREFIX class Tenant(BaseModel): """Object for Tenant.""" tenant_type: TenantType tenant_uuid: UUID model_config = ConfigDict( frozen=True, ) def __repr__(self) -> str: """Representation of Tenant.""" return f"{self.tenant_type}#{self.tenant_uuid}" def __lt__(self, other: object) -> bool: """Rich comparison method for less than.""" return repr(self) < repr(other) def __le__(self, other: object) -> bool: """Rich comparison method for less than or equal to.""" return repr(self) <= repr(other) def __eq__(self, other: object) -> bool: """Rich comparison method for equal to.""" return repr(self) == repr(other) def __ne__(self, other: object) -> bool: """Rich comparison method for not equal to.""" return repr(self) != repr(other) def __gt__(self, other: object) -> bool: """Rich comparison method for greater than.""" return repr(self) > repr(other) def __ge__(self, other: object) -> bool: """Rich comparison method for greater than or equal to.""" return repr(self) >= repr(other) class TenantHierarchy(BaseModel): """Object for Tenant Hierarchy. Future work: - Add parent_company when available. """ subaccount: Optional[Tenant] = None account: Optional[Tenant] = None company_brand: Optional[Tenant] parent_company: Optional[Tenant] = None def to_array(self) -> List[str]: """Transform TenantHierarchy into list for a cerbos check_resources request. Our Cerbos policies expect tenant_hierarchy to be a list of UUIDS. """ tenant_hierarchy = [] if self.parent_company: tenant_hierarchy.append(str(self.parent_company.tenant_uuid)) if self.company_brand: tenant_hierarchy.append(str(self.company_brand.tenant_uuid)) if self.account: tenant_hierarchy.append(str(self.account.tenant_uuid)) if self.subaccount: tenant_hierarchy.append(str(self.subaccount.tenant_uuid)) return tenant_hierarchy class IdExchangeTenantHierarchy(TenantHierarchy): """TenantHierarchy plus IdExchange data.""" tenant_id: str | int tenant_type: TenantType tenant_uuid: UUID def to_key_vals_tuple(self) -> List[Tuple[str, str]]: """Transform object into tuple for cache key formation.""" return [ ("tenant_id", str(self.tenant_id)), ("tenant_type", str(self.tenant_type.value)), ("tenant_uuid", str(self.tenant_uuid)), ] def to_resource_attributes_dict(self) -> Dict[str, Any]: """Transform object into tenant dict in cerbos resource attributes.""" return { "tenant_type": str(self.tenant_type.value), "tenant_uuid": str(self.tenant_uuid), "tenant_hierarchy": self.to_array(), } def to_cache_keys(self) -> List[str]: """Transform object into list of tenant hierarchy cache keys.""" return [ make_complicated_cache_key( prefix=TENANT_HIERARCHY_KEY_PREFIX, key_vals=[ ("tenant_id", str(self.tenant_id)), ("tenant_type", self.tenant_type.value), ], ), make_complicated_cache_key( prefix=TENANT_HIERARCHY_KEY_PREFIX, key_vals=[ ("tenant_type", self.tenant_type.value), ("tenant_uuid", str(self.tenant_uuid)), ], ), ] class TenantWithMaybeHierarchy(Tenant): """A tenant where tenant_hierarchy may or may not have been passed.""" tenant_hierarchy: Optional[list[UUID]] = None def __hash__(self) -> int: """Hash TenantWithMaybeHierarchy using its representation.""" return hash(repr(self)) def needs_hierarchy(self) -> bool: """Return True if tenant_hierarchy is missing/still needed.""" return self.tenant_hierarchy is None class TenantWithMaybeHierarchyResourceAttributes(BaseModel): """Attributes for a resource that belongs to a tenant, maybe with hierarchy.""" tenant: TenantWithMaybeHierarchy class IdToUuidExchangeTenant(BaseModel): """Attribute indicating client needs PDP to exchange tenant_id for tenant_uuid.""" tenant_type: TenantType tenant_id: int | str model_config = ConfigDict( frozen=True, ) def __repr__(self) -> str: """Representation of IdToUuidExchangeTenant.""" return f"{self.tenant_type}#{self.tenant_id}" def __lt__(self, other: object) -> bool: """Rich comparison method for less than.""" return repr(self) < repr(other) def __le__(self, other: object) -> bool: """Rich comparison method for less than or equal to.""" return repr(self) <= repr(other) def __eq__(self, other: object) -> bool: """Rich comparison method for equal to.""" return repr(self) == repr(other) def __ne__(self, other: object) -> bool: """Rich comparison method for not equal to.""" return repr(self) != repr(other) def __gt__(self, other: object) -> bool: """Rich comparison method for greater than.""" return repr(self) > repr(other) def __ge__(self, other: object) -> bool: """Rich comparison method for greater than or equal to.""" return repr(self) >= repr(other) def __hash__(self) -> int: """Override hash function to be based entirely on hashing repr.""" return hash(self.__repr__()) def to_key_vals_tuple(self) -> List[Tuple[str, str]]: """Transform object into tuple for cache key formation.""" return [ ("tenant_id", str(self.tenant_id)), ("tenant_type", str(self.tenant_type.value)), ("tenant_uuid", "*"), ] def to_cache_key(self) -> str: """Transform object into tenant hierarchy cache key.""" return make_complicated_cache_key( prefix=TENANT_HIERARCHY_KEY_PREFIX, key_vals=[ ("tenant_id", str(self.tenant_id)), ("tenant_type", self.tenant_type.value), ], ) class IdToUuidExchangeTenantResourceAttributes(BaseModel): """Attributes for a resource that needs to exchange a Tenant Id for UUID.""" id_to_uuid_exchange_tenant: IdToUuidExchangeTenant class UuidToIdExchangeTenant(Tenant): """Attribute indicating client needs to exchange tenant_uuid for tenant_id.""" def to_key_vals_tuple(self) -> List[Tuple[str, str]]: """Transform object into tuple for cache key formation.""" return [ ("tenant_id", "*"), ("tenant_type", str(self.tenant_type.value)), ("tenant_uuid", str(self.tenant_uuid)), ] def to_cache_key(self) -> str: """Transform object into tenant hierarchy cache key.""" return make_complicated_cache_key( prefix=TENANT_HIERARCHY_KEY_PREFIX, key_vals=[ ("tenant_type", self.tenant_type.value), ("tenant_uuid", str(self.tenant_uuid)), ], )