ethyca-fides 2.58.2rc0__py2.py3-none-any.whl → 2.58.3b0__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.58.2rc0.dist-info → ethyca_fides-2.58.3b0.dist-info}/METADATA +20 -11
- {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b0.dist-info}/RECORD +221 -211
- {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b0.dist-info}/WHEEL +1 -1
- {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b0.dist-info}/entry_points.txt +0 -1
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/67d01c4e124e_add_reject_all_mechanism_to_privacy_.py +56 -0
- fides/api/alembic/migrations/versions/6e565c16dae1_add_tcf_publisher_restrictions.py +107 -0
- fides/api/alembic/migrations/versions/9288f729cac4_add_tcf_configuration_fk_to_experience_.py +62 -0
- fides/api/alembic/migrations/versions/99c603c1b8f9_add_password_login_enabled_and_totp_secret_to_fidesuser.py +45 -0
- fides/api/api/deps.py +8 -2
- fides/api/api/v1/endpoints/user_endpoints.py +8 -12
- fides/api/cryptography/identity_salt.py +12 -13
- fides/api/custom_types.py +6 -1
- fides/api/db/base.py +5 -0
- fides/api/db/system.py +1 -3
- fides/api/migrations/hash_migration_job.py +2 -2
- fides/api/models/attachment.py +80 -11
- fides/api/models/comment.py +45 -15
- fides/api/models/detection_discovery.py +31 -0
- fides/api/models/fides_user.py +26 -9
- fides/api/models/fides_user_invite.py +2 -0
- fides/api/models/privacy_experience.py +68 -0
- fides/api/models/privacy_request/privacy_request.py +23 -6
- fides/api/models/tcf_publisher_restrictions.py +465 -0
- fides/api/schemas/connection_configuration/connection_config.py +30 -16
- fides/api/schemas/user.py +5 -1
- fides/api/service/deps.py +9 -0
- fides/api/service/storage/s3.py +14 -1
- fides/api/task/graph_task.py +1 -1
- fides/api/util/collection_util.py +48 -9
- fides/cli/commands/pull.py +77 -13
- fides/core/api.py +2 -1
- fides/core/pull.py +38 -7
- fides/service/error_handling/__init__.py +0 -0
- fides/service/error_handling/error_handler.py +202 -0
- fides/service/user/__init__.py +0 -0
- fides/service/user/user_service.py +140 -0
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/1376-f2e68d1cfdacfd48.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/146-9567a3d2f9e21b83.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1817-5c32a7592d18a859.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{2397-ee53235fb21b5e97.js → 2397-eae0fd1e90821f7e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3855-b6b7865dedd7bc2a.js → 3855-c02445526594fc1f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3872-4e053c20d546f027.js → 3872-0b61e674a790491b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/3938-ebc24cf8e57d7665.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4060-8d165e1236ea521a.js → 4060-fd2f97afa5ba80d4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/4121-28beb1c0ce3330b6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4450-d354a1152f4747e2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4481-865277e9623e6014.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{5258-0658dc2274df6832.js → 5258-506e9e524f8dd8c8.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{5480-f49696df5e8ae500.js → 5480-ff3e05a015ee2799.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{5487-3ad50d21cdbc9209.js → 5487-f281d138cb89b5c9.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/5973-28d2af853d8498d7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6315-43837fe5ed5ecf5d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{6372-ca9c12ac8902365b.js → 6372-8479ec83d73af02b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6853-8941824350c3c1a8.js → 6853-3562089cc16a6799.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6954-3b887fb444f9228c.js → 6954-23438f7f9729748b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{7751-a8f31c062d4cb09d.js → 7751-95349028f1ee3fb5.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{79-f9b948ebb186900f.js → 79-488979db197d250c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{7980-4bd08957448dea32.js → 7980-923cd5125080d75a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{9046-04bd7becea207cb1.js → 9046-399597b0e3484cdf.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/9767-d1d54cb9b74c0693.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{main-24f422f93845a596.js → main-090643377c8254e6.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{main-app-94a0711202e08b15.js → main-app-59156a9331ac7bce.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-fc89ce7bed454c84.js → _app-fadef5a6a65d3ec4.js} +8 -8
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-9acaab973dfe86e2.js → manual-9beb93c78931d391.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-d258f0c25fa020bf.js → add-systems-661ff00f91fe62df.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/[id]-78de4bde88e18b0f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-a9d9402c219d13e5.js → new-c02b14c50b19bd91.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience-7dcbcd6f74029d90.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices-c27810fc7d8ad4c0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{reporting-788cf0e34829af46.js → reporting-71c8a8557a0fb316.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-900004e402c31797.js → data-catalog-6dae602b509b00b5.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-b66831fdafcdf67c.js → [systemId]-44fe13c0557746dd.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-49367fa740570834.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-f17380b9fd139dde.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-dfc91b69873ffaab.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-803c1b577ab17ae3.js → new-afb5d98731bc1bb1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-fa743ddc7f89d76b.js → dataset-13e06b0a4a8c114c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-bbe1ca2793798e6b.js → [id]-d796cf4c25d0b988.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-abc17fef69cd951b.js → new-814849a549132ffa.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-be295129568a929c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{index-bfaacdb55a5a6c9f.js → index-b0926c4083d4ac88.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-dadebdf2d6fbcc64.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-eef56c95b08aa24c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-b1d39bb680cfd6d2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-ea8c82f36520e542.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-2a56dc41a292a468.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-d85c0d16ba09ba35.js → privacy-requests-f394d59981a4f50c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-afedc48ef4e7f858.js → datamap-b461324d13ae41e6.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-b359f061d3b9d455.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-52d030b1db2ca1b9.js → custom-fields-1c770b51690416d8.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-2f03e981234c40ad.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-a08693d0d1e10bc8.js → organization-b208f9fd45ebb829.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-151571cff4e85894.js → test-datasets-37c8930711ca2b8e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-4f5a28226575c976.js → [id]-d5857bb2ec2f8b67.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{systems-abd68fc5ddde5482.js → systems-4f07a39a7def714a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-16b4d75c49276add.js → taxonomy-8b5ef781c76b3b9e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-be690621a944bfe2.js → new-082c3156175f9267.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/[id]-963b0dbbf93b9e49.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-6c9ad62479a7d03e.js → user-management-cb40808c1509f191.js} +1 -1
- fides/ui-build/static/admin/_next/static/css/687135955af5b7e1.css +1 -0
- fides/ui-build/static/admin/_next/static/css/b89fc4b36b501cf6.css +1 -0
- fides/ui-build/static/admin/_next/static/sSd6wygps1Baq1nFEC_PK/_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/images/connector-logos/website.svg +10 -0
- 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 +4 -4
- fides/ui-build/static/admin/lib/fides.js +3 -3
- 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/service/user/fides_user_service.py +0 -128
- fides/ui-build/static/admin/_next/static/chunks/1150-035a721a04f4451e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1376-5cea5ef9362215e8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3412-7ec8751b8182e1bf.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4450-36234280bee624ff.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4481-aab99ff80f707473.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5973-52aee296edc44f7e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6315-fa1519cdf080f42d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7453-39761c38da31257e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9767-1a23925d2cb27b51.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/[id]-fb75fa0aea77678d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience-c946b33b0322b8ad.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices-ea57f9d6ad17e957.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-5f9ef1f99818117c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-d001337d1bb73bd1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-11b3ce9f61d9bfe9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-a78a73b65929853a.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-d4329043219fed9b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-1e60754abec1ee6b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-fe765154315782cf.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-f5f7a8069909ef24.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-9f7eaad05e5b9292.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-89524101b7279f6e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-24cba38685dc872c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/[id]-78eaf933f755bfe8.js +0 -1
- fides/ui-build/static/admin/_next/static/css/113d823fe71f6af0.css +0 -1
- fides/ui-build/static/admin/_next/static/css/15fb7d4837a1de34.css +0 -1
- fides/ui-build/static/admin/_next/static/o0mKeH0cB6eAYV6qOlVD0/_buildManifest.js +0 -1
- {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b0.dist-info/licenses}/LICENSE +0 -0
- {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{1100-053fc6b76c65a00f.js → 1100-45c0634b4f51d10c.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3086-b5054ec2c75700b9.js → 3086-d1ba90bc6ac9174b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{355-8a77c9a1cd027f2e.js → 355-45bc45d448c9911b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{4723-0a3c5e2ce143a7d0.js → 4723-ee2929f284f324fe.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5826-e5dcb4e68cfe6289.js → 5826-1f4f74bf3b5348e4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{8433-b3008ecaf9834e7f.js → 8433-c4c765833ab9cc07.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{8499-43606100edf42fdf.js → 8499-14fb2a3207baa9a6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9282-2bfbdca45e84e810.js → 9282-1a48b10b114d01f4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9327-2cba327d10586683.js → 9327-4970d356f7000c0b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9999-637e0e5341f15f4a.js → 9999-3de189da9fffcc53.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{404-b202c0d8f6fc75c3.js → 404-29560aa2e6a60963.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-ba975134a85588f8.js → multiple-a55d3b39681cca27.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{ant-poc-b9932971a479f3a7.js → ant-poc-404f3c9018952800.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-b34e5324461d0c87.js → add-vendors-9ec60eaff5eece51.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-723cc3d4f5740ea6.js → configure-4403425cf6cb43d1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-72251b48e2e03a1e.js → [id]-75c2ed6ba3de48cd.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-9611dd42856d6062.js → new-487ae57dc7e2ded2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-e5748812ba055a56.js → properties-f67fda6a71d0a46b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-39d65f13cc8f1cf8.js → consent-75395dfc224cf44b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-a29850536c85d4b8.js → [projectUrn]-fa65c67ec8c4b5e6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-c44ce244122e96d5.js → projects-e76a07ee6ee8d55f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-8cb714cdd44ac40e.js → datamap-c0714c24a342ae76.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-d79551d4c64c398c.js → [...subfieldNames]-704553f5329fb9d4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-7e5df4a0de7540bb.js → [collectionName]-06c19dca941edb14.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-6ba18f92ba561114.js → [datasetId]-eac517f43d5f53a8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-ef8000d7388dc915.js → integrations-373fb3a596099fc9.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-ad02e019b2467958.js → [id]-66f5fbadd8455805.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-f9693cb6a0b7ded8.js → add-template-d441abb1f045940f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-2987edc77388e85a.js → configure-b8c94b10ab90b061.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-94e2faa73dd6a3e6.js → [id]-f4fb941df069b7bf.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-630a6a3dd6502ba6.js → add-property-bccb6ffab25aa214.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{properties-20ca2f963906674b.js → properties-9a7ac623370b7c00.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-a49d0f84cf0cf05e.js → about-4412a7b468b6d4bf.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-fa42d8f18df44927.js → domain-records-d9088f5cd9fb2822.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-6fd6071e2009b8f2.js → email-templates-cf09ad896c7396a6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-66c757325cb58467.js → locations-759564ca0ae62840.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-c6c239996cfa6ae8.js → regulations-2dce4501fca920b3.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{webpack-2c7ccac5843c4d8e.js → webpack-03e375f6d6b2c71b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/{o0mKeH0cB6eAYV6qOlVD0 → sSd6wygps1Baq1nFEC_PK}/_ssgManifest.js +0 -0
@@ -122,6 +122,7 @@ def extract_key_for_address(
|
|
122
122
|
return f"{dataset}:{collection}"
|
123
123
|
|
124
124
|
|
125
|
+
# pylint: disable=too-many-branches
|
125
126
|
def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str, Any]:
|
126
127
|
"""
|
127
128
|
Converts a dictionary of paths/values into a nested dictionary
|
@@ -149,17 +150,29 @@ def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str,
|
|
149
150
|
for i, current_key in enumerate(keys[:-1]):
|
150
151
|
next_key = keys[i + 1]
|
151
152
|
if next_key.isdigit():
|
152
|
-
target
|
153
|
+
if isinstance(target, dict): # Only call setdefault on dictionaries
|
154
|
+
target = target.setdefault(current_key, [])
|
155
|
+
elif isinstance(
|
156
|
+
target, list
|
157
|
+
): # If target is a list, handle differently
|
158
|
+
idx = int(current_key)
|
159
|
+
while len(target) <= idx:
|
160
|
+
target.append([]) # Add a list since next_key is a digit
|
161
|
+
target = target[idx]
|
153
162
|
else:
|
154
163
|
if isinstance(target, dict):
|
155
164
|
target = target.setdefault(current_key, {})
|
156
165
|
elif isinstance(target, list):
|
157
|
-
|
166
|
+
idx = int(current_key)
|
167
|
+
while len(target) <= idx:
|
158
168
|
target.append({})
|
159
|
-
target = target[
|
169
|
+
target = target[idx]
|
160
170
|
try:
|
161
171
|
if isinstance(target, list):
|
162
|
-
|
172
|
+
idx = int(keys[-1]) if keys[-1].isdigit() else len(target)
|
173
|
+
while len(target) <= idx:
|
174
|
+
target.append(None)
|
175
|
+
target[idx] = value
|
163
176
|
else:
|
164
177
|
# If the value is a dictionary, add its components to the queue for processing
|
165
178
|
if isinstance(value, dict):
|
@@ -176,10 +189,12 @@ def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str,
|
|
176
189
|
return output
|
177
190
|
|
178
191
|
|
192
|
+
# pylint: disable=too-many-branches
|
179
193
|
def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str, Any]:
|
180
194
|
"""
|
181
195
|
Recursively flatten a dictionary or list into a flat dictionary with dot-notation keys.
|
182
196
|
Handles nested dictionaries and arrays with proper indices.
|
197
|
+
Preserves empty lists and dictionaries.
|
183
198
|
|
184
199
|
example:
|
185
200
|
|
@@ -191,7 +206,9 @@ def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str,
|
|
191
206
|
"D": [
|
192
207
|
{"E": "3"},
|
193
208
|
{"E": "4"}
|
194
|
-
]
|
209
|
+
],
|
210
|
+
"E": [],
|
211
|
+
"F": {}
|
195
212
|
}
|
196
213
|
|
197
214
|
becomes
|
@@ -200,7 +217,9 @@ def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str,
|
|
200
217
|
"A.B": "1",
|
201
218
|
"A.C": "2",
|
202
219
|
"D.0.E": "3",
|
203
|
-
"D.1.E": "4"
|
220
|
+
"D.1.E": "4",
|
221
|
+
"E": [],
|
222
|
+
"F": {}
|
204
223
|
}
|
205
224
|
|
206
225
|
Args:
|
@@ -211,20 +230,40 @@ def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str,
|
|
211
230
|
Returns:
|
212
231
|
A flattened dictionary with dot-notation keys
|
213
232
|
"""
|
214
|
-
items = {}
|
233
|
+
items: Dict[str, Any] = {}
|
215
234
|
|
216
235
|
if isinstance(data, dict):
|
236
|
+
# Handle top-level empty dictionary case
|
237
|
+
if not data and not prefix:
|
238
|
+
return {}
|
239
|
+
|
240
|
+
# If the dictionary is empty but has a prefix, store it as is
|
241
|
+
if not data:
|
242
|
+
items[prefix] = {}
|
243
|
+
return items
|
244
|
+
|
217
245
|
for k, v in data.items():
|
218
246
|
new_key = f"{prefix}{separator}{k}" if prefix else k
|
219
247
|
if isinstance(v, (dict, list)):
|
220
|
-
|
248
|
+
if not v: # Handle empty dict or list
|
249
|
+
items[new_key] = v
|
250
|
+
else:
|
251
|
+
items.update(flatten_dict(v, new_key, separator))
|
221
252
|
else:
|
222
253
|
items[new_key] = v
|
223
254
|
elif isinstance(data, list):
|
255
|
+
# If the list is empty, store it as is
|
256
|
+
if not data:
|
257
|
+
items[prefix] = []
|
258
|
+
return items
|
259
|
+
|
224
260
|
for i, v in enumerate(data):
|
225
261
|
new_key = f"{prefix}{separator}{i}"
|
226
262
|
if isinstance(v, (dict, list)):
|
227
|
-
|
263
|
+
if not v: # Handle empty dict or list
|
264
|
+
items[new_key] = v
|
265
|
+
else:
|
266
|
+
items.update(flatten_dict(v, new_key, separator))
|
228
267
|
else:
|
229
268
|
items[new_key] = v
|
230
269
|
else:
|
fides/cli/commands/pull.py
CHANGED
@@ -5,9 +5,11 @@ from click_default_group import DefaultGroup
|
|
5
5
|
|
6
6
|
from fides.cli.options import fides_key_argument, manifests_dir_argument
|
7
7
|
from fides.cli.utils import with_analytics, with_server_health_check
|
8
|
-
from fides.common.utils import echo_red
|
8
|
+
from fides.common.utils import echo_green, echo_red
|
9
9
|
from fides.core import parse as _parse
|
10
10
|
from fides.core import pull as _pull
|
11
|
+
from fides.core.api_helpers import list_server_resources
|
12
|
+
from fides.core.pull import remove_nulls, write_manifest_file
|
11
13
|
from fides.core.utils import git_is_dirty
|
12
14
|
|
13
15
|
|
@@ -60,26 +62,88 @@ def pull_all(
|
|
60
62
|
|
61
63
|
@pull.command(name="dataset") # type: ignore
|
62
64
|
@click.pass_context
|
63
|
-
@
|
65
|
+
@click.argument("fides_key", required=False)
|
64
66
|
@manifests_dir_argument
|
65
|
-
|
67
|
+
@click.option(
|
68
|
+
"--all-resources",
|
69
|
+
"-a",
|
70
|
+
is_flag=True,
|
71
|
+
default=False,
|
72
|
+
help="Pull all datasets from the server.",
|
73
|
+
)
|
74
|
+
@click.option(
|
75
|
+
"--separate-files",
|
76
|
+
is_flag=True,
|
77
|
+
default=False,
|
78
|
+
help="Write each dataset to a separate file named after its fides_key.",
|
79
|
+
)
|
80
|
+
@with_analytics
|
81
|
+
@with_server_health_check
|
82
|
+
def pull_dataset(
|
66
83
|
ctx: click.Context,
|
67
|
-
fides_key: str,
|
84
|
+
fides_key: Optional[str],
|
68
85
|
manifests_dir: str,
|
86
|
+
all_resources: bool,
|
87
|
+
separate_files: bool,
|
69
88
|
) -> None:
|
70
89
|
"""
|
71
|
-
Retrieve
|
90
|
+
Retrieve datasets from the server and update the local manifest files.
|
91
|
+
|
92
|
+
If FIDES_KEY is provided, only that dataset will be pulled.
|
93
|
+
If --all-resources is specified, all datasets will be pulled.
|
94
|
+
If --separate-files is specified, each dataset will be written to a separate file.
|
72
95
|
"""
|
96
|
+
if not fides_key and not all_resources:
|
97
|
+
echo_red("Error: Either FIDES_KEY or --all-resources must be specified.")
|
98
|
+
raise SystemExit(1)
|
73
99
|
|
74
100
|
config = ctx.obj["CONFIG"]
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
101
|
+
|
102
|
+
# Check for unstaged git changes before proceeding
|
103
|
+
if git_is_dirty(manifests_dir):
|
104
|
+
echo_red(
|
105
|
+
f"There are unstaged changes in your manifest directory: '{manifests_dir}' \nAborting pull!"
|
106
|
+
)
|
107
|
+
raise SystemExit(1)
|
108
|
+
|
109
|
+
if fides_key:
|
110
|
+
_pull.pull(
|
111
|
+
url=config.cli.server_url,
|
112
|
+
manifests_dir=manifests_dir,
|
113
|
+
headers=config.user.auth_header,
|
114
|
+
fides_key=fides_key,
|
115
|
+
resource_type="dataset",
|
116
|
+
all_resources_file=None,
|
117
|
+
)
|
118
|
+
elif all_resources:
|
119
|
+
# Get all available datasets from server
|
120
|
+
datasets = list_server_resources(
|
121
|
+
url=config.cli.server_url,
|
122
|
+
headers=config.user.auth_header,
|
123
|
+
resource_type="dataset",
|
124
|
+
exclude_keys=[],
|
125
|
+
)
|
126
|
+
|
127
|
+
if not datasets:
|
128
|
+
echo_red("No datasets found on the server.")
|
129
|
+
return
|
130
|
+
|
131
|
+
# Remove null values
|
132
|
+
datasets = [remove_nulls(dataset) for dataset in datasets]
|
133
|
+
|
134
|
+
if separate_files:
|
135
|
+
# Write each dataset to a separate file
|
136
|
+
for dataset in datasets:
|
137
|
+
if "fides_key" in dataset:
|
138
|
+
fides_key = dataset["fides_key"]
|
139
|
+
manifest_path = f"{manifests_dir.rstrip('/')}/{fides_key}.yml"
|
140
|
+
write_manifest_file(manifest_path, {"dataset": [dataset]})
|
141
|
+
else:
|
142
|
+
# Write all datasets to a single file
|
143
|
+
all_datasets_file = f"{manifests_dir.rstrip('/')}/datasets.yml"
|
144
|
+
write_manifest_file(all_datasets_file, {"dataset": datasets})
|
145
|
+
|
146
|
+
echo_green("Pull complete.")
|
83
147
|
|
84
148
|
|
85
149
|
@pull.command(name="system") # type: ignore
|
fides/core/api.py
CHANGED
@@ -4,7 +4,8 @@ from typing import Dict, List, Optional, Union
|
|
4
4
|
|
5
5
|
import requests
|
6
6
|
|
7
|
-
from fides.api.util.endpoint_utils
|
7
|
+
# Not using the constant value from fides.api.util.endpoint_utils to reduce the startup time for the CLI
|
8
|
+
API_PREFIX = "/api/v1"
|
8
9
|
|
9
10
|
|
10
11
|
def generate_resource_url(
|
fides/core/pull.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"""This module handles the logic for syncing remote resource versions into their local file."""
|
2
2
|
|
3
|
-
from typing import Dict, List, Optional
|
3
|
+
from typing import Any, Dict, List, Optional
|
4
4
|
|
5
5
|
import yaml
|
6
6
|
from fideslang import model_list
|
@@ -13,6 +13,30 @@ from fides.core.utils import get_manifest_list
|
|
13
13
|
MODEL_LIST = model_list
|
14
14
|
|
15
15
|
|
16
|
+
def remove_nulls(obj: Any) -> Any:
|
17
|
+
"""
|
18
|
+
Recursively remove all null values from dictionaries and lists.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
obj: Any Python object (dict, list, etc.)
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
The transformed data structure with null values removed
|
25
|
+
"""
|
26
|
+
if isinstance(obj, list):
|
27
|
+
# For lists: process each item and filter out None values
|
28
|
+
return [remove_nulls(item) for item in obj if item is not None]
|
29
|
+
|
30
|
+
if isinstance(obj, dict):
|
31
|
+
# For dictionaries: process each key-value pair and filter out pairs with None values
|
32
|
+
return {
|
33
|
+
key: remove_nulls(value) for key, value in obj.items() if value is not None
|
34
|
+
}
|
35
|
+
|
36
|
+
# Return all other data types unchanged
|
37
|
+
return obj
|
38
|
+
|
39
|
+
|
16
40
|
def write_manifest_file(manifest_path: str, manifest: Dict) -> None:
|
17
41
|
"""
|
18
42
|
Write a manifest file out.
|
@@ -54,6 +78,8 @@ def pull_existing_resources(
|
|
54
78
|
)
|
55
79
|
|
56
80
|
if server_resource:
|
81
|
+
# Remove null values from the server resource
|
82
|
+
server_resource = remove_nulls(server_resource)
|
57
83
|
updated_resource_list.append(server_resource)
|
58
84
|
print(
|
59
85
|
f" - {resource_type.capitalize()} with fides_key: {fides_key} is being updated from the server..."
|
@@ -77,16 +103,17 @@ def pull_resource_by_key(
|
|
77
103
|
) -> None:
|
78
104
|
"""
|
79
105
|
Pull a resource from the server by its fides_key and update the local manifest file if it exists,
|
80
|
-
otherwise a new manifest file at {manifests_dir}/{resource_type}.
|
106
|
+
otherwise a new manifest file at {manifests_dir}/{resource_type}.yml
|
81
107
|
"""
|
82
108
|
if manifests_dir[-1] == "/":
|
83
109
|
manifests_dir = manifests_dir[:-1]
|
84
|
-
manifest_path = f"{manifests_dir}/{
|
85
|
-
print(f"Pulling {resource_type} with fides_key: {fides_key}...", end="
|
110
|
+
manifest_path = f"{manifests_dir}/{fides_key}.yml"
|
111
|
+
print(f"Pulling {resource_type} with fides_key: {fides_key}...", end="\n")
|
86
112
|
server_resource = get_server_resource(url, resource_type, fides_key, headers)
|
87
|
-
print("done.")
|
88
113
|
|
89
114
|
if server_resource:
|
115
|
+
# Remove null values from the server resource
|
116
|
+
server_resource = remove_nulls(server_resource)
|
90
117
|
try:
|
91
118
|
manifest = load_yaml_into_dict(manifest_path)
|
92
119
|
except FileNotFoundError:
|
@@ -94,10 +121,8 @@ def pull_resource_by_key(
|
|
94
121
|
f"Manifest file {manifest_path} does not already exist and will be created"
|
95
122
|
)
|
96
123
|
manifest = {}
|
97
|
-
print("Writing out resource to file...", end=" ")
|
98
124
|
manifest[resource_type] = [server_resource]
|
99
125
|
write_manifest_file(manifest_path, manifest)
|
100
|
-
print("done.")
|
101
126
|
|
102
127
|
else:
|
103
128
|
echo_red(
|
@@ -127,6 +152,12 @@ def pull_missing_resources(
|
|
127
152
|
for resource in MODEL_LIST
|
128
153
|
}
|
129
154
|
|
155
|
+
# Remove null values from all resources
|
156
|
+
for resource_type, resources in resource_manifest.items():
|
157
|
+
resource_manifest[resource_type] = [
|
158
|
+
remove_nulls(resource) for resource in (resources or [])
|
159
|
+
]
|
160
|
+
|
130
161
|
resource_manifest = {
|
131
162
|
key: value for key, value in resource_manifest.items() if value
|
132
163
|
}
|
File without changes
|
@@ -0,0 +1,202 @@
|
|
1
|
+
from functools import wraps
|
2
|
+
from typing import Any, Callable, Optional, TypeVar
|
3
|
+
|
4
|
+
from fastapi import HTTPException
|
5
|
+
from loguru import logger
|
6
|
+
from starlette.status import HTTP_400_BAD_REQUEST, HTTP_422_UNPROCESSABLE_ENTITY
|
7
|
+
|
8
|
+
T = TypeVar("T")
|
9
|
+
|
10
|
+
|
11
|
+
class ErrorHandler:
|
12
|
+
"""Utility class for handling errors consistently throughout the application.
|
13
|
+
|
14
|
+
Usage Examples:
|
15
|
+
-----------------------------------------------------------------------------
|
16
|
+
|
17
|
+
1. Basic Validation:
|
18
|
+
```python
|
19
|
+
from fastapi import FastAPI
|
20
|
+
from fides.service.error_handling.error_handler import ErrorHandler
|
21
|
+
|
22
|
+
app = FastAPI()
|
23
|
+
|
24
|
+
@app.post("/users")
|
25
|
+
def create_user(age: int):
|
26
|
+
# Simple validation
|
27
|
+
ErrorHandler.validate(age >= 18, "User must be 18 or older")
|
28
|
+
return {"message": "User created"}
|
29
|
+
|
30
|
+
@app.get("/items/{item_id}")
|
31
|
+
def get_item(item_id: str):
|
32
|
+
# Direct error raising
|
33
|
+
if not item_id:
|
34
|
+
ErrorHandler.raise_error("Item ID is required", status_code=400)
|
35
|
+
return {"item_id": item_id}
|
36
|
+
```
|
37
|
+
|
38
|
+
2. Exception Handling Decorator:
|
39
|
+
```python
|
40
|
+
@app.post("/orders")
|
41
|
+
@ErrorHandler.handle_exceptions("Failed to create order")
|
42
|
+
def create_order(order_data: dict):
|
43
|
+
if not order_data.get("items"):
|
44
|
+
raise ValueError("Order must contain items")
|
45
|
+
# Process order...
|
46
|
+
return {"message": "Order created"}
|
47
|
+
|
48
|
+
@app.get("/products/{product_id}")
|
49
|
+
@ErrorHandler.handle_exceptions("Failed to fetch product", status_code=404)
|
50
|
+
def get_product(product_id: str):
|
51
|
+
product = database.get_product(product_id)
|
52
|
+
if not product:
|
53
|
+
raise ValueError("Product not found")
|
54
|
+
return product
|
55
|
+
```
|
56
|
+
|
57
|
+
3. Complex Validation:
|
58
|
+
```python
|
59
|
+
@app.post("/payments")
|
60
|
+
@ErrorHandler.handle_exceptions("Payment processing failed")
|
61
|
+
def process_payment(payment: dict):
|
62
|
+
# Validate amount
|
63
|
+
ErrorHandler.validate(
|
64
|
+
payment.get("amount", 0) > 0,
|
65
|
+
"Payment amount must be positive",
|
66
|
+
HTTP_400_BAD_REQUEST
|
67
|
+
)
|
68
|
+
|
69
|
+
# Validate currency
|
70
|
+
ErrorHandler.validate(
|
71
|
+
payment.get("currency") in ["USD", "EUR"],
|
72
|
+
"Invalid currency",
|
73
|
+
HTTP_400_BAD_REQUEST,
|
74
|
+
"Unsupported currency provided" # Optional log message
|
75
|
+
)
|
76
|
+
|
77
|
+
# Custom error for insufficient funds
|
78
|
+
if payment.get("amount", 0) > get_balance():
|
79
|
+
ErrorHandler.raise_error(
|
80
|
+
"Insufficient funds",
|
81
|
+
status_code=HTTP_400_BAD_REQUEST,
|
82
|
+
log_message="User attempted payment exceeding balance"
|
83
|
+
)
|
84
|
+
|
85
|
+
return {"status": "payment processed"}
|
86
|
+
```
|
87
|
+
|
88
|
+
4. Error Logging:
|
89
|
+
```python
|
90
|
+
@app.post("/imports")
|
91
|
+
@ErrorHandler.handle_exceptions("Import failed")
|
92
|
+
def import_data(data: dict):
|
93
|
+
try:
|
94
|
+
process_import(data)
|
95
|
+
except Exception as e:
|
96
|
+
# Log error with custom message before raising
|
97
|
+
ErrorHandler.raise_error(
|
98
|
+
"Import failed: invalid format",
|
99
|
+
status_code=400,
|
100
|
+
log_message=f"Import failed with error: {str(e)}"
|
101
|
+
)
|
102
|
+
return {"status": "import complete"}
|
103
|
+
```
|
104
|
+
|
105
|
+
Key Features:
|
106
|
+
- Simple validation with custom error messages
|
107
|
+
- Consistent error handling across endpoints
|
108
|
+
- Built-in error logging
|
109
|
+
- HTTP status code customization
|
110
|
+
- Exception handling decorator for common patterns
|
111
|
+
"""
|
112
|
+
|
113
|
+
@staticmethod
|
114
|
+
def raise_error(
|
115
|
+
detail: str,
|
116
|
+
status_code: int = HTTP_422_UNPROCESSABLE_ENTITY,
|
117
|
+
log_message: Optional[str] = None,
|
118
|
+
) -> None:
|
119
|
+
"""Raise an HTTPException with consistent logging.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
detail: Error message to include in the HTTPException
|
123
|
+
status_code: HTTP status code to use (default: 422)
|
124
|
+
log_message: Optional message to log before raising the exception
|
125
|
+
|
126
|
+
Raises:
|
127
|
+
HTTPException: Always raised with the provided details
|
128
|
+
"""
|
129
|
+
if log_message:
|
130
|
+
logger.error(log_message)
|
131
|
+
raise HTTPException(status_code=status_code, detail=detail)
|
132
|
+
|
133
|
+
@staticmethod
|
134
|
+
def validate(
|
135
|
+
condition: bool,
|
136
|
+
detail: str,
|
137
|
+
status_code: int = HTTP_400_BAD_REQUEST,
|
138
|
+
log_message: Optional[str] = None,
|
139
|
+
) -> None:
|
140
|
+
"""Validate a condition and raise an error if it's False.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
condition: The condition to check
|
144
|
+
detail: Error message if condition is False
|
145
|
+
status_code: HTTP status code to use if condition is False
|
146
|
+
log_message: Optional message to log before raising the exception
|
147
|
+
|
148
|
+
Raises:
|
149
|
+
HTTPException: If the condition is False
|
150
|
+
"""
|
151
|
+
if not condition:
|
152
|
+
ErrorHandler.raise_error(detail, status_code, log_message)
|
153
|
+
|
154
|
+
@classmethod
|
155
|
+
def handle_exceptions(
|
156
|
+
cls, error_message: str, status_code: int = HTTP_422_UNPROCESSABLE_ENTITY
|
157
|
+
) -> Callable[[Callable[..., T]], Callable[..., T]]:
|
158
|
+
"""Decorator to handle exceptions consistently.
|
159
|
+
|
160
|
+
Args:
|
161
|
+
error_message: Base error message to use
|
162
|
+
status_code: HTTP status code to use for unexpected errors
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
Callable: The decorated function that handles exceptions
|
166
|
+
|
167
|
+
Note:
|
168
|
+
This decorator will catch specific exceptions and convert them to HTTPExceptions.
|
169
|
+
HTTPExceptions are re-raised as is, while other exceptions are wrapped with
|
170
|
+
additional context.
|
171
|
+
"""
|
172
|
+
|
173
|
+
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
174
|
+
@wraps(func)
|
175
|
+
def wrapper(*args: Any, **kwargs: Any) -> Optional[T]:
|
176
|
+
try:
|
177
|
+
return func(*args, **kwargs)
|
178
|
+
except HTTPException:
|
179
|
+
# Re-raise HTTP exceptions without modification
|
180
|
+
raise
|
181
|
+
except (ValueError, TypeError, AttributeError, KeyError) as e:
|
182
|
+
# Handle common validation and data access errors
|
183
|
+
cls.raise_error(
|
184
|
+
f"{error_message}: {str(e)}",
|
185
|
+
status_code,
|
186
|
+
f"{error_message}: {e}",
|
187
|
+
)
|
188
|
+
except Exception as e: # pylint: disable=broad-except
|
189
|
+
# Log unexpected errors with full context but present a sanitized message
|
190
|
+
logger.error(
|
191
|
+
f"Unexpected error in {func.__name__}: {str(e)}", exc_info=True
|
192
|
+
)
|
193
|
+
cls.raise_error(
|
194
|
+
f"{error_message}: An unexpected error occurred",
|
195
|
+
status_code,
|
196
|
+
f"{error_message}: Unexpected {type(e).__name__}",
|
197
|
+
)
|
198
|
+
return None # This line is never reached but satisfies the return type checker
|
199
|
+
|
200
|
+
return wrapper # type: ignore[return-value]
|
201
|
+
|
202
|
+
return decorator
|
File without changes
|
@@ -0,0 +1,140 @@
|
|
1
|
+
import uuid
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import Optional, Tuple
|
4
|
+
|
5
|
+
from loguru import logger
|
6
|
+
from sqlalchemy.orm import Session
|
7
|
+
|
8
|
+
from fides.api.api.v1.endpoints.messaging_endpoints import user_email_invite_status
|
9
|
+
from fides.api.common_exceptions import AuthorizationError
|
10
|
+
from fides.api.models.client import ClientDetail
|
11
|
+
from fides.api.models.fides_user import FidesUser
|
12
|
+
from fides.api.models.fides_user_invite import FidesUserInvite
|
13
|
+
from fides.api.schemas.messaging.messaging import (
|
14
|
+
MessagingActionType,
|
15
|
+
UserInviteBodyParams,
|
16
|
+
)
|
17
|
+
from fides.api.schemas.redis_cache import Identity
|
18
|
+
from fides.api.service.messaging.message_dispatch_service import dispatch_message
|
19
|
+
from fides.config import FidesConfig
|
20
|
+
from fides.config.config_proxy import ConfigProxy
|
21
|
+
|
22
|
+
|
23
|
+
class UserService:
|
24
|
+
def __init__(self, db: Session, config: FidesConfig, config_proxy: ConfigProxy):
|
25
|
+
self.db = db
|
26
|
+
self.config = config
|
27
|
+
self.config_proxy = config_proxy
|
28
|
+
|
29
|
+
def invite_user(self, user: FidesUser) -> None:
|
30
|
+
"""
|
31
|
+
Generates a user invite and sends the invite code to the user via email.
|
32
|
+
|
33
|
+
This is a no-op if email messaging isn't configured.
|
34
|
+
"""
|
35
|
+
|
36
|
+
# invite user via email if email messaging is enabled and the Admin UI URL is defined
|
37
|
+
if user_email_invite_status(db=self.db, config_proxy=self.config_proxy).enabled:
|
38
|
+
invite_code = str(uuid.uuid4())
|
39
|
+
FidesUserInvite.create(
|
40
|
+
db=self.db, data={"username": user.username, "invite_code": invite_code}
|
41
|
+
)
|
42
|
+
user.update(self.db, data={"disabled": True})
|
43
|
+
# TODO: refactor to use MessagingService
|
44
|
+
dispatch_message(
|
45
|
+
self.db,
|
46
|
+
action_type=MessagingActionType.USER_INVITE,
|
47
|
+
to_identity=Identity(email=user.email_address),
|
48
|
+
service_type=self.config_proxy.notifications.notification_service_type,
|
49
|
+
message_body_params=UserInviteBodyParams(
|
50
|
+
username=user.username, invite_code=invite_code
|
51
|
+
),
|
52
|
+
)
|
53
|
+
else:
|
54
|
+
logger.debug(
|
55
|
+
"Skipping invitation email, an email messaging provider is not enabled",
|
56
|
+
)
|
57
|
+
|
58
|
+
def perform_login(
|
59
|
+
self,
|
60
|
+
client_id_byte_length: int,
|
61
|
+
client_secret_byte_length: int,
|
62
|
+
user: FidesUser,
|
63
|
+
skip_save: Optional[bool] = False,
|
64
|
+
) -> ClientDetail:
|
65
|
+
"""Performs a login by updating the FidesUser instance and creating and returning
|
66
|
+
an associated ClientDetail.
|
67
|
+
|
68
|
+
If the username or password was bad, skip_save should be True. We still run through
|
69
|
+
parallel operations to keep the timing of operations similar, but should skip
|
70
|
+
saving to the database.
|
71
|
+
"""
|
72
|
+
|
73
|
+
client = user.client
|
74
|
+
if not client:
|
75
|
+
logger.info("Creating client for login")
|
76
|
+
client, _ = ClientDetail.create_client_and_secret(
|
77
|
+
self.db,
|
78
|
+
client_id_byte_length,
|
79
|
+
client_secret_byte_length,
|
80
|
+
scopes=[], # type: ignore
|
81
|
+
roles=user.permissions.roles, # type: ignore
|
82
|
+
systems=user.system_ids, # type: ignore
|
83
|
+
user_id=user.id,
|
84
|
+
in_memory=skip_save, # If login flow has already errored, don't persist this to the database
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
# Refresh the client just in case - for example, scopes and roles were added via the db directly.
|
88
|
+
client.roles = user.permissions.roles # type: ignore
|
89
|
+
client.systems = user.system_ids # type: ignore
|
90
|
+
if not skip_save:
|
91
|
+
client.save(self.db)
|
92
|
+
|
93
|
+
if user.permissions and (not user.permissions.roles and not user.systems): # type: ignore
|
94
|
+
logger.warning("User {} needs roles or systems to login.", user.id)
|
95
|
+
raise AuthorizationError(detail="Not Authorized for this action")
|
96
|
+
|
97
|
+
if not skip_save:
|
98
|
+
user.last_login_at = datetime.utcnow()
|
99
|
+
user.save(self.db)
|
100
|
+
|
101
|
+
return client
|
102
|
+
|
103
|
+
def accept_invite(
|
104
|
+
self, user: FidesUser, new_password: str
|
105
|
+
) -> Tuple[FidesUser, str]:
|
106
|
+
"""
|
107
|
+
Updates the user password and enables the user. Also removes the user invite from the database.
|
108
|
+
Returns a tuple of the updated user and their access code.
|
109
|
+
"""
|
110
|
+
|
111
|
+
# update password and enable
|
112
|
+
user.update_password(db=self.db, new_password=new_password)
|
113
|
+
user.update(
|
114
|
+
self.db,
|
115
|
+
data={"disabled": False, "disabled_reason": None},
|
116
|
+
)
|
117
|
+
self.db.refresh(user)
|
118
|
+
|
119
|
+
# delete invite
|
120
|
+
if user.username:
|
121
|
+
invite = FidesUserInvite.get_by(
|
122
|
+
db=self.db, field="username", value=user.username
|
123
|
+
)
|
124
|
+
if invite:
|
125
|
+
invite.delete(self.db)
|
126
|
+
else:
|
127
|
+
logger.warning("Username is missing, skipping invite deletion.")
|
128
|
+
|
129
|
+
client = self.perform_login(
|
130
|
+
self.config.security.oauth_client_id_length_bytes,
|
131
|
+
self.config.security.oauth_client_secret_length_bytes,
|
132
|
+
user,
|
133
|
+
)
|
134
|
+
|
135
|
+
logger.info("Creating login access token")
|
136
|
+
access_code = client.create_access_code_jwe(
|
137
|
+
self.config.security.app_encryption_key
|
138
|
+
)
|
139
|
+
|
140
|
+
return user, access_code
|