"""Shared validation logic.""" from dataclasses import dataclass from typing import Dict, List from flask import current_app, request from owsrequest import flask_request, request as requests from requests.exceptions import RequestException from abacus_state.constants.constants import ( COLLABORATOR_RESOURCE_NAME, LABEL_RESOURCE_NAME, OWS_PERMISSIONS_ACCOUNT_ENDPOINT_URL, OWS_PERMISSIONS_SERVICE_NAME, PARENT_TABLE_NAMES, VENDOR_RESOURCE_NAME, ) from abacus_state.models.account_payee import AccountPayee from abacus_state.models.payee import Payee @dataclass class ResourceAccessCheck: """Helper class for checking resource access.""" resource_type: str ids: list[str] def _get_profile_resources(profile_type, profile_id, resource_type): """Get resources info from ows-permissions microservice for a given profile.""" return requests.process( current_app.config.get('SERVICE_NAME'), current_app.config.get('ENVIRONMENT'), 'GET', OWS_PERMISSIONS_SERVICE_NAME, OWS_PERMISSIONS_ACCOUNT_ENDPOINT_URL.format( profile_type=profile_type, profile_id=profile_id, resource_type=resource_type, ), ) def _get_vendor_ids(profile_type, profile_id) -> list[str]: try: response = _get_profile_resources(profile_type, profile_id, LABEL_RESOURCE_NAME) response.raise_for_status() vendor_items = response.json().get('items', []) except (RequestException, ValueError): return [] return [ str(item['vendorId']) for item in vendor_items if item.get('type') == VENDOR_RESOURCE_NAME and isinstance(item['vendorId'], int) ] def _get_collaborator_ids(profile_type, profile_id) -> list[str]: try: response = _get_profile_resources( profile_type, profile_id, COLLABORATOR_RESOURCE_NAME ) response.raise_for_status() collaborator_items = response.json().get('items', []) except (RequestException, ValueError): return [] return [str(item['id']) for item in collaborator_items] def build_resource_access_check( parent_table_name: str, parent_table_id: str, ) -> ResourceAccessCheck: """Create a helper object to perform a check for resource access.""" if parent_table_name == PARENT_TABLE_NAMES.PAYEE: payee_collaborator = Payee.get_typed_payee(parent_table_id) return ResourceAccessCheck( resource_type=COLLABORATOR_RESOURCE_NAME, ids=[str(payee_collaborator.collaborator_id)], ) elif parent_table_name == PARENT_TABLE_NAMES.ACCOUNT_PAYEE: accounts = AccountPayee.get_payees_by_ids([parent_table_id]) return ResourceAccessCheck( resource_type=LABEL_RESOURCE_NAME, ids=[str(account.account_id) for account in accounts], ) else: raise NotImplementedError( f'Parent table name {parent_table_name} not supported' ) def _combine_access_checks_by_type( access_checks: List[ResourceAccessCheck], ) -> List[ResourceAccessCheck]: """Combine resource access checks together by type.""" checks_by_type: Dict[str, ResourceAccessCheck] = dict() for access_check in access_checks: combined = checks_by_type.setdefault(access_check.resource_type, access_check) combined.ids = list(set([*combined.ids, *access_check.ids])) return list(checks_by_type.values()) def validate_record_owner_access_checks( access_checks: List[ResourceAccessCheck], ) -> bool: """Validate if request was made by the owner of the payees record.""" if not access_checks: return False profile_type, profile_id = flask_request.get_profile_headers(request) if not (profile_type and profile_id): return False access_checks = _combine_access_checks_by_type(access_checks) for access_check in access_checks: resource_ids = [] if access_check.resource_type == LABEL_RESOURCE_NAME: resource_ids = _get_vendor_ids(profile_type, profile_id) elif access_check.resource_type == COLLABORATOR_RESOURCE_NAME: resource_ids = _get_collaborator_ids(profile_type, profile_id) if not resource_ids and len(access_check.ids): return False if not set(access_check.ids).issubset(set(resource_ids)): return False return True