ethyca-fides 2.73.2b0__py2.py3-none-any.whl → 2.74.0rc1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ethyca-fides might be problematic. Click here for more details.
- {ethyca_fides-2.73.2b0.dist-info → ethyca_fides-2.74.0rc1.dist-info}/METADATA +2 -1
- {ethyca_fides-2.73.2b0.dist-info → ethyca_fides-2.74.0rc1.dist-info}/RECORD +273 -276
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/xx_2025_10_29_1659_80d28dea3b6b_added_duplicate_group_table.py +79 -0
- fides/api/alembic/migrations/versions/xx_2025_11_05_0200_f1a2b3c4d5e6_create_staged_resource_error_table.py +82 -0
- fides/api/api/v1/endpoints/privacy_request_endpoints.py +33 -6
- fides/api/db/database.py +225 -0
- fides/api/models/detection_discovery/__init__.py +2 -0
- fides/api/models/detection_discovery/core.py +10 -0
- fides/api/models/detection_discovery/staged_resource_error.py +25 -0
- fides/api/models/privacy_preference.py +1 -0
- fides/api/models/privacy_request/duplicate_group.py +84 -0
- fides/api/models/privacy_request/privacy_request.py +53 -8
- fides/api/schemas/privacy_request.py +16 -4
- fides/api/service/async_dsr/strategies/async_dsr_strategy_polling.py +13 -6
- fides/api/service/privacy_request/duplication_detection.py +424 -0
- fides/api/service/privacy_request/request_runner_service.py +5 -0
- fides/api/service/saas_request/saas_request_override_factory.py +7 -2
- fides/api/util/logger_context_utils.py +3 -1
- fides/api/util/memory_watchdog.py +118 -0
- fides/common/api/v1/urn_registry.py +1 -0
- fides/config/__init__.py +7 -1
- fides/config/celery_settings.py +42 -0
- fides/service/privacy_request/privacy_request_service.py +66 -5
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/FZTEUgamBvOhgPWce135w/_buildManifest.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{1115-26393b586775ea29.js → 1115-0da062111df309bf.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6148-59a59d5c5925344f.js → 1533-84e250d1f26e6d7d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/1817-508b16628e8eb225.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1840-5bbe6d878ed73fb4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3214-90ce0a366b0f461a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3377-eb5cd82b3ee6ab0c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3615-5e2d062d684b8fa1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3655-93ecd09f1cb9dbef.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4259.d1507e0db19cbed7.js → 4259.05038c9b78467244.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/4277-13bcf4516326d474.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/454-d5c2c84f1a14e4f1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5487-8dedd1ca94fbba54.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/549-6e2442db533a711e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5643-55d758444a8d7162.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5724-1e40975cefa405f0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6084-82e2df433fe5ba85.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6344-3e21444374f8059f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6362-12e3fd23130ccf15.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6372-a8d0f08dac1ebd0e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6780-3db5133c1f4c6f1e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6954-aa0c60ee1092be8e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7079-6e6efc3396ff1ebb.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7218-d297a4a06f924b09.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{7245-c9bc628d078c2170.js → 7245-686665c197b58e68.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/7654-716cf37a020b3d11.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/8939-4925751c57c51f87.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9014-eeae6f581158e645.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{9046-d9c6498368b993d1.js → 9046-e4daf28840a69fd6.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{5596-29a7c8322530b7cf.js → 9195-550bd50d538c5f79.js} +3 -3
- fides/ui-build/static/admin/_next/static/chunks/9341-bfc0e59bcc56c604.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9450-b7b7bb1d755ecf57.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9682-da69ac5d06f281da.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9911-ece086f2230e34f0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9965-56c5e4fc9cd3b3a5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-ee588a308812715d.js → _app-e64fd8510033a27c.js} +63 -63
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-7081e0e49f67716c.js → manual-ddd9d7d40847fc28.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-1b02a4991201b7e4.js → [id]-9b1f2b1c06968166.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-a5e738a234dadc7e.js → new-115a085e5d42de45.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-8c164c4b8310214e.js → [id]-3de34624829cbce8.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-db789002d84c8829.js → new-dc95e7ed278d1a29.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-e3ad3a55624e302a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-32bd7a7c990e5bf6.js → [resourceUrn]-dd82729296dee5c5.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]-49e5477eb1a11b92.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-07e7d38ce34e1e6c.js → projects-dfc1ead4a12c9ffa.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-b07a0707f8c2ec0d.js → [resourceUrn]-8442eb219958ac7e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources-feda358d1801c18d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-d16acb6fc07aad46.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/datastore/[monitorId]-c51a1e98c45d231a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/website/[monitorId]/[systemId]-bcfe38eebca30f8c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/website/{[monitorId]-73085f50abb775c0.js → [monitorId]-f66d0655897c4400.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-6f1e012cd641da19.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-24a82e07a0008516.js → activity-581d6248fcf98d17.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-b072cf25aefc98f6.js → [resourceUrn]-ddc1c1641e1e9430.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-fd3e8817d8e6dee4.js → detection-2b48f7e524743b2b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-146624cf59792bf7.js → [resourceUrn]-862b67418600251e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-9695cc9c85592ec5.js → discovery-0ffec855f5df262c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-6a030ab8c2e2b0db.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-5f88280db168083e.js → [...subfieldNames]-6cb66f649b8ca4bf.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-bfbcf19c28c794ae.js → [collectionName]-0b008dad90b00aaa.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-6fbe2b584a509226.js → [datasetId]-5566edf9a9d1be2d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-d4ca1f485b6e9e02.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset-85dee7e81dc4bafb.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/[id]-e905e018a2cab35d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/new-912723bc86299b1a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-f1f0affc18327033.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{fides-js-docs-1f4335dca5c09860.js → fides-js-docs-5235760b3e508d7d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-1b94e2d769a182b2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-a733e5d7c3ce9bb8.js → integrations-adfe6c5ac5b703d0.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/new-privacy-requests-f9be7080ebbb7445.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/providers/{[key]-720cde29f81db47f.js → [key]-f94e3accf9507ebf.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/providers/{new-3668866076b53016.js → new-5e83220ff1f2a250.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/templates-1621a4b87c432117.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/{ant-components-9cfb469de7b4aeab.js → ant-components-e02516d9fd314528.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-1fe486f3af832c80.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-2c0ec8fed16c20ae.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-baf77d34a3b3bece.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-ee8820fe0fa14c77.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields/{[id]-8634aae3259def37.js → [id]-8eb862182f19a6c2.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields/{new-2d9dcca17965dc57.js → new-37c29ef618e9fe3c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-1db425150dcb1b6b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-f108bf5015144d2f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-59c89489fa32a4cb.js → [id]-0e7c7228d01290ea.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{systems-cfaa37a0df83674b.js → systems-adc13b542e10a37d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-23dd250da26511c5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/webpack-6f97ebe373e7ef6b.js +1 -0
- fides/ui-build/static/admin/_next/static/css/65ae906f224cd8ae.css +1 -0
- fides/ui-build/static/admin/_next/static/css/d5701118537cbdd2.css +1 -0
- fides/ui-build/static/admin/add-systems/manual.html +1 -1
- fides/ui-build/static/admin/add-systems/multiple.html +1 -1
- fides/ui-build/static/admin/add-systems.html +1 -1
- fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
- fides/ui-build/static/admin/consent/configure.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
- fides/ui-build/static/admin/consent/properties.html +1 -1
- fides/ui-build/static/admin/consent/reporting.html +1 -1
- fides/ui-build/static/admin/consent.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
- fides/ui-build/static/admin/data-catalog.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/datastore/[monitorId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/datastore.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/website/[monitorId]/[systemId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/website/[monitorId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/website.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
- fides/ui-build/static/admin/data-discovery/activity.html +1 -1
- fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/detection.html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
- fides/ui-build/static/admin/datamap.html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
- fides/ui-build/static/admin/dataset/new.html +1 -1
- fides/ui-build/static/admin/dataset.html +1 -1
- fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
- fides/ui-build/static/admin/datastore-connection/new.html +1 -1
- fides/ui-build/static/admin/datastore-connection.html +1 -1
- fides/ui-build/static/admin/index.html +1 -1
- fides/ui-build/static/admin/integrations/[id].html +1 -1
- fides/ui-build/static/admin/integrations.html +1 -1
- fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
- fides/ui-build/static/admin/lib/fides-preview.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
- fides/ui-build/static/admin/login/[provider].html +1 -1
- fides/ui-build/static/admin/login.html +1 -1
- fides/ui-build/static/admin/new-privacy-requests.html +1 -1
- fides/ui-build/static/admin/notifications/digests/[id].html +1 -1
- fides/ui-build/static/admin/notifications/digests/new.html +1 -1
- fides/ui-build/static/admin/notifications/digests.html +1 -1
- fides/ui-build/static/admin/notifications/providers/[key].html +1 -1
- fides/ui-build/static/admin/notifications/providers/new.html +1 -1
- fides/ui-build/static/admin/notifications/providers.html +1 -1
- fides/ui-build/static/admin/notifications/templates/[id].html +1 -1
- fides/ui-build/static/admin/notifications/templates/add-template.html +1 -1
- fides/ui-build/static/admin/notifications/templates.html +1 -1
- fides/ui-build/static/admin/notifications.html +1 -1
- fides/ui-build/static/admin/poc/ant-components.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
- fides/ui-build/static/admin/poc/forms.html +1 -1
- fides/ui-build/static/admin/poc/table-migration.html +1 -1
- fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
- fides/ui-build/static/admin/privacy-requests.html +1 -1
- fides/ui-build/static/admin/properties/[id].html +1 -1
- fides/ui-build/static/admin/properties/add-property.html +1 -1
- fides/ui-build/static/admin/properties.html +1 -1
- fides/ui-build/static/admin/reporting/datamap.html +1 -1
- fides/ui-build/static/admin/sandbox/privacy-notices.html +1 -1
- fides/ui-build/static/admin/settings/about/alpha.html +1 -1
- fides/ui-build/static/admin/settings/about.html +1 -1
- fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
- fides/ui-build/static/admin/settings/consent.html +1 -1
- fides/ui-build/static/admin/settings/custom-fields/[id].html +1 -1
- fides/ui-build/static/admin/settings/custom-fields/new.html +1 -1
- fides/ui-build/static/admin/settings/custom-fields.html +1 -1
- fides/ui-build/static/admin/settings/domain-records.html +1 -1
- fides/ui-build/static/admin/settings/domains.html +1 -1
- fides/ui-build/static/admin/settings/email-templates.html +1 -1
- fides/ui-build/static/admin/settings/locations.html +1 -1
- fides/ui-build/static/admin/settings/organization.html +1 -1
- fides/ui-build/static/admin/settings/privacy-requests.html +1 -1
- fides/ui-build/static/admin/settings/regulations.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id].html +1 -1
- fides/ui-build/static/admin/systems.html +1 -1
- fides/ui-build/static/admin/taxonomy.html +1 -1
- fides/ui-build/static/admin/user-management/new.html +1 -1
- fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
- fides/ui-build/static/admin/user-management.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/1099-31f9d973bc287df8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1316-2606e19807c08aa5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1437-8b1f6c8797c68bfd.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1467-8808ec8836e033f9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/155-f6302d32cba4cab6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1817-2d5cf537a2992c79.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2962-342ad1b4ab402ded.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3377-988ac2f3a2e8d5d4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3446-f40c352c43ac950c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/346-aa3b88efb85f2e28.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3700-f695f2f6b8251971.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3772-9f1713f9d5f97a10.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3873-d18e47b327445db5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5163-e682273cd76a7d07.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5279-bd6cccabdd6ca447.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/549-28537a6de666944b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5619-9b50cec521203989.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5643-3459282d296a3c59.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6277-3759894435cb8569.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6315-e2fb5ea77179a871.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6344-ca66a6e10d128179.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6419-11d67f7fd4e2f247.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6954-84789a4e4fb04eb9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/699-8ca44b0de9fa20f0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7488-cf92601852e3c509.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7773-9ae233109bc64ec2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8011-75af8b480fa114e6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8373-22b4d20e8cc06b3a.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8765-f622a35b40a7ec63.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9037-453224ba3ee65b13.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9187-7438242f0d380bb0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9278-08cc704317fe535e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9682-d1a3afa1394f8304.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9729-fcf6ff4e3534e4a8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9899-d6437facac926264.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9965-25621dd507e0cfd6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-11f1683aa15e1a62.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]-d60761c20382b259.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources-aed94957009eb3fd.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-127c114dd8f102ed.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/datastore/[monitorId]-5aa7a9fa96160de8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/website/[monitorId]/[systemId]-899bf30dde8b3292.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-823d0dd77e66585b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-3b100c44ea9e3988.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-3d1e48f4b95d7f6b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset-d3c6ecf7f29bea6e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/[id]-0a4aa42be2da0255.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/new-14313e441a13192c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-f139d1ce26404f30.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-b2d3d28b10a758e6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/new-privacy-requests-df0c95e408c54c7e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/templates-a14c876b49422597.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-cdd3754289a28317.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-6e4c535b6d614596.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-632b3ee563d070f2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-5b6807dced8d03c5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-7dce52bfc1b2652c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-b72d36243a0a545c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-56a5434969cbe9ba.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/webpack-c2c11809187b9f84.js +0 -1
- fides/ui-build/static/admin/_next/static/css/d41a048a166d50e4.css +0 -1
- fides/ui-build/static/admin/_next/static/css/d78390d6134d8328.css +0 -1
- fides/ui-build/static/admin/_next/static/wCNFtmYQhEDMaMPeBB4BM/_buildManifest.js +0 -1
- {ethyca_fides-2.73.2b0.dist-info → ethyca_fides-2.74.0rc1.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.73.2b0.dist-info → ethyca_fides-2.74.0rc1.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.73.2b0.dist-info → ethyca_fides-2.74.0rc1.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.73.2b0.dist-info → ethyca_fides-2.74.0rc1.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{wCNFtmYQhEDMaMPeBB4BM → FZTEUgamBvOhgPWce135w}/_ssgManifest.js +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{2040-7eed8491ca7276ed.js → 2040-fe1a06d82c0413f1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{2397-083fc511acb6105d.js → 2397-40b8db1cb2f23e2a.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{2921-34a43f2f8f5e5e69.js → 2921-b10bbc3a9104933b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3696-6f90e41a53d22920.js → 3696-6db05a35ae806825.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3923-f0a85dc5c3684fa0.js → 3923-44255a63d6d80ff5.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{401-7c345d019bb9bcbd.js → 401-fe8db8b5d8f600de.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{4496-4ff19366c597ec16.js → 4496-bed72bd5639075be.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{4817-1f3e6ea38625d8d5.js → 4817-d29f40d4ce729f37.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5185-e7f8b81dd3dfbe0b.js → 5185-b2ac9fecc00b67e7.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5258-62d6bc19add60aa6.js → 5258-4672eae0656430f9.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5783-21775c232dce7af7.js → 5783-6055edba275155ca.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6853-de9905d28e5b19b3.js → 6853-1adbdf6418ec3d62.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7059-2bb7c38578549703.js → 7059-12be23a345a94c1e.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7476-a02d970ea4d3f7d0.js → 7476-a43c046c24de37cc.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7630-a11610c2b31ab2ca.js → 7630-c654c61ba98d8c74.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{796-e36d610066135f8c.js → 796-e83ace3c6ab99ac7.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{8212-393420e5a9751791.js → 8212-b9e8295ca883c9f8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9330-f753636a31c4ea04.js → 9330-e519adec48222d45.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9826-a737a9956c1d0905.js → 9826-657652d55936a8c6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{404-eb019192ce498f32.js → 404-d079b8bf35250874.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-a188f84239f4b2a8.js → multiple-b4d18c1f4d414f5f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-ee9df33ebd471099.js → add-systems-d451bc8932330141.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-4f9cf087fcee87e6.js → add-vendors-c24663cd5dec57db.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-9af75caefc74eaca.js → configure-d93418688bd258eb.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-b08abefec298ccf1.js → privacy-experience-d9b7b311195df29e.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-6528eb24165aceb6.js → privacy-notices-cdfc9bb19f47c709.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-5cba30eba1e97e56.js → properties-0b995b01dc4dbd1f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-614af0a2c2ba966c.js → consent-b37ed76849330edd.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{index-1343fa525a206571.js → index-692d27dbe9392c9f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/notifications/digests/{[id]-63b3be660fb12c0f.js → [id]-caaa8602a1d449b1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/notifications/digests/{new-d3b577962dd33266.js → new-9b106b1d2d93985b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/notifications/{digests-aed9afd988a48acf.js → digests-6a1ded8cdde836c4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/notifications/{providers-36a0ac36062abd02.js → providers-a03cbd698a23e5b3.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/notifications/templates/{[id]-8063dceb32310c85.js → [id]-3cde574b3c8447c0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/notifications/templates/{add-template-4931c70bee62232f.js → add-template-0448bb4ae8536c58.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{notifications-93af719dab3bd053.js → notifications-4ea28f6b1dd63642.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{AntForm-e715cc654fb6a5cd.js → AntForm-fec08bea801b4918.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikAntFormItem-72ae299bcb6adae6.js → FormikAntFormItem-d911e5fbf5a4a888.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikControlled-2337f8c81a766eb0.js → FormikControlled-91b1adcac6a57b2d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikField-0af454f55494f6fa.js → FormikField-de309d8813b1ebfb.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{forms-d1b90ffa996fbd89.js → forms-f2943c1309062284.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-19724b9e0581b96d.js → table-migration-03eda417711ae909.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-cc41ae605f2b55ae.js → storage-ea6f78fa8b2d3f6c.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-fa82cffba448ccd8.js → configure-bda7b474493e7128.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-bc1c289647e52c48.js → [id]-30d298a47e85709f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-cbfaa23d96f5ed0b.js → add-property-438084cca0d0f10d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{properties-a15a3fd0ed88f39c.js → properties-17fd44d98f5bd5b6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/sandbox/{privacy-notices-afe921f6e2a526fb.js → privacy-notices-8c80391025ca7339.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/{[purpose_id]-19de0024418a4924.js → [purpose_id]-7c19810858b708cc.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-3b87002753b23ca5.js → domain-records-e334b43fa5c5b1e6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-aacd9d0ad47082d4.js → domains-9d18eb5c38d85522.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-3cdd0b39901190ba.js → email-templates-cb937ed7c4b1e5a8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-61076eedbfd137b9.js → locations-835281251f0785cd.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-b07b11d6002f8c8c.js → organization-7fd050c92866938c.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{privacy-requests-084a2b4431d35322.js → privacy-requests-59ea66130fca0d05.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-b7c0d3b1b754e70f.js → regulations-b0fe1051d908f366.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-5d5a68e555d18693.js → [id]-aed30fb22ae7c9ec.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-e6a211d8a0401086.js → user-management-6b88ca3e02ee67c9.js} +0 -0
|
@@ -4,12 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
|
|
8
8
|
|
|
9
9
|
from celery.result import AsyncResult
|
|
10
10
|
from loguru import logger
|
|
11
11
|
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
|
|
12
|
-
from sqlalchemy.dialects.postgresql import JSONB
|
|
12
|
+
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
13
13
|
from sqlalchemy.ext.declarative import declared_attr
|
|
14
14
|
from sqlalchemy.ext.mutable import MutableDict, MutableList
|
|
15
15
|
from sqlalchemy.orm import Query, RelationshipProperty, Session, backref, relationship
|
|
@@ -69,6 +69,7 @@ from fides.api.models.pre_approval_webhook import (
|
|
|
69
69
|
PreApprovalWebhook,
|
|
70
70
|
PreApprovalWebhookReply,
|
|
71
71
|
)
|
|
72
|
+
from fides.api.models.privacy_request.duplicate_group import DuplicateGroup
|
|
72
73
|
from fides.api.models.privacy_request.execution_log import (
|
|
73
74
|
COMPLETED_EXECUTION_LOG_STATUSES,
|
|
74
75
|
EXITED_EXECUTION_LOG_STATUSES,
|
|
@@ -225,7 +226,16 @@ class PrivacyRequest(
|
|
|
225
226
|
location = Column(String, nullable=True)
|
|
226
227
|
|
|
227
228
|
# Duplicate detection - group ID to link duplicates together
|
|
228
|
-
duplicate_request_group_id = Column(
|
|
229
|
+
duplicate_request_group_id = Column(
|
|
230
|
+
UUID(as_uuid=True),
|
|
231
|
+
ForeignKey("duplicate_group.id"),
|
|
232
|
+
nullable=True,
|
|
233
|
+
)
|
|
234
|
+
duplicate_group = relationship(
|
|
235
|
+
DuplicateGroup,
|
|
236
|
+
back_populates="privacy_requests",
|
|
237
|
+
uselist=False,
|
|
238
|
+
)
|
|
229
239
|
|
|
230
240
|
# A PrivacyRequest can be soft deleted, so we store when it was deleted
|
|
231
241
|
deleted_at = Column(DateTime(timezone=True), nullable=True)
|
|
@@ -604,7 +614,10 @@ class PrivacyRequest(
|
|
|
604
614
|
"""Verify the identification code supplied by the user
|
|
605
615
|
If verified, change the status of the request to "pending", and set the datetime the identity was verified.
|
|
606
616
|
"""
|
|
607
|
-
if not self.status
|
|
617
|
+
if not self.status in [
|
|
618
|
+
PrivacyRequestStatus.identity_unverified,
|
|
619
|
+
PrivacyRequestStatus.duplicate,
|
|
620
|
+
]:
|
|
608
621
|
raise IdentityVerificationException(
|
|
609
622
|
f"Invalid identity verification request. Privacy request '{self.id}' status = {self.status.value}." # type: ignore # pylint: disable=no-member
|
|
610
623
|
)
|
|
@@ -682,12 +695,29 @@ class PrivacyRequest(
|
|
|
682
695
|
},
|
|
683
696
|
)
|
|
684
697
|
|
|
685
|
-
def
|
|
686
|
-
"""
|
|
698
|
+
def identity_prefix_cache_and_keys(self) -> Tuple[str, FidesopsRedis, List[str]]:
|
|
699
|
+
"""Returns the prefix and cache keys for the identity data for this request"""
|
|
687
700
|
prefix = f"id-{self.id}-identity-*"
|
|
688
701
|
cache: FidesopsRedis = get_cache()
|
|
689
702
|
keys = cache.keys(prefix)
|
|
690
|
-
|
|
703
|
+
return prefix, cache, keys
|
|
704
|
+
|
|
705
|
+
def verify_cache_for_identity_data(self) -> bool:
|
|
706
|
+
"""Verifies if the identity data is cached for this request"""
|
|
707
|
+
_, _, keys = self.identity_prefix_cache_and_keys()
|
|
708
|
+
return len(keys) > 0
|
|
709
|
+
|
|
710
|
+
def get_cached_identity_data(self) -> Dict[str, Any]:
|
|
711
|
+
"""Retrieves any identity data pertaining to this request from the cache"""
|
|
712
|
+
result: Dict[str, Any] = {}
|
|
713
|
+
prefix, cache, keys = self.identity_prefix_cache_and_keys()
|
|
714
|
+
|
|
715
|
+
if not keys:
|
|
716
|
+
logger.debug(f"Cache miss for request {self.id}, falling back to DB")
|
|
717
|
+
identity = self.get_persisted_identity()
|
|
718
|
+
self.cache_identity(identity)
|
|
719
|
+
keys = cache.keys(prefix)
|
|
720
|
+
|
|
691
721
|
for key in keys:
|
|
692
722
|
value = cache.get(key)
|
|
693
723
|
if value:
|
|
@@ -705,10 +735,25 @@ class PrivacyRequest(
|
|
|
705
735
|
|
|
706
736
|
def get_cached_custom_privacy_request_fields(self) -> Dict[str, Any]:
|
|
707
737
|
"""Retrieves any custom fields pertaining to this request from the cache"""
|
|
738
|
+
result: Dict[str, Any] = {}
|
|
708
739
|
prefix = f"id-{self.id}-custom-privacy-request-field-*"
|
|
740
|
+
|
|
709
741
|
cache: FidesopsRedis = get_cache()
|
|
710
742
|
keys = cache.keys(prefix)
|
|
711
|
-
|
|
743
|
+
|
|
744
|
+
if not keys:
|
|
745
|
+
logger.debug(f"Cache miss for request {self.id}, falling back to DB")
|
|
746
|
+
custom_privacy_request_fields = (
|
|
747
|
+
self.get_persisted_custom_privacy_request_fields()
|
|
748
|
+
)
|
|
749
|
+
self.cache_custom_privacy_request_fields(
|
|
750
|
+
{
|
|
751
|
+
key: CustomPrivacyRequestFieldSchema(**value)
|
|
752
|
+
for key, value in custom_privacy_request_fields.items()
|
|
753
|
+
}
|
|
754
|
+
)
|
|
755
|
+
keys = cache.keys(prefix)
|
|
756
|
+
|
|
712
757
|
for key in keys:
|
|
713
758
|
value = cache.get(key)
|
|
714
759
|
if value:
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from enum import Enum as EnumType
|
|
3
3
|
from typing import Any, Dict, List, Optional, Type, Union
|
|
4
|
+
from uuid import UUID
|
|
4
5
|
|
|
5
6
|
from fideslang.validation import FidesKey
|
|
6
|
-
from pydantic import ConfigDict, Field, field_serializer, field_validator
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
|
|
7
8
|
|
|
8
9
|
from fides.api.custom_types import SafeStr
|
|
9
10
|
from fides.api.graph.config import CollectionAddress
|
|
@@ -309,6 +310,11 @@ class PrivacyRequestStatus(str, EnumType):
|
|
|
309
310
|
duplicate = "duplicate" # Request identified as duplicate of another request
|
|
310
311
|
|
|
311
312
|
|
|
313
|
+
class IdentityValue(BaseModel):
|
|
314
|
+
label: str
|
|
315
|
+
value: Optional[str] = None
|
|
316
|
+
|
|
317
|
+
|
|
312
318
|
class PrivacyRequestResponse(FidesSchema):
|
|
313
319
|
"""Schema to check the status of a PrivacyRequest"""
|
|
314
320
|
|
|
@@ -329,7 +335,7 @@ class PrivacyRequestResponse(FidesSchema):
|
|
|
329
335
|
# as it is an API response field, and we don't want to reveal any more
|
|
330
336
|
# about our PII structure than is explicitly stored in the cache on request
|
|
331
337
|
# creation.
|
|
332
|
-
identity: Optional[Dict[str, Union[Optional[str],
|
|
338
|
+
identity: Optional[Dict[str, Union[Optional[str], IdentityValue]]] = None
|
|
333
339
|
custom_privacy_request_fields: Optional[Dict[str, Any]] = None
|
|
334
340
|
policy: PolicySchema
|
|
335
341
|
action_required_details: Optional[CheckpointActionRequiredDetails] = None
|
|
@@ -343,7 +349,7 @@ class PrivacyRequestResponse(FidesSchema):
|
|
|
343
349
|
deleted_by: Optional[str] = None
|
|
344
350
|
finalized_at: Optional[datetime] = None
|
|
345
351
|
finalized_by: Optional[str] = None
|
|
346
|
-
duplicate_request_group_id: Optional[
|
|
352
|
+
duplicate_request_group_id: Optional[UUID] = None
|
|
347
353
|
|
|
348
354
|
model_config = ConfigDict(from_attributes=True, use_enum_values=True)
|
|
349
355
|
|
|
@@ -370,6 +376,12 @@ class DenyPrivacyRequests(ReviewPrivacyRequestIds):
|
|
|
370
376
|
reason: Optional[SafeStr] = None
|
|
371
377
|
|
|
372
378
|
|
|
379
|
+
class CancelPrivacyRequests(ReviewPrivacyRequestIds):
|
|
380
|
+
"""Pass in a list of privacy request ids and cancellation reason"""
|
|
381
|
+
|
|
382
|
+
reason: Optional[SafeStr] = None
|
|
383
|
+
|
|
384
|
+
|
|
373
385
|
class BulkPostPrivacyRequests(BulkResponse):
|
|
374
386
|
"""Schema with mixed success/failure responses for Bulk Create of PrivacyRequest responses."""
|
|
375
387
|
|
|
@@ -458,7 +470,7 @@ class PrivacyRequestFilter(FidesSchema):
|
|
|
458
470
|
errored_gt: Optional[datetime] = None
|
|
459
471
|
external_id: Optional[str] = None
|
|
460
472
|
location: Optional[str] = None
|
|
461
|
-
action_type: Optional[ActionType] = None
|
|
473
|
+
action_type: Optional[Union[ActionType, List[ActionType]]] = None
|
|
462
474
|
verbose: Optional[bool] = False
|
|
463
475
|
include_identities: Optional[bool] = False
|
|
464
476
|
include_custom_privacy_request_fields: Optional[bool] = False
|
|
@@ -551,13 +551,20 @@ class AsyncPollingStrategy(AsyncDSRStrategy):
|
|
|
551
551
|
self.result_request.result_path,
|
|
552
552
|
)
|
|
553
553
|
|
|
554
|
-
#
|
|
555
|
-
if
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
554
|
+
# Checks if we have a polling result, response could be empty in case there was no data to access
|
|
555
|
+
if polling_result:
|
|
556
|
+
# Ensure we have the expected polling result type
|
|
557
|
+
if not isinstance(polling_result, PollingResult):
|
|
558
|
+
raise PrivacyRequestError(
|
|
559
|
+
"Polling result must be PollingResult instance"
|
|
560
|
+
)
|
|
560
561
|
|
|
562
|
+
# Store results on the sub-request
|
|
563
|
+
self._store_sub_request_result(polling_result, sub_request, polling_task)
|
|
564
|
+
else:
|
|
565
|
+
logger.info(
|
|
566
|
+
f"No result response for sub-request {sub_request.id} for task {polling_task.id}"
|
|
567
|
+
)
|
|
561
568
|
# Mark as complete using existing method
|
|
562
569
|
sub_request.update_status(self.session, ExecutionLogStatus.complete.value)
|
|
563
570
|
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from sqlalchemy.orm import Session
|
|
7
|
+
|
|
8
|
+
from fides.api.models.privacy_request.duplicate_group import (
|
|
9
|
+
DuplicateGroup,
|
|
10
|
+
generate_rule_version,
|
|
11
|
+
)
|
|
12
|
+
from fides.api.models.privacy_request.privacy_request import PrivacyRequest
|
|
13
|
+
from fides.api.schemas.policy import ActionType
|
|
14
|
+
from fides.api.schemas.privacy_request import PrivacyRequestStatus
|
|
15
|
+
from fides.api.task.conditional_dependencies.schemas import (
|
|
16
|
+
Condition,
|
|
17
|
+
ConditionGroup,
|
|
18
|
+
ConditionLeaf,
|
|
19
|
+
GroupOperator,
|
|
20
|
+
Operator,
|
|
21
|
+
)
|
|
22
|
+
from fides.api.task.conditional_dependencies.sql_translator import (
|
|
23
|
+
SQLConditionTranslator,
|
|
24
|
+
)
|
|
25
|
+
from fides.config.config_proxy import ConfigProxy
|
|
26
|
+
from fides.config.duplicate_detection_settings import DuplicateDetectionSettings
|
|
27
|
+
|
|
28
|
+
ACTIONED_REQUEST_STATUSES = [
|
|
29
|
+
PrivacyRequestStatus.approved,
|
|
30
|
+
PrivacyRequestStatus.in_processing,
|
|
31
|
+
PrivacyRequestStatus.requires_manual_finalization,
|
|
32
|
+
PrivacyRequestStatus.requires_input,
|
|
33
|
+
PrivacyRequestStatus.paused,
|
|
34
|
+
PrivacyRequestStatus.awaiting_email_send,
|
|
35
|
+
PrivacyRequestStatus.error,
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DuplicateDetectionService:
|
|
40
|
+
def __init__(self, db: Session):
|
|
41
|
+
self.db = db
|
|
42
|
+
self._config = ConfigProxy(db).privacy_request_duplicate_detection
|
|
43
|
+
|
|
44
|
+
def is_enabled(self) -> bool:
|
|
45
|
+
return self._config.enabled
|
|
46
|
+
|
|
47
|
+
def _create_identity_conditions(
|
|
48
|
+
self, current_request: PrivacyRequest
|
|
49
|
+
) -> list[Condition]:
|
|
50
|
+
"""Creates conditions for matching identity fields.
|
|
51
|
+
|
|
52
|
+
For identity field matching using the EAV pattern in ProvidedIdentity, we need to match both field_name
|
|
53
|
+
and hashed_value. This function creates the required nested conditions for each identity field.
|
|
54
|
+
Also adds a condition for the policy_id to ensure that we are only matching requests for the same policy.
|
|
55
|
+
"""
|
|
56
|
+
conditions: list[Condition] = []
|
|
57
|
+
current_identities: dict[str, str] = {
|
|
58
|
+
pi.field_name: pi.hashed_value
|
|
59
|
+
for pi in current_request.provided_identities # type: ignore [attr-defined]
|
|
60
|
+
if pi.field_name in self._config.match_identity_fields
|
|
61
|
+
}
|
|
62
|
+
if len(current_identities) != len(self._config.match_identity_fields):
|
|
63
|
+
missing_fields = [
|
|
64
|
+
field
|
|
65
|
+
for field in self._config.match_identity_fields
|
|
66
|
+
if field not in current_identities.keys()
|
|
67
|
+
]
|
|
68
|
+
logger.debug(
|
|
69
|
+
f"Some identity fields were not found in the current request: {missing_fields}"
|
|
70
|
+
)
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
for field_name, hashed_value in current_identities.items():
|
|
74
|
+
identity_condition = ConditionGroup(
|
|
75
|
+
logical_operator=GroupOperator.and_,
|
|
76
|
+
conditions=[
|
|
77
|
+
ConditionLeaf(
|
|
78
|
+
field_address="privacyrequest.provided_identities.field_name",
|
|
79
|
+
operator=Operator.eq,
|
|
80
|
+
value=field_name,
|
|
81
|
+
),
|
|
82
|
+
ConditionLeaf(
|
|
83
|
+
field_address="privacyrequest.provided_identities.hashed_value",
|
|
84
|
+
operator=Operator.eq,
|
|
85
|
+
value=hashed_value,
|
|
86
|
+
),
|
|
87
|
+
],
|
|
88
|
+
)
|
|
89
|
+
conditions.append(identity_condition)
|
|
90
|
+
policy_condition = ConditionLeaf(
|
|
91
|
+
field_address="privacyrequest.policy_id",
|
|
92
|
+
operator=Operator.eq,
|
|
93
|
+
value=current_request.policy_id,
|
|
94
|
+
)
|
|
95
|
+
conditions.append(policy_condition)
|
|
96
|
+
return conditions
|
|
97
|
+
|
|
98
|
+
def _create_time_window_condition(self, time_window_days: int) -> Condition:
|
|
99
|
+
"""Creates a condition for matching requests within a time window."""
|
|
100
|
+
cutoff_date = datetime.now(timezone.utc) - timedelta(days=time_window_days)
|
|
101
|
+
condition = ConditionLeaf(
|
|
102
|
+
field_address="privacyrequest.created_at",
|
|
103
|
+
operator=Operator.gte,
|
|
104
|
+
value=cutoff_date.isoformat(),
|
|
105
|
+
)
|
|
106
|
+
return condition
|
|
107
|
+
|
|
108
|
+
def create_duplicate_detection_conditions(
|
|
109
|
+
self,
|
|
110
|
+
current_request: PrivacyRequest,
|
|
111
|
+
) -> Optional[ConditionGroup]:
|
|
112
|
+
"""
|
|
113
|
+
Create conditions for duplicate detection based on configuration.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
current_request: The current privacy request to find duplicates for
|
|
117
|
+
config: Duplicate detection configuration settings
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A ConditionGroup with AND operator, or None if no conditions can be created
|
|
121
|
+
"""
|
|
122
|
+
if len(self._config.match_identity_fields) == 0:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
identity_conditions = self._create_identity_conditions(current_request)
|
|
126
|
+
if not identity_conditions:
|
|
127
|
+
return None # Only proceed if we have identity conditions
|
|
128
|
+
|
|
129
|
+
time_window_condition = self._create_time_window_condition(
|
|
130
|
+
self._config.time_window_days
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Combine all conditions with AND operator
|
|
134
|
+
all_conditions: list[Condition] = [*identity_conditions, time_window_condition]
|
|
135
|
+
return ConditionGroup(
|
|
136
|
+
logical_operator=GroupOperator.and_, conditions=all_conditions
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def find_duplicate_privacy_requests(
|
|
140
|
+
self,
|
|
141
|
+
current_request: PrivacyRequest,
|
|
142
|
+
) -> list[PrivacyRequest]:
|
|
143
|
+
"""
|
|
144
|
+
Find potential duplicate privacy requests based on duplicate detection configuration.
|
|
145
|
+
|
|
146
|
+
Uses the SQLConditionTranslator to build queries from conditions, which handles
|
|
147
|
+
the ProvidedIdentity relationship using SQLAlchemy's .any() method.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
current_request: The privacy request to check for duplicates
|
|
151
|
+
config: Duplicate detection configuration settings
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List of PrivacyRequest objects that match the duplicate criteria,
|
|
155
|
+
does not include the current request
|
|
156
|
+
"""
|
|
157
|
+
condition = self.create_duplicate_detection_conditions(current_request)
|
|
158
|
+
|
|
159
|
+
if condition is None:
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
translator = SQLConditionTranslator(self.db)
|
|
163
|
+
query = translator.generate_query_from_condition(condition)
|
|
164
|
+
|
|
165
|
+
query = query.filter(PrivacyRequest.id != current_request.id).filter(
|
|
166
|
+
PrivacyRequest.deleted_at.is_(None)
|
|
167
|
+
)
|
|
168
|
+
return query.all()
|
|
169
|
+
|
|
170
|
+
def generate_dedup_key(self, request: PrivacyRequest) -> str:
|
|
171
|
+
"""
|
|
172
|
+
Generate a dedup key for a request based on the duplicate detection settings.
|
|
173
|
+
"""
|
|
174
|
+
current_identities: dict[str, str] = {
|
|
175
|
+
pi.field_name: pi.hashed_value
|
|
176
|
+
for pi in request.provided_identities # type: ignore [attr-defined]
|
|
177
|
+
if pi.field_name in self._config.match_identity_fields
|
|
178
|
+
}
|
|
179
|
+
if len(current_identities) != len(self._config.match_identity_fields):
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"This request does not contain the required identity fields for duplicate detection."
|
|
182
|
+
)
|
|
183
|
+
return "|".join(
|
|
184
|
+
[
|
|
185
|
+
current_identities[field]
|
|
186
|
+
for field in sorted(self._config.match_identity_fields)
|
|
187
|
+
]
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def update_duplicate_group_ids(
|
|
191
|
+
self,
|
|
192
|
+
request: PrivacyRequest,
|
|
193
|
+
duplicates: list[PrivacyRequest],
|
|
194
|
+
duplicate_group_id: UUID,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""
|
|
197
|
+
Update the duplicate request group ids for a request and its duplicates.
|
|
198
|
+
Args:
|
|
199
|
+
request: The privacy request to update
|
|
200
|
+
duplicates: The list of duplicate requests to update
|
|
201
|
+
duplicate_group_id: The duplicate request group id to update
|
|
202
|
+
"""
|
|
203
|
+
update_all = [request] + duplicates
|
|
204
|
+
try:
|
|
205
|
+
for privacy_request in update_all:
|
|
206
|
+
privacy_request.duplicate_request_group_id = duplicate_group_id # type: ignore [assignment]
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"Failed to update duplicate request group ids: {e}")
|
|
209
|
+
raise e
|
|
210
|
+
|
|
211
|
+
def mark_as_duplicate(self, request: PrivacyRequest, message: str) -> None:
|
|
212
|
+
"""
|
|
213
|
+
Mark a request as a duplicate.
|
|
214
|
+
"""
|
|
215
|
+
request.update(self.db, data={"status": PrivacyRequestStatus.duplicate})
|
|
216
|
+
logger.debug(message)
|
|
217
|
+
self.add_error_execution_log(request, message)
|
|
218
|
+
|
|
219
|
+
def add_error_execution_log(self, request: PrivacyRequest, message: str) -> None:
|
|
220
|
+
request.add_error_execution_log(
|
|
221
|
+
db=self.db,
|
|
222
|
+
connection_key=None,
|
|
223
|
+
dataset_name="Duplicate Request Detection",
|
|
224
|
+
collection_name=None,
|
|
225
|
+
message=message,
|
|
226
|
+
action_type=(
|
|
227
|
+
request.policy.get_action_type() # type: ignore [arg-type]
|
|
228
|
+
if request.policy
|
|
229
|
+
else ActionType.access
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def add_success_execution_log(self, request: PrivacyRequest, message: str) -> None:
|
|
234
|
+
request.add_success_execution_log(
|
|
235
|
+
db=self.db,
|
|
236
|
+
connection_key=None,
|
|
237
|
+
dataset_name="Duplicate Request Detection",
|
|
238
|
+
collection_name=None,
|
|
239
|
+
message=message,
|
|
240
|
+
action_type=(
|
|
241
|
+
request.policy.get_action_type() # type: ignore [arg-type]
|
|
242
|
+
if request.policy
|
|
243
|
+
else ActionType.access
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def verified_identity_cases(
|
|
248
|
+
self, request: PrivacyRequest, duplicates: list[PrivacyRequest]
|
|
249
|
+
) -> bool:
|
|
250
|
+
"""
|
|
251
|
+
Apply verified identity rules to determine if a request is a duplicate request.
|
|
252
|
+
- If this request does not have a verified identity, it may be a duplicate if another request in the group is verified.
|
|
253
|
+
- If this is the first request to be verified, it is not a duplicate request
|
|
254
|
+
- If other requests identities were verified before this request, it is a duplicate request
|
|
255
|
+
Args:
|
|
256
|
+
request: The privacy request to check
|
|
257
|
+
duplicates: The list of duplicate requests
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
True if the request is a duplicate request, False otherwise
|
|
261
|
+
"""
|
|
262
|
+
verified_in_group = [
|
|
263
|
+
duplicate for duplicate in duplicates if duplicate.identity_verified_at
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
# The request identity is not verified.
|
|
267
|
+
if not request.identity_verified_at:
|
|
268
|
+
if len(verified_in_group) > 0:
|
|
269
|
+
message = f"Request {request.id} is a duplicate: it is duplicating request(s) {[duplicate.id for duplicate in verified_in_group]}."
|
|
270
|
+
self.mark_as_duplicate(request, message)
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
canonical_request = min(duplicates, key=lambda x: x.created_at) # type: ignore [arg-type, return-value]
|
|
274
|
+
canonical_request_created_at = canonical_request.created_at or datetime.now(
|
|
275
|
+
timezone.utc
|
|
276
|
+
)
|
|
277
|
+
request_created_at = request.created_at or datetime.now(timezone.utc)
|
|
278
|
+
if request_created_at < canonical_request_created_at:
|
|
279
|
+
message = f"Request {request.id} is not a duplicate: it is the first request to be created in the group."
|
|
280
|
+
logger.debug(message)
|
|
281
|
+
self.add_success_execution_log(request, message)
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
message = f"Request {request.id} is a duplicate: it is duplicating request(s) ['{canonical_request.id}']."
|
|
285
|
+
self.mark_as_duplicate(request, message)
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
# The request identity is verified.
|
|
289
|
+
if not verified_in_group:
|
|
290
|
+
message = f"Request {request.id} is not a duplicate: it is the first request to be verified in the group."
|
|
291
|
+
logger.debug(message)
|
|
292
|
+
for duplicate in duplicates:
|
|
293
|
+
dup_message = f"Request {duplicate.id} is a duplicate: it is duplicating request(s) ['{request.id}']."
|
|
294
|
+
self.mark_as_duplicate(duplicate, dup_message)
|
|
295
|
+
self.add_success_execution_log(request, message)
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
# If this request is the first with a verified identity, it is not a duplicate.
|
|
299
|
+
canonical_request = min(verified_in_group, key=lambda x: x.identity_verified_at) # type: ignore [arg-type, return-value]
|
|
300
|
+
canonical_request_verified_at = (
|
|
301
|
+
canonical_request.identity_verified_at or datetime.now(timezone.utc)
|
|
302
|
+
)
|
|
303
|
+
request_verified_at = request.identity_verified_at or datetime.now(timezone.utc)
|
|
304
|
+
if request_verified_at < canonical_request_verified_at:
|
|
305
|
+
message = f"Request {request.id} is not a duplicate: it is the first request to be verified in the group."
|
|
306
|
+
logger.debug(message)
|
|
307
|
+
self.add_success_execution_log(request, message)
|
|
308
|
+
for duplicate in duplicates:
|
|
309
|
+
dup_message = f"Request {duplicate.id} is a duplicate: it is duplicating request(s) ['{request.id}']."
|
|
310
|
+
self.mark_as_duplicate(duplicate, dup_message)
|
|
311
|
+
return False
|
|
312
|
+
message = f"Request {request.id} is a duplicate: it is duplicating request(s) ['{canonical_request.id}']."
|
|
313
|
+
self.mark_as_duplicate(request, message)
|
|
314
|
+
return True
|
|
315
|
+
|
|
316
|
+
# pylint: disable=too-many-return-statements
|
|
317
|
+
def is_duplicate_request(self, request: PrivacyRequest) -> bool:
|
|
318
|
+
"""
|
|
319
|
+
Determine if a request is a duplicate request and assigns a duplicate request group id.
|
|
320
|
+
|
|
321
|
+
The hierarchy is:
|
|
322
|
+
1. Actioned requests: if this request duplicates an actioned request, it is a duplicate.
|
|
323
|
+
2. Verified identity requests:
|
|
324
|
+
a. if this request has a verified identity:
|
|
325
|
+
- If none of the duplicates have a verified identity, it is not a duplicate.
|
|
326
|
+
- If duplicates have verified identities, but this request is the first with a verified identity, it is not a duplicate.
|
|
327
|
+
b. if this request does not have a verified identity:
|
|
328
|
+
- If no duplicates have a verified identity, and this was the first created request, it is not a duplicate.
|
|
329
|
+
3. First created request: if this is the first created request in the group, it is not a duplicate.
|
|
330
|
+
4. If no canonical requests are found (meaning all requests are marked as duplicates), this request is not a duplicate.
|
|
331
|
+
- Could occur if configuration changes and previous requests were already marked as duplicates.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
request: The privacy request to check
|
|
335
|
+
config: Duplicate detection configuration settings
|
|
336
|
+
Returns:
|
|
337
|
+
True if the request is a duplicate request, False otherwise
|
|
338
|
+
"""
|
|
339
|
+
if request.policy.get_action_type() == ActionType.consent:
|
|
340
|
+
message = f"Consent request {request.id} is not a duplicate."
|
|
341
|
+
logger.info(message)
|
|
342
|
+
self.add_success_execution_log(request, message)
|
|
343
|
+
return False
|
|
344
|
+
duplicates = self.find_duplicate_privacy_requests(request)
|
|
345
|
+
rule_version = generate_rule_version(
|
|
346
|
+
DuplicateDetectionSettings(
|
|
347
|
+
enabled=self._config.enabled,
|
|
348
|
+
time_window_days=self._config.time_window_days,
|
|
349
|
+
match_identity_fields=self._config.match_identity_fields,
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
try:
|
|
353
|
+
dedup_key = self.generate_dedup_key(request)
|
|
354
|
+
except ValueError as e:
|
|
355
|
+
message = f"Request {request.id} is not a duplicate: {e}"
|
|
356
|
+
logger.warning(message)
|
|
357
|
+
self.add_success_execution_log(request, message)
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
_, duplicate_group = DuplicateGroup.get_or_create(
|
|
361
|
+
db=self.db, data={"rule_version": rule_version, "dedup_key": dedup_key}
|
|
362
|
+
)
|
|
363
|
+
if duplicate_group is None:
|
|
364
|
+
message = f"Failed to create duplicate group for request {request.id} with dedup key {dedup_key}"
|
|
365
|
+
logger.error(message)
|
|
366
|
+
self.add_error_execution_log(request, message)
|
|
367
|
+
return False
|
|
368
|
+
|
|
369
|
+
self.update_duplicate_group_ids(request, duplicates, duplicate_group.id) # type: ignore [arg-type]
|
|
370
|
+
|
|
371
|
+
if request.status in ACTIONED_REQUEST_STATUSES:
|
|
372
|
+
message = (
|
|
373
|
+
f"Request {request.id} is not a duplicate: it is an actioned request."
|
|
374
|
+
)
|
|
375
|
+
logger.debug(message)
|
|
376
|
+
self.add_success_execution_log(request, message)
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
# if this is the only request in the group, it is not a duplicate
|
|
380
|
+
if len(duplicates) == 0:
|
|
381
|
+
message = f"Request {request.id} is not a duplicate."
|
|
382
|
+
logger.debug(message)
|
|
383
|
+
self.add_success_execution_log(request, message)
|
|
384
|
+
return False
|
|
385
|
+
|
|
386
|
+
if request.status == PrivacyRequestStatus.duplicate:
|
|
387
|
+
return True
|
|
388
|
+
|
|
389
|
+
# only compare to non-duplicate/complete requests for the following cases
|
|
390
|
+
canonical_requests = [
|
|
391
|
+
duplicate
|
|
392
|
+
for duplicate in duplicates
|
|
393
|
+
if duplicate.status
|
|
394
|
+
not in [PrivacyRequestStatus.duplicate, PrivacyRequestStatus.complete]
|
|
395
|
+
]
|
|
396
|
+
# If no non-duplicate requests are found, this request is not a duplicate.
|
|
397
|
+
if len(canonical_requests) == 0:
|
|
398
|
+
message = f"Request {request.id} is not a duplicate."
|
|
399
|
+
logger.debug(message)
|
|
400
|
+
self.add_success_execution_log(request, message)
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
# If any requests in group are actioned, this request is a duplicate.
|
|
404
|
+
actioned_in_group = [
|
|
405
|
+
duplicate
|
|
406
|
+
for duplicate in canonical_requests
|
|
407
|
+
if duplicate.status in ACTIONED_REQUEST_STATUSES
|
|
408
|
+
]
|
|
409
|
+
if len(actioned_in_group) > 0:
|
|
410
|
+
message = f"Request {request.id} is a duplicate: it is duplicating actioned request(s) {[duplicate.id for duplicate in actioned_in_group]}."
|
|
411
|
+
self.mark_as_duplicate(request, message)
|
|
412
|
+
return True
|
|
413
|
+
# Check against verified identity rules.
|
|
414
|
+
return self.verified_identity_cases(request, canonical_requests)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def check_for_duplicates(db: Session, privacy_request: PrivacyRequest) -> None:
|
|
418
|
+
duplicate_detection_service = DuplicateDetectionService(db)
|
|
419
|
+
if duplicate_detection_service.is_enabled():
|
|
420
|
+
logger.info(
|
|
421
|
+
"Duplicate detection is enabled. Checking if privacy request is a duplicate."
|
|
422
|
+
)
|
|
423
|
+
if duplicate_detection_service.is_duplicate_request(privacy_request):
|
|
424
|
+
logger.info("Terminating privacy request: request is a duplicate.")
|
|
@@ -71,6 +71,7 @@ from fides.api.service.privacy_request.attachment_handling import (
|
|
|
71
71
|
get_attachments_content,
|
|
72
72
|
process_attachments_for_upload,
|
|
73
73
|
)
|
|
74
|
+
from fides.api.service.privacy_request.duplication_detection import check_for_duplicates
|
|
74
75
|
from fides.api.service.storage.storage_uploader_service import upload
|
|
75
76
|
from fides.api.task.filter_results import filter_data_categories
|
|
76
77
|
from fides.api.task.graph_runners import access_runner, consent_runner, erasure_runner
|
|
@@ -446,6 +447,10 @@ def run_privacy_request(
|
|
|
446
447
|
logger.info("Terminating privacy request: request deleted.")
|
|
447
448
|
return
|
|
448
449
|
|
|
450
|
+
check_for_duplicates(db=session, privacy_request=privacy_request)
|
|
451
|
+
if privacy_request.status == PrivacyRequestStatus.duplicate:
|
|
452
|
+
return
|
|
453
|
+
|
|
449
454
|
logger.info("Dispatching privacy request")
|
|
450
455
|
privacy_request.start_processing(session)
|
|
451
456
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
from inspect import Signature, signature
|
|
3
|
-
from typing import TYPE_CHECKING, Callable, Dict, List, Union
|
|
3
|
+
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Union
|
|
4
4
|
|
|
5
5
|
from loguru import logger
|
|
6
6
|
|
|
@@ -319,7 +319,12 @@ def validate_polling_result_override_function(f: Callable) -> None:
|
|
|
319
319
|
|
|
320
320
|
# Check return type annotation - handle both direct class and string literals
|
|
321
321
|
return_annotation = sig.return_annotation
|
|
322
|
-
if return_annotation not in (
|
|
322
|
+
if return_annotation not in (
|
|
323
|
+
PollingResult,
|
|
324
|
+
"PollingResult",
|
|
325
|
+
Optional[PollingResult],
|
|
326
|
+
"Optional[PollingResult]",
|
|
327
|
+
):
|
|
323
328
|
raise InvalidSaaSRequestOverrideException(
|
|
324
329
|
"Polling result override function must return a PollingResult"
|
|
325
330
|
)
|
|
@@ -165,7 +165,9 @@ def request_details(
|
|
|
165
165
|
|
|
166
166
|
if response is not None:
|
|
167
167
|
if CONFIG.dev_mode and response.content:
|
|
168
|
-
details[LoggerContextKeys.response.value] = response.content.decode(
|
|
168
|
+
details[LoggerContextKeys.response.value] = response.content.decode(
|
|
169
|
+
"utf-8", errors="replace"
|
|
170
|
+
)
|
|
169
171
|
|
|
170
172
|
details[LoggerContextKeys.status_code.value] = response.status_code
|
|
171
173
|
|