diff --git a/README.rst b/README.rst index c006f40..66fb08e 100644 --- a/README.rst +++ b/README.rst @@ -8,6 +8,7 @@ Purpose ******* Backend Django IDA for checking users against Specially Designated Nationals (SDN) - Treasury Department and Nonproliferation Sanctions (ISN) - State Department, as well as managing these sanctions hit records. +This service also maintains a fallback copy that saves data from the Consolidated Screening List provided by the ITA, which is only utilized when our primary approach by calling the government's SDN API is not an option, due to an API outage/timeout. Getting Started *************** @@ -91,9 +92,44 @@ Getting Help Documentation ============= -This is a new service, and is a work in progress (as most things are)! There is no official home for this app's technical docs just yet, but there could be soon. Please reach out to someone on the Purchase Squad if you have questions. +How to use this service +------------------------------------------------ -(TODO: `Set up documentation `_) +The sanctions endpoint will check users against the SDN API and return a hit count - if there is a match found, a record in the sanctions database (SanctionsCheckFailure) will be created. + +Example of making a POST request to the `api/v1/sdn-check/` endpoint: + +.. code-block:: + + response = self.client.post( + 'https://sanctions.edx.org/api/v1/sdn-check/', + timeout=settings.SANCTIONS_CLIENT_TIMEOUT, + json={ + 'lms_user_id': user.lms_user_id, # optional + 'username': user.username, # optional + 'full_name': full_name, + 'city': city, + 'country': country, + 'metadata': { # optional, any key/value can be added + 'order_identifer': 'EDX-123456', + 'purchase_type': 'program', + 'order_total': '989.00' + }, + 'system_identifier': 'commerce-coordinator', # optional + }, + ) + + # Expected response if there is no SDN match + {"hit_count": 0, "sdn_response": {"total": 0, "sources": [], "results": []}, "sanctions_check_failure_id": null} + + # Expected response if there is a SDN match + {"hit_count": 1, "sdn_response": { # SDN API RESPONSE HERE }, "sanctions_check_failure_id": 1} + + # Please note that if there is match, but there is an issue in making a SanctionsCheckFailure record, + # sanctions_check_failure_id will be null. The presence/absence of the ID value is not always directly correlated to the hit_count. + + +Please reach out to someone on the Purchase Squad if you have questions. License ******* diff --git a/sanctions/apps/api/v1/tests/test_views.py b/sanctions/apps/api/v1/tests/test_views.py index 6d3d622..652798e 100644 --- a/sanctions/apps/api/v1/tests/test_views.py +++ b/sanctions/apps/api/v1/tests/test_views.py @@ -77,6 +77,7 @@ def test_sdn_check_search_succeeds( assert response.status_code == 200 assert response.json()['hit_count'] == 4 assert response.json()['sdn_response'] == {'total': 4} + assert response.json()['sanctions_check_failure_id'] is not None mock_fallback.assert_not_called() assert SanctionsCheckFailure.objects.count() == 1 @@ -115,5 +116,6 @@ def test_sdn_check_search_succeeds_despite_DB_issue( assert response.status_code == 200 assert response.json()['hit_count'] == 4 assert response.json()['sdn_response'] == {'total': 4} + assert response.json()['sanctions_check_failure_id'] is None assert SanctionsCheckFailure.objects.count() == 0 diff --git a/sanctions/apps/api/v1/views.py b/sanctions/apps/api/v1/views.py index 9e70c01..75cde54 100644 --- a/sanctions/apps/api/v1/views.py +++ b/sanctions/apps/api/v1/views.py @@ -47,7 +47,7 @@ def post(self, request): full_name = payload.get('full_name') city = payload.get('city') country = payload.get('country') - sdn_api_list = payload.get('sdn_api_list', 'ISN,SDN') # Set SDN lists to a sane default + sdn_api_list = settings.SDN_CHECK_API_LIST sdn_check = SDNClient( sdn_api_url=settings.SDN_CHECK_API_URL, @@ -77,6 +77,8 @@ def post(self, request): sdn_check_response = {'total': sdn_fallback_hit_count} hit_count = sdn_check_response['total'] + sanctions_check_failure = None + if hit_count > 0: logger.info( 'SDNCheckView request received for lms user [%s]. It received %d hit(s).', @@ -88,18 +90,18 @@ def post(self, request): metadata = payload.get('metadata', {}) username = payload.get('username') system_identifier = payload.get('system_identifier') - sanctions_type = 'ISN,SDN' + # This try/except is here to make us fault tolerant. Callers of this # API should not be held up if we are having DB troubles. Log the error # and continue through the code to reply to them. try: - SanctionsCheckFailure.objects.create( + sanctions_check_failure = SanctionsCheckFailure.objects.create( full_name=full_name, username=username, lms_user_id=lms_user_id, city=city, country=country, - sanctions_type=sanctions_type, + sanctions_type=sdn_api_list, system_identifier=system_identifier, metadata=metadata, sanctions_response=sdn_check_response, @@ -126,7 +128,7 @@ def post(self, request): full_name, city, country, - sanctions_type, + sdn_api_list, system_identifier, metadata, sdn_check_response, @@ -140,6 +142,7 @@ def post(self, request): json_data = { 'hit_count': hit_count, 'sdn_response': sdn_check_response, + 'sanctions_check_failure_id': sanctions_check_failure.id if sanctions_check_failure else None, } return JsonResponse(json_data, status=200) diff --git a/sanctions/apps/sanctions/management/commands/populate_sdn_fallback_data_and_metadata.py b/sanctions/apps/sanctions/management/commands/populate_sdn_fallback_data_and_metadata.py index 8d6c402..ecb06bd 100644 --- a/sanctions/apps/sanctions/management/commands/populate_sdn_fallback_data_and_metadata.py +++ b/sanctions/apps/sanctions/management/commands/populate_sdn_fallback_data_and_metadata.py @@ -52,7 +52,7 @@ def _hit_opsgenie_heartbeat(self): def handle(self, *args, **options): # download the CSV locally, to check size and pass along to import threshold = options['threshold'] - url = settings.SDN_CSL_LIST_URL + url = settings.CONSOLIDATED_SCREENING_LIST_URL timeout = settings.SDN_CHECK_REQUEST_TIMEOUT with requests.Session() as s: diff --git a/sanctions/settings/base.py b/sanctions/settings/base.py index d2e658b..01e857e 100644 --- a/sanctions/settings/base.py +++ b/sanctions/settings/base.py @@ -97,11 +97,12 @@ def root(*path_fragments): # SDN Check SDN_CHECK_REQUEST_TIMEOUT = 5 # Value is in seconds. -# Settings to download the government CSL list -SDN_CSL_LIST_URL = "https://data.trade.gov/downloadable_consolidated_screening_list/v1/consolidated.csv" +# Settings to download the government CSL +CONSOLIDATED_SCREENING_LIST_URL = 'https://data.trade.gov/downloadable_consolidated_screening_list/v1/consolidated.csv' # Settings to check government purchase restriction lists -SDN_CHECK_API_URL = "https://data.trade.gov/consolidated_screening_list/v1/search" -SDN_CHECK_API_KEY = "replace-me" +SDN_CHECK_API_URL = 'https://data.trade.gov/consolidated_screening_list/v1/search' +SDN_CHECK_API_KEY = 'replace-me' +SDN_CHECK_API_LIST = 'ISN,SDN' # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases