Skip to content

Commit

Permalink
UEI Validation Waivers (#4065)
Browse files Browse the repository at this point in the history
* Change UEI validation to account for waivers. Add basic UEI waiver admin panel.

* Fix form name typo

* Expiration dates implementation comment

* Protect `is_active` filter from malformed entries

* `test_serializers` to TestCase from SImpleTestCase due to waiver DB hit

* test_uei update to account for weird empty resp

* Migration - UeiValidationWaiver uei is not unique

* Test case - waived UEI still passes

* Break out filter funct, lint all the things!

* Mock SAM reuslts from the waived UEI payload test

* Sometimes, we one-line things
  • Loading branch information
jperson1 authored Jul 18, 2024
1 parent cc0fd61 commit cd24168
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 62 deletions.
4 changes: 2 additions & 2 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ class UEISerializer(serializers.Serializer):
api.uei.call_sam_api
api.uei.parse_sam_uei_json
+ If we don't have errors by that point, flatten the data.
api.serializsers.UEISerializer.validate_auditee_uei
api.serializers.UEISerializer.validate_auditee_uei
+ If we don't encounter errors at that point, return the flattened data.
api.serializsers.UEISerializer.validate_auditee_uei
api.serializers.UEISerializer.validate_auditee_uei
"""

Expand Down
21 changes: 18 additions & 3 deletions backend/api/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.test import SimpleTestCase
from model_bakery import baker

from api.test_uei import valid_uei_results
from api.test_uei import missing_uei_results, valid_uei_results
from api.serializers import (
EligibilitySerializer,
UEISerializer,
Expand All @@ -15,7 +15,7 @@
AccessAndSubmissionSerializer,
CERTIFIERS_HAVE_DIFFERENT_EMAILS,
)
from audit.models import User, Access
from audit.models import User, Access, UeiValidationWaiver


class EligibilityStepTests(SimpleTestCase):
Expand Down Expand Up @@ -64,7 +64,7 @@ def test_none_organization_type(self):
self.assertFalse(EligibilitySerializer(data=organization_type_none).is_valid())


class UEIValidatorStepTests(SimpleTestCase):
class UEIValidatorStepTests(TestCase):
def test_valid_uei_payload(self):
"""
UEI should meet UEI Technical Specifications defined in the UEI validator
Expand Down Expand Up @@ -95,6 +95,21 @@ def test_invalid_uei_payload(self):
# Invalid
self.assertFalse(UEISerializer(data=invalid).is_valid())

def test_waived_uei_payload(self):
"""
A UEI with an applicable validation waiver should still pass, regardless of the SAM result.
It should still meet the UEI Technical Specifications defined in the UEI validator.
"""
valid = {"auditee_uei": "SUPERC00LUE1"}

baker.make(UeiValidationWaiver, uei=valid["auditee_uei"])

# Valid, even if it's not a real UEI. Mock the SAM call as though the entity doesnt exist.
with patch("api.uei.SESSION.get") as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = json.loads(missing_uei_results)
self.assertTrue(UEISerializer(data=valid).is_valid())

def test_quirky_uei_payload(self):
"""
It turns out that some entries can be missing fields that we thought would
Expand Down
2 changes: 1 addition & 1 deletion backend/api/test_uei.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ def bad_reqexception(*args, **kwds):
self.assertTrue(results["errors"])
self.assertEquals(
results["errors"],
["SAM.gov unexpected JSON shape"],
["UEI was not found in SAM.gov"],
)

def test_get_uei_info_from_sam_gov_inactive_result(self):
Expand Down
150 changes: 98 additions & 52 deletions backend/api/uei.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from typing import Optional
import logging
import requests
import ssl
from typing import Optional
import urllib3
import requests

from config.settings import SAM_API_URL, SAM_API_KEY
from audit.models import UeiValidationWaiver
from config.settings import SAM_API_URL, SAM_API_KEY, GSA_FAC_WAIVER

logger = logging.getLogger(__name__)


class CustomHttpAdapter(requests.adapters.HTTPAdapter):
Expand Down Expand Up @@ -57,59 +61,42 @@ def call_sam_api(
return None, error


def parse_sam_uei_json(response: dict) -> dict:
def parse_sam_uei_json(response: dict, filter_field: str) -> dict:
"""
Parse the SAM.gov response dictionary, which requires some vetting to get
at the nested values we want to check.
Primarily we want to know that the totalRecords value is one, that there's
only one item in entityData, that item is a dictionary which can be
queried for an item["entityRegistration"]["ueiStatus"] value
case-insensitively equal to "active", and that entityData can be queried
for an item["coreData"]["entityInformation"]["fiscalYearEndCloseDate"] value.
Primarily we want to know that at least one record is found, and that it matches
our desired filters. A valid record is a dictionary which can be queried for an
item["entityRegistration"]["ueiStatus"] value case-insensitively equal to "active",
and that entityData can be queried for an
item["coreData"]["entityInformation"]["fiscalYearEndCloseDate"] value.
"""
# Ensure the UEI exists in SAM.gov
# Ensure at least one record was found
if response.get("totalRecords", 0) < 1:
return {"valid": False, "errors": ["UEI was not found in SAM.gov"]}

# Ensure there's only one entry:
# Nope! 2023-10-05: turns out you can get multiple valid entries.
entries = response.get("entityData", [])
if len(entries) > 1:
if len(entries) == 0:
return {"valid": False, "errors": ["UEI was not found in SAM.gov"]}

def is_active(entry):
return entry["entityRegistration"]["registrationStatus"] == "Active"
# Separate out the entries that are "active" on the filter_field
actives = filter_actives(entries, filter_field)

actives = list(filter(is_active, entries))
if actives:
entry = actives[0]
else:
entry = entries[0]
elif len(entries) == 1:
# If there are one or more "active" entries on the filter_field, take the first.
# Otherwise, take or reject the first non-active depending on the field.
if actives:
entry = actives[0]
elif filter_field == "registrationStatus":
entry = entries[0]
else:
elif filter_field == "ueiStatus":
return {"valid": False, "errors": ["UEI was not found in SAM.gov"]}
elif filter_field == "_":
entry = entries[0]

# Get the ueiStatus and catch errors if the JSON shape is unexpected:
# Make sure the ueiStatus and fiscalYearEndCloseDate exist and catch errors if the JSON shape is unexpected:
try:
_ = entry.get("entityRegistration", {}).get("ueiStatus", "").upper()
except AttributeError:
return {
"valid": False,
"errors": ["SAM.gov unexpected JSON shape"],
}

# 2023-10-05 comment out the following, as checking for this creates more
# problems for our users than it's worth.
# Ensure the status is active:
# if status != "ACTIVE":
# return {
# "valid": False,
# "errors": ["UEI is not listed as active from SAM.gov response data"],
# }

# Get the fiscalYearEndCloseDate and catch errors if the JSON shape is unexpected:
try:
_ = (
entry.get("coreData", {})
.get("entityInformation", {})
Expand All @@ -128,9 +115,19 @@ def is_active(entry):
def get_uei_info_from_sam_gov(uei: str) -> dict:
"""
This utility function will query sam.gov to determine the status and
return information about a provided UEI (or throws an Exception)
return information about a provided UEI (or throws an Exception).
In the case of a UEI validation waiver, either takes the exisiting inactive
UEI or provides placeholder data.
UEI checks in order:
1. Best case. Check for samRegistered "Yes". If one exists, take it.
If there are several, use the first with registrationStatus "Active".
2. Check for samRegistered "No". Take it as long as the ueiStatus is "Active".
3. Check for a waiver:
a. Check for that possibly non-registered, inactive UEI.
b. Worst case: Let it through with a placeholder name.
"""

# SAM API Params
api_params = {
"ueiSAM": uei,
Expand All @@ -141,30 +138,79 @@ def get_uei_info_from_sam_gov(uei: str) -> dict:
# SAM API headers
api_headers = {"X-Api-Key": SAM_API_KEY}

# Call the SAM API
# 1. Best case. samRegistered "Yes"
resp, error = call_sam_api(SAM_API_URL, api_params, api_headers)
if resp is None:
return {"valid": False, "errors": [error]}

# Get the response status code
if resp.status_code != 200:
error = f"SAM.gov API response status code invalid: {resp.status_code}"
return {"valid": False, "errors": [error]}

results = parse_sam_uei_json(resp.json())
results = parse_sam_uei_json(resp.json(), "registrationStatus")
if results["valid"] and (not results.get("errors")):
return results

# Try again with samRegistered set to No:
# 2. Check samRegistered "No"
api_params = api_params | {"samRegistered": "No"}
# Call the SAM API
resp, error = call_sam_api(SAM_API_URL, api_params, api_headers)
if resp is None:
return {"valid": False, "errors": [error]}

# Get the response status code
if resp.status_code != 200:
error = f"SAM.gov API response status code invalid: {resp.status_code}"
return {"valid": False, "errors": [error]}
results = parse_sam_uei_json(resp.json(), "ueiStatus")
if results["valid"] and (not results.get("errors")):
return results

# To honor expiration dates:
# from datetime import date
# today = date.today()
# waiver = UeiValidationWaiver.objects.filter(uei=uei, expiration__gte=today).first()

# 3. Check for a waiver.
waiver = UeiValidationWaiver.objects.filter(uei=uei).first()
if not waiver:
return {"valid": False, "errors": ["UEI was not found in SAM.gov"]}

logger.info(f"ueiValidationWaiver applied for {waiver}")

# 3a. Take the first samRegistered "No" from step 2, regardless of ueiStatus.
results = parse_sam_uei_json(resp.json(), "_")
if results["valid"] and (not results.get("errors")):
return results

# 3b. Worst case. Let it through with a placeholder name and no other data.
return get_placeholder_sam(uei)


def get_placeholder_sam(uei: str) -> dict:
"""
Return a dictionary with placeholder data as though it were parsed from SAM.gov.
Only provides placeholders for required fields, to unblock UEIs with waivers.
"""
placeholder_entry = {
"coreData": {},
"entityRegistration": {
"legalBusinessName": GSA_FAC_WAIVER,
"ueiSAM": uei,
},
}

return {"valid": True, "response": placeholder_entry}


def filter_actives(entries, filter_field):
"""
Filter function. Given a list of entries and a filter field, returns a list of
entries that are "Active" on that field.
An entry is kept if the value on the filter field is non-case sensitive "ACTIVE".
An entry is not kept otherwise, or if the shape of the entry is wrong.
"""

def is_active(entry):
if isinstance(entry, dict):
return entry["entityRegistration"].get(filter_field, "").upper() == "ACTIVE"
else:
return False

return parse_sam_uei_json(resp.json())
return list(filter(is_active, entries))
22 changes: 21 additions & 1 deletion backend/audit/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.conf import settings
from django.contrib import admin, messages
from audit.forms import SacValidationWaiverForm
from audit.forms import SacValidationWaiverForm, UeiValidationWaiverForm
from audit.models import (
Access,
DeletedAccess,
Expand All @@ -9,6 +9,7 @@
SingleAuditReportFile,
SubmissionEvent,
SacValidationWaiver,
UeiValidationWaiver,
)
from audit.validators import (
validate_auditee_certification_json,
Expand Down Expand Up @@ -221,10 +222,29 @@ def handle_auditee_certification(self, request, obj, sac):
)


class UeiValidationWaiverAdmin(admin.ModelAdmin):
form = UeiValidationWaiverForm
list_display = (
"id",
"uei",
"timestamp",
"approver_email",
"requester_email",
)
search_fields = (
"id",
"uei",
"approver_email",
"requester_email",
)
readonly_fields = ("timestamp",)


admin.site.register(Access, AccessAdmin)
admin.site.register(DeletedAccess, DeletedAccessAdmin)
admin.site.register(ExcelFile, ExcelFileAdmin)
admin.site.register(SingleAuditChecklist, SACAdmin)
admin.site.register(SingleAuditReportFile, AuditReportAdmin)
admin.site.register(SubmissionEvent, SubmissionEventAdmin)
admin.site.register(SacValidationWaiver, SacValidationWaiverAdmin)
admin.site.register(UeiValidationWaiver, UeiValidationWaiverAdmin)
8 changes: 7 additions & 1 deletion backend/audit/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django import forms
from django.contrib.postgres.forms import SimpleArrayField
from audit.models import SacValidationWaiver
from audit.models import SacValidationWaiver, UeiValidationWaiver
from config.settings import (
AGENCY_NAMES,
GAAP_RESULTS,
Expand Down Expand Up @@ -146,3 +146,9 @@ def __init__(self, *args, **kwargs):
class Meta:
model = SacValidationWaiver
fields = "__all__"


class UeiValidationWaiverForm(forms.ModelForm):
class Meta:
model = UeiValidationWaiver
fields = "__all__"
18 changes: 18 additions & 0 deletions backend/audit/migrations/0010_alter_ueivalidationwaiver_uei.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-07-09 14:42

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("audit", "0009_ueivalidationwaiver_sacvalidationwaiver"),
]

operations = [
migrations.AlterField(
model_name="ueivalidationwaiver",
name="uei",
field=models.TextField(verbose_name="UEI"),
),
]
2 changes: 2 additions & 0 deletions backend/audit/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
SingleAuditChecklistManager,
SingleAuditReportFile,
SacValidationWaiver,
UeiValidationWaiver,
User,
excel_file_path,
generate_sac_report_id,
Expand All @@ -28,6 +29,7 @@
SingleAuditChecklistManager,
SingleAuditReportFile,
SacValidationWaiver,
UeiValidationWaiver,
User,
]
_functions = [
Expand Down
9 changes: 7 additions & 2 deletions backend/audit/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,11 +698,16 @@ def save(self, *args, **kwargs):
class UeiValidationWaiver(models.Model):
"""Records of UEIs that are permitted to be inactive."""

uei = models.TextField("UEI", unique=True)
# Method overrides:
def __str__(self):
return f"#{self.id}--{self.uei}"

# Not unique, in the case that one UEI needs to be waived several times with different expiration dates.
uei = models.TextField("UEI")
timestamp = (
models.DateTimeField(
"When the waiver was created",
default=datetime.now(timezone.utc),
default=django_timezone.now,
),
)
expiration = (
Expand Down

0 comments on commit cd24168

Please sign in to comment.