Skip to content

Commit b765116

Browse files
JadeCaraJade Wibbels
andcommitted
[ENG-1978] validation errors from api v 1 privacy request prevent loading request manager UI (#6964)
Co-authored-by: Jade Wibbels <[email protected]>
1 parent 384da44 commit b765116

File tree

3 files changed

+244
-4
lines changed

3 files changed

+244
-4
lines changed

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,38 @@ Changes can also be flagged with a GitHub label for tracking purposes. The URL o
2121

2222
## [Unreleased](https://github.com/ethyca/fides/compare/2.74.1..main)
2323

24+
<<<<<<< HEAD
25+
=======
26+
### Added
27+
- Added a setting to enable duplicate detection of privacy requests [#6936](https://github.com/ethyca/fides/pull/6936)
28+
- Added Admin UI support for the Fides v3 API [#6933](https://github.com/ethyca/fides/pull/6933)
29+
- Added table to store SaaS template datasets for future diff comparison [#6913](https://github.com/ethyca/fides/pull/6913)
30+
- Added GPC conditional button option for privacy experiences [#6945](https://github.com/ethyca/fides/pull/6945)
31+
- Added default identity definitions [#6952](https://github.com/ethyca/fides/pull/6952)
32+
33+
### Changed
34+
- Updated logging configuration to intercept all standard library logs and route them through Loguru[#6891](https://github.com/ethyca/fides/pull/6891)
35+
- Replaced filter modal with a filter bar in the new request manager screen [#6943](https://github.com/ethyca/fides/pull/6943)
36+
- Changed name of main file in DSR package from welcome.html to clickme.html [#6923](https://github.com/ethyca/fides/pull/6923)
37+
- De-select list items after performing an action in the action center [#6942](https://github.com/ethyca/fides/pull/6942)
38+
- Updated identity verification emails to use ansync calls bringing them in line with other messaging endpoints [#6949](https://github.com/ethyca/fides/pull/6949)
39+
40+
### Fixed
41+
- Data Subject email identity no longer included in Generic DSR email list if the DSR was submitted on a policy that has more than just the erasure action type. [#6938](https://github.com/ethyca/fides/pull/6938)
42+
- Fixed the IdentityValue schema so it uses Multivalue instead of string [#6964](https://github.com/ethyca/fides/pull/6964)
43+
44+
### Developer Experience
45+
- Improved pluralization handling throughout Admin UI with centralized utility function [#6930](https://github.com/ethyca/fides/pull/6930)
46+
- Switched `ConfigurableTestMonitor` to use `test_datastore` `ConnectionType` rather than `fides` [#6940](https://github.com/ethyca/fides/pull/6940) https://github.com/ethyca/fides/labels/db-migration
47+
- Added rules and commands for the AI assistant. [#6944](https://github.com/ethyca/fides/pull/6944)
48+
49+
### Fixed
50+
- Fixed async polling initial requests not respecting ignore_errors configuration [#6924](https://github.com/ethyca/fides/pull/6924)
51+
52+
### Changed
53+
- Monitor field filters no longer reset to default values when selecting resources from the tree [#6935](https://github.com/ethyca/fides/pull/6935)
54+
55+
>>>>>>> fbbee8fb29 ([ENG-1978] validation errors from api v 1 privacy request prevent loading request manager UI (#6964))
2456
## [2.74.1](https://github.com/ethyca/fides/compare/2.74.0..2.74.1)
2557

2658
### Fixed

src/fides/api/schemas/privacy_request.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
from fides.api.schemas.base_class import FidesSchema
1515
from fides.api.schemas.policy import ActionType, CurrentStep
1616
from fides.api.schemas.policy import PolicyResponse as PolicySchema
17-
from fides.api.schemas.redis_cache import CustomPrivacyRequestField, Identity
17+
from fides.api.schemas.redis_cache import (
18+
CustomPrivacyRequestField,
19+
Identity,
20+
MultiValue,
21+
)
1822
from fides.api.schemas.user import PrivacyRequestUser
1923
from fides.api.util.collection_util import Row
2024
from fides.api.util.encryption.aes_gcm_encryption_scheme import verify_encryption_key
@@ -311,8 +315,19 @@ class PrivacyRequestStatus(str, EnumType):
311315

312316

313317
class IdentityValue(BaseModel):
318+
"""Represents an identity value with a label in API responses.
319+
320+
The value field accepts MultiValue types which match what LabeledIdentity supports:
321+
- int
322+
- str
323+
- List[Union[int, str]]
324+
325+
This allows the schema to accept list values that were previously causing
326+
validation errors.
327+
"""
328+
314329
label: str
315-
value: Optional[str] = None
330+
value: Optional[MultiValue] = None
316331

317332

318333
class PrivacyRequestResponse(FidesSchema):

tests/ops/api/v1/endpoints/privacy_request/test_privacy_request_endpoints.py

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,14 @@
5353
MessagingServiceType,
5454
RequestReceiptBodyParams,
5555
RequestReviewDenyBodyParams,
56-
SubjectIdentityVerificationBodyParams,
5756
)
5857
from fides.api.schemas.policy import ActionType, CurrentStep, PolicyResponse
59-
from fides.api.schemas.privacy_request import PrivacyRequestSource, PrivacyRequestStatus
58+
from fides.api.schemas.privacy_request import (
59+
IdentityValue,
60+
PrivacyRequestResponse,
61+
PrivacyRequestSource,
62+
PrivacyRequestStatus,
63+
)
6064
from fides.api.schemas.redis_cache import (
6165
CustomPrivacyRequestField,
6266
Identity,
@@ -1210,6 +1214,195 @@ def test_get_privacy_requests_with_identity(
12101214
assert resp["items"][0]["id"] == succeeded_privacy_request.id
12111215
assert resp["items"][0].get("identity") is None
12121216

1217+
@pytest.mark.parametrize(
1218+
"custom_identities,expected_identity_values",
1219+
[
1220+
# Test case 1: List of integers
1221+
(
1222+
{
1223+
"regi_id": LabeledIdentity(label="Regi ID", value=[12345678]),
1224+
},
1225+
{
1226+
"regi_id": {"label": "Regi ID", "value": [12345678]},
1227+
},
1228+
),
1229+
# Test case 2: List of strings
1230+
(
1231+
{
1232+
"agent_id": LabeledIdentity(
1233+
label="Agent ID", value=["one", "two", "three"]
1234+
),
1235+
},
1236+
{
1237+
"agent_id": {"label": "Agent ID", "value": ["one", "two", "three"]},
1238+
},
1239+
),
1240+
# Test case 3: Mixed list
1241+
(
1242+
{
1243+
"user_id": LabeledIdentity(
1244+
label="User ID", value=[12345678, "one", "two", "three"]
1245+
),
1246+
},
1247+
{
1248+
"user_id": {
1249+
"label": "User ID",
1250+
"value": [12345678, "one", "two", "three"],
1251+
},
1252+
},
1253+
),
1254+
# Test case 4: All three cases together
1255+
(
1256+
{
1257+
"regi_id": LabeledIdentity(label="Regi ID", value=[12345678]),
1258+
"agent_id": LabeledIdentity(
1259+
label="Agent ID", value=["one", "two", "three"]
1260+
),
1261+
"user_id": LabeledIdentity(
1262+
label="User ID", value=[12345678, "one", "two", "three"]
1263+
),
1264+
},
1265+
{
1266+
"regi_id": {"label": "Regi ID", "value": [12345678]},
1267+
"agent_id": {"label": "Agent ID", "value": ["one", "two", "three"]},
1268+
"user_id": {
1269+
"label": "User ID",
1270+
"value": [12345678, "one", "two", "three"],
1271+
},
1272+
},
1273+
),
1274+
# Test case 5: Single integer in list
1275+
(
1276+
{
1277+
"customer_id": LabeledIdentity(label="Customer ID", value=[999]),
1278+
},
1279+
{
1280+
"customer_id": {"label": "Customer ID", "value": [999]},
1281+
},
1282+
),
1283+
# Test case 6: Empty list
1284+
(
1285+
{
1286+
"empty_list": LabeledIdentity(label="Empty List", value=[]),
1287+
},
1288+
{
1289+
"empty_list": {"label": "Empty List", "value": []},
1290+
},
1291+
),
1292+
# Test case 7: String value (not a list)
1293+
(
1294+
{
1295+
"customer_name": LabeledIdentity(
1296+
label="Customer Name", value="John Doe"
1297+
),
1298+
},
1299+
{
1300+
"customer_name": {"label": "Customer Name", "value": "John Doe"},
1301+
},
1302+
),
1303+
# Test case 8: Integer value (not a list)
1304+
(
1305+
{
1306+
"account_number": LabeledIdentity(
1307+
label="Account Number", value=98765
1308+
),
1309+
},
1310+
{
1311+
"account_number": {"label": "Account Number", "value": 98765},
1312+
},
1313+
),
1314+
# Test case 9: Mixed types - string, int, and list
1315+
(
1316+
{
1317+
"name": LabeledIdentity(label="Name", value="Jane Smith"),
1318+
"id": LabeledIdentity(label="ID", value=456789),
1319+
"tags": LabeledIdentity(label="Tags", value=["tag1", "tag2"]),
1320+
},
1321+
{
1322+
"name": {"label": "Name", "value": "Jane Smith"},
1323+
"id": {"label": "ID", "value": 456789},
1324+
"tags": {"label": "Tags", "value": ["tag1", "tag2"]},
1325+
},
1326+
),
1327+
],
1328+
)
1329+
def test_get_privacy_requests_with_custom_identities(
1330+
self,
1331+
api_client: TestClient,
1332+
url,
1333+
generate_auth_header,
1334+
db,
1335+
policy,
1336+
custom_identities,
1337+
expected_identity_values,
1338+
):
1339+
"""Test that privacy requests with custom identities containing various value types
1340+
can be retrieved and validated correctly.
1341+
1342+
This test would have caught the validation error where IdentityValue.value
1343+
was too restrictive and couldn't accept list values like [12345678] or
1344+
['one', 'two', 'three'].
1345+
1346+
The test is parametrized to cover:
1347+
- List values
1348+
- String values
1349+
- Integer values
1350+
- Mixed types (string, int, list)
1351+
1352+
Note: LabeledIdentity only accepts MultiValue types (int, str, or list of int/str)
1353+
"""
1354+
# Create a privacy request
1355+
privacy_request = PrivacyRequest.create(
1356+
db=db,
1357+
data={
1358+
"status": PrivacyRequestStatus.pending,
1359+
"policy_id": policy.id,
1360+
"client_id": policy.client_id,
1361+
},
1362+
)
1363+
1364+
# Persist identity with custom fields that have various value types
1365+
identity_dict = {"email": "[email protected]", **custom_identities}
1366+
privacy_request.persist_identity(db=db, identity=Identity(**identity_dict))
1367+
1368+
auth_header = generate_auth_header(scopes=[PRIVACY_REQUEST_READ])
1369+
response = api_client.get(
1370+
url + f"?include_identities=true", headers=auth_header
1371+
)
1372+
assert response.status_code == 200
1373+
resp = response.json()
1374+
1375+
# Verify the response validates correctly
1376+
assert len(resp["items"]) == 1
1377+
assert resp["items"][0]["id"] == privacy_request.id
1378+
1379+
# Verify the identity field is present and contains the list values
1380+
identity = resp["items"][0]["identity"]
1381+
assert identity is not None
1382+
1383+
# Verify standard identity field
1384+
assert identity["email"] == {
1385+
"label": "Email",
1386+
"value": "[email protected]",
1387+
}
1388+
1389+
# Verify custom identity fields with various value types
1390+
for field_name, expected_value in expected_identity_values.items():
1391+
assert identity[field_name] == expected_value
1392+
1393+
validated_response = PrivacyRequestResponse(**resp["items"][0])
1394+
assert validated_response.identity is not None
1395+
1396+
# Verify each custom identity field can be accessed and has the correct value
1397+
for field_name, expected_value in expected_identity_values.items():
1398+
assert field_name in validated_response.identity
1399+
identity_value = validated_response.identity[field_name]
1400+
assert isinstance(identity_value, IdentityValue)
1401+
assert identity_value.value == expected_value["value"]
1402+
assert identity_value.label == expected_value["label"]
1403+
1404+
privacy_request.delete(db)
1405+
12131406
def test_get_privacy_requests_with_custom_fields(
12141407
self,
12151408
api_client: TestClient,

0 commit comments

Comments
 (0)