import abc from dataclasses import dataclass from typing import ClassVar from audience_common.auth.account import Account, AccountAccess from audience_common.cache.backends import AsyncCache from campaigns.auth.exceptions import PermissionDenied from campaigns.connectors.cache import cached from campaigns.connectors.ows_permissions import ( AnyVendor, OwsPermissionsClient, Subaccount, Vendor, ) class AuthorizationBackend(abc.ABC): async def authorize_account( self, profile_id: int, *, account: Account | None = None, ) -> AccountAccess: account_access = await self.get_account_access(profile_id) if account and not account_access.has_access(account): raise PermissionDenied( "You don't have access to perform actions on the selected account." ) return account_access @abc.abstractmethod async def get_account_access(self, profile_id: int) -> AccountAccess: pass @dataclass class PermissionsAuthorizationBackend(AuthorizationBackend): ows_permissions_client: OwsPermissionsClient cache: AsyncCache cache_timeout: int profile_type: ClassVar[str] = "AudienceProfile" async def get_account_access(self, profile_id: int) -> AccountAccess: return await self._get_account_access(profile_id) @cached("auth:account-access", timeout_param="cache_timeout") async def _get_account_access(self, profile_id: int) -> AccountAccess: # Get ows-permissions resources resources = await self.ows_permissions_client.get_resources( profile_id=profile_id, profile_type=self.profile_type, ) # Get account access allow_any_vendor = False accounts = [] for resource in resources: if isinstance(resource, AnyVendor): allow_any_vendor = True continue if isinstance(resource, Vendor): account = Account(vendor_id=resource.id, subaccount_id=0) elif isinstance(resource, Subaccount): account = Account( vendor_id=resource.vendor_id, subaccount_id=resource.id, ) else: continue accounts.append(account) if not accounts and not allow_any_vendor: raise PermissionDenied("Account access denied.") return AccountAccess(allowed_any_account=allow_any_vendor, accounts=accounts) class FullAccessAuthorizationBackend(AuthorizationBackend): async def get_account_access(self, profile_id: int) -> AccountAccess: return AccountAccess.full_access()