ethyca-fides 2.57.1b8__py2.py3-none-any.whl → 2.57.1rc0__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.
- {ethyca_fides-2.57.1b8.dist-info → ethyca_fides-2.57.1rc0.dist-info}/METADATA +1 -1
- {ethyca_fides-2.57.1b8.dist-info → ethyca_fides-2.57.1rc0.dist-info}/RECORD +207 -217
- fides/_version.py +3 -3
- fides/api/api/v1/endpoints/config_endpoints.py +2 -4
- fides/api/api/v1/endpoints/generic_overrides.py +6 -48
- fides/api/models/asset.py +18 -37
- fides/api/models/attachment.py +8 -6
- fides/api/models/comment.py +17 -17
- fides/api/models/connectionconfig.py +1 -1
- fides/api/models/detection_discovery.py +0 -12
- fides/api/models/location_regulation_selections.py +0 -1
- fides/api/models/{privacy_request/privacy_request.py → privacy_request.py} +691 -82
- fides/api/models/sql_models.py +5 -28
- fides/api/oauth/jwt.py +0 -18
- fides/api/oauth/utils.py +11 -11
- fides/api/schemas/dataset.py +2 -22
- fides/api/schemas/saas/strategy_configuration.py +0 -1
- fides/api/service/pagination/pagination_strategy_cursor.py +1 -15
- fides/api/service/pagination/pagination_strategy_link.py +1 -1
- fides/api/tasks/storage.py +111 -12
- fides/config/__init__.py +1 -1
- fides/data/language/languages.yml +0 -2
- fides/data/location/locations.yml +1 -244
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/3320-87c75df57a47487e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3627-fb83adac32c128e6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3662-420d9807c30008ab.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3949-9888699e2ac564c4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4481-ca8d2c75d634b6bc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4723-81d28e5be5c7b6d7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5574-a4047e826a8cd4c3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5834-bd9ed01c4ab2ef5c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5973-67c7562997f7d5cb.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6277-515c922445b8edc5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6954-6eb480eb132239c3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7044-9dc90893067f38ae.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7980-2597c279c30fbcda.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9767-4c591bd478c72650.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-e53d13729068a001.js → _app-9ceff88561dc1250.js} +8 -8
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-5593bb1b6de0473d.js → manual-d7f60624cbc12217.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-6a7208eee5e588fb.js → multiple-18b0b521255289a3.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-136bcbd20ac59bf5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-ba0c5fa9764cbd89.js → add-vendors-64f83e9a2b777bf3.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-1af99ffa7a507e86.js → configure-e11ace4f273ebb47.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-c02510104f9e503a.js → [id]-443e6dd191ce5588.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-e96415629f3b4223.js → new-36162d5bea29dcc2.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-66827da90b8a2d1e.js → [id]-2d651499c07d12d9.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-13d48f980bf707e5.js → new-36e57d2f2446be82.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-942d68a88b321067.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-00526324583139ab.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-f0ab51d0d5f995de.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-2220c30c3863b5a5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-740824dfa6823e26.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset-5541ecd2f62711a5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-9bb953098f68f1ee.js → [id]-0616437e1a82d3ed.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-cdcf42d4cb24b45a.js → new-f8218440494e8532.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-a6448ce6ba7f0cf5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-1a6965d78bfb26b0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-f263e6bacf0f2d19.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-b96ee3fea3920fcf.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-1b0f9469cb65abfc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-eebd2f4ead19cfd6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-4769f55b138073f7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-775f55b3f80cd452.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-14def4ca3cc9cda5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-21f26c0dc4d09e9f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-44f7fbfee9cf9267.js → [id]-82d9af34546adaa2.js} +1 -1
- fides/ui-build/static/admin/_next/static/css/957d0e4fea846ff0.css +1 -0
- fides/ui-build/static/admin/_next/static/rOPehh5NMs1hjog7nTw1q/_buildManifest.js +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/ant-poc.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/[monitorId]/[systemId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId].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-headless.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +3 -3
- fides/ui-build/static/admin/lib/fides.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/messaging/[id].html +1 -1
- fides/ui-build/static/admin/messaging/add-template.html +1 -1
- fides/ui-build/static/admin/messaging.html +1 -1
- fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/messaging.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/settings/about.html +1 -1
- fides/ui-build/static/admin/settings/consent.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/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/api/alembic/migrations/versions/1d2f04ac19f2_add_page_column_to_asset_table.py +0 -49
- fides/api/alembic/migrations/versions/7c3fbee90c78_add_user_assigned_data_uses_and_system_.py +0 -54
- fides/api/alembic/migrations/versions/9a1eacd2666b_add_description_column_to_asset.py +0 -25
- fides/api/models/privacy_request/__init__.py +0 -44
- fides/api/models/privacy_request/consent.py +0 -155
- fides/api/models/privacy_request/execution_log.py +0 -108
- fides/api/models/privacy_request/provided_identity.py +0 -149
- fides/api/models/privacy_request/request_task.py +0 -284
- fides/api/models/privacy_request/webhook.py +0 -94
- fides/api/service/storage/s3.py +0 -133
- fides/api/service/storage/util.py +0 -10
- fides/ui-build/static/admin/_next/static/chunks/1376-c177d8d0c60b1166.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3320-317483e6c10bae51.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/355-6a87b50acf54e985.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3662-6a0cafa3023aa7a2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4481-f2f57e7a75a90e79.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4723-1a785d558dfb6441.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/605-c782df008a0ff167.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6362-72451aeb32815d26.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6954-f7daf613febc36ef.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7434-3d4a0beb84ec57f7.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7495-2f5c8dfba2eabb4f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7958-d2bc63e1be612f36.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7980-91800d54c5964ba3.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-a33f82e79cb09478.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-1a3bb713fc7bc298.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-5892ca5d93ea6165.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-5c37c3ad1c3cfba5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-4fac7e0a54eebc32.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-45016545c099e183.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset-5ddd3cd7d29e89c0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-108945832a7edd7d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-e96e959d10afebea.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-5a161e167c251519.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-c3e234ad95e6e213.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-347e203faa31716f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-729fa8ed0f10bc8e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-ff6edba56dfec090.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-ad368857e86fe01b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-f45920358a405167.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-ae133b6e8e1107f2.js +0 -1
- fides/ui-build/static/admin/_next/static/css/dd9922a434d21e22.css +0 -1
- fides/ui-build/static/admin/_next/static/lO31cDp9wEzsNdiUnl8It/_buildManifest.js +0 -1
- {ethyca_fides-2.57.1b8.dist-info → ethyca_fides-2.57.1rc0.dist-info}/LICENSE +0 -0
- {ethyca_fides-2.57.1b8.dist-info → ethyca_fides-2.57.1rc0.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.57.1b8.dist-info → ethyca_fides-2.57.1rc0.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.57.1b8.dist-info → ethyca_fides-2.57.1rc0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{1150-d4a5eef84e70de22.js → 1150-73440d7b319558e8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{2397-c6d0dcb3b921555f.js → 2397-7177ecf4ebe68feb.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3086-d099019708303444.js → 3086-be6b52546c3efc90.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3855-4d5d936a58a0dd5f.js → 3855-5c7b11871f59e0e0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3872-2b2bf5ebd4b0a70f.js → 3872-f3d5054bdc584eaa.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5246-ac8b0fcb44b00af7.js → 5246-9fc6af1a6499e0a4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5258-a05dcab2f9315dab.js → 5258-3a650be9142cf914.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5480-c6c1fb87006e471a.js → 5480-f5bec5e881f72f8d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5487-509639439bf9fb46.js → 5487-d96e1abc93f92631.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5826-e3970cd9440be33b.js → 5826-16e497af363a0cbc.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6315-daf94fa7520fb253.js → 6315-dee79f6861c94d2d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6372-41afe1e6a9181cc7.js → 6372-f1b54f3cb4888660.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6853-7f95c36e396d3fb8.js → 6853-cacea421af3bfc26.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7751-130668529ab1d0b6.js → 7751-25e0c1307988ffd7.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{79-1c326c00cf111c00.js → 79-ba88d0cf4c65aaa2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{8433-b2a96b825124b596.js → 8433-969280d321d1c3a2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{8499-34620330ecf2ac53.js → 8499-07507004e8275df2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9046-e7b9a3fd010b01ad.js → 9046-80f1ea44f89fe32c.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9282-6ee061da1eb51d65.js → 9282-1a5a2f6f4d9ed586.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9999-8600f6f495a80b38.js → 9999-f2e40d5b13343220.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{404-8e0baa6e128803ab.js → 404-1087258931760074.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{ant-poc-39267961e03be6c5.js → ant-poc-b3b4d0a98450ffd1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-ed38f18c095141fe.js → privacy-experience-35e9df60b21e21a6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-7339b3cdf5d4da32.js → privacy-notices-da37afbe5ec81339.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-b5b3196eba90246a.js → properties-c57b209feef7c2da.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{reporting-38b8ec1a221610ff.js → reporting-97fea4ac45093cbd.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-df2388d953c85c2a.js → consent-24bef021ee71e36c.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-5e851b9f27bda8a4.js → [resourceUrn]-6cbd79481199812d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-9f9112b22fdfb32a.js → [projectUrn]-46b9790da2fec05a.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-ddca867936c89a72.js → projects-1f965b9c496071d1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-85e3d6f1b2afd3e7.js → [resourceUrn]-e7833c1c606081a9.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-9831226436d884b5.js → resources-bed368d048ea6883.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-a0749240265da17d.js → activity-1d7936f05d23097f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-819ac99e7a56465a.js → datamap-9cd0c40bef77b120.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-259d4a94861afe2b.js → [...subfieldNames]-ab9bc1a3640547db.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-25283f5bcac8fa73.js → [collectionName]-17ec8385bb1fa6d4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-aaedf8f2a849c97f.js → [datasetId]-e14c1c07658f8a10.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-61adfcd5f7f69101.js → datastore-connection-b8eaa9b9d3832b30.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{index-1072281d6cfe8295.js → index-94e6d589c4edf360.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-268918d8c1dfe82a.js → [id]-6b032de0a6c2c400.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-cd75949bee7497b6.js → add-template-c0d6ae68ff7979c6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-d1f4178b8a7e2145.js → [id]-bbe5854b7d19b7e9.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-37e800a20e654064.js → configure-4bad69cd42c9722c.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-92145503c4d2d75e.js → [id]-b4c808a8a0287d11.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-ab94805694ebd35c.js → add-property-5a701477b006a63b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{properties-5e623dcd92418638.js → properties-9a1899dfe052023e.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-710839776b9995ce.js → datamap-a3fa3ad77730a03b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-175d39bdd74f148c.js → about-d073be113e9ca7b0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-1262d596c0cdcd81.js → custom-fields-744e7bbc200557e7.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-f0c6e6034d37dbcf.js → domain-records-c5cd5fb578de9515.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-6ee49492a7769f89.js → email-templates-39c7ae3602ac69b2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-deb754d42982562b.js → locations-45e33ba111f8f8b0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-f9e97decfe56b018.js → regulations-94975aeab348528e.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{systems-4d6a17a7a24aca97.js → systems-c320df35d51dc537.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-46f47c04a01319cf.js → taxonomy-181ea5b0ac975239.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-5b752ba934baee94.js → [id]-622e16a17a11c096.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-4fbb29cf0a611aae.js → user-management-aa872b21bb835d34.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{webpack-815e3c35038201da.js → webpack-4df2ba5ee2d40f0a.js} +0 -0
- /fides/ui-build/static/admin/_next/static/{lO31cDp9wEzsNdiUnl8It → rOPehh5NMs1hjog7nTw1q}/_ssgManifest.js +0 -0
@@ -4,16 +4,27 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import json
|
6
6
|
from datetime import datetime, timedelta
|
7
|
-
from
|
7
|
+
from enum import Enum as EnumType
|
8
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
8
9
|
|
9
10
|
from celery.result import AsyncResult
|
10
11
|
from loguru import logger
|
11
|
-
from
|
12
|
+
from pydantic import BaseModel, ConfigDict
|
13
|
+
from sqlalchemy import (
|
14
|
+
Boolean,
|
15
|
+
Column,
|
16
|
+
DateTime,
|
17
|
+
ForeignKey,
|
18
|
+
Integer,
|
19
|
+
String,
|
20
|
+
UniqueConstraint,
|
21
|
+
)
|
12
22
|
from sqlalchemy.dialects.postgresql import JSONB
|
13
23
|
from sqlalchemy.ext.declarative import declared_attr
|
14
24
|
from sqlalchemy.ext.mutable import MutableDict, MutableList
|
15
25
|
from sqlalchemy.orm import Query, RelationshipProperty, Session, backref, relationship
|
16
26
|
from sqlalchemy.orm.dynamic import AppenderQuery
|
27
|
+
from sqlalchemy.sql import text
|
17
28
|
from sqlalchemy_utils.types.encrypted.encrypted_type import (
|
18
29
|
AesGcmEngine,
|
19
30
|
StringEncryptedType,
|
@@ -39,10 +50,8 @@ from fides.api.graph.config import (
|
|
39
50
|
CollectionAddress,
|
40
51
|
)
|
41
52
|
from fides.api.migrations.hash_migration_mixin import HashMigrationMixin
|
42
|
-
from fides.api.models.attachment import Attachment, AttachmentReference
|
43
53
|
from fides.api.models.audit_log import AuditLog
|
44
54
|
from fides.api.models.client import ClientDetail
|
45
|
-
from fides.api.models.comment import Comment, CommentReference
|
46
55
|
from fides.api.models.fides_user import FidesUser
|
47
56
|
from fides.api.models.manual_webhook import AccessManualWebhook
|
48
57
|
from fides.api.models.policy import (
|
@@ -55,21 +64,14 @@ from fides.api.models.pre_approval_webhook import (
|
|
55
64
|
PreApprovalWebhook,
|
56
65
|
PreApprovalWebhookReply,
|
57
66
|
)
|
58
|
-
from fides.api.
|
59
|
-
|
60
|
-
EXITED_EXECUTION_LOG_STATUSES,
|
61
|
-
ExecutionLog,
|
62
|
-
)
|
63
|
-
from fides.api.models.privacy_request.provided_identity import ProvidedIdentity
|
64
|
-
from fides.api.models.privacy_request.request_task import RequestTask
|
65
|
-
from fides.api.models.privacy_request.webhook import (
|
66
|
-
CallbackType,
|
67
|
-
SecondPartyRequestFormat,
|
68
|
-
generate_request_callback_pre_approval_jwe,
|
69
|
-
generate_request_callback_resume_jwe,
|
70
|
-
)
|
67
|
+
from fides.api.oauth.jwt import generate_jwe
|
68
|
+
from fides.api.schemas.base_class import FidesSchema
|
71
69
|
from fides.api.schemas.drp_privacy_request import DrpPrivacyRequestCreate
|
72
|
-
from fides.api.schemas.external_https import
|
70
|
+
from fides.api.schemas.external_https import (
|
71
|
+
RequestTaskJWE,
|
72
|
+
SecondPartyResponseFormat,
|
73
|
+
WebhookJWE,
|
74
|
+
)
|
73
75
|
from fides.api.schemas.masking.masking_secrets import MaskingSecretCache
|
74
76
|
from fides.api.schemas.policy import ActionType, CurrentStep
|
75
77
|
from fides.api.schemas.privacy_request import (
|
@@ -82,10 +84,16 @@ from fides.api.schemas.privacy_request import (
|
|
82
84
|
from fides.api.schemas.redis_cache import (
|
83
85
|
CustomPrivacyRequestField as CustomPrivacyRequestFieldSchema,
|
84
86
|
)
|
85
|
-
from fides.api.schemas.redis_cache import
|
87
|
+
from fides.api.schemas.redis_cache import (
|
88
|
+
Identity,
|
89
|
+
IdentityBase,
|
90
|
+
LabeledIdentity,
|
91
|
+
MultiValue,
|
92
|
+
)
|
86
93
|
from fides.api.tasks import celery_app
|
87
94
|
from fides.api.util.cache import (
|
88
95
|
FidesopsRedis,
|
96
|
+
celery_tasks_in_flight,
|
89
97
|
get_all_cache_keys_for_privacy_request,
|
90
98
|
get_async_task_tracking_cache_key,
|
91
99
|
get_cache,
|
@@ -102,12 +110,98 @@ from fides.api.util.decrypted_identity_automaton import DecryptedIdentityAutomat
|
|
102
110
|
from fides.api.util.identity_verification import IdentityVerificationMixin
|
103
111
|
from fides.api.util.logger import Pii
|
104
112
|
from fides.api.util.logger_context_utils import Contextualizable, LoggerContextKeys
|
113
|
+
from fides.common.api.scope_registry import (
|
114
|
+
PRIVACY_REQUEST_CALLBACK_RESUME,
|
115
|
+
PRIVACY_REQUEST_REVIEW,
|
116
|
+
)
|
105
117
|
from fides.config import CONFIG
|
106
118
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
119
|
+
# Locations from which privacy request execution can be resumed, in order.
|
120
|
+
EXECUTION_CHECKPOINTS = [
|
121
|
+
CurrentStep.pre_webhooks,
|
122
|
+
CurrentStep.access,
|
123
|
+
CurrentStep.upload_access,
|
124
|
+
CurrentStep.erasure,
|
125
|
+
CurrentStep.finalize_erasure,
|
126
|
+
CurrentStep.consent,
|
127
|
+
CurrentStep.finalize_consent,
|
128
|
+
CurrentStep.email_post_send,
|
129
|
+
CurrentStep.post_webhooks,
|
130
|
+
]
|
131
|
+
|
132
|
+
|
133
|
+
EmailRequestFulfillmentBodyParams = Dict[
|
134
|
+
CollectionAddress, Optional[CheckpointActionRequired]
|
135
|
+
]
|
136
|
+
|
137
|
+
|
138
|
+
class CallbackType(EnumType):
|
139
|
+
"""We currently have three types of Webhooks: pre-approval, pre (-execution), post (-execution)"""
|
140
|
+
|
141
|
+
pre_approval = "pre_approval"
|
142
|
+
pre = "pre" # pre-execution
|
143
|
+
post = "post" # post-execution
|
144
|
+
|
145
|
+
|
146
|
+
class SecondPartyRequestFormat(BaseModel):
|
147
|
+
"""
|
148
|
+
The request body we will use when calling a user's HTTP endpoint from fides.api
|
149
|
+
This class is defined here to avoid circular import issues between this file and
|
150
|
+
models.policy
|
151
|
+
"""
|
152
|
+
|
153
|
+
privacy_request_id: str
|
154
|
+
privacy_request_status: PrivacyRequestStatus
|
155
|
+
direction: WebhookDirection
|
156
|
+
callback_type: CallbackType
|
157
|
+
identity: Identity
|
158
|
+
policy_action: Optional[ActionType] = None
|
159
|
+
model_config = ConfigDict(use_enum_values=True)
|
160
|
+
|
161
|
+
|
162
|
+
def generate_request_callback_resume_jwe(webhook: PolicyPreWebhook) -> str:
|
163
|
+
"""
|
164
|
+
Generate a JWE to be used to resume privacy request execution.
|
165
|
+
"""
|
166
|
+
jwe = WebhookJWE(
|
167
|
+
webhook_id=webhook.id,
|
168
|
+
scopes=[PRIVACY_REQUEST_CALLBACK_RESUME],
|
169
|
+
iat=datetime.now().isoformat(),
|
170
|
+
)
|
171
|
+
return generate_jwe(
|
172
|
+
json.dumps(jwe.model_dump(mode="json")),
|
173
|
+
CONFIG.security.app_encryption_key,
|
174
|
+
)
|
175
|
+
|
176
|
+
|
177
|
+
def generate_request_callback_pre_approval_jwe(webhook: PreApprovalWebhook) -> str:
|
178
|
+
"""
|
179
|
+
Generate a JWE to be used to mark privacy requests as eligible / not-eligible for pre approval.
|
180
|
+
"""
|
181
|
+
jwe = WebhookJWE(
|
182
|
+
webhook_id=webhook.id,
|
183
|
+
scopes=[PRIVACY_REQUEST_REVIEW],
|
184
|
+
iat=datetime.now().isoformat(),
|
185
|
+
)
|
186
|
+
return generate_jwe(
|
187
|
+
json.dumps(jwe.model_dump(mode="json")),
|
188
|
+
CONFIG.security.app_encryption_key,
|
189
|
+
)
|
190
|
+
|
191
|
+
|
192
|
+
def generate_request_task_callback_jwe(request_task: RequestTask) -> str:
|
193
|
+
"""
|
194
|
+
Generate a JWE to be used to resume privacy request execution when a
|
195
|
+
callback endpoint is hit for a RequestTask
|
196
|
+
"""
|
197
|
+
jwe = RequestTaskJWE(
|
198
|
+
request_task_id=request_task.id,
|
199
|
+
scopes=[PRIVACY_REQUEST_CALLBACK_RESUME],
|
200
|
+
iat=datetime.now().isoformat(),
|
201
|
+
)
|
202
|
+
return generate_jwe(
|
203
|
+
json.dumps(jwe.model_dump(mode="json")),
|
204
|
+
CONFIG.security.app_encryption_key,
|
111
205
|
)
|
112
206
|
|
113
207
|
|
@@ -168,20 +262,6 @@ class PrivacyRequest(
|
|
168
262
|
Policy,
|
169
263
|
backref="privacy_requests",
|
170
264
|
)
|
171
|
-
attachments = relationship(
|
172
|
-
Attachment,
|
173
|
-
secondary="attachment_reference",
|
174
|
-
primaryjoin="PrivacyRequest.id == AttachmentReference.reference_id",
|
175
|
-
secondaryjoin="Attachment.id == AttachmentReference.attachment_id",
|
176
|
-
order_by="Attachment.created_at",
|
177
|
-
)
|
178
|
-
comments = relationship(
|
179
|
-
Comment,
|
180
|
-
secondary="comment_reference",
|
181
|
-
primaryjoin="PrivacyRequest.id == CommentReference.reference_id",
|
182
|
-
secondaryjoin="Comment.id == CommentReference.comment_id",
|
183
|
-
order_by="Comment.created_at",
|
184
|
-
)
|
185
265
|
property_id = Column(String, nullable=True)
|
186
266
|
|
187
267
|
cancel_reason = Column(String(200))
|
@@ -1027,51 +1107,6 @@ class PrivacyRequest(
|
|
1027
1107
|
"""Return existing Consent Request Tasks for the current privacy request"""
|
1028
1108
|
return self.request_tasks.filter(RequestTask.action_type == ActionType.consent)
|
1029
1109
|
|
1030
|
-
def get_comment_by_id(self, db: Session, comment_id: str) -> Optional[Comment]:
|
1031
|
-
"""Get the comment associated with the privacy request"""
|
1032
|
-
comment = (
|
1033
|
-
db.query(Comment)
|
1034
|
-
.join(CommentReference, Comment.id == CommentReference.comment_id)
|
1035
|
-
.filter(
|
1036
|
-
CommentReference.reference_id
|
1037
|
-
== self.id, # Ensure the comment is linked to this privacy request
|
1038
|
-
Comment.id == comment_id, # Match the specific comment ID
|
1039
|
-
)
|
1040
|
-
.first()
|
1041
|
-
)
|
1042
|
-
if not comment:
|
1043
|
-
logger.info(
|
1044
|
-
f"Comment with id {comment_id} not found on privacy request {self.id}"
|
1045
|
-
)
|
1046
|
-
return comment
|
1047
|
-
|
1048
|
-
def get_attachment_by_id(
|
1049
|
-
self, db: Session, attachment_id: str
|
1050
|
-
) -> Optional[Attachment]:
|
1051
|
-
"""Get the attachment associated with the privacy request"""
|
1052
|
-
attachment = (
|
1053
|
-
db.query(Attachment)
|
1054
|
-
.join(
|
1055
|
-
AttachmentReference, Attachment.id == AttachmentReference.attachment_id
|
1056
|
-
)
|
1057
|
-
.filter(
|
1058
|
-
AttachmentReference.reference_id == self.id,
|
1059
|
-
Attachment.id == attachment_id,
|
1060
|
-
)
|
1061
|
-
.first()
|
1062
|
-
)
|
1063
|
-
if not attachment:
|
1064
|
-
logger.info(
|
1065
|
-
f"Attachment with id {attachment_id} not found on privacy request {self.id}"
|
1066
|
-
)
|
1067
|
-
return attachment
|
1068
|
-
|
1069
|
-
def delete_attachment_by_id(self, db: Session, attachment_id: str) -> None:
|
1070
|
-
"""Delete the attachment associated with the privacy request"""
|
1071
|
-
attachment = self.get_attachment_by_id(db, attachment_id)
|
1072
|
-
if attachment:
|
1073
|
-
attachment.delete(db)
|
1074
|
-
|
1075
1110
|
def get_existing_request_task(
|
1076
1111
|
self,
|
1077
1112
|
db: Session,
|
@@ -1342,6 +1377,128 @@ class PrivacyRequestNotifications(Base):
|
|
1342
1377
|
notify_after_failures = Column(Integer, nullable=False)
|
1343
1378
|
|
1344
1379
|
|
1380
|
+
class ProvidedIdentityType(EnumType):
|
1381
|
+
"""Enum for privacy request identity types"""
|
1382
|
+
|
1383
|
+
email = "email"
|
1384
|
+
phone_number = "phone_number"
|
1385
|
+
ga_client_id = "ga_client_id"
|
1386
|
+
ljt_readerID = "ljt_readerID"
|
1387
|
+
fides_user_device_id = "fides_user_device_id"
|
1388
|
+
external_id = "external_id"
|
1389
|
+
|
1390
|
+
|
1391
|
+
class ProvidedIdentity(HashMigrationMixin, Base): # pylint: disable=R0904
|
1392
|
+
"""
|
1393
|
+
A table for storing identity fields and values provided at privacy request
|
1394
|
+
creation time.
|
1395
|
+
"""
|
1396
|
+
|
1397
|
+
privacy_request_id = Column(
|
1398
|
+
String,
|
1399
|
+
ForeignKey(
|
1400
|
+
PrivacyRequest.id_field_path, ondelete="CASCADE", onupdate="CASCADE"
|
1401
|
+
),
|
1402
|
+
)
|
1403
|
+
privacy_request = relationship(
|
1404
|
+
PrivacyRequest,
|
1405
|
+
backref="provided_identities",
|
1406
|
+
) # Which privacy request this identity belongs to
|
1407
|
+
|
1408
|
+
field_name = Column(
|
1409
|
+
String,
|
1410
|
+
index=False,
|
1411
|
+
nullable=False,
|
1412
|
+
)
|
1413
|
+
field_label = Column(
|
1414
|
+
String,
|
1415
|
+
index=False,
|
1416
|
+
nullable=True,
|
1417
|
+
)
|
1418
|
+
hashed_value = Column(
|
1419
|
+
String,
|
1420
|
+
index=True,
|
1421
|
+
unique=False,
|
1422
|
+
nullable=True,
|
1423
|
+
) # This field is used as a blind index for exact match searches
|
1424
|
+
encrypted_value = Column(
|
1425
|
+
MutableDict.as_mutable(
|
1426
|
+
StringEncryptedType(
|
1427
|
+
JSONTypeOverride,
|
1428
|
+
CONFIG.security.app_encryption_key,
|
1429
|
+
AesGcmEngine,
|
1430
|
+
"pkcs5",
|
1431
|
+
)
|
1432
|
+
),
|
1433
|
+
nullable=True,
|
1434
|
+
) # Type bytea in the db
|
1435
|
+
consent = relationship(
|
1436
|
+
"Consent", back_populates="provided_identity", cascade="delete, delete-orphan"
|
1437
|
+
)
|
1438
|
+
consent_request = relationship(
|
1439
|
+
"ConsentRequest",
|
1440
|
+
back_populates="provided_identity",
|
1441
|
+
cascade="delete, delete-orphan",
|
1442
|
+
)
|
1443
|
+
|
1444
|
+
@classmethod
|
1445
|
+
def bcrypt_hash_value(
|
1446
|
+
cls,
|
1447
|
+
value: MultiValue,
|
1448
|
+
encoding: str = "UTF-8",
|
1449
|
+
) -> str:
|
1450
|
+
"""
|
1451
|
+
Temporary function used to hash values to the previously used bcrypt hashes.
|
1452
|
+
This can be removed once the bcrypt to SHA-256 migration is complete.
|
1453
|
+
"""
|
1454
|
+
|
1455
|
+
SALT = "$2b$12$UErimNtlsE6qgYf2BrI1Du"
|
1456
|
+
value_str = str(value)
|
1457
|
+
hashed_value = hash_credential_with_salt(
|
1458
|
+
value_str.encode(encoding),
|
1459
|
+
SALT.encode(encoding),
|
1460
|
+
)
|
1461
|
+
return hashed_value
|
1462
|
+
|
1463
|
+
@classmethod
|
1464
|
+
def hash_value(
|
1465
|
+
cls,
|
1466
|
+
value: MultiValue,
|
1467
|
+
encoding: str = "UTF-8",
|
1468
|
+
) -> str:
|
1469
|
+
"""Utility function to hash the value with a generated salt"""
|
1470
|
+
SALT = get_identity_salt()
|
1471
|
+
value_str = str(value)
|
1472
|
+
hashed_value = hash_value_with_salt(
|
1473
|
+
value_str.encode(encoding),
|
1474
|
+
SALT.encode(encoding),
|
1475
|
+
)
|
1476
|
+
return hashed_value
|
1477
|
+
|
1478
|
+
def migrate_hashed_fields(self) -> None:
|
1479
|
+
if value := self.encrypted_value.get("value"):
|
1480
|
+
self.hashed_value = self.hash_value(value)
|
1481
|
+
self.is_hash_migrated = True
|
1482
|
+
|
1483
|
+
def as_identity_schema(self) -> Identity:
|
1484
|
+
"""Creates an Identity schema from a ProvidedIdentity record in the application DB."""
|
1485
|
+
|
1486
|
+
identity_dict = {}
|
1487
|
+
if any(
|
1488
|
+
[
|
1489
|
+
not self.field_name,
|
1490
|
+
not self.encrypted_value,
|
1491
|
+
]
|
1492
|
+
):
|
1493
|
+
return Identity()
|
1494
|
+
|
1495
|
+
value = self.encrypted_value.get("value") # type:ignore
|
1496
|
+
if self.field_label:
|
1497
|
+
value = LabeledIdentity(label=self.field_label, value=value)
|
1498
|
+
identity_dict[self.field_name] = value
|
1499
|
+
return Identity(**identity_dict)
|
1500
|
+
|
1501
|
+
|
1345
1502
|
class CustomPrivacyRequestField(HashMigrationMixin, Base):
|
1346
1503
|
@declared_attr
|
1347
1504
|
def __tablename__(self) -> str:
|
@@ -1444,6 +1601,135 @@ class CustomPrivacyRequestField(HashMigrationMixin, Base):
|
|
1444
1601
|
self.is_hash_migrated = True
|
1445
1602
|
|
1446
1603
|
|
1604
|
+
class Consent(Base):
|
1605
|
+
"""The DB ORM model for Consent."""
|
1606
|
+
|
1607
|
+
provided_identity_id = Column(
|
1608
|
+
String,
|
1609
|
+
ForeignKey(ProvidedIdentity.id),
|
1610
|
+
nullable=False,
|
1611
|
+
)
|
1612
|
+
data_use = Column(String, nullable=False)
|
1613
|
+
data_use_description = Column(String)
|
1614
|
+
opt_in = Column(Boolean, nullable=False)
|
1615
|
+
has_gpc_flag = Column(
|
1616
|
+
Boolean,
|
1617
|
+
server_default="f",
|
1618
|
+
default=False,
|
1619
|
+
nullable=False,
|
1620
|
+
)
|
1621
|
+
conflicts_with_gpc = Column(
|
1622
|
+
Boolean,
|
1623
|
+
server_default="f",
|
1624
|
+
default=False,
|
1625
|
+
nullable=False,
|
1626
|
+
)
|
1627
|
+
|
1628
|
+
provided_identity = relationship(ProvidedIdentity, back_populates="consent")
|
1629
|
+
|
1630
|
+
UniqueConstraint(provided_identity_id, data_use, name="uix_identity_data_use")
|
1631
|
+
|
1632
|
+
identity: Optional[IdentityBase] = None
|
1633
|
+
|
1634
|
+
|
1635
|
+
class ConsentRequest(IdentityVerificationMixin, Base):
|
1636
|
+
"""Tracks consent requests."""
|
1637
|
+
|
1638
|
+
property_id = Column(
|
1639
|
+
String,
|
1640
|
+
nullable=True,
|
1641
|
+
)
|
1642
|
+
provided_identity_id = Column(
|
1643
|
+
String, ForeignKey(ProvidedIdentity.id), nullable=False
|
1644
|
+
)
|
1645
|
+
provided_identity = relationship(
|
1646
|
+
ProvidedIdentity,
|
1647
|
+
back_populates="consent_request",
|
1648
|
+
)
|
1649
|
+
|
1650
|
+
custom_fields = relationship(
|
1651
|
+
CustomPrivacyRequestField, back_populates="consent_request"
|
1652
|
+
)
|
1653
|
+
|
1654
|
+
preferences = Column(
|
1655
|
+
MutableList.as_mutable(JSONB),
|
1656
|
+
nullable=True,
|
1657
|
+
)
|
1658
|
+
|
1659
|
+
identity_verified_at = Column(
|
1660
|
+
DateTime(timezone=True),
|
1661
|
+
nullable=True,
|
1662
|
+
)
|
1663
|
+
|
1664
|
+
source = Column(EnumColumn(PrivacyRequestSource), nullable=True)
|
1665
|
+
|
1666
|
+
privacy_request_id = Column(String, ForeignKey(PrivacyRequest.id), nullable=True)
|
1667
|
+
privacy_request = relationship(PrivacyRequest)
|
1668
|
+
|
1669
|
+
def get_cached_identity_data(self) -> Dict[str, Any]:
|
1670
|
+
"""Retrieves any identity data pertaining to this request from the cache."""
|
1671
|
+
prefix = f"id-{self.id}-identity-*"
|
1672
|
+
cache: FidesopsRedis = get_cache()
|
1673
|
+
keys = cache.keys(prefix)
|
1674
|
+
return {key.split("-")[-1]: cache.get(key) for key in keys}
|
1675
|
+
|
1676
|
+
def verify_identity(
|
1677
|
+
self,
|
1678
|
+
db: Session,
|
1679
|
+
provided_code: Optional[str] = None,
|
1680
|
+
) -> None:
|
1681
|
+
"""
|
1682
|
+
A method to call the internal identity verification method provided by the
|
1683
|
+
`IdentityVerificationMixin`.
|
1684
|
+
"""
|
1685
|
+
self._verify_identity(provided_code=provided_code)
|
1686
|
+
self.identity_verified_at = datetime.utcnow()
|
1687
|
+
self.save(db)
|
1688
|
+
|
1689
|
+
def persist_custom_privacy_request_fields(
|
1690
|
+
self,
|
1691
|
+
db: Session,
|
1692
|
+
custom_privacy_request_fields: Optional[
|
1693
|
+
Dict[str, CustomPrivacyRequestFieldSchema]
|
1694
|
+
],
|
1695
|
+
) -> None:
|
1696
|
+
if not custom_privacy_request_fields:
|
1697
|
+
return
|
1698
|
+
|
1699
|
+
if CONFIG.execution.allow_custom_privacy_request_field_collection:
|
1700
|
+
for key, item in custom_privacy_request_fields.items():
|
1701
|
+
if item.value:
|
1702
|
+
hashed_value = CustomPrivacyRequestField.hash_value(item.value)
|
1703
|
+
CustomPrivacyRequestField.create(
|
1704
|
+
db=db,
|
1705
|
+
data={
|
1706
|
+
"consent_request_id": self.id,
|
1707
|
+
"field_name": key,
|
1708
|
+
"field_label": item.label,
|
1709
|
+
"encrypted_value": {"value": item.value},
|
1710
|
+
"hashed_value": hashed_value,
|
1711
|
+
},
|
1712
|
+
)
|
1713
|
+
else:
|
1714
|
+
logger.info(
|
1715
|
+
"Custom fields provided in consent request {}, but config setting 'CONFIG.execution.allow_custom_privacy_request_field_collection' prevents their storage.",
|
1716
|
+
self.id,
|
1717
|
+
)
|
1718
|
+
|
1719
|
+
def get_persisted_custom_privacy_request_fields(self) -> Dict[str, Any]:
|
1720
|
+
return {
|
1721
|
+
field.field_name: {
|
1722
|
+
"label": field.field_label,
|
1723
|
+
"value": field.encrypted_value["value"],
|
1724
|
+
}
|
1725
|
+
for field in self.custom_fields # type: ignore[attr-defined]
|
1726
|
+
}
|
1727
|
+
|
1728
|
+
|
1729
|
+
# Unique text to separate a step from a collection address, so we can store two values in one.
|
1730
|
+
PAUSED_SEPARATOR = "__fidesops_paused_sep__"
|
1731
|
+
|
1732
|
+
|
1447
1733
|
def cache_action_required(
|
1448
1734
|
cache_key: str,
|
1449
1735
|
step: Optional[CurrentStep] = None,
|
@@ -1487,6 +1773,86 @@ def get_action_required_details(
|
|
1487
1773
|
return None
|
1488
1774
|
|
1489
1775
|
|
1776
|
+
COMPLETED_EXECUTION_LOG_STATUSES = [
|
1777
|
+
ExecutionLogStatus.complete,
|
1778
|
+
ExecutionLogStatus.skipped,
|
1779
|
+
]
|
1780
|
+
EXITED_EXECUTION_LOG_STATUSES = [
|
1781
|
+
ExecutionLogStatus.complete,
|
1782
|
+
ExecutionLogStatus.error,
|
1783
|
+
ExecutionLogStatus.skipped,
|
1784
|
+
]
|
1785
|
+
|
1786
|
+
|
1787
|
+
class ExecutionLog(Base):
|
1788
|
+
"""
|
1789
|
+
Stores the individual execution logs associated with a PrivacyRequest.
|
1790
|
+
|
1791
|
+
Execution logs contain information about the individual queries as they progress through the workflow
|
1792
|
+
generated by the query builder.
|
1793
|
+
"""
|
1794
|
+
|
1795
|
+
connection_key = Column(String, index=True)
|
1796
|
+
# Name of the fides-annotated dataset, for example: my-mongo-db
|
1797
|
+
dataset_name = Column(String, index=True)
|
1798
|
+
# Name of the particular collection or table affected
|
1799
|
+
collection_name = Column(String, index=True)
|
1800
|
+
# A JSON Array describing affected fields along with their data categories and paths
|
1801
|
+
fields_affected = Column(MutableList.as_mutable(JSONB), nullable=True)
|
1802
|
+
# Contains info, warning, or error messages
|
1803
|
+
message = Column(String)
|
1804
|
+
action_type = Column(
|
1805
|
+
EnumColumn(ActionType),
|
1806
|
+
index=True,
|
1807
|
+
nullable=False,
|
1808
|
+
)
|
1809
|
+
status = Column(
|
1810
|
+
EnumColumn(
|
1811
|
+
ExecutionLogStatus,
|
1812
|
+
native_enum=True,
|
1813
|
+
values_callable=lambda x: [
|
1814
|
+
i.value for i in x
|
1815
|
+
], # Using ExecutionLogStatus values in database, even though app is using the names.
|
1816
|
+
),
|
1817
|
+
index=True,
|
1818
|
+
nullable=False,
|
1819
|
+
)
|
1820
|
+
|
1821
|
+
privacy_request_id = Column(
|
1822
|
+
String,
|
1823
|
+
nullable=False,
|
1824
|
+
index=True,
|
1825
|
+
)
|
1826
|
+
|
1827
|
+
# Use clock_timestamp() instead of NOW() to get the actual current time at row creation,
|
1828
|
+
# regardless of transaction state. This prevents timestamp caching within transactions
|
1829
|
+
# and ensures more accurate creation times.
|
1830
|
+
# https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
|
1831
|
+
|
1832
|
+
created_at = Column(
|
1833
|
+
DateTime(timezone=True), server_default=text("clock_timestamp()")
|
1834
|
+
)
|
1835
|
+
updated_at = Column(
|
1836
|
+
DateTime(timezone=True),
|
1837
|
+
server_default=text("clock_timestamp()"),
|
1838
|
+
onupdate=text("clock_timestamp()"),
|
1839
|
+
)
|
1840
|
+
|
1841
|
+
|
1842
|
+
def can_run_checkpoint(
|
1843
|
+
request_checkpoint: CurrentStep, from_checkpoint: Optional[CurrentStep] = None
|
1844
|
+
) -> bool:
|
1845
|
+
"""Determine whether we should run a specific checkpoint in privacy request execution
|
1846
|
+
|
1847
|
+
If there's no from_checkpoint specified we should always run the current checkpoint.
|
1848
|
+
"""
|
1849
|
+
if not from_checkpoint:
|
1850
|
+
return True
|
1851
|
+
return EXECUTION_CHECKPOINTS.index(
|
1852
|
+
request_checkpoint
|
1853
|
+
) >= EXECUTION_CHECKPOINTS.index(from_checkpoint)
|
1854
|
+
|
1855
|
+
|
1490
1856
|
def _parse_cache_to_checkpoint_action_required(
|
1491
1857
|
cache: dict[str, Any]
|
1492
1858
|
) -> CheckpointActionRequired:
|
@@ -1508,3 +1874,246 @@ def _parse_cache_to_checkpoint_action_required(
|
|
1508
1874
|
collection=collection,
|
1509
1875
|
action_needed=action_needed,
|
1510
1876
|
)
|
1877
|
+
|
1878
|
+
|
1879
|
+
class TraversalDetails(FidesSchema):
|
1880
|
+
"""Schema to format saving pre-calculated traversal details on RequestTask.traversal_details"""
|
1881
|
+
|
1882
|
+
dataset_connection_key: str
|
1883
|
+
incoming_edges: List[Tuple[str, str]]
|
1884
|
+
outgoing_edges: List[Tuple[str, str]]
|
1885
|
+
input_keys: List[str]
|
1886
|
+
skipped_nodes: Optional[List[Tuple[str, str]]] = None
|
1887
|
+
|
1888
|
+
# TODO: remove this method once we support custom request fields in DSR graph.
|
1889
|
+
@classmethod
|
1890
|
+
def create_empty_traversal(cls, connection_key: str) -> TraversalDetails:
|
1891
|
+
"""
|
1892
|
+
Creates an "empty" TraversalDetails object that only has the dataset connection key set.
|
1893
|
+
This is a bit of a hacky workaround needed to implement the Dynamic Erasure Emails feature,
|
1894
|
+
but we should no longer need it once the custom_request_fields are included in our graph
|
1895
|
+
traversal
|
1896
|
+
"""
|
1897
|
+
return cls(
|
1898
|
+
dataset_connection_key=connection_key,
|
1899
|
+
incoming_edges=[],
|
1900
|
+
outgoing_edges=[],
|
1901
|
+
input_keys=[],
|
1902
|
+
skipped_nodes=[],
|
1903
|
+
)
|
1904
|
+
|
1905
|
+
|
1906
|
+
class RequestTask(Base):
|
1907
|
+
"""
|
1908
|
+
An individual Task for a Privacy Request.
|
1909
|
+
|
1910
|
+
When we execute a PrivacyRequest, we build a graph by combining the current datasets with the identity data
|
1911
|
+
and we save the nodes (collections) in the graph as Request Tasks.
|
1912
|
+
|
1913
|
+
Currently, we build access, erasure, and consent Request Tasks.
|
1914
|
+
"""
|
1915
|
+
|
1916
|
+
privacy_request_id = Column(
|
1917
|
+
String,
|
1918
|
+
ForeignKey(PrivacyRequest.id_field_path, ondelete="SET NULL"),
|
1919
|
+
nullable=True,
|
1920
|
+
index=True,
|
1921
|
+
)
|
1922
|
+
|
1923
|
+
# Identifiers of this request task
|
1924
|
+
collection_address = Column(
|
1925
|
+
String, nullable=False, index=True
|
1926
|
+
) # Of the format dataset_name:collection_name for convenience
|
1927
|
+
dataset_name = Column(String, nullable=False, index=True)
|
1928
|
+
collection_name = Column(String, nullable=False, index=True)
|
1929
|
+
action_type = Column(EnumColumn(ActionType), nullable=False, index=True)
|
1930
|
+
|
1931
|
+
# Note that RequestTasks share statuses with ExecutionLogs. When a RequestTask changes state, an ExecutionLog
|
1932
|
+
# is also created with that state. These are tied tightly together in GraphTask.
|
1933
|
+
status = Column(
|
1934
|
+
EnumColumn(
|
1935
|
+
ExecutionLogStatus,
|
1936
|
+
native_enum=False,
|
1937
|
+
values_callable=lambda x: [
|
1938
|
+
i.value for i in x
|
1939
|
+
], # Using ExecutionLogStatus values in database, even though app is using the names.
|
1940
|
+
), # character varying in database
|
1941
|
+
index=True,
|
1942
|
+
nullable=False,
|
1943
|
+
)
|
1944
|
+
|
1945
|
+
upstream_tasks = Column(
|
1946
|
+
MutableList.as_mutable(JSONB)
|
1947
|
+
) # List of collection address strings
|
1948
|
+
downstream_tasks = Column(
|
1949
|
+
MutableList.as_mutable(JSONB)
|
1950
|
+
) # List of collection address strings
|
1951
|
+
all_descendant_tasks = Column(
|
1952
|
+
MutableList.as_mutable(JSONB)
|
1953
|
+
) # All tasks that can be reached by the current task. This is useful when this task fails,
|
1954
|
+
# and we can mark every single one of these as failed.
|
1955
|
+
|
1956
|
+
# Raw data retrieved from an access request is stored here. This contains all of the
|
1957
|
+
# intermediate data we retrieved, needed for downstream tasks, but hasn't been filtered
|
1958
|
+
# by data category for the end user.
|
1959
|
+
access_data = Column( # An encrypted JSON String - saved as a list of Rows
|
1960
|
+
StringEncryptedType(
|
1961
|
+
type_in=JSONTypeOverride,
|
1962
|
+
key=CONFIG.security.app_encryption_key,
|
1963
|
+
engine=AesGcmEngine,
|
1964
|
+
padding="pkcs5",
|
1965
|
+
),
|
1966
|
+
)
|
1967
|
+
|
1968
|
+
# This is the raw access data saved in erasure format (with placeholders preserved) to perform a masking request.
|
1969
|
+
# First saved on the access node, and then copied to the corresponding erasure node.
|
1970
|
+
data_for_erasures = Column( # An encrypted JSON String - saved as a list of rows
|
1971
|
+
StringEncryptedType(
|
1972
|
+
type_in=JSONTypeOverride,
|
1973
|
+
key=CONFIG.security.app_encryption_key,
|
1974
|
+
engine=AesGcmEngine,
|
1975
|
+
padding="pkcs5",
|
1976
|
+
),
|
1977
|
+
)
|
1978
|
+
|
1979
|
+
# Written after an erasure is completed
|
1980
|
+
rows_masked = Column(Integer)
|
1981
|
+
# Written after a consent request is completed - not all consent
|
1982
|
+
# connectors will end up sending a request
|
1983
|
+
consent_sent = Column(Boolean)
|
1984
|
+
|
1985
|
+
# For async tasks awaiting callback
|
1986
|
+
callback_succeeded = Column(Boolean)
|
1987
|
+
|
1988
|
+
# Stores a serialized collection that can be transformed back into a Collection to help
|
1989
|
+
# execute the current task
|
1990
|
+
collection = Column(MutableDict.as_mutable(JSONB))
|
1991
|
+
# Stores key details from traversal.traverse in the format of TraversalDetails
|
1992
|
+
traversal_details = Column(MutableDict.as_mutable(JSONB))
|
1993
|
+
|
1994
|
+
privacy_request: RelationshipProperty[PrivacyRequest] = relationship(
|
1995
|
+
"PrivacyRequest",
|
1996
|
+
back_populates="request_tasks",
|
1997
|
+
uselist=False,
|
1998
|
+
)
|
1999
|
+
|
2000
|
+
@property
|
2001
|
+
def request_task_address(self) -> CollectionAddress:
|
2002
|
+
"""Convert the collection_address into Collection Address format"""
|
2003
|
+
return CollectionAddress.from_string(self.collection_address)
|
2004
|
+
|
2005
|
+
@property
|
2006
|
+
def is_root_task(self) -> bool:
|
2007
|
+
"""Convenience helper for asserting whether the task is a root task"""
|
2008
|
+
return self.request_task_address == ROOT_COLLECTION_ADDRESS
|
2009
|
+
|
2010
|
+
@property
|
2011
|
+
def is_terminator_task(self) -> bool:
|
2012
|
+
"""Convenience helper for asserting whether the task is a terminator task"""
|
2013
|
+
return self.request_task_address == TERMINATOR_ADDRESS
|
2014
|
+
|
2015
|
+
def get_cached_task_id(self) -> Optional[str]:
|
2016
|
+
"""Gets the cached celery task ID for this request task."""
|
2017
|
+
cache: FidesopsRedis = get_cache()
|
2018
|
+
task_id = cache.get(get_async_task_tracking_cache_key(self.id))
|
2019
|
+
return task_id
|
2020
|
+
|
2021
|
+
def get_access_data(self) -> List[Row]:
|
2022
|
+
"""Helper to retrieve access data or default to empty list"""
|
2023
|
+
return self.access_data or []
|
2024
|
+
|
2025
|
+
def get_data_for_erasures(self) -> List[Row]:
|
2026
|
+
"""Helper to retrieve erasure data needed to build masking requests or default to empty list"""
|
2027
|
+
return self.data_for_erasures or []
|
2028
|
+
|
2029
|
+
def update_status(self, db: Session, status: ExecutionLogStatus) -> None:
|
2030
|
+
"""Helper method to update a task's status"""
|
2031
|
+
self.status = status
|
2032
|
+
self.save(db)
|
2033
|
+
|
2034
|
+
def get_tasks_with_same_action_type(
|
2035
|
+
self, db: Session, collection_address_str: str
|
2036
|
+
) -> Query:
|
2037
|
+
"""Fetch task on the same privacy request and action type as current by collection address"""
|
2038
|
+
return db.query(RequestTask).filter(
|
2039
|
+
RequestTask.privacy_request_id == self.privacy_request_id,
|
2040
|
+
RequestTask.action_type == self.action_type,
|
2041
|
+
RequestTask.collection_address == collection_address_str,
|
2042
|
+
)
|
2043
|
+
|
2044
|
+
def get_pending_downstream_tasks(self, db: Session) -> Query:
|
2045
|
+
"""Returns the immediate downstream task objects that are still pending"""
|
2046
|
+
return db.query(RequestTask).filter(
|
2047
|
+
RequestTask.privacy_request_id == self.privacy_request_id,
|
2048
|
+
RequestTask.action_type == self.action_type,
|
2049
|
+
RequestTask.collection_address.in_(self.downstream_tasks or []),
|
2050
|
+
RequestTask.status == ExecutionLogStatus.pending,
|
2051
|
+
)
|
2052
|
+
|
2053
|
+
def can_queue_request_task(self, db: Session, should_log: bool = False) -> bool:
|
2054
|
+
"""Returns True if upstream tasks are complete and the current Request Task
|
2055
|
+
is not running in another celery task.
|
2056
|
+
|
2057
|
+
This check ignores its database status - that is checked elsewhere.
|
2058
|
+
"""
|
2059
|
+
return self.upstream_tasks_complete(
|
2060
|
+
db, should_log
|
2061
|
+
) and not self.request_task_running(should_log)
|
2062
|
+
|
2063
|
+
def upstream_tasks_complete(self, db: Session, should_log: bool = False) -> bool:
|
2064
|
+
"""Determines if all of the upstream tasks of the current task are complete"""
|
2065
|
+
upstream_tasks: Query = self.upstream_tasks_objects(db)
|
2066
|
+
tasks_complete: bool = all(
|
2067
|
+
upstream_task.status in COMPLETED_EXECUTION_LOG_STATUSES
|
2068
|
+
for upstream_task in upstream_tasks
|
2069
|
+
) and upstream_tasks.count() == len(self.upstream_tasks or [])
|
2070
|
+
|
2071
|
+
if not tasks_complete and should_log:
|
2072
|
+
logger.debug(
|
2073
|
+
"Upstream tasks incomplete for {} task {}.",
|
2074
|
+
self.action_type.value,
|
2075
|
+
self.collection_address,
|
2076
|
+
)
|
2077
|
+
|
2078
|
+
return tasks_complete
|
2079
|
+
|
2080
|
+
def upstream_tasks_objects(self, db: Session) -> Query:
|
2081
|
+
"""Returns Request Task objects that are upstream of the current Request Task"""
|
2082
|
+
upstream_tasks: Query = db.query(RequestTask).filter(
|
2083
|
+
RequestTask.privacy_request_id == self.privacy_request_id,
|
2084
|
+
RequestTask.collection_address.in_(self.upstream_tasks or []),
|
2085
|
+
RequestTask.action_type == self.action_type,
|
2086
|
+
)
|
2087
|
+
return upstream_tasks
|
2088
|
+
|
2089
|
+
def request_task_running(self, should_log: bool = False) -> bool:
|
2090
|
+
"""Returns a rough measure if the Request Task is already running -
|
2091
|
+
not 100% accurate.
|
2092
|
+
|
2093
|
+
This is further only applicable if you are running workers and
|
2094
|
+
CONFIG.execution.task_always_eager=False. This is just an extra check to reduce possible
|
2095
|
+
over-scheduling, but it is also okay if the same node runs multiple times.
|
2096
|
+
"""
|
2097
|
+
celery_task_id: Optional[str] = self.get_cached_task_id()
|
2098
|
+
if not celery_task_id:
|
2099
|
+
return False
|
2100
|
+
|
2101
|
+
if should_log:
|
2102
|
+
logger.debug(
|
2103
|
+
"Celery Task ID {} found for {} task {}.",
|
2104
|
+
celery_task_id,
|
2105
|
+
self.action_type.value,
|
2106
|
+
self.collection_address,
|
2107
|
+
)
|
2108
|
+
|
2109
|
+
task_in_flight: bool = celery_tasks_in_flight([celery_task_id])
|
2110
|
+
|
2111
|
+
if task_in_flight and should_log:
|
2112
|
+
logger.debug(
|
2113
|
+
"Celery Task {} already processing for {} task {}.",
|
2114
|
+
celery_task_id,
|
2115
|
+
self.action_type.value,
|
2116
|
+
self.collection_address,
|
2117
|
+
)
|
2118
|
+
|
2119
|
+
return task_in_flight
|