ethyca-fides 2.69.1b2__py2.py3-none-any.whl → 2.69.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.69.1b2.dist-info → ethyca_fides-2.69.1rc0.dist-info}/METADATA +2 -2
- {ethyca_fides-2.69.1b2.dist-info → ethyca_fides-2.69.1rc0.dist-info}/RECORD +204 -197
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/78dbe23d8204_adding_privacy_request_redaction_patterns.py +52 -0
- fides/api/api/v1/api.py +2 -0
- fides/api/api/v1/endpoints/oauth_endpoints.py +18 -6
- fides/api/api/v1/endpoints/privacy_request_endpoints.py +5 -3
- fides/api/api/v1/endpoints/privacy_request_redaction_patterns_endpoints.py +95 -0
- fides/api/api/v1/endpoints/user_endpoints.py +26 -3
- fides/api/db/base.py +3 -0
- fides/api/models/client.py +1 -0
- fides/api/models/privacy_request_redaction_pattern.py +64 -0
- fides/api/oauth/utils.py +117 -6
- fides/api/schemas/privacy_request_redaction_patterns.py +55 -0
- fides/api/service/privacy_request/dsr_package/dsr_data_preprocessor.py +231 -0
- fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +286 -120
- fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +4 -2
- fides/api/service/privacy_request/dsr_package/templates/collection_index.html +3 -1
- fides/api/service/privacy_request/dsr_package/templates/dataset_index.html +1 -1
- fides/api/service/privacy_request/dsr_package/utils.py +268 -0
- fides/api/service/privacy_request/request_runner_service.py +8 -2
- fides/api/service/privacy_request/request_service.py +1 -1
- fides/api/service/storage/streaming/smart_open_streaming_storage.py +107 -170
- fides/api/service/storage/util.py +579 -0
- fides/api/task/graph_runners.py +32 -2
- fides/api/task/graph_task.py +2 -4
- fides/api/task/manual/manual_task_graph_task.py +11 -9
- fides/api/tasks/storage.py +2 -2
- fides/common/api/scope_registry.py +8 -0
- fides/common/api/v1/urn_registry.py +3 -0
- fides/config/execution_settings.py +4 -0
- fides/config/security_settings.py +1 -4
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/OmXHlY9MvjoZH9jDkAytl/_buildManifest.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4831-fd99c0b3784de128.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{5258-e880b606a2293803.js → 5258-b0de22a8521686ab.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-ef8e1c986bc5b795.js → _app-fcdad91f6f66292b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/privacy-requests-2ecc073f41628f62.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-de8cb3739ab99c09.js → new-92f52c43f522a350.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-05d61c80a556b2d5.js → [id]-64452dfae2c5e614.js} +1 -1
- 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/[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/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/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/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/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.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 -0
- 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/task/scheduler_utils.py +0 -39
- fides/ui-build/static/admin/_next/static/0agWtBSaxTBxQfxPA99Ra/_buildManifest.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4121-c8d5d717e31899e1.js +0 -1
- {ethyca_fides-2.69.1b2.dist-info → ethyca_fides-2.69.1rc0.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.69.1b2.dist-info → ethyca_fides-2.69.1rc0.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.69.1b2.dist-info → ethyca_fides-2.69.1rc0.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.69.1b2.dist-info → ethyca_fides-2.69.1rc0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{0agWtBSaxTBxQfxPA99Ra → OmXHlY9MvjoZH9jDkAytl}/_ssgManifest.js +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{1817-3d9e110e007853f0.js → 1817-0ca16d288fad916d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3620-31ebb43dba84cbbd.js → 3620-602eb74dc896d556.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3729-a1ca1608efc11ac4.js → 3729-c17ac8031a4c4fd1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3872-a91143aa35fa8ef8.js → 3872-f78dec02f0d959ae.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{4608-23bbd4c3c4a59f42.js → 4608-be8cba73f5d7c326.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{4786-0827aae7aceadd22.js → 4786-61154adf88e448e1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{4808-78ca630f2d2503cd.js → 4808-dd4157aa72648068.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5487-8c635883dcaa9c2a.js → 5487-02d00bad7c6830e0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6084-0096d7de64ef8015.js → 6084-c153669d5567e242.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6954-9d46e2276c461c26.js → 6954-5296188c19d7d0ac.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7476-d1b0af9ade392e5b.js → 7476-45c5088baa8b66af.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7630-da0a7ce4e3a0d62c.js → 7630-7ed6c6117775dffe.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{787-3499983fa346b380.js → 787-a8c7eab617e2fceb.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{79-f197fc4db8d530e5.js → 79-65674011d455af4d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{796-db1e30119ea973c7.js → 796-9e1ca1a4030707c5.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{8002-971e29181f72edd1.js → 8002-24af20d679efc04e.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9826-b0b3d3cfb13bfbc1.js → 9826-dbae8dee941a7fac.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-9dc7e70ab5b05723.js → manual-ace203dfacacbdc4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-4b79a1652297ed9a.js → multiple-920fb469e0dda1d2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-1632a59203fe8eab.js → add-systems-bd0d82078e67cac3.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-1ca9df7ca91bd101.js → add-vendors-406170eaae4329c6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-07bdbc9ae4137db4.js → configure-7207ab23bdb36ce8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-2795cd4115a77c94.js → privacy-experience-9dda4de5ec580279.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-e02921dc82dccbb1.js → [id]-b378576cba255609.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-98f9e4ba3610628a.js → new-2ca1de7b88094ab0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-17ed82777810d1c6.js → privacy-notices-0d4844d0b808e6e4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-09610b10923d9268.js → consent-3e8bdefe714254ec.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-da1a48336daff6f8.js → [resourceUrn]-2c29ff7a01198f30.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-d8e776f1e64e4ba8.js → [projectUrn]-04cfe2cfba7b7cd8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-75b9629b0d9cdf96.js → projects-5f2d7b24804f861f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-470da05db63767cd.js → [resourceUrn]-8eb581024bc0172f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-6c3714ee97a718c1.js → resources-de704de849960f01.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-6984c033b8fe3a13.js → data-catalog-30108b00ac769fc3.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-2f0a33ef9ba1f1da.js → [systemId]-e1ba213fb666b3f4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-e9d4f25b20ff6781.js → [monitorId]-6d133580045abdda.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-9c428d3ef0985915.js → action-center-9a81d42a474e1e48.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-c3a97e6721ca0abe.js → [resourceUrn]-8f736b078e9842da.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-a0a7de552ef71f5b.js → detection-eb814e3c22807871.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-109754fec0755339.js → [resourceUrn]-6875b7783fcfda2f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-88654783b06b3b21.js → discovery-172dbd7740e212ca.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-89136e6800dc9369.js → datamap-c7390e046b2e2b7f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-8f58192dcb54883d.js → [...subfieldNames]-dfd71c1e9c458b89.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-dcb4ab380a77aa1e.js → [collectionName]-7cdc42ec5493b83d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-6f16d43071fb9c11.js → [datasetId]-e12b11ba15bc3fc1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-97f06e21580f1f6a.js → new-e32fccc4ca520d2b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-674bb3940f088ecc.js → dataset-7c59a6abf6ba6207.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-6f77d8647fca71e0.js → [id]-927b7e476c4b47d0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-821dd1269834cfa2.js → new-cbe100d50df34285.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-23e4caf79faa8106.js → datastore-connection-cce20440b177050b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{index-23eb64eed81dcb69.js → index-6cd8708106331b8d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-3a4cd3fe9094fba3.js → [id]-4c3c413a2668df53.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-57e618d7b16ac69a.js → integrations-95402b5001c07ef2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-c9a323eb6a929476.js → [id]-3c6dc2f6e6bae960.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-b9bb09e46921a590.js → add-template-4a6d4023a7791be8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-82c631a12b5a008c.js → messaging-76b204c9b98d656f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-38360083348c3d6c.js → table-migration-48500551fd6a7602.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-0d0bb9eb004a3336.js → [id]-0f25a76dd18c5e20.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{messaging-f9320a58f489f5b7.js → messaging-ad6ad3e5bd72765d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-d0cfa8aeddd43a40.js → storage-6032d82f0fc2893d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-72ca94ec5ed85733.js → configure-d83e5bd52a638234.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-5a5edc8a4aa7c30a.js → privacy-requests-baf31c3e4b081046.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-5ec775c4904fdbfe.js → [id]-e784c05d056b2371.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-a6812c0916f2949e.js → add-property-0a7a2db148a7561a.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-3e72e9f91991c119.js → alpha-a82f3df840d5c1b5.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-6aab092f4871cecb.js → about-d06fb16487705b9d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-be47008304106395.js → consent-93a978443bf299db.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-ae1b57589da7b175.js → custom-fields-9ecb803099082bf4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-23a6d7a921150188.js → domain-records-16fdd91a81074dd1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-2a9e8859ab4d9de6.js → domains-4cdd6001e7cb9aee.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-4f9f0fdf9925ae90.js → email-templates-1914de830ce5cfc4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-46f7af35cee4a8bb.js → locations-2e635dcd11b78224.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-a596a96cb8d0aa8e.js → organization-f547f1f33c12faf3.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-6ed5fc2410e00857.js → regulations-7c02e469d8c5bd74.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-86811e3cda277e77.js → test-datasets-20b1193ed76c56b0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-5a43f108d8047d5b.js → [id]-6e15332935f6b538.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{systems-045a841e22e85ea8.js → systems-fbc8761ef4d55516.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-1b3f2d4bcb0e164d.js → taxonomy-4d7827fc9c46b6b8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-2cab41659f1ee7da.js → user-management-9cec020f89544426.js} +0 -0
fides/_version.py
CHANGED
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-09-
|
|
11
|
+
"date": "2025-09-03T19:56:24+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "2.69.
|
|
14
|
+
"full-revisionid": "792b5cd4c5cb409b37e792476fd5b9d15be7ddb6",
|
|
15
|
+
"version": "2.69.1rc0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
fides/api/alembic/migrations/versions/78dbe23d8204_adding_privacy_request_redaction_patterns.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""adding privacy requeste redaction patterns
|
|
2
|
+
|
|
3
|
+
Revision ID: 78dbe23d8204
|
|
4
|
+
Revises: b1a2c3d4e5f6
|
|
5
|
+
Create Date: 2025-08-30 05:40:17.816172
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
from alembic import op
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = "78dbe23d8204"
|
|
14
|
+
down_revision = "b1a2c3d4e5f6"
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade():
|
|
20
|
+
op.create_table(
|
|
21
|
+
"privacy_request_redaction_pattern",
|
|
22
|
+
sa.Column("id", sa.String(length=255), nullable=False),
|
|
23
|
+
sa.Column(
|
|
24
|
+
"created_at",
|
|
25
|
+
sa.DateTime(timezone=True),
|
|
26
|
+
server_default=sa.text("now()"),
|
|
27
|
+
nullable=True,
|
|
28
|
+
),
|
|
29
|
+
sa.Column(
|
|
30
|
+
"updated_at",
|
|
31
|
+
sa.DateTime(timezone=True),
|
|
32
|
+
server_default=sa.text("now()"),
|
|
33
|
+
nullable=True,
|
|
34
|
+
),
|
|
35
|
+
sa.Column("pattern", sa.String(), nullable=False),
|
|
36
|
+
sa.PrimaryKeyConstraint("id"),
|
|
37
|
+
sa.UniqueConstraint("pattern"),
|
|
38
|
+
)
|
|
39
|
+
op.create_index(
|
|
40
|
+
op.f("ix_privacy_request_redaction_pattern_id"),
|
|
41
|
+
"privacy_request_redaction_pattern",
|
|
42
|
+
["id"],
|
|
43
|
+
unique=False,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def downgrade():
|
|
48
|
+
op.drop_index(
|
|
49
|
+
op.f("ix_privacy_request_redaction_pattern_id"),
|
|
50
|
+
table_name="privacy_request_redaction_pattern",
|
|
51
|
+
)
|
|
52
|
+
op.drop_table("privacy_request_redaction_pattern")
|
fides/api/api/v1/api.py
CHANGED
|
@@ -17,6 +17,7 @@ from fides.api.api.v1.endpoints import (
|
|
|
17
17
|
policy_webhook_endpoints,
|
|
18
18
|
pre_approval_webhook_endpoints,
|
|
19
19
|
privacy_request_endpoints,
|
|
20
|
+
privacy_request_redaction_patterns_endpoints,
|
|
20
21
|
registration_endpoints,
|
|
21
22
|
saas_config_endpoints,
|
|
22
23
|
storage_endpoints,
|
|
@@ -41,6 +42,7 @@ api_router.include_router(policy_endpoints.router)
|
|
|
41
42
|
api_router.include_router(policy_webhook_endpoints.router)
|
|
42
43
|
api_router.include_router(pre_approval_webhook_endpoints.router)
|
|
43
44
|
api_router.include_router(privacy_request_endpoints.router)
|
|
45
|
+
api_router.include_router(privacy_request_redaction_patterns_endpoints.router)
|
|
44
46
|
api_router.include_router(identity_verification_endpoints.router)
|
|
45
47
|
api_router.include_router(storage_endpoints.router)
|
|
46
48
|
api_router.include_router(messaging_endpoints.router)
|
|
@@ -8,8 +8,8 @@ from loguru import logger
|
|
|
8
8
|
from sqlalchemy.orm import Session
|
|
9
9
|
from starlette.status import (
|
|
10
10
|
HTTP_400_BAD_REQUEST,
|
|
11
|
+
HTTP_403_FORBIDDEN,
|
|
11
12
|
HTTP_404_NOT_FOUND,
|
|
12
|
-
HTTP_422_UNPROCESSABLE_ENTITY,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
from fides.api.api.deps import get_db
|
|
@@ -26,7 +26,7 @@ from fides.api.models.client import ClientDetail
|
|
|
26
26
|
from fides.api.models.connectionconfig import ConnectionConfig, ConnectionTestStatus
|
|
27
27
|
from fides.api.models.fides_user import FidesUser
|
|
28
28
|
from fides.api.oauth.roles import ROLES_TO_SCOPES_MAPPING
|
|
29
|
-
from fides.api.oauth.utils import verify_oauth_client
|
|
29
|
+
from fides.api.oauth.utils import verify_client_can_assign_scopes, verify_oauth_client
|
|
30
30
|
from fides.api.schemas.client import ClientCreatedResponse
|
|
31
31
|
from fides.api.schemas.oauth import AccessToken, OAuth2ClientCredentialsRequestForm
|
|
32
32
|
from fides.api.service.authentication.authentication_strategy import (
|
|
@@ -123,22 +123,28 @@ async def acquire_access_token(
|
|
|
123
123
|
|
|
124
124
|
@router.post(
|
|
125
125
|
CLIENT,
|
|
126
|
-
dependencies=[Security(verify_oauth_client, scopes=[CLIENT_CREATE])],
|
|
127
126
|
response_model=ClientCreatedResponse,
|
|
128
127
|
)
|
|
129
128
|
def create_client(
|
|
130
129
|
*,
|
|
130
|
+
request: Request,
|
|
131
131
|
db: Session = Depends(get_db),
|
|
132
132
|
scopes: List[str] = Body([]),
|
|
133
|
+
requesting_client: ClientDetail = Security(
|
|
134
|
+
verify_oauth_client, scopes=[CLIENT_CREATE]
|
|
135
|
+
),
|
|
133
136
|
) -> ClientCreatedResponse:
|
|
134
137
|
"""Creates a new client and returns the credentials. Only direct scopes can be added to the client via this endpoint."""
|
|
135
138
|
logger.info("Creating new client")
|
|
136
139
|
if not all(scope in SCOPE_REGISTRY for scope in scopes):
|
|
137
140
|
raise HTTPException(
|
|
138
|
-
status_code=
|
|
141
|
+
status_code=HTTP_403_FORBIDDEN,
|
|
139
142
|
detail=f"Invalid Scope. Scopes must be one of {SCOPE_REGISTRY}.",
|
|
140
143
|
)
|
|
141
144
|
|
|
145
|
+
# Security check: Verify that the requesting client has all the scopes they're trying to assign
|
|
146
|
+
verify_client_can_assign_scopes(request, requesting_client, scopes, db)
|
|
147
|
+
|
|
142
148
|
client, secret = ClientDetail.create_client_and_secret(
|
|
143
149
|
db,
|
|
144
150
|
CONFIG.security.oauth_client_id_length_bytes,
|
|
@@ -180,13 +186,16 @@ def get_client_scopes(client_id: str, db: Session = Depends(get_db)) -> List[str
|
|
|
180
186
|
|
|
181
187
|
@router.put(
|
|
182
188
|
CLIENT_SCOPE,
|
|
183
|
-
dependencies=[Security(verify_oauth_client, scopes=[CLIENT_UPDATE])],
|
|
184
189
|
response_model=None,
|
|
185
190
|
)
|
|
186
191
|
def set_client_scopes(
|
|
187
192
|
client_id: str,
|
|
188
193
|
scopes: List[str],
|
|
194
|
+
request: Request,
|
|
189
195
|
db: Session = Depends(get_db),
|
|
196
|
+
requesting_client: ClientDetail = Security(
|
|
197
|
+
verify_oauth_client, scopes=[CLIENT_UPDATE]
|
|
198
|
+
),
|
|
190
199
|
) -> None:
|
|
191
200
|
"""Overwrites the client's directly-assigned scopes with those provided.
|
|
192
201
|
Roles cannot be edited via this endpoint.
|
|
@@ -197,10 +206,13 @@ def set_client_scopes(
|
|
|
197
206
|
|
|
198
207
|
if not all(elem in SCOPE_REGISTRY for elem in scopes):
|
|
199
208
|
raise HTTPException(
|
|
200
|
-
status_code=
|
|
209
|
+
status_code=HTTP_403_FORBIDDEN,
|
|
201
210
|
detail=f"Invalid Scope. Scopes must be one of {SCOPE_REGISTRY}.",
|
|
202
211
|
)
|
|
203
212
|
|
|
213
|
+
# Security check: Verify that the requesting client has all the scopes they're trying to assign
|
|
214
|
+
verify_client_can_assign_scopes(request, requesting_client, scopes, db)
|
|
215
|
+
|
|
204
216
|
logger.info("Updating client scopes")
|
|
205
217
|
client.update(db, data={"scopes": scopes})
|
|
206
218
|
|
|
@@ -1667,15 +1667,17 @@ def privacy_request_data_transfer(
|
|
|
1667
1667
|
detail=f"Rule key {rule_key} not found",
|
|
1668
1668
|
)
|
|
1669
1669
|
|
|
1670
|
-
|
|
1671
|
-
|
|
1670
|
+
value_dict: Dict[str, Optional[List[Row]]] = cache.get_encoded_objects_by_prefix(
|
|
1671
|
+
f"{privacy_request_id}__access_request"
|
|
1672
1672
|
)
|
|
1673
1673
|
|
|
1674
|
-
if not
|
|
1674
|
+
if not value_dict:
|
|
1675
1675
|
raise HTTPException(
|
|
1676
1676
|
status_code=HTTP_404_NOT_FOUND,
|
|
1677
1677
|
detail=f"No access request information found for privacy request id {privacy_request_id}",
|
|
1678
1678
|
)
|
|
1679
|
+
|
|
1680
|
+
access_result = {k.split("__")[-1]: v for k, v in value_dict.items()}
|
|
1679
1681
|
datasets = DatasetConfig.all(db=db)
|
|
1680
1682
|
if not datasets:
|
|
1681
1683
|
raise HTTPException(
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from fastapi import Depends, HTTPException, Security
|
|
2
|
+
from fastapi.routing import APIRouter
|
|
3
|
+
from loguru import logger
|
|
4
|
+
from sqlalchemy.orm import Session
|
|
5
|
+
from starlette.status import HTTP_200_OK
|
|
6
|
+
|
|
7
|
+
from fides.api.api.deps import get_db
|
|
8
|
+
from fides.api.models.privacy_request_redaction_pattern import (
|
|
9
|
+
PrivacyRequestRedactionPattern,
|
|
10
|
+
)
|
|
11
|
+
from fides.api.oauth.utils import verify_oauth_client
|
|
12
|
+
from fides.api.schemas.privacy_request_redaction_patterns import (
|
|
13
|
+
PrivacyRequestRedactionPatternsRequest,
|
|
14
|
+
PrivacyRequestRedactionPatternsResponse,
|
|
15
|
+
)
|
|
16
|
+
from fides.common.api.scope_registry import (
|
|
17
|
+
PRIVACY_REQUEST_REDACTION_PATTERNS_READ,
|
|
18
|
+
PRIVACY_REQUEST_REDACTION_PATTERNS_UPDATE,
|
|
19
|
+
)
|
|
20
|
+
from fides.common.api.v1.urn_registry import (
|
|
21
|
+
PRIVACY_REQUEST_REDACTION_PATTERNS,
|
|
22
|
+
V1_URL_PREFIX,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
router = APIRouter(
|
|
26
|
+
tags=["Privacy Request Redaction Patterns"],
|
|
27
|
+
prefix=V1_URL_PREFIX,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.get(
|
|
32
|
+
PRIVACY_REQUEST_REDACTION_PATTERNS,
|
|
33
|
+
status_code=HTTP_200_OK,
|
|
34
|
+
response_model=PrivacyRequestRedactionPatternsResponse,
|
|
35
|
+
dependencies=[
|
|
36
|
+
Security(verify_oauth_client, scopes=[PRIVACY_REQUEST_REDACTION_PATTERNS_READ])
|
|
37
|
+
],
|
|
38
|
+
)
|
|
39
|
+
def get_privacy_request_redaction_patterns(
|
|
40
|
+
*, db: Session = Depends(get_db)
|
|
41
|
+
) -> PrivacyRequestRedactionPatternsResponse:
|
|
42
|
+
"""
|
|
43
|
+
Get the current privacy request redaction patterns configuration.
|
|
44
|
+
|
|
45
|
+
Returns the list of regex patterns used to mask dataset, collection,
|
|
46
|
+
and field names in privacy request package reports.
|
|
47
|
+
"""
|
|
48
|
+
logger.info("Getting privacy request redaction patterns configuration")
|
|
49
|
+
|
|
50
|
+
patterns = PrivacyRequestRedactionPattern.get_patterns(db)
|
|
51
|
+
return PrivacyRequestRedactionPatternsResponse(patterns=patterns)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@router.put(
|
|
55
|
+
PRIVACY_REQUEST_REDACTION_PATTERNS,
|
|
56
|
+
status_code=HTTP_200_OK,
|
|
57
|
+
response_model=PrivacyRequestRedactionPatternsResponse,
|
|
58
|
+
dependencies=[
|
|
59
|
+
Security(
|
|
60
|
+
verify_oauth_client, scopes=[PRIVACY_REQUEST_REDACTION_PATTERNS_UPDATE]
|
|
61
|
+
)
|
|
62
|
+
],
|
|
63
|
+
)
|
|
64
|
+
def update_privacy_request_redaction_patterns(
|
|
65
|
+
*,
|
|
66
|
+
db: Session = Depends(get_db),
|
|
67
|
+
request: PrivacyRequestRedactionPatternsRequest,
|
|
68
|
+
) -> PrivacyRequestRedactionPatternsResponse:
|
|
69
|
+
"""
|
|
70
|
+
Update the privacy request redaction patterns configuration.
|
|
71
|
+
|
|
72
|
+
This is a complete replacement of the patterns list. To clear all patterns,
|
|
73
|
+
send an empty list.
|
|
74
|
+
"""
|
|
75
|
+
logger.info(
|
|
76
|
+
"Updating privacy request redaction patterns configuration with {} patterns",
|
|
77
|
+
len(request.patterns),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
updated = PrivacyRequestRedactionPattern.replace_patterns(
|
|
82
|
+
db=db, patterns=request.patterns
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
logger.info("Successfully updated privacy request redaction patterns")
|
|
86
|
+
return PrivacyRequestRedactionPatternsResponse(patterns=updated)
|
|
87
|
+
|
|
88
|
+
except Exception as exc:
|
|
89
|
+
logger.exception(
|
|
90
|
+
"Failed to update privacy request redaction patterns: {}", str(exc)
|
|
91
|
+
)
|
|
92
|
+
raise HTTPException(
|
|
93
|
+
status_code=500,
|
|
94
|
+
detail="Failed to update privacy request redaction patterns",
|
|
95
|
+
) from exc
|
|
@@ -117,7 +117,7 @@ def verify_user_read_scopes(
|
|
|
117
117
|
db: Session = Depends(get_db),
|
|
118
118
|
) -> ClientDetail:
|
|
119
119
|
"""
|
|
120
|
-
Custom dependency that verifies the user has either
|
|
120
|
+
Custom dependency that verifies the user has either USER_READ or USER_READ_OWN scope.
|
|
121
121
|
Returns the client if authorized.
|
|
122
122
|
"""
|
|
123
123
|
token_data, client = extract_token_and_load_client(authorization, db)
|
|
@@ -159,7 +159,7 @@ async def update_user(
|
|
|
159
159
|
) -> FidesUser:
|
|
160
160
|
"""
|
|
161
161
|
Update a user given a `user_id`. If the user is not updating their own data,
|
|
162
|
-
they need the
|
|
162
|
+
they need the USER_UPDATE scope
|
|
163
163
|
"""
|
|
164
164
|
user = FidesUser.get(db=db, object_id=user_id)
|
|
165
165
|
if not user:
|
|
@@ -208,6 +208,17 @@ def update_user_password(
|
|
|
208
208
|
|
|
209
209
|
current_user.update_password(db=db, new_password=data.new_password)
|
|
210
210
|
|
|
211
|
+
# Delete the user's associated OAuth client to invalidate all existing sessions
|
|
212
|
+
if current_user.client:
|
|
213
|
+
try:
|
|
214
|
+
current_user.client.delete(db)
|
|
215
|
+
except Exception as exc:
|
|
216
|
+
logger.exception(
|
|
217
|
+
"Unable to delete user client during password reset for user {}: {}",
|
|
218
|
+
current_user.id,
|
|
219
|
+
exc,
|
|
220
|
+
)
|
|
221
|
+
|
|
211
222
|
logger.info("Updated user with id: '{}'.", current_user.id)
|
|
212
223
|
return current_user
|
|
213
224
|
|
|
@@ -236,6 +247,18 @@ def force_update_password(
|
|
|
236
247
|
)
|
|
237
248
|
|
|
238
249
|
user.update_password(db=db, new_password=data.new_password)
|
|
250
|
+
|
|
251
|
+
# Delete the user's associated OAuth client to invalidate all existing sessions
|
|
252
|
+
if user.client:
|
|
253
|
+
try:
|
|
254
|
+
user.client.delete(db)
|
|
255
|
+
except Exception as exc:
|
|
256
|
+
logger.exception(
|
|
257
|
+
"Unable to delete user client during admin-forced password reset for user {}: {}",
|
|
258
|
+
user.id,
|
|
259
|
+
exc,
|
|
260
|
+
)
|
|
261
|
+
|
|
239
262
|
logger.info("Updated user with id: '{}'.", user.id)
|
|
240
263
|
return user
|
|
241
264
|
|
|
@@ -548,7 +571,7 @@ def get_user(
|
|
|
548
571
|
client: ClientDetail = Security(verify_user_read_scopes),
|
|
549
572
|
authorization: str = Security(oauth2_scheme),
|
|
550
573
|
) -> FidesUser:
|
|
551
|
-
"""Returns a User based on an Id. Users with
|
|
574
|
+
"""Returns a User based on an Id. Users with USER_READ_OWN scope can only access their own data."""
|
|
552
575
|
user: Optional[FidesUser] = FidesUser.get_by_key_or_id(db, data={"id": user_id})
|
|
553
576
|
if user is None:
|
|
554
577
|
raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail="User not found")
|
fides/api/db/base.py
CHANGED
|
@@ -66,6 +66,9 @@ from fides.api.models.privacy_preference import (
|
|
|
66
66
|
ServedNoticeHistory,
|
|
67
67
|
)
|
|
68
68
|
from fides.api.models.privacy_request import PrivacyRequest
|
|
69
|
+
from fides.api.models.privacy_request_redaction_pattern import (
|
|
70
|
+
PrivacyRequestRedactionPattern,
|
|
71
|
+
)
|
|
69
72
|
from fides.api.models.property import (
|
|
70
73
|
MessagingTemplateToProperty,
|
|
71
74
|
PrivacyExperienceConfigProperty,
|
fides/api/models/client.py
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import List, Set
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Column, String
|
|
4
|
+
from sqlalchemy.ext.declarative import declared_attr
|
|
5
|
+
from sqlalchemy.orm import Session
|
|
6
|
+
|
|
7
|
+
from fides.api.db.base_class import Base
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PrivacyRequestRedactionPattern(Base):
|
|
11
|
+
"""
|
|
12
|
+
Stores one regex pattern per row for masking dataset, collection, and field names
|
|
13
|
+
in DSR package reports.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@declared_attr
|
|
17
|
+
def __tablename__(self) -> str:
|
|
18
|
+
return "privacy_request_redaction_pattern"
|
|
19
|
+
|
|
20
|
+
# One pattern per row; unique to prevent duplicates
|
|
21
|
+
pattern = Column(String, nullable=False, unique=True)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def get_patterns(cls, db: Session) -> List[str]:
|
|
25
|
+
"""
|
|
26
|
+
Get the current list of masking patterns ordered by creation time.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of regex pattern strings, or None if no patterns are configured
|
|
30
|
+
"""
|
|
31
|
+
rows = db.query(cls).order_by(cls.created_at.asc()).all()
|
|
32
|
+
if not rows:
|
|
33
|
+
return []
|
|
34
|
+
return [row.pattern for row in rows]
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def replace_patterns(cls, db: Session, patterns: List[str]) -> List[str]:
|
|
38
|
+
"""
|
|
39
|
+
Replace the set of stored patterns with the provided list using set reconciliation.
|
|
40
|
+
|
|
41
|
+
- Adds patterns that are not present
|
|
42
|
+
- Removes patterns that are no longer desired
|
|
43
|
+
- Returns the resulting canonical list in deterministic order
|
|
44
|
+
"""
|
|
45
|
+
# Normalize: trim whitespace, remove empties and duplicates
|
|
46
|
+
desired: Set[str] = {p.strip() for p in patterns if p and p.strip()}
|
|
47
|
+
|
|
48
|
+
# Fetch existing patterns from DB as a set
|
|
49
|
+
existing: Set[str] = set(p for (p,) in db.query(cls.pattern).all())
|
|
50
|
+
|
|
51
|
+
to_add = desired - existing
|
|
52
|
+
to_remove = existing - desired
|
|
53
|
+
|
|
54
|
+
if to_remove:
|
|
55
|
+
db.query(cls).filter(cls.pattern.in_(list(to_remove))).delete(
|
|
56
|
+
synchronize_session=False
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if to_add:
|
|
60
|
+
db.bulk_save_objects([cls(pattern=p) for p in to_add])
|
|
61
|
+
|
|
62
|
+
db.commit()
|
|
63
|
+
|
|
64
|
+
return sorted(desired)
|
fides/api/oauth/utils.py
CHANGED
|
@@ -4,16 +4,16 @@ import json
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from functools import update_wrapper
|
|
6
6
|
from types import FunctionType
|
|
7
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, cast
|
|
8
8
|
|
|
9
|
-
from fastapi import Depends, HTTPException, Security
|
|
9
|
+
from fastapi import Depends, HTTPException, Request, Security
|
|
10
10
|
from fastapi.security import SecurityScopes
|
|
11
11
|
from jose import exceptions, jwe
|
|
12
12
|
from jose.constants import ALGORITHMS
|
|
13
13
|
from loguru import logger
|
|
14
14
|
from pydantic import ValidationError
|
|
15
15
|
from sqlalchemy.orm import Session
|
|
16
|
-
from starlette.status import HTTP_404_NOT_FOUND
|
|
16
|
+
from starlette.status import HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND
|
|
17
17
|
|
|
18
18
|
from fides.api.api.deps import get_db
|
|
19
19
|
from fides.api.common_exceptions import AuthenticationError, AuthorizationError
|
|
@@ -30,7 +30,7 @@ from fides.api.models.fides_user_permissions import FidesUserPermissions
|
|
|
30
30
|
from fides.api.models.policy import PolicyPreWebhook
|
|
31
31
|
from fides.api.models.pre_approval_webhook import PreApprovalWebhook
|
|
32
32
|
from fides.api.models.privacy_request import RequestTask
|
|
33
|
-
from fides.api.oauth.roles import get_scopes_from_roles
|
|
33
|
+
from fides.api.oauth.roles import ROLES_TO_SCOPES_MAPPING, get_scopes_from_roles
|
|
34
34
|
from fides.api.request_context import set_user_id
|
|
35
35
|
from fides.api.schemas.external_https import (
|
|
36
36
|
DownloadTokenJWE,
|
|
@@ -80,6 +80,28 @@ def is_callback_token_expired(issued_at: Optional[datetime]) -> bool:
|
|
|
80
80
|
).total_seconds() / 60.0 > CONFIG.execution.privacy_request_delay_timeout
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def is_token_invalidated(issued_at: datetime, client: ClientDetail) -> bool:
|
|
84
|
+
"""
|
|
85
|
+
Return True if the token should be considered invalid due to security events
|
|
86
|
+
(e.g., user password reset) that occurred after the token was issued.
|
|
87
|
+
|
|
88
|
+
Any errors accessing related objects are logged and treated as non-invalidating.
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
if (
|
|
92
|
+
client.user is not None
|
|
93
|
+
and client.user.password_reset_at is not None
|
|
94
|
+
and issued_at < client.user.password_reset_at
|
|
95
|
+
):
|
|
96
|
+
return True
|
|
97
|
+
return False
|
|
98
|
+
except Exception as exc:
|
|
99
|
+
logger.exception(
|
|
100
|
+
"Unable to evaluate password reset timestamp for client user: {}", exc
|
|
101
|
+
)
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
83
105
|
def _get_webhook_jwe_or_error(
|
|
84
106
|
security_scopes: SecurityScopes, authorization: str = Security(oauth2_scheme)
|
|
85
107
|
) -> WebhookJWE:
|
|
@@ -225,7 +247,7 @@ async def get_current_user(
|
|
|
225
247
|
created_at=datetime.utcnow(),
|
|
226
248
|
)
|
|
227
249
|
|
|
228
|
-
return client.user
|
|
250
|
+
return cast(FidesUser, client.user)
|
|
229
251
|
|
|
230
252
|
|
|
231
253
|
def verify_callback_oauth_policy_pre_webhook(
|
|
@@ -370,8 +392,10 @@ def extract_token_and_load_client(
|
|
|
370
392
|
logger.debug("Auth token expired.")
|
|
371
393
|
raise AuthorizationError(detail="Not Authorized for this action")
|
|
372
394
|
|
|
395
|
+
issued_at_dt = datetime.fromisoformat(issued_at)
|
|
396
|
+
|
|
373
397
|
if is_token_expired(
|
|
374
|
-
|
|
398
|
+
issued_at_dt,
|
|
375
399
|
token_duration_override or CONFIG.security.oauth_access_token_expire_minutes,
|
|
376
400
|
):
|
|
377
401
|
raise AuthorizationError(detail="Not Authorized for this action")
|
|
@@ -394,6 +418,12 @@ def extract_token_and_load_client(
|
|
|
394
418
|
logger.debug("Auth token belongs to an invalid client_id.")
|
|
395
419
|
raise AuthorizationError(detail="Not Authorized for this action")
|
|
396
420
|
|
|
421
|
+
# Invalidate tokens issued prior to the user's most recent password reset.
|
|
422
|
+
# This ensures any existing sessions are expired immediately after a password change.
|
|
423
|
+
if is_token_invalidated(issued_at_dt, client):
|
|
424
|
+
logger.debug("Auth token issued before latest password reset.")
|
|
425
|
+
raise AuthorizationError(detail="Not Authorized for this action")
|
|
426
|
+
|
|
397
427
|
# Populate request-scoped context with the authenticated user identifier.
|
|
398
428
|
# Prefer the linked user_id; fall back to the client id when this is the
|
|
399
429
|
# special root client (which has no associated FidesUser row).
|
|
@@ -476,6 +506,87 @@ def has_scope_subset(user_scopes: List[str], endpoint_scopes: SecurityScopes) ->
|
|
|
476
506
|
return set(endpoint_scopes.scopes).issubset(user_scopes)
|
|
477
507
|
|
|
478
508
|
|
|
509
|
+
def get_client_effective_scopes(client: "ClientDetail") -> List[str]:
|
|
510
|
+
"""
|
|
511
|
+
Get all scopes available to a client, including both direct scopes and role-derived scopes.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
client: The ClientDetail instance
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
List of scope strings that the client has access to
|
|
518
|
+
"""
|
|
519
|
+
effective_scopes = set()
|
|
520
|
+
|
|
521
|
+
# Add direct scopes
|
|
522
|
+
if client.scopes:
|
|
523
|
+
effective_scopes.update(client.scopes)
|
|
524
|
+
|
|
525
|
+
# Add role-derived scopes
|
|
526
|
+
if client.roles:
|
|
527
|
+
for role in client.roles:
|
|
528
|
+
role_scopes = ROLES_TO_SCOPES_MAPPING.get(role, [])
|
|
529
|
+
effective_scopes.update(role_scopes)
|
|
530
|
+
|
|
531
|
+
# Add user permission scopes if client is associated with a user
|
|
532
|
+
# Note: client.user is available via SQLAlchemy backref from FidesUser.client relationship
|
|
533
|
+
user = getattr(client, "user", None) # Use getattr to avoid mypy attr-defined error
|
|
534
|
+
if user and hasattr(user, "permissions") and user.permissions:
|
|
535
|
+
effective_scopes.update(user.permissions.total_scopes)
|
|
536
|
+
|
|
537
|
+
return sorted(list(effective_scopes))
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def verify_client_can_assign_scopes(
|
|
541
|
+
request: "Request",
|
|
542
|
+
requesting_client: "ClientDetail",
|
|
543
|
+
scopes: List[str],
|
|
544
|
+
db: "Session",
|
|
545
|
+
) -> None:
|
|
546
|
+
"""
|
|
547
|
+
Verify that a requesting client has permission to assign the given scopes.
|
|
548
|
+
|
|
549
|
+
Raises HTTPException if the client lacks permission.
|
|
550
|
+
Root client is exempt from this check.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
request: FastAPI request object containing Authorization header
|
|
554
|
+
requesting_client: The client making the request
|
|
555
|
+
scopes: List of scopes to be assigned
|
|
556
|
+
db: Database session
|
|
557
|
+
|
|
558
|
+
Raises:
|
|
559
|
+
HTTPException: If the client lacks permission to assign the scopes
|
|
560
|
+
"""
|
|
561
|
+
# Root client can assign any scope
|
|
562
|
+
if requesting_client.id == CONFIG.security.oauth_root_client_id:
|
|
563
|
+
return
|
|
564
|
+
|
|
565
|
+
# Get the actual token scopes (not the client's database scopes)
|
|
566
|
+
authorization = request.headers.get("Authorization", "").replace("Bearer ", "")
|
|
567
|
+
token_data, _ = extract_token_and_load_client(authorization, db)
|
|
568
|
+
|
|
569
|
+
# Get token's effective scopes
|
|
570
|
+
token_scopes = token_data.get("scopes", [])
|
|
571
|
+
|
|
572
|
+
# Check if user has the scopes via roles as well
|
|
573
|
+
has_scope_via_role = has_permissions(
|
|
574
|
+
token_data=token_data,
|
|
575
|
+
client=requesting_client,
|
|
576
|
+
endpoint_scopes=SecurityScopes(scopes),
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# If they don't have all scopes via direct assignment or roles, check individual scopes
|
|
580
|
+
if not has_scope_via_role:
|
|
581
|
+
unauthorized_scopes = set(scopes) - set(token_scopes)
|
|
582
|
+
|
|
583
|
+
if unauthorized_scopes:
|
|
584
|
+
raise HTTPException(
|
|
585
|
+
status_code=HTTP_403_FORBIDDEN,
|
|
586
|
+
detail=f"Cannot assign scopes that you do not have. Missing scopes: {sorted(unauthorized_scopes)}",
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
|
|
479
590
|
def create_temporary_user_for_login_flow(config: FidesConfig) -> FidesUser:
|
|
480
591
|
"""
|
|
481
592
|
Create a temporary FidesUser in-memory with an attached in-memory ClientDetail
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field, field_validator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PrivacyRequestRedactionPatternsRequest(BaseModel):
|
|
8
|
+
"""Request schema for updating privacy request redaction patterns."""
|
|
9
|
+
|
|
10
|
+
patterns: List[str] = Field(
|
|
11
|
+
description="List of regex patterns used to redact dataset, collection, and field names in privacy request package reports",
|
|
12
|
+
max_length=100, # Limit number of patterns
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
@field_validator("patterns")
|
|
16
|
+
@classmethod
|
|
17
|
+
def validate_patterns(cls, patterns: List[str]) -> List[str]:
|
|
18
|
+
"""Validate regex patterns with ReDoS protection via Pydantic's default rust-regex engine."""
|
|
19
|
+
if not patterns:
|
|
20
|
+
return patterns
|
|
21
|
+
|
|
22
|
+
validated_patterns = []
|
|
23
|
+
for i, pattern in enumerate(patterns):
|
|
24
|
+
if not isinstance(pattern, str):
|
|
25
|
+
raise ValueError(f"Pattern at index {i} must be a string")
|
|
26
|
+
|
|
27
|
+
pattern = pattern.strip()
|
|
28
|
+
if not pattern:
|
|
29
|
+
raise ValueError(f"Pattern at index {i} cannot be empty")
|
|
30
|
+
|
|
31
|
+
# Reasonable length limit for regex patterns
|
|
32
|
+
if len(pattern) > 500:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"Pattern at index {i} is too long (max 500 characters)"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Pydantic's rust-regex engine provides ReDoS protection
|
|
38
|
+
try:
|
|
39
|
+
re.compile(pattern, re.IGNORECASE)
|
|
40
|
+
except re.error as e:
|
|
41
|
+
raise ValueError(f"Invalid regex pattern at index {i}: {e}")
|
|
42
|
+
|
|
43
|
+
validated_patterns.append(pattern)
|
|
44
|
+
|
|
45
|
+
return validated_patterns
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PrivacyRequestRedactionPatternsResponse(BaseModel):
|
|
49
|
+
"""Response schema for privacy request redaction patterns."""
|
|
50
|
+
|
|
51
|
+
patterns: List[str] = Field(
|
|
52
|
+
description="List of regex patterns used to redact dataset, collection, and field names in privacy request package reports"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
model_config = {"from_attributes": True}
|