ethyca-fides 2.58.1rc0__py2.py3-none-any.whl → 2.58.2b0__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.
Files changed (163) hide show
  1. {ethyca_fides-2.58.1rc0.dist-info → ethyca_fides-2.58.2b0.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.58.1rc0.dist-info → ethyca_fides-2.58.2b0.dist-info}/RECORD +159 -154
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/67d01c4e124e_add_reject_all_mechanism_to_privacy_.py +56 -0
  5. fides/api/alembic/migrations/versions/6e565c16dae1_add_tcf_publisher_restrictions.py +107 -0
  6. fides/api/api/deps.py +8 -2
  7. fides/api/cryptography/identity_salt.py +12 -13
  8. fides/api/custom_types.py +6 -1
  9. fides/api/db/base.py +5 -0
  10. fides/api/migrations/hash_migration_job.py +2 -2
  11. fides/api/models/attachment.py +80 -11
  12. fides/api/models/comment.py +45 -15
  13. fides/api/models/privacy_experience.py +42 -0
  14. fides/api/models/privacy_request/privacy_request.py +23 -6
  15. fides/api/models/tcf_publisher_restrictions.py +186 -0
  16. fides/api/schemas/connection_configuration/connection_config.py +30 -16
  17. fides/api/service/storage/s3.py +14 -1
  18. fides/api/task/graph_task.py +1 -1
  19. fides/service/error_handling/__init__.py +0 -0
  20. fides/service/error_handling/error_handler.py +202 -0
  21. fides/ui-build/static/admin/404.html +1 -1
  22. fides/ui-build/static/admin/_next/static/chunks/{1150-035a721a04f4451e.js → 1150-2642cd9cdc8a52f6.js} +1 -1
  23. fides/ui-build/static/admin/_next/static/chunks/1376-98cbf3789b9145c3.js +1 -0
  24. fides/ui-build/static/admin/_next/static/chunks/{2397-ee53235fb21b5e97.js → 2397-0d1c289b788fcc11.js} +1 -1
  25. fides/ui-build/static/admin/_next/static/chunks/3412-da7ad82093c5b879.js +1 -0
  26. fides/ui-build/static/admin/_next/static/chunks/{3855-b6b7865dedd7bc2a.js → 3855-63495367531cb776.js} +1 -1
  27. fides/ui-build/static/admin/_next/static/chunks/{3872-4e053c20d546f027.js → 3872-472bb47eb34d8fdb.js} +1 -1
  28. fides/ui-build/static/admin/_next/static/chunks/{4060-8d165e1236ea521a.js → 4060-53a5c6347690a8fa.js} +1 -1
  29. fides/ui-build/static/admin/_next/static/chunks/{4450-36234280bee624ff.js → 4450-f5e6eb9270f03bf0.js} +1 -1
  30. fides/ui-build/static/admin/_next/static/chunks/{5258-0658dc2274df6832.js → 5258-cf7b27ef51f38392.js} +1 -1
  31. fides/ui-build/static/admin/_next/static/chunks/{5480-f49696df5e8ae500.js → 5480-52dc446be40725f5.js} +1 -1
  32. fides/ui-build/static/admin/_next/static/chunks/{5487-3ad50d21cdbc9209.js → 5487-8678d75ee1d1ef09.js} +1 -1
  33. fides/ui-build/static/admin/_next/static/chunks/{5973-52aee296edc44f7e.js → 5973-88141f9b92f20e0a.js} +1 -1
  34. fides/ui-build/static/admin/_next/static/chunks/{6315-fa1519cdf080f42d.js → 6315-1adb10a8b98b4a13.js} +1 -1
  35. fides/ui-build/static/admin/_next/static/chunks/{6372-ca9c12ac8902365b.js → 6372-e0bb9f8d07cc3b04.js} +1 -1
  36. fides/ui-build/static/admin/_next/static/chunks/{6853-8941824350c3c1a8.js → 6853-8700d87c9e1f6940.js} +1 -1
  37. fides/ui-build/static/admin/_next/static/chunks/{6954-3b887fb444f9228c.js → 6954-85a998d74391caaf.js} +1 -1
  38. fides/ui-build/static/admin/_next/static/chunks/{7453-39761c38da31257e.js → 7453-ef1887105c062d37.js} +1 -1
  39. fides/ui-build/static/admin/_next/static/chunks/{7751-a8f31c062d4cb09d.js → 7751-a70fe0e5f67f5538.js} +1 -1
  40. fides/ui-build/static/admin/_next/static/chunks/{79-f9b948ebb186900f.js → 79-8e060d36d36c752c.js} +1 -1
  41. fides/ui-build/static/admin/_next/static/chunks/{7980-4bd08957448dea32.js → 7980-72f745bff9fabcc9.js} +1 -1
  42. fides/ui-build/static/admin/_next/static/chunks/{9046-04bd7becea207cb1.js → 9046-742ad2bb5108d4c5.js} +1 -1
  43. fides/ui-build/static/admin/_next/static/chunks/{9767-1a23925d2cb27b51.js → 9767-06d9d54a452ec9f4.js} +1 -1
  44. fides/ui-build/static/admin/_next/static/chunks/{main-24f422f93845a596.js → main-090643377c8254e6.js} +1 -1
  45. fides/ui-build/static/admin/_next/static/chunks/{main-app-94a0711202e08b15.js → main-app-59156a9331ac7bce.js} +1 -1
  46. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-fc89ce7bed454c84.js → _app-d4f59c13cb550ff4.js} +1 -1
  47. fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-d258f0c25fa020bf.js → add-systems-fac606150b65d494.js} +1 -1
  48. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-c946b33b0322b8ad.js → privacy-experience-d6909fbd52309a89.js} +1 -1
  49. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-ea57f9d6ad17e957.js → privacy-notices-e7acf6d9d30b1fd9.js} +1 -1
  50. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{reporting-788cf0e34829af46.js → reporting-100234c23a85d235.js} +1 -1
  51. fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-900004e402c31797.js → data-catalog-fffd4942be7760dd.js} +1 -1
  52. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-b66831fdafcdf67c.js → [systemId]-df8512ccd0ea9829.js} +1 -1
  53. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-11b3ce9f61d9bfe9.js → activity-8aeded3289d61405.js} +1 -1
  54. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-803c1b577ab17ae3.js → new-4d4a31d0186a4a5b.js} +1 -1
  55. fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-fa743ddc7f89d76b.js → dataset-29317d18ce34adfe.js} +1 -1
  56. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-bbe1ca2793798e6b.js → [id]-14c57e5069e9cccd.js} +1 -1
  57. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-abc17fef69cd951b.js → new-4e0057c72fac3a9e.js} +1 -1
  58. fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-a78a73b65929853a.js → datastore-connection-60a01ede4d8b56fd.js} +1 -1
  59. fides/ui-build/static/admin/_next/static/chunks/pages/{index-bfaacdb55a5a6c9f.js → index-b70def65e264270e.js} +1 -1
  60. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-528c98747299d138.js +1 -0
  61. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-fe765154315782cf.js → [id]-1bdec4c3e51f5199.js} +1 -1
  62. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{messaging-f5f7a8069909ef24.js → messaging-5e2687ab5ab10275.js} +1 -1
  63. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-9f7eaad05e5b9292.js → storage-2914aade73dcaecc.js} +1 -1
  64. fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-d85c0d16ba09ba35.js → privacy-requests-dea4dd41db4d382b.js} +1 -1
  65. fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-afedc48ef4e7f858.js → datamap-0da40a92590792af.js} +1 -1
  66. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-3ac1e5d3de5dd4a7.js +1 -0
  67. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-52d030b1db2ca1b9.js → custom-fields-dfcd7a4b6aa773bd.js} +1 -1
  68. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-a08693d0d1e10bc8.js → organization-0e0aa552f520f913.js} +1 -1
  69. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-151571cff4e85894.js → test-datasets-1d83d5178b3eb216.js} +1 -1
  70. fides/ui-build/static/admin/_next/static/chunks/pages/{systems-abd68fc5ddde5482.js → systems-8490aaaee9d76a4a.js} +1 -1
  71. fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-16b4d75c49276add.js → taxonomy-be1ffe267b1602e1.js} +1 -1
  72. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-78eaf933f755bfe8.js → [id]-c0378fd1a26a71da.js} +1 -1
  73. fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-6c9ad62479a7d03e.js → user-management-3ca3c687e72d1364.js} +1 -1
  74. fides/ui-build/static/admin/_next/static/{7Gn2YyMsVjWkBPSaVWEi9 → u9w7grMtLxEveFsXqNFab}/_buildManifest.js +1 -1
  75. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  76. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  77. fides/ui-build/static/admin/add-systems.html +1 -1
  78. fides/ui-build/static/admin/ant-poc.html +1 -1
  79. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  80. fides/ui-build/static/admin/consent/configure.html +1 -1
  81. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  82. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  83. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  84. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  85. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  86. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  87. fides/ui-build/static/admin/consent/properties.html +1 -1
  88. fides/ui-build/static/admin/consent/reporting.html +1 -1
  89. fides/ui-build/static/admin/consent.html +1 -1
  90. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  91. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  92. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  93. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  94. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  95. fides/ui-build/static/admin/data-catalog.html +1 -1
  96. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  97. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  98. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  99. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  100. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  101. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  102. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  103. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  104. fides/ui-build/static/admin/datamap.html +1 -1
  105. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  106. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  107. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  108. fides/ui-build/static/admin/dataset/new.html +1 -1
  109. fides/ui-build/static/admin/dataset.html +1 -1
  110. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  111. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  112. fides/ui-build/static/admin/datastore-connection.html +1 -1
  113. fides/ui-build/static/admin/index.html +1 -1
  114. fides/ui-build/static/admin/integrations/[id].html +1 -1
  115. fides/ui-build/static/admin/integrations.html +1 -1
  116. fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
  117. fides/ui-build/static/admin/lib/fides-headless.js +1 -1
  118. fides/ui-build/static/admin/lib/fides-tcf.js +3 -3
  119. fides/ui-build/static/admin/lib/fides.js +2 -2
  120. fides/ui-build/static/admin/login/[provider].html +1 -1
  121. fides/ui-build/static/admin/login.html +1 -1
  122. fides/ui-build/static/admin/messaging/[id].html +1 -1
  123. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  124. fides/ui-build/static/admin/messaging.html +1 -1
  125. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  126. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  127. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  128. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  129. fides/ui-build/static/admin/privacy-requests.html +1 -1
  130. fides/ui-build/static/admin/properties/[id].html +1 -1
  131. fides/ui-build/static/admin/properties/add-property.html +1 -1
  132. fides/ui-build/static/admin/properties.html +1 -1
  133. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  134. fides/ui-build/static/admin/settings/about.html +1 -1
  135. fides/ui-build/static/admin/settings/consent.html +1 -1
  136. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  137. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  138. fides/ui-build/static/admin/settings/domains.html +1 -1
  139. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  140. fides/ui-build/static/admin/settings/locations.html +1 -1
  141. fides/ui-build/static/admin/settings/organization.html +1 -1
  142. fides/ui-build/static/admin/settings/regulations.html +1 -1
  143. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  144. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  145. fides/ui-build/static/admin/systems.html +1 -1
  146. fides/ui-build/static/admin/taxonomy.html +1 -1
  147. fides/ui-build/static/admin/user-management/new.html +1 -1
  148. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  149. fides/ui-build/static/admin/user-management.html +1 -1
  150. fides/ui-build/static/admin/_next/static/chunks/1376-5cea5ef9362215e8.js +0 -1
  151. fides/ui-build/static/admin/_next/static/chunks/3412-7ec8751b8182e1bf.js +0 -1
  152. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-d4329043219fed9b.js +0 -1
  153. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-89524101b7279f6e.js +0 -1
  154. {ethyca_fides-2.58.1rc0.dist-info → ethyca_fides-2.58.2b0.dist-info}/LICENSE +0 -0
  155. {ethyca_fides-2.58.1rc0.dist-info → ethyca_fides-2.58.2b0.dist-info}/WHEEL +0 -0
  156. {ethyca_fides-2.58.1rc0.dist-info → ethyca_fides-2.58.2b0.dist-info}/entry_points.txt +0 -0
  157. {ethyca_fides-2.58.1rc0.dist-info → ethyca_fides-2.58.2b0.dist-info}/top_level.txt +0 -0
  158. /fides/ui-build/static/admin/_next/static/chunks/{4723-0a3c5e2ce143a7d0.js → 4723-1dd1d16f404d56a2.js} +0 -0
  159. /fides/ui-build/static/admin/_next/static/chunks/{5826-e5dcb4e68cfe6289.js → 5826-ef0aa43ffad83acc.js} +0 -0
  160. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-723cc3d4f5740ea6.js → configure-a4e636eecaba5324.js} +0 -0
  161. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-fa42d8f18df44927.js → domain-records-72ec54ee8755a503.js} +0 -0
  162. /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-4f5a28226575c976.js → [id]-8aae66669bdc0883.js} +0 -0
  163. /fides/ui-build/static/admin/_next/static/{7Gn2YyMsVjWkBPSaVWEi9 → u9w7grMtLxEveFsXqNFab}/_ssgManifest.js +0 -0
