"""Utils for PP Authorization checks.""" from typing import Any from ddtrace import tracer from flask import g from python_pdp_sdk.backends.authorization_backend import ( AuthorizationBackend, ResourceAction, ) from abacus_state.constants.constants import PARENT_TABLE_NAMES from abacus_state.models.account_payee import AccountPayee def get_resource_type(parent_table_name: str) -> str: """From a parent_table_name, get the resource_type to be used.""" match parent_table_name: case PARENT_TABLE_NAMES.ACCOUNT_PAYEE | PARENT_TABLE_NAMES.CONTRACT: return 'account' case _: return parent_table_name def get_action(parent_table_name: str, action: str) -> str: """From a parent table name and action, get the PP action to be used.""" return f'{action}_{parent_table_name}_abacus_state' class AbacusStateResourceGetter: """Generalizes getting attributes per parent_table_name/parent_table_id.""" def __init__(self) -> None: """Initialize AbacusStateResourceGetter.""" pass @tracer.wrap() def get_attributes( self, *args: Any, **kwargs: Any, ) -> dict[str, Any]: """Return attributes for the abacus state.""" assert 'parent_table_name' in kwargs assert 'parent_table_id' in kwargs parent_table_name = str(kwargs['parent_table_name']) parent_table_id = int(kwargs['parent_table_id']) match parent_table_name: case PARENT_TABLE_NAMES.ACCOUNT_PAYEE: return self._get_account_payee_attributes(parent_table_id) # TODO: case PARENT_TABLE_NAMES.CONTRACT: return {} @tracer.wrap() def _get_account_payee_attributes(self, parent_table_id: int) -> dict[str, Any]: """Fetch account_payee resource attributes.""" account_payees = AccountPayee.get_payees_by_ids([parent_table_id]) assert len(account_payees) == 1 return { 'id_to_uuid_exchange_tenant': { 'tenant_type': 'account', 'tenant_id': account_payees[0].account_id, } } @tracer.wrap() def authorize_dataloader_states( authorization_backend: AuthorizationBackend, states: list[dict[str, dict[str, Any] | None]], ) -> bool: """Authorize a list of abacus states, in a rather brute force fashion. 1. Anything `None` is "authorized" 2. If every state in the list is `None`, it is "authorized" - return True. 3. Use PP to perform authorization check for each non-None abacus state 4. If all non-None abacus states are authorized, return True. Else, return False. """ # Filter out all None states_data: list[dict[str, Any]] = [ state['data'] for state in states if state['data'] is not None ] # Nothing to check, just assume we're allowed to see Nones if not states_data: return True resource_actions = _build_resource_actions(states_data) decisions = authorization_backend.is_authorized_many_resources_and_actions( resource_actions ) if len(decisions) != len(resource_actions) or not all(decisions): g.log.warn( 'Unauthorized - many states', resources={ 'identity_id': g.request_context.jwt_identity_id, 'auth_response': decisions, }, ) return False return True @tracer.wrap() def _build_resource_actions( states: list[dict[str, Any]], ) -> list[ResourceAction]: """From a list of abacus states, create a list of ResourceActions.""" resource_getter = AbacusStateResourceGetter() resource_actions = [] for state in states: resource_actions.append( ResourceAction( resource_id=state['abacus_state_id'], attributes=resource_getter.get_attributes(**state), resource_type=get_resource_type(state['parent_table_name']), action=get_action(state['parent_table_name'], 'view'), ) ) return resource_actions