Skip to content

Commit

Permalink
feat: Add SanctionsCheckFailure ID in response and update docs with h…
Browse files Browse the repository at this point in the history
…ow to use sdn-check endpoint (#22)

REV-3766
  • Loading branch information
julianajlk authored Nov 20, 2023
1 parent d4fcf53 commit 39f03a0
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 12 deletions.
40 changes: 38 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
***************
Expand Down Expand Up @@ -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 <https://openedx.atlassian.net/wiki/spaces/DOC/pages/21627535/Publish+Documentation+on+Read+the+Docs>`_)
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
*******
Expand Down
2 changes: 2 additions & 0 deletions sanctions/apps/api/v1/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
13 changes: 8 additions & 5 deletions sanctions/apps/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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).',
Expand All @@ -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,
Expand All @@ -126,7 +128,7 @@ def post(self, request):
full_name,
city,
country,
sanctions_type,
sdn_api_list,
system_identifier,
metadata,
sdn_check_response,
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions sanctions/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 39f03a0

Please sign in to comment.