fides/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-04-02T12:06:35-0600",
11
+ "date": "2025-04-02T16:21:34-0300",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "8acf1908449d28a9d2b040b52b42b44afbc23628",
15
- "version": "2.58.1rc0"
14
+ "full-revisionid": "fe5691256243517ec2cac351e75000e269a4fa50",
15
+ "version": "2.58.2b0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -0,0 +1,56 @@
1
+ """add_reject_all_mechanism_to_privacy_experience_config
2
+
3
+ Revision ID: 67d01c4e124e
4
+ Revises: 1d2f04ac19f2
5
+ Create Date: 2025-03-27 15:26:24.635947
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from sqlalchemy.dialects import postgresql
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "67d01c4e124e"
15
+ down_revision = "1d2f04ac19f2"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+
23
+ # Add the reject all mechanism enum and column
24
+ op.execute(
25
+ "CREATE TYPE rejectallmechanism AS ENUM ('REJECT_ALL', 'REJECT_CONSENT_ONLY')"
26
+ )
27
+ op.add_column(
28
+ "privacyexperienceconfig",
29
+ sa.Column(
30
+ "reject_all_mechanism",
31
+ sa.Enum("REJECT_ALL", "REJECT_CONSENT_ONLY", name="rejectallmechanism"),
32
+ nullable=True,
33
+ ),
34
+ )
35
+ op.add_column(
36
+ "privacyexperienceconfighistory",
37
+ sa.Column(
38
+ "reject_all_mechanism",
39
+ sa.Enum("REJECT_ALL", "REJECT_CONSENT_ONLY", name="rejectallmechanism"),
40
+ nullable=True,
41
+ ),
42
+ )
43
+
44
+ # Data migration - set the reject_all_mechanism for all TCF experience configs to REJECT_ALL
45
+ op.execute(
46
+ "UPDATE privacyexperienceconfig SET reject_all_mechanism = 'REJECT_ALL' WHERE component = 'tcf_overlay'"
47
+ )
48
+ # ### end Alembic commands ###
49
+
50
+
51
+ def downgrade():
52
+ # ### commands auto generated by Alembic - please adjust! ###
53
+ op.drop_column("privacyexperienceconfig", "reject_all_mechanism")
54
+ op.drop_column("privacyexperienceconfighistory", "reject_all_mechanism")
55
+ op.execute("DROP TYPE IF EXISTS rejectallmechanism")
56
+ # ### end Alembic commands ###
@@ -0,0 +1,107 @@
1
+ """Add TCF Publisher Restrictions
2
+
3
+ Revision ID: 6e565c16dae1
4
+ Revises: 67d01c4e124e
5
+ Create Date: 2025-04-02 12:35:34.105607
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from sqlalchemy.dialects import postgresql
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "6e565c16dae1"
15
+ down_revision = "67d01c4e124e"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # Create tcf_configuration table
22
+ op.create_table(
23
+ "tcf_configuration",
24
+ sa.Column("id", sa.String(length=255), nullable=False),
25
+ sa.Column(
26
+ "created_at",
27
+ sa.DateTime(timezone=True),
28
+ server_default=sa.text("now()"),
29
+ nullable=True,
30
+ ),
31
+ sa.Column(
32
+ "updated_at",
33
+ sa.DateTime(timezone=True),
34
+ server_default=sa.text("now()"),
35
+ nullable=True,
36
+ ),
37
+ sa.Column("name", sa.String(), nullable=False),
38
+ sa.PrimaryKeyConstraint("id"),
39
+ sa.UniqueConstraint("name"),
40
+ )
41
+ # Create tcf_publisher_restriction table
42
+ op.create_table(
43
+ "tcf_publisher_restriction",
44
+ sa.Column("id", sa.String(length=255), nullable=False),
45
+ sa.Column(
46
+ "created_at",
47
+ sa.DateTime(timezone=True),
48
+ server_default=sa.text("now()"),
49
+ nullable=True,
50
+ ),
51
+ sa.Column(
52
+ "updated_at",
53
+ sa.DateTime(timezone=True),
54
+ server_default=sa.text("now()"),
55
+ nullable=True,
56
+ ),
57
+ sa.Column("tcf_configuration_id", sa.String(255), nullable=False),
58
+ sa.Column("purpose_id", sa.Integer(), nullable=False),
59
+ sa.Column(
60
+ "restriction_type",
61
+ sa.Enum(
62
+ "purpose_restriction",
63
+ "require_consent",
64
+ "require_legitimate_interest",
65
+ name="tcfrestrictiontype",
66
+ ),
67
+ nullable=False,
68
+ ),
69
+ sa.Column(
70
+ "vendor_restriction",
71
+ sa.Enum(
72
+ "restrict_all_vendors",
73
+ "allow_specific_vendors",
74
+ "restrict_specific_vendors",
75
+ name="tcfvendorrestriction",
76
+ ),
77
+ nullable=False,
78
+ ),
79
+ sa.Column(
80
+ "range_entries",
81
+ postgresql.ARRAY(postgresql.JSONB(astext_type=sa.Text())),
82
+ server_default="{}",
83
+ nullable=False,
84
+ ),
85
+ sa.ForeignKeyConstraint(
86
+ ["tcf_configuration_id"],
87
+ ["tcf_configuration.id"],
88
+ ondelete="CASCADE",
89
+ ),
90
+ sa.PrimaryKeyConstraint("id"),
91
+ )
92
+ op.create_index(
93
+ op.f("ix_tcf_publisher_restriction_config_purpose"),
94
+ "tcf_publisher_restriction",
95
+ ["tcf_configuration_id", "purpose_id"],
96
+ unique=False,
97
+ )
98
+
99
+
100
+ def downgrade():
101
+ # Drop tables
102
+ op.drop_table("tcf_publisher_restriction")
103
+ op.drop_table("tcf_configuration")
104
+
105
+ # Drop enums
106
+ op.execute("DROP TYPE tcfrestrictiontype")
107
+ op.execute("DROP TYPE tcfvendorrestriction")
fides/api/api/deps.py CHANGED
@@ -29,8 +29,14 @@ def get_db() -> Generator:
29
29
 
30
30
 
31
31
  @contextmanager
32
- def get_db_contextmanager() -> Generator[Session, None, None]:
33
- """Return our database session as a context manager"""
32
+ def get_autoclose_db_session() -> Generator[Session, None, None]:
33
+ """
34
+ Return a database session as a context manager that automatically closes when the context exits.
35
+
36
+ Unlike get_api_session which is managed by FastAPI's dependency injection,
37
+ this context manager explicitly closes the session when exiting the context.
38
+ Use this when you need manual control over the session lifecycle outside of API endpoints.
39
+ """
34
40
  try:
35
41
  db = get_api_session()
36
42
  yield db
@@ -3,9 +3,8 @@ from functools import cache
3
3
 
4
4
  from loguru import logger
5
5
 
6
- from fides.api.db.session import get_db_session
6
+ from fides.api.api.deps import get_autoclose_db_session
7
7
  from fides.api.models.identity_salt import IdentitySalt
8
- from fides.config import CONFIG
9
8
 
10
9
 
11
10
  @cache
@@ -15,14 +14,14 @@ def get_identity_salt() -> str:
15
14
  This function is cached to avoid repeated calls to the database for the same value.
16
15
  """
17
16
 
18
- SessionLocal = get_db_session(CONFIG)
19
- db = SessionLocal()
20
- existing_salt = db.query(IdentitySalt).first()
21
- if existing_salt is None:
22
- new_salt = IdentitySalt.create(
23
- db, data={"encrypted_value": {"value": secrets.token_hex(32)}}
24
- )
25
- logger.info("Created new identity salt.")
26
- return new_salt.encrypted_value.get("value")
27
- logger.info("Caching existing identity salt.")
28
- return existing_salt.encrypted_value.get("value")
17
+ with get_autoclose_db_session() as db:
18
+ existing_salt = db.query(IdentitySalt).first()
19
+ if existing_salt is None:
20
+ new_salt = IdentitySalt.create(
21
+ db, data={"encrypted_value": {"value": secrets.token_hex(32)}}
22
+ )
23
+ logger.info("Created new identity salt.")
24
+ return new_salt.encrypted_value.get("value")
25
+
26
+ logger.info("Caching existing identity salt.")
27
+ return existing_salt.encrypted_value.get("value")
fides/api/custom_types.py CHANGED
@@ -66,8 +66,13 @@ def validate_html_str(val: str) -> str:
66
66
  "strong",
67
67
  "u",
68
68
  }
69
+
70
+ ALLOWED_ATTRIBUTES = {
71
+ "a": {"href", "hreflang", "target"},
72
+ }
73
+
69
74
  if val:
70
- return clean(val, tags=ALLOWED_HTML_TAGS)
75
+ return clean(val, tags=ALLOWED_HTML_TAGS, attributes=ALLOWED_ATTRIBUTES)
71
76
  return val
72
77
 
73
78
 
fides/api/db/base.py CHANGED
@@ -8,6 +8,7 @@ from fides.api.models.attachment import Attachment, AttachmentReference
8
8
  from fides.api.models.audit_log import AuditLog
9
9
  from fides.api.models.authentication_request import AuthenticationRequest
10
10
  from fides.api.models.client import ClientDetail
11
+ from fides.api.models.comment import Comment, CommentReference
11
12
  from fides.api.models.connectionconfig import ConnectionConfig
12
13
  from fides.api.models.consent_automation import ConsentAutomation
13
14
  from fides.api.models.custom_asset import CustomAsset
@@ -58,4 +59,8 @@ from fides.api.models.storage import StorageConfig
58
59
  from fides.api.models.system_compass_sync import SystemCompassSync
59
60
  from fides.api.models.system_history import SystemHistory
60
61
  from fides.api.models.system_manager import SystemManager
62
+ from fides.api.models.tcf_publisher_restrictions import (
63
+ TCFConfiguration,
64
+ TCFPublisherRestriction,
65
+ )
61
66
  from fides.api.models.tcf_purpose_overrides import TCFPurposeOverride
@@ -2,7 +2,7 @@ from loguru import logger
2
2
  from sqlalchemy import text
3
3
  from sqlalchemy.orm import Session
4
4
 
5
- from fides.api.api.deps import get_db_contextmanager
5
+ from fides.api.api.deps import get_autoclose_db_session
6
6
  from fides.api.db.base_class import FidesBase
7
7
  from fides.api.migrations.hash_migration_mixin import HashMigrationMixin
8
8
  from fides.api.migrations.hash_migration_tracker import HashMigrationTracker
@@ -47,7 +47,7 @@ def bcrypt_migration_task() -> None:
47
47
  Job to migrate all the tables using bcrypt hashes for general data (excludes tables with credentials).
48
48
  """
49
49
 
50
- with get_db_contextmanager() as db:
50
+ with get_autoclose_db_session() as db:
51
51
  # Do a single pass to check if any of the models have already been migrated.
52
52
  # This will allow us to optimize searching for these models by not calling
53
53
  # the previously used bcrypt hash.
@@ -1,11 +1,13 @@
1
1
  import os
2
2
  from enum import Enum as EnumType
3
- from typing import IO, Any, Optional
3
+ from io import BytesIO
4
+ from typing import IO, TYPE_CHECKING, Any, Optional, Tuple, Union
4
5
 
6
+ from fideslang.validation import AnyHttpUrlString
5
7
  from loguru import logger as log
6
8
  from sqlalchemy import Column
7
9
  from sqlalchemy import Enum as EnumColumn
8
- from sqlalchemy import ForeignKey, String, UniqueConstraint
10
+ from sqlalchemy import ForeignKey, String, UniqueConstraint, orm
9
11
  from sqlalchemy.ext.declarative import declared_attr
10
12
  from sqlalchemy.orm import Session, relationship
11
13
 
@@ -23,6 +25,10 @@ from fides.api.service.storage.util import (
23
25
  get_local_filename,
24
26
  )
25
27
 
28
+ if TYPE_CHECKING:
29
+ from fides.api.models.comment import Comment
30
+ from fides.api.models.privacy_request import PrivacyRequest
31
+
26
32
 
27
33
  class AttachmentType(str, EnumType):
28
34
  """
@@ -53,7 +59,9 @@ class AttachmentReference(Base):
53
59
  """Overriding base class method to set the table name."""
54
60
  return "attachment_reference"
55
61
 
56
- attachment_id = Column(String, ForeignKey("attachment.id"), nullable=False)
62
+ attachment_id = Column(
63
+ String, ForeignKey("attachment.id", ondelete="CASCADE"), nullable=False
64
+ )
57
65
  reference_id = Column(String, nullable=False)
58
66
  reference_type = Column(EnumColumn(AttachmentReferenceType), nullable=False)
59
67
 
@@ -63,11 +71,8 @@ class AttachmentReference(Base):
63
71
  ),
64
72
  )
65
73
 
66
- attachment = relationship(
67
- "Attachment",
68
- back_populates="references",
69
- uselist=False,
70
- )
74
+ # Relationships
75
+ attachment = relationship("Attachment", back_populates="references")
71
76
 
72
77
  @classmethod
73
78
  def create(
@@ -100,8 +105,11 @@ class Attachment(Base):
100
105
  references = relationship(
101
106
  "AttachmentReference",
102
107
  back_populates="attachment",
103
- cascade="all, delete",
108
+ cascade="all, delete-orphan",
104
109
  uselist=True,
110
+ foreign_keys=[AttachmentReference.attachment_id],
111
+ primaryjoin=lambda: Attachment.id
112
+ == orm.foreign(AttachmentReference.attachment_id),
105
113
  )
106
114
 
107
115
  config = relationship(
@@ -127,13 +135,34 @@ class Attachment(Base):
127
135
 
128
136
  if self.config.type == StorageType.local:
129
137
  filename = get_local_filename(self.id)
138
+
139
+ # Validate that attachment is a file-like object
140
+ if not hasattr(attachment, "read"):
141
+ raise TypeError(f"Expected a file-like object, got {type(attachment)}")
142
+
143
+ # Reset the file pointer to the beginning
144
+ try:
145
+ attachment.seek(0)
146
+ except Exception as e:
147
+ raise ValueError(f"Failed to reset file pointer for attachment: {e}")
148
+
149
+ # Write the file in chunks to avoid loading the entire content into memory
130
150
  with open(filename, "wb") as file:
131
- file.write(attachment.read())
151
+ for chunk in iter(
152
+ lambda: attachment.read(1024 * 1024), b""
153
+ ): # 1 MB chunks
154
+ if not isinstance(chunk, bytes):
155
+ raise TypeError(f"Expected bytes, got {type(chunk)}")
156
+ file.write(chunk)
157
+
158
+ log.info(f"Uploaded {self.file_name} to local storage at {filename}")
132
159
  return
133
160
 
134
161
  raise ValueError(f"Unsupported storage type: {self.config.type}")
135
162
 
136
- def retrieve_attachment(self) -> Optional[bytes]:
163
+ def retrieve_attachment(
164
+ self,
165
+ ) -> Optional[Tuple[BytesIO, Union[AnyHttpUrlString, str]]]:
137
166
  """Returns the attachment from S3 in bytes form."""
138
167
  if self.config.type == StorageType.s3:
139
168
  bucket_name = f"{self.config.details[StorageDetails.BUCKET.value]}"
@@ -208,4 +237,44 @@ class Attachment(Base):
208
237
  def delete(self, db: Session) -> None:
209
238
  """Deletes an attachment record from the database and deletes the attachment from S3."""
210
239
  self.delete_attachment_from_storage()
240
+ for attachment_reference in self.references:
241
+ attachment_reference.delete(db)
211
242
  super().delete(db=db)
243
+
244
+ @staticmethod
245
+ def delete_attachments_for_reference_and_type(
246
+ db: Session, reference_id: str, reference_type: AttachmentReferenceType
247
+ ) -> None:
248
+ """
249
+ Deletes attachments associated with a given reference_id and reference_type.
250
+ Deletes all references to the attachments.
251
+
252
+ Args:
253
+ db: Database session
254
+ reference_id: ID of the reference
255
+ reference_type: Type of the reference
256
+
257
+ Examples:
258
+
259
+ - Delete all attachments associated with a comment.
260
+ ``Attachment.delete_attachments_for_reference_and_type(
261
+ db, comment.id, AttachmentReferenceType.comment
262
+ )``
263
+ - Delete all attachments associated with a privacy request.
264
+ ``Attachment.delete_attachments_for_reference_and_type(
265
+ db, privacy_request.id, AttachmentReferenceType.privacy_request
266
+ )``
267
+ """
268
+ # Query attachments explicitly to avoid lazy loading
269
+ attachments = (
270
+ db.query(Attachment)
271
+ .join(AttachmentReference)
272
+ .filter(
273
+ AttachmentReference.reference_id == reference_id,
274
+ AttachmentReference.reference_type == reference_type,
275
+ )
276
+ .all()
277
+ )
278
+
279
+ for attachment in attachments:
280
+ attachment.delete(db)
@@ -3,15 +3,17 @@ from typing import TYPE_CHECKING, Any
3
3
 
4
4
  from sqlalchemy import Column
5
5
  from sqlalchemy import Enum as EnumColumn
6
- from sqlalchemy import ForeignKey, String, UniqueConstraint
6
+ from sqlalchemy import ForeignKey, String, UniqueConstraint, orm
7
7
  from sqlalchemy.ext.declarative import declared_attr
8
8
  from sqlalchemy.orm import Session, relationship
9
9
 
10
10
  from fides.api.db.base_class import Base
11
+ from fides.api.models.attachment import Attachment, AttachmentReferenceType
11
12
 
12
13
  if TYPE_CHECKING:
13
- from fides.api.models.attachment import Attachment
14
+ from fides.api.models.attachment import AttachmentReference
14
15
  from fides.api.models.fides_user import FidesUser
16
+ from fides.api.models.privacy_request import PrivacyRequest
15
17
 
16
18
 
17
19
  class CommentType(str, EnumType):
@@ -87,23 +89,51 @@ class Comment(Base):
87
89
  references = relationship(
88
90
  "CommentReference",
89
91
  back_populates="comment",
90
- cascade="all, delete",
91
- uselist=True,
92
- )
93
-
94
- attachments = relationship(
95
- "Attachment",
96
- secondary="attachment_reference",
97
- primaryjoin="Comment.id == AttachmentReference.reference_id",
98
- secondaryjoin="Attachment.id == AttachmentReference.attachment_id",
99
- order_by="Attachment.created_at",
92
+ cascade="all, delete-orphan",
100
93
  uselist=True,
94
+ foreign_keys=[CommentReference.comment_id],
95
+ primaryjoin=lambda: Comment.id == orm.foreign(CommentReference.comment_id),
101
96
  )
102
97
 
103
98
  def delete(self, db: Session) -> None:
104
99
  """Delete the comment and all associated references."""
105
100
  # Delete the comment
106
- for attachment in self.attachments:
107
- if len(attachment.references) == 1:
108
- attachment.delete(db)
101
+ Attachment.delete_attachments_for_reference_and_type(
102
+ db, self.id, AttachmentReferenceType.comment
103
+ )
104
+ for reference in self.references:
105
+ reference.delete(db)
109
106
  db.delete(self)
107
+
108
+ @staticmethod
109
+ def delete_comments_for_reference_and_type(
110
+ db: Session, reference_id: str, reference_type: CommentReferenceType
111
+ ) -> None:
112
+ """
113
+ Deletes comments associated with a given reference_id and reference_type.
114
+ Delete all references to the comments.
115
+
116
+ Args:
117
+ db: Database session.
118
+ reference_id: The reference id to delete.
119
+ reference_type: The reference type to delete
120
+
121
+ Examples:
122
+ - Delete all comments associated with a privacy request.
123
+ ``Comment.delete_comments_for_reference_and_type(
124
+ db, privacy_request.id, CommentReferenceType.privacy_request
125
+ )``
126
+ """
127
+ # Query comments explicitly to avoid lazy loading
128
+ comments = (
129
+ db.query(Comment)
130
+ .join(CommentReference)
131
+ .filter(
132
+ CommentReference.reference_id == reference_id,
133
+ CommentReference.reference_type == reference_type,
134
+ )
135
+ .all()
136
+ )
137
+
138
+ for comment in comments:
139
+ comment.delete(db)
@@ -46,6 +46,21 @@ class Layer1ButtonOption(Enum):
46
46
 
47
47
  ACKNOWLEDGE = "acknowledge"
48
48
  OPT_IN_OPT_OUT = "opt_in_opt_out"
49
+ OPT_IN_ONLY = "opt_in_only"
50
+
51
+
52
+ class RejectAllMechanism(Enum):
53
+ """
54
+ Reject all mechanism options - not formalized in the db.
55
+ Used to configure the behavior of the reject all button in TCF experiences
56
+ """
57
+
58
+ # Reject both consent and legitimate interest preferences (all purposes, special features, vendors)
59
+ # This is the default behavior
60
+ REJECT_ALL = "reject_all"
61
+ # Reject only consent preferences (all purposes, special features, vendors).
62
+ # Do not reject any legitimate interest preferences.
63
+ REJECT_CONSENT_ONLY = "reject_consent_only"
49
64
 
50
65
 
51
66
  # Fides JS UX Types - there should only be one of these defined per region
@@ -179,6 +194,10 @@ class PrivacyExperienceConfig(PrivacyExperienceConfigBase, Base):
179
194
  The Privacy Experience Configuration model that stores shared configuration for Privacy Experiences.
180
195
 
181
196
  - Translations, Notices, and Regions (via Privacy Experiences) are linked to this resource.
197
+
198
+ If you're adding a new PrivacyExperienceConfig, make sure to use the `create` method since it has
199
+ custom logic that ensures other resources are created/updated as needed, as well as setting the
200
+ expected values for some fields in the experience config itself.
182
201
  """
183
202
 
184
203
  allow_language_selection = Column(
@@ -203,6 +222,13 @@ class PrivacyExperienceConfig(PrivacyExperienceConfigBase, Base):
203
222
  String, ForeignKey(ExperienceConfigTemplate.id_field_path)
204
223
  ) # The template from which this config was created if applicable
205
224
 
225
+ # Mechanism to use when the reject all button is clicked in a TCF experience
226
+ # Nullable because this is not applicable for other experience types
227
+ reject_all_mechanism = Column(
228
+ EnumColumn(RejectAllMechanism),
229
+ nullable=True,
230
+ )
231
+
206
232
  # Relationships
207
233
  experiences = relationship(
208
234
  "PrivacyExperience",
@@ -304,6 +330,15 @@ class PrivacyExperienceConfig(PrivacyExperienceConfigBase, Base):
304
330
  # Link Properties to this Privacy Experience config via the PrivacyExperienceConfigProperty table
305
331
  link_properties_to_experience_config(db, properties, experience_config)
306
332
 
333
+ # If the reject all mechanism is not set and the experience config is a TCF experience,
334
+ # set the reject all mechanism to REJECT_ALL
335
+ if (
336
+ experience_config.component == ComponentType.tcf_overlay
337
+ and experience_config.reject_all_mechanism is None
338
+ ):
339
+ experience_config.reject_all_mechanism = RejectAllMechanism.REJECT_ALL # type: ignore
340
+ experience_config.save(db)
341
+
307
342
  return experience_config
308
343
 
309
344
  def update(self, db: Session, *, data: dict[str, Any]) -> PrivacyExperienceConfig:
@@ -507,6 +542,13 @@ class PrivacyExperienceConfigHistory(
507
542
  index=True,
508
543
  ) # If a translation is deleted, this is set to null, but the overall record remains in the database for reporting purposes
509
544
 
545
+ # Mechanism to use when the reject all button is clicked in a TCF experience
546
+ # Nullable because this is not applicable for other experience types
547
+ reject_all_mechanism = Column(
548
+ EnumColumn(RejectAllMechanism),
549
+ nullable=True,
550
+ )
551
+
510
552
  version = Column(Float, nullable=False, default=1.0)
511
553
 
512
554
 
@@ -39,10 +39,14 @@ from fides.api.graph.config import (
39
39
  CollectionAddress,
40
40
  )
41
41
  from fides.api.migrations.hash_migration_mixin import HashMigrationMixin
42
- from fides.api.models.attachment import Attachment, AttachmentReference
42
+ from fides.api.models.attachment import (
43
+ Attachment,
44
+ AttachmentReference,
45
+ AttachmentReferenceType,
46
+ )
43
47
  from fides.api.models.audit_log import AuditLog
44
48
  from fides.api.models.client import ClientDetail
45
- from fides.api.models.comment import Comment, CommentReference
49
+ from fides.api.models.comment import Comment, CommentReference, CommentReferenceType
46
50
  from fides.api.models.fides_user import FidesUser
47
51
  from fides.api.models.manual_webhook import AccessManualWebhook
48
52
  from fides.api.models.policy import (
@@ -169,18 +173,25 @@ class PrivacyRequest(
169
173
  backref="privacy_requests",
170
174
  )
171
175
  attachments = relationship(
172
- Attachment,
176
+ "Attachment",
173
177
  secondary="attachment_reference",
174
- primaryjoin="PrivacyRequest.id == AttachmentReference.reference_id",
178
+ primaryjoin="and_(PrivacyRequest.id == AttachmentReference.reference_id, "
179
+ "AttachmentReference.reference_type == 'privacy_request')",
175
180
  secondaryjoin="Attachment.id == AttachmentReference.attachment_id",
176
181
  order_by="Attachment.created_at",
182
+ viewonly=True,
183
+ uselist=True,
177
184
  )
185
+
178
186
  comments = relationship(
179
- Comment,
187
+ "Comment",
180
188
  secondary="comment_reference",
181
- primaryjoin="PrivacyRequest.id == CommentReference.reference_id",
189
+ primaryjoin="and_(PrivacyRequest.id == CommentReference.reference_id, "
190
+ "CommentReference.reference_type == 'privacy_request')",
182
191
  secondaryjoin="Comment.id == CommentReference.comment_id",
183
192
  order_by="Comment.created_at",
193
+ viewonly=True,
194
+ uselist=True,
184
195
  )
185
196
  property_id = Column(String, nullable=True)
186
197
 
@@ -323,6 +334,12 @@ class PrivacyRequest(
323
334
  deleting this object from the database
324
335
  """
325
336
  self.clear_cached_values()
337
+ Attachment.delete_attachments_for_reference_and_type(
338
+ db, self.id, AttachmentReferenceType.privacy_request
339
+ )
340
+ Comment.delete_comments_for_reference_and_type(
341
+ db, self.id, CommentReferenceType.privacy_request
342
+ )
326
343
 
327
344
  for provided_identity in self.provided_identities: # type: ignore[attr-defined]
328
345
  provided_identity.delete(db=db)