"""Logic for processing secure document.""" import typing from typing import Optional from abacus_common_logic.utils.dates import current_timestamp from boto3.dynamodb.conditions import Attr from flask import current_app, g from owsrequest import flask_request from owsresponse import response from payee.config import cmp from payee.connectors.secure_data.document import SecureDocument from payee.connectors.secure_data.dynamodb_client import get_dynamodb_table from payee.connectors.secure_data.manager import SecureDocumentManager from payee.constants import error from payee.constants.constants import ( ORCHARD_IDENTITY_ID, ORCHARD_PROFILE_ID, ORCHARD_PROFILE_TYPE, ) from payee.utils.exception import OwsRequestHeadersException def get_secure_document_manager(sdm_config=None): """Return the configured secure document manager.""" if not sdm_config: sdm_config = current_app.config.get('SDM_CONFIG') table = get_dynamodb_table(sdm_config) return SecureDocumentManager(table, cmp) def get_sdm_config() -> dict: """Get SDM config.""" return current_app.config.get('SDM_CONFIG') def create_secure_document_details(sdm_config, document_class, **params): """Create secure_documents details for the specified payee.""" payoneer_client_reference_id = params.pop('payoneer_client_reference_id', None) account_payee_id = params.get('account_payee_id', None) payoneer_client_reference_id = ( payoneer_client_reference_id if payoneer_client_reference_id else account_payee_id ) secure_document = save_secure_document_details( payoneer_client_reference_id, sdm_config, document_class, **params, **get_audit_fields(), ) message = document_class.build_response( payoneer_client_reference_id, secure_document ) return message def save_secure_document_details_by_payee_id( account_payee_id: int, document_class: type[SecureDocument], **params ) -> None: """Save secure document details for payee by ID.""" save_secure_document_details( account_payee_id, get_sdm_config(), document_class, **params ) def save_secure_document_details_version( account_payee_id: int, document_class: type[SecureDocument], **params ) -> str: """Save secure document details version/revision for payee without touching current.""" sdm = get_secure_document_manager() secure_document = document_class(params) return sdm.save_versioned_item( document_class.owner_type(), account_payee_id, secure_document ) def save_secure_document_details( payoneer_client_reference_id: int | str, sdm_config: dict, document_class: type[SecureDocument], **params, ) -> SecureDocument: """Save secure document details for payee.""" sdm = get_secure_document_manager(sdm_config) secure_document = document_class(params) if secure_document.has_obscured_values: _preprocess_data(payoneer_client_reference_id, secure_document, sdm) sdm.save_item( document_class.owner_type(), payoneer_client_reference_id, secure_document ) return secure_document def delete_secure_document( account_payee_id: int, document_class: type[SecureDocument] ) -> None: """Delete current (#0) version of the document for the specified payee.""" sdm = get_secure_document_manager() sdm.delete_current_item( document_class.owner_type(), account_payee_id, document_class ) def _preprocess_data( payoneer_client_reference_id: int | str, secure_document: SecureDocument, sdm: SecureDocumentManager, ): """Apply changes to data prior to saving.""" document_class = secure_document.__class__ current_item = sdm.get_item( document_class.owner_type(), payoneer_client_reference_id, document_class ) for key, field in secure_document.fields.items(): if not field.has_obscured_value: continue original_value = None if current_item: original_value = current_item.get_value(key) if original_value: field.value = original_value def has_secure_document_details(payoneer_client_reference_id, document_class): """Check whether document exists for a given payee.""" sdm = get_secure_document_manager() payee_document = sdm.get_item( document_class.owner_type(), payoneer_client_reference_id, document_class ) return payee_document def get_secure_document_details(sdm_config, payee_id, document_class): """Retrieve document details for the specified payee.""" sdm = get_secure_document_manager(sdm_config) current_item, history = sdm.get_items( document_class.owner_type(), payee_id, document_class ) return current_item, history def get_secure_documents_list_response(account_payee_id, documents_list): """Retrieve secure documents details history for the specified payee.""" history_items = [] sdm = get_secure_document_manager() for item in documents_list: history_items.append( { 'account_payee_id': account_payee_id, 'revision': item.get_value('revision').split(sdm.delimiter)[1], } ) return {'items': history_items} def get_secure_document_revision( account_payee_id, document_class, revision=None ) -> Optional[SecureDocument]: """Retrieve version of payment-details for the specified payee.""" sdm_config = current_app.config.get('SDM_CONFIG') sdm = get_secure_document_manager(sdm_config) item = sdm.get_item( document_class.owner_type(), account_payee_id, document_class, revision ) return item def purge_secure_document_details( account_payee_id: int, document_class: type[SecureDocument] ) -> None: """Purge document details for the specified payee.""" sdm = get_secure_document_manager() sdm.purge_documents(document_class.owner_type(), account_payee_id) def dataload_secure_document_details( document_class, ids, revisions_data, obscure_pii=False ): """Dataload versioned secure document.""" res_data = [] for item in revisions_data: if ids.get(item['accountPayeeId']): try: secure_document = get_secure_document_revision( item['accountPayeeId'], document_class, item['revision'] ) if secure_document is None: payload = {'data': None} else: payload = { 'data': document_class.build_response( item['accountPayeeId'], secure_document, obscure_pii=obscure_pii, ) } except Exception as e: payload = {'error': str(e)} else: payload = {'error': error.ERROR_DONT_HAVE_PERMISSIONS} res_data.append(payload) return response.Response(message=res_data) def search_secure_documents_by_owner_id(document_class, ids, is_payee_document=False): """Search documents with scan in dynamodb by params.""" # TODO improve search with PartiQL and boto3 `execute_statement` sdm = get_secure_document_manager() result = [] search_params = [ '{document_class}{delimiter}{document_id}'.format( document_class=document_class.ovner_type(), delimiter=sdm.delimiter, document_id=doc_id, ) for doc_id in ids if ids[doc_id] ] scan_kwargs = {'FilterExpression': Attr('owner').is_in(search_params)} document_items = sdm.scan_items(document_class, scan_kwargs) for item in document_items: try: _, account_payee_id = sdm.extract_owner_partition_key( item.get(sdm.partition_key) ) item = document_class(item) result.append(document_class.build_response(account_payee_id, item)) except Exception as e: result.append({'error': str(e)}) return result def validate_dataload_request(req_body): """Validate the request body. Should be like list of objects. [ {'accountPayeeId': 123, 'revision': 'yyy'}, {'accountPayeeId': 321, 'revision': None}, ] """ account_payee_ids = [] for item in req_body: if 'accountPayeeId' not in item or not item['accountPayeeId']: return False elif 'revision' not in item: return False else: item['accountPayeeId'] = int(item['accountPayeeId']) if item['revision'] is None: item['revision'] = '0' # current revision if isinstance(item['revision'], int): item['revision'] = str(item['revision']) account_payee_ids.append(item['accountPayeeId']) return list(set(account_payee_ids)) def get_audit_fields() -> typing.Dict[str, str]: """Add modified_* fields to be stored in secure_document.""" jwt_identity_id = g.request_context.jwt_identity_id ows_headers = flask_request.get_ows_headers() if ( ORCHARD_PROFILE_ID not in ows_headers or ORCHARD_PROFILE_TYPE not in ows_headers or ORCHARD_IDENTITY_ID not in ows_headers ) and not jwt_identity_id: raise OwsRequestHeadersException(error.ERROR_OWS_HEADERS) return { 'modified_by_profile_id': ows_headers.get(ORCHARD_PROFILE_ID), 'modified_by_profile_type': ows_headers.get(ORCHARD_PROFILE_TYPE), 'modified_by_identity': ows_headers.get(ORCHARD_IDENTITY_ID) or jwt_identity_id, 'modified_at': current_timestamp().strftime(SecureDocumentManager.date_format), }