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
@@ -0,0 +1,186 @@
1
+ from enum import Enum
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from pydantic import BaseModel, Field, ValidationError, model_validator
5
+ from sqlalchemy import Column
6
+ from sqlalchemy import Enum as EnumColumn
7
+ from sqlalchemy import ForeignKey, Index, Integer, String
8
+ from sqlalchemy.dialects.postgresql import ARRAY, JSONB
9
+ from sqlalchemy.ext.declarative import declared_attr
10
+ from sqlalchemy.orm import Session
11
+
12
+ from fides.api.db.base_class import Base
13
+
14
+
15
+ class TCFRestrictionType(str, Enum):
16
+ """Enum for TCF restriction types"""
17
+
18
+ purpose_restriction = "purpose_restriction"
19
+ require_consent = "require_consent"
20
+ require_legitimate_interest = "require_legitimate_interest"
21
+
22
+
23
+ class TCFVendorRestriction(str, Enum):
24
+ """Enum for TCF vendor restriction types"""
25
+
26
+ restrict_all_vendors = "restrict_all_vendors"
27
+ allow_specific_vendors = "allow_specific_vendors"
28
+ restrict_specific_vendors = "restrict_specific_vendors"
29
+
30
+
31
+ # This is Pydantic model used for validation, not a database model!
32
+ class RangeEntry(BaseModel):
33
+ """
34
+ Pydantic model that represents a vendor range entry as per the TCF spec,
35
+ used for Publisher Restrictions.
36
+ A range entry must have a start_vendor_id and optionally an end_vendor_id.
37
+ If end_vendor_id is present, it must be greater than start_vendor_id.
38
+ """
39
+
40
+ start_vendor_id: int = Field(description="The starting vendor ID in the range")
41
+ end_vendor_id: Optional[int] = Field(
42
+ default=None, description="The ending vendor ID in the range (inclusive)"
43
+ )
44
+
45
+ @model_validator(mode="after")
46
+ def validate_vendor_range(self) -> "RangeEntry":
47
+ """Validates that end_vendor_id is greater than start_vendor_id if present."""
48
+ if (
49
+ self.end_vendor_id is not None
50
+ and self.end_vendor_id <= self.start_vendor_id
51
+ ):
52
+ raise ValueError("end_vendor_id must be greater than start_vendor_id")
53
+ return self
54
+
55
+ def get_end(self) -> int:
56
+ """Get the effective end of the range."""
57
+ return self.end_vendor_id or self.start_vendor_id
58
+
59
+ def overlaps_with(self, other: "RangeEntry") -> bool:
60
+ """
61
+ Check if this range overlaps with another range.
62
+ Two ranges overlap if the end of the range that starts first is greater than
63
+ or equal to the start of the range that starts second.
64
+ """
65
+ # Sort ranges by start_vendor_id
66
+ first = self if self.start_vendor_id <= other.start_vendor_id else other
67
+ second = other if self.start_vendor_id <= other.start_vendor_id else self
68
+
69
+ # If first range's end is >= second range's start, they overlap
70
+ return first.get_end() >= second.start_vendor_id
71
+
72
+
73
+ class TCFConfiguration(Base):
74
+ """
75
+ Stores TCF Configuration settings.
76
+ """
77
+
78
+ @declared_attr
79
+ def __tablename__(self) -> str:
80
+ return "tcf_configuration"
81
+
82
+ name = Column(String, nullable=False, index=True, unique=True)
83
+
84
+
85
+ class TCFPublisherRestriction(Base):
86
+ """
87
+ Stores TCF Publisher Restrictions. TCF Publisher Restrictions belong to a TCF Configuration,
88
+ and specify the restriction type and vendor restriction for a given purpose.
89
+ """
90
+
91
+ @declared_attr
92
+ def __tablename__(self) -> str:
93
+ return "tcf_publisher_restriction"
94
+
95
+ tcf_configuration_id = Column(
96
+ String(255),
97
+ ForeignKey("tcf_configuration.id", ondelete="CASCADE"),
98
+ nullable=False,
99
+ index=True,
100
+ )
101
+ purpose_id = Column(Integer, nullable=False)
102
+ restriction_type = Column(EnumColumn(TCFRestrictionType), nullable=False)
103
+ vendor_restriction = Column(EnumColumn(TCFVendorRestriction), nullable=False)
104
+
105
+ # range_entries represents a list of RangeEntry objects as per the TCF spec.
106
+ # A RangeEntry object will have a start_vendor_id and an end_vendor_id (the
107
+ # end vendor id is included in the range).
108
+ # If the range represents a single vendor, then the end_vendor_id can be omitted.
109
+ # TCF spec also includes an IsARange boolean, which we are omitting because it
110
+ # can be inferred from the presence of the end_vendor_id.
111
+ range_entries = Column(
112
+ ARRAY(JSONB),
113
+ nullable=True,
114
+ server_default="{}",
115
+ default=list,
116
+ )
117
+
118
+ __table_args__ = (
119
+ Index(
120
+ "ix_tcf_publisher_restriction_config_purpose",
121
+ "tcf_configuration_id",
122
+ "purpose_id",
123
+ ),
124
+ )
125
+
126
+ @staticmethod
127
+ def validate_entires_for_vendor_restriction(
128
+ entries: List[RangeEntry], vendor_restriction: TCFVendorRestriction
129
+ ) -> None:
130
+ """
131
+ Validates that if vendor_restriction is restrict_all_vendors, then the entries list is empty.
132
+ """
133
+ if vendor_restriction == TCFVendorRestriction.restrict_all_vendors and entries:
134
+ raise ValueError("restrict_all_vendors cannot have any range entries")
135
+
136
+ @staticmethod
137
+ def check_for_overlaps(entries: List[RangeEntry]) -> None:
138
+ """
139
+ Check for overlapping ranges in a list of RangeEntry objects,
140
+ raises a ValueError if any pair of RangeEntry overlaps with each other.
141
+ Sorts the ranges by start_vendor_id and compares adjacent ranges.
142
+ """
143
+ # Sort ranges by start_vendor_id
144
+ sorted_entries = sorted(entries, key=lambda x: x.start_vendor_id)
145
+
146
+ # Compare each range with the next one
147
+ for i in range(len(sorted_entries) - 1):
148
+ current = sorted_entries[i]
149
+ next_entry = sorted_entries[i + 1]
150
+ if current.overlaps_with(next_entry):
151
+ raise ValueError(
152
+ f"Overlapping ranges found: {current.model_dump()} overlaps with {next_entry.model_dump()}"
153
+ )
154
+
155
+ @classmethod
156
+ def create(
157
+ cls,
158
+ db: Session,
159
+ *,
160
+ data: Dict[str, Any],
161
+ check_name: bool = True,
162
+ ) -> "TCFPublisherRestriction":
163
+ """
164
+ Create a new TCFPublisherRestriction with validated range_entries.
165
+ Validates each range entry using the RangeEntry Pydantic model.
166
+ """
167
+ raw_range_entries = data.get("range_entries", [])
168
+
169
+ try:
170
+ # Validate each range entry using the Pydantic model
171
+ validated_entries = [
172
+ RangeEntry.model_validate(entry) for entry in raw_range_entries
173
+ ]
174
+ except ValidationError as e:
175
+ raise ValueError(f"Invalid range entry: {str(e)}")
176
+
177
+ # Validate the entries for the vendor restriction
178
+ cls.validate_entires_for_vendor_restriction(
179
+ validated_entries, data["vendor_restriction"]
180
+ )
181
+ # Check for overlapping ranges
182
+ cls.check_for_overlaps(validated_entries)
183
+
184
+ data["range_entries"] = [entry.model_dump() for entry in validated_entries]
185
+
186
+ return super().create(db=db, data=data, check_name=check_name)
@@ -71,29 +71,23 @@ def mask_sensitive_fields(
71
71
  return new_connection_secrets
72
72
 
73
73
 
74
- class ConnectionConfigurationResponse(BaseModel):
74
+ class ConnectionConfigSecretsMixin(BaseModel):
75
75
  """
76
- Describes the returned schema for a ConnectionConfiguration.
76
+ A schema mixin to declare a connection config `secrets` attribute
77
+ and handle masking of sensitive values based on `connection_type`
78
+ and (optionally) a `saas_config`.
77
79
  """
78
80
 
79
- name: Optional[str] = None
80
- key: FidesKey
81
- description: Optional[str] = None
82
81
  connection_type: ConnectionType
83
- access: AccessLevel
84
- created_at: datetime
85
- updated_at: Optional[datetime] = None
86
- disabled: Optional[bool] = False
87
- last_test_timestamp: Optional[datetime] = None
88
- last_test_succeeded: Optional[bool] = None
89
- saas_config: Optional[SaaSConfigBase] = None
90
82
  secrets: Optional[Dict[str, Any]] = None
91
- authorized: Optional[bool] = False
92
- enabled_actions: Optional[List[ActionType]] = None
83
+ saas_config: Optional[SaaSConfigBase] = None
93
84
 
94
85
  @model_validator(mode="after")
95
- def mask_sensitive_values(self) -> "ConnectionConfigurationResponse":
96
- """Mask sensitive values in the response."""
86
+ def mask_sensitive_values(self) -> "ConnectionConfigSecretsMixin":
87
+ """
88
+ Mask sensitive values in the `secrets` attribute based on `connection_type`
89
+ and (optionally) a `saas_config`.
90
+ """
97
91
  if self.secrets is None:
98
92
  return self
99
93
 
@@ -116,6 +110,26 @@ class ConnectionConfigurationResponse(BaseModel):
116
110
  self.secrets = mask_sensitive_fields(cast(dict, self.secrets), secret_schema)
117
111
  return self
118
112
 
113
+
114
+ class ConnectionConfigurationResponse(ConnectionConfigSecretsMixin):
115
+ """
116
+ Describes the returned schema for a ConnectionConfiguration.
117
+
118
+ The mixin base class ensures that `secrets` sensitive values are masked.
119
+ """
120
+
121
+ name: Optional[str] = None
122
+ key: FidesKey
123
+ description: Optional[str] = None
124
+ access: AccessLevel
125
+ created_at: datetime
126
+ updated_at: Optional[datetime] = None
127
+ disabled: Optional[bool] = False
128
+ last_test_timestamp: Optional[datetime] = None
129
+ last_test_succeeded: Optional[bool] = None
130
+ authorized: Optional[bool] = False
131
+ enabled_actions: Optional[List[ActionType]] = None
132
+
119
133
  model_config = ConfigDict(from_attributes=True)
120
134
 
121
135
 
@@ -64,7 +64,7 @@ def generic_upload_to_s3( # pylint: disable=R0913
64
64
  size_threshold: int = LARGE_FILE_THRESHOLD, # 5 MB threshold
65
65
  ) -> Optional[AnyHttpUrlString]:
66
66
  """
67
- Uploads arbitrary data to S3 returned from an access request.
67
+ Uploads file like objects to S3.
68
68
  Handles both small and large uploads.
69
69
 
70
70
  :param storage_secrets: S3 storage secrets
@@ -75,6 +75,19 @@ def generic_upload_to_s3( # pylint: disable=R0913
75
75
  """
76
76
  logger.info("Starting S3 Upload of {}", file_key)
77
77
 
78
+ # Validate that the document is a file-like object
79
+ if not hasattr(document, "read") or not hasattr(document, "seek"):
80
+ raise TypeError(
81
+ f"The 'document' parameter must be a file-like object supporting 'read' and 'seek'. "
82
+ f"Received: {type(document)}"
83
+ )
84
+
85
+ # Ensure the file pointer is at the beginning
86
+ try:
87
+ document.seek(0)
88
+ except Exception as e:
89
+ raise ValueError(f"Failed to reset file pointer for document: {e}")
90
+
78
91
  s3_client = maybe_get_s3_client(auth_method, storage_secrets)
79
92
 
80
93
  # Define a transfer configuration for multipart uploads
@@ -10,7 +10,7 @@ from loguru import logger
10
10
  from ordered_set import OrderedSet
11
11
  from sqlalchemy.orm import Session
12
12
 
13
- from fides.api.api.deps import get_db_contextmanager as get_db
13
+ from fides.api.api.deps import get_autoclose_db_session as get_db
14
14
  from fides.api.common_exceptions import (
15
15
  ActionDisabled,
16
16
  AwaitingAsyncTaskCallback,
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
@@ -1 +1 @@
1
- <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link data-next-font="" rel="preconnect" href="/" crossorigin="anonymous"/><link rel="preload" href="/_next/static/css/113d823fe71f6af0.css" as="style"/><link rel="stylesheet" href="/_next/static/css/113d823fe71f6af0.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-2c7ccac5843c4d8e.js" defer=""></script><script src="/_next/static/chunks/framework-c92fc3344e6fd165.js" defer=""></script><script src="/_next/static/chunks/main-24f422f93845a596.js" defer=""></script><script src="/_next/static/chunks/pages/_app-fc89ce7bed454c84.js" defer=""></script><script src="/_next/static/chunks/pages/404-b202c0d8f6fc75c3.js" defer=""></script><script src="/_next/static/7Gn2YyMsVjWkBPSaVWEi9/_buildManifest.js" defer=""></script><script src="/_next/static/7Gn2YyMsVjWkBPSaVWEi9/_ssgManifest.js" defer=""></script><style>.data-ant-cssinjs-cache-path{content:"";}</style></head><body><div id="__next"><div style="height:100%;display:flex"></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/404","query":{},"buildId":"7Gn2YyMsVjWkBPSaVWEi9","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link data-next-font="" rel="preconnect" href="/" crossorigin="anonymous"/><link rel="preload" href="/_next/static/css/113d823fe71f6af0.css" as="style"/><link rel="stylesheet" href="/_next/static/css/113d823fe71f6af0.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-2c7ccac5843c4d8e.js" defer=""></script><script src="/_next/static/chunks/framework-c92fc3344e6fd165.js" defer=""></script><script src="/_next/static/chunks/main-090643377c8254e6.js" defer=""></script><script src="/_next/static/chunks/pages/_app-d4f59c13cb550ff4.js" defer=""></script><script src="/_next/static/chunks/pages/404-b202c0d8f6fc75c3.js" defer=""></script><script src="/_next/static/u9w7grMtLxEveFsXqNFab/_buildManifest.js" defer=""></script><script src="/_next/static/u9w7grMtLxEveFsXqNFab/_ssgManifest.js" defer=""></script><style>.data-ant-cssinjs-cache-path{content:"";}</style></head><body><div id="__next"><div style="height:100%;display:flex"></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/404","query":{},"buildId":"u9w7grMtLxEveFsXqNFab","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1150],{7617:function(e,s,t){t.d(s,{q:function(){return a}});var i=t(24246),n=t(77181);let a=e=>{let{label:s,isDisabled:t,...a}=e;return(0,i.jsx)(n.OK9,{"data-testid":"tab-".concat(s),_selected:{fontWeight:"600",color:"complimentary.500",borderColor:"complimentary.500"},fontSize:a.fontSize,fontWeight:"500",color:"gray.500",isDisabled:t||!1,children:s})};s.Z=e=>{let{data:s,border:t="partial",...l}=e;return(0,i.jsxs)(n.mQc,{colorScheme:"complimentary",...l,children:[(0,i.jsx)(n.tdY,{width:"partial"===t?"max-content":void 0,children:s.map(e=>(0,i.jsx)(a,{label:e.label,isDisabled:e.isDisabled,fontSize:l.fontSize},e.label))}),(0,i.jsx)(n.nPR,{children:s.map(e=>(0,i.jsx)(n.x45,{px:0,"data-testid":"tab-panel-".concat(e.label),children:e.content},e.label))})]})}},58452:function(e,s,t){var i=t(24246),n=t(77181);s.Z=e=>{let{isOpen:s,onClose:t,onConfirm:a,onCancel:l,title:r,message:d,cancelButtonText:o,continueButtonText:c,isLoading:h,returnFocusOnClose:x,isCentered:m,testId:u="confirmation-modal",icon:p}=e;return(0,i.jsxs)(n.u_l,{isOpen:s,onClose:t,size:"lg",returnFocusOnClose:null==x||x,isCentered:m,children:[(0,i.jsx)(n.ZAr,{}),(0,i.jsxs)(n.hzk,{textAlign:"center",p:6,"data-testid":u,children:[p?(0,i.jsx)(n.M5Y,{mb:2,children:p}):null,r?(0,i.jsx)(n.xBx,{fontWeight:"medium",pb:0,children:r}):null,d?(0,i.jsx)(n.fef,{children:d}):null,(0,i.jsx)(n.mzw,{children:(0,i.jsxs)(n.MIq,{columns:2,width:"100%",children:[(0,i.jsx)(n.wpx,{onClick:()=>{l&&l(),t()},size:"large",className:"mr-3","data-testid":"cancel-btn",disabled:h,children:o||"Cancel"}),(0,i.jsx)(n.wpx,{type:"primary",size:"large",onClick:a,"data-testid":"continue-btn",loading:h,children:c||"Continue"})]})})]})]})}},41150:function(e,s,t){t.d(s,{Z:function(){return H}});var i=t(24246),n=t(77181),a=t(91437),l=()=>(0,i.jsxs)(n.xuv,{children:[(0,i.jsx)(n.xuv,{pb:4,fontSize:"18px",fontWeight:"semibold",children:"Role Description"}),(0,i.jsx)(n.gCW,{spacing:4,children:a.K.map(e=>(0,i.jsxs)(n.xuv,{width:"100%",padding:4,borderRadius:"md",backgroundColor:"gray.75",fontSize:"14px",children:[(0,i.jsx)(n.xuv,{fontWeight:"semibold",children:e.label}),(0,i.jsx)(n.xuv,{color:"gray.700",children:e.description})]},e.roleKey))})]}),r=t(16134),d=t(7617),o=t(21702),c=t(19904),h=t(34090),x=t(86677),m=t(27378),u=t(812),p=t(58452),j=t(77830),w=t(91613),g=t(46628),f=t(86780),b=t(1315),y=t(95492),v=t(65497);let C=e=>{let{assignedSystems:s,onAssignedSystemChange:t}=e,a=(0,r.C)(v.Ux);if((0,v.d6)(a,{skip:!a}),0===s.length)return null;let l=e=>{t(s.filter(s=>s.fides_key!==e.fides_key))};return(0,i.jsxs)(n.iA_,{size:"sm","data-testid":"assign-systems-delete-table",children:[(0,i.jsx)(n.hrZ,{children:(0,i.jsxs)(n.Tr,{children:[(0,i.jsx)(n.Th,{children:"System"}),(0,i.jsx)(n.Th,{})]})}),(0,i.jsx)(n.p3B,{children:s.map(e=>(0,i.jsxs)(n.Tr,{_hover:{bg:"gray.50"},"data-testid":"row-".concat(e.fides_key),children:[(0,i.jsx)(n.Td,{children:e.name}),(0,i.jsx)(n.Td,{textAlign:"end",children:(0,i.jsx)(n.wpx,{"aria-label":"Unassign system from user",icon:(0,i.jsx)(y.l,{}),onClick:()=>l(e),"data-testid":"unassign-btn"})})]},e.fides_key))})]})};var S=e=>{let{allSystems:s,assignedSystems:t,onChange:a}=e,l=e=>{t.find(s=>s.fides_key===e.fides_key)?a(t.filter(s=>s.fides_key!==e.fides_key)):a([...t,e])};return(0,i.jsx)(n.xuv,{overflowY:"auto",maxHeight:"300px",children:(0,i.jsxs)(n.iA_,{size:"sm","data-testid":"assign-systems-table",maxHeight:"50vh",overflowY:"scroll",children:[(0,i.jsx)(n.hrZ,{position:"sticky",top:0,background:"white",zIndex:1,children:(0,i.jsxs)(n.Tr,{children:[(0,i.jsx)(n.Th,{children:"System"}),(0,i.jsx)(n.Th,{children:"Assign"})]})}),(0,i.jsx)(n.p3B,{children:s.map(e=>{let s=!!t.find(s=>s.fides_key===e.fides_key);return(0,i.jsxs)(n.Tr,{_hover:{bg:"gray.50"},"data-testid":"row-".concat(e.fides_key),children:[(0,i.jsx)(n.Td,{children:e.name}),(0,i.jsx)(n.Td,{children:(0,i.jsx)(n.rAg,{checked:s,onChange:()=>l(e),"data-testid":"assign-switch"})})]},e.fides_key)})})]})})};let k=(e,s)=>{var t,i;return(null===(t=e.name)||void 0===t?void 0:t.toLocaleLowerCase().includes(s.toLocaleLowerCase()))||(null===(i=e.description)||void 0===i?void 0:i.toLocaleLowerCase().includes(s.toLocaleLowerCase()))};var _=e=>{let{isOpen:s,onClose:t,assignedSystems:a,onAssignedSystemChange:l}=e,{data:r}=(0,b.K3)(),[d,o]=(0,m.useState)(""),[c,h]=(0,m.useState)(a),x=async()=>{l(c),t()},u=!r||0===r.length,p=(0,m.useMemo)(()=>r?r.filter(e=>k(e,d)):[],[r,d]),j=(0,m.useMemo)(()=>{let e=new Set(c.map(e=>e.fides_key));return p.every(s=>e.has(s.fides_key))},[p,c]);return(0,i.jsxs)(n.u_l,{isOpen:s,onClose:t,size:"2xl",children:[(0,i.jsx)(n.ZAr,{}),(0,i.jsxs)(n.hzk,{p:8,"data-testid":"confirmation-modal",children:[(0,i.jsxs)(n.xBx,{fontWeight:"medium",display:"flex",justifyContent:"space-between",alignItems:"center",children:[(0,i.jsx)(n.xvT,{children:"Assign systems"}),(0,i.jsxs)(n.j8w,{color:"success",children:["Assigned to ",a.length," systems"]})]}),(0,i.jsx)(n.fef,{"data-testid":"assign-systems-modal-body",children:u?(0,i.jsx)(n.xvT,{children:"No systems found"}):(0,i.jsxs)(n.Kqy,{spacing:4,children:[(0,i.jsxs)(n.kCb,{justifyContent:"space-between",children:[(0,i.jsx)(n.xvT,{fontSize:"sm",flexGrow:1,fontWeight:"medium",children:"Assign systems in your organization to this user"}),(0,i.jsx)(n.xuv,{children:(0,i.jsxs)(n.NIc,{display:"flex",alignItems:"center",children:[(0,i.jsx)(n.lXp,{fontSize:"sm",htmlFor:"assign-all-systems",mb:"0",children:"Assign all systems"}),(0,i.jsx)(n.rAg,{size:"small",id:"assign-all-systems",checked:j,onChange:e=>{e&&r?h(p):h(r?r.filter(e=>!p.includes(e)):[])},"data-testid":"assign-all-systems-toggle"})]})})]}),(0,i.jsx)(f.Z,{search:d,onChange:o,placeholder:"Search for systems","data-testid":"system-search",withIcon:!0}),(0,i.jsx)(S,{allSystems:p,assignedSystems:c,onChange:h})]})}),(0,i.jsx)(n.mzw,{justifyContent:"flex-start",children:(0,i.jsxs)("div",{children:[(0,i.jsx)(n.wpx,{onClick:t,className:"mr-2","data-testid":"cancel-btn",children:"Cancel"}),u?null:(0,i.jsx)(n.wpx,{type:"primary",onClick:x,"data-testid":"confirm-btn",children:"Confirm"})]})})]})]})},P=e=>{let{label:s,roleKey:t,isSelected:a,isDisabled:l,assignedSystems:r,onAssignedSystemChange:d}=e,{setFieldValue:c}=(0,h.u6)(),x=(0,n.qY0)(),m=l?"You do not have sufficient permissions to assign this role.":void 0;return a?(0,i.jsxs)(n.Kqy,{borderRadius:"md",border:"1px solid",borderColor:"gray.200",p:4,backgroundColor:"gray.50","aria-selected":"true",spacing:4,"data-testid":"selected",children:[(0,i.jsxs)(n.kCb,{alignItems:"center",justifyContent:"space-between",children:[(0,i.jsx)(n.xvT,{fontSize:"md",fontWeight:"semibold",children:s}),(0,i.jsx)(n.StI,{})]}),t!==o.A7.APPROVER?(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(n.kCb,{alignItems:"center",children:[(0,i.jsx)(n.xvT,{fontSize:"sm",fontWeight:"semibold",mr:1,children:"Assigned systems"}),(0,i.jsx)(w.Z,{label:"Assigned systems refer to those systems that have been specifically allocated to a user for management purposes. Users assigned to a system possess full edit permissions and are listed as the Data Steward for the respective system."})]}),(0,i.jsx)(n.wpx,{disabled:l,title:m,type:"primary",size:"small",onClick:x.onOpen,"data-testid":"assign-systems-btn",children:"Assign systems +"}),(0,i.jsx)(C,{assignedSystems:r,onAssignedSystemChange:d}),x.isOpen?(0,i.jsx)(_,{isOpen:x.isOpen,onClose:x.onClose,assignedSystems:r,onAssignedSystemChange:d}):null]}):null]}):(0,i.jsx)(n.wpx,{onClick:()=>{c("roles",[t])},"data-testid":"role-option-".concat(s),title:m,disabled:l,children:s})};let A={roles:[]};var T=()=>{var e;let s=(0,n.pmc)(),t=(0,x.useRouter)(),l=(0,r.C)(v.Ux);(0,v.d6)(l,{skip:!l});let{isOpen:d,onOpen:f,onClose:b}=(0,n.qY0)(),y=(0,r.C)(v.R$),[C,S]=(0,m.useState)(y),[k]=(0,v.G$)();(0,m.useEffect)(()=>{S(y)},[y]);let{data:_,isLoading:T}=(0,v.gU)(null!=l?l:"",{skip:!l}),[R]=(0,v.lD)(),z=async e=>{if(d&&b(),!l)return;let t=e.roles.includes(o.A7.APPROVER),i=await R({user_id:l,payload:{roles:e.roles}});if((0,u.D4)(i)){s((0,g.Vo)((0,u.e$)(i.error)));return}if(!t){let e=C.map(e=>e.fides_key),t=await k({userId:l,fidesKeys:e});if((0,u.D4)(t)){s((0,g.Vo)((0,u.e$)(t.error)));return}}s((0,g.t5)("Permissions updated"))},I=async e=>{l&&(C.length>0&&e.roles.includes(o.A7.APPROVER)?f():await z(e))},q=(0,c.Tg)([o.Sh.USER_PERMISSION_ASSIGN_OWNERS]);if(!l)return null;if(T)return(0,i.jsx)(n.$jN,{});if(!q&&(null==_?void 0:null===(e=_.roles)||void 0===e?void 0:e.includes(o.A7.OWNER)))return(0,i.jsx)(n.xvT,{"data-testid":"insufficient-access",children:"You do not have sufficient access to change this user's permissions."});let E=(null==_?void 0:_.roles)?{roles:_.roles}:A;return(0,i.jsx)(h.J9,{onSubmit:I,initialValues:E,enableReinitialize:!0,children:e=>{let{values:s,isSubmitting:l,dirty:r}=e;return(0,i.jsxs)(h.l0,{children:[(0,i.jsxs)(n.Kqy,{spacing:7,children:[(0,i.jsxs)(n.Kqy,{spacing:3,"data-testid":"role-options",children:[(0,i.jsxs)(n.kCb,{alignItems:"center",children:[(0,i.jsx)(n.xvT,{fontSize:"sm",fontWeight:"semibold",mr:1,children:"User role"}),(0,i.jsx)(w.Z,{label:"A user's role in the organization determines what parts of the UI they can access and which functions are available to them."})]}),a.K.map(e=>{let t=s.roles.indexOf(e.roleKey)>=0;return(0,i.jsx)(P,{isSelected:t,isDisabled:e.roleKey===o.A7.OWNER&&!q,assignedSystems:C,onAssignedSystemChange:S,...e},e.roleKey)})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(n.wpx,{onClick:()=>t.push(j.e3),children:"Cancel"}),(0,i.jsx)(n.wpx,{type:"primary",htmlType:"submit",loading:l,disabled:!r&&C===y,"data-testid":"save-btn",children:"Save"})]})]}),(0,i.jsx)(p.Z,{isOpen:d,onClose:b,onConfirm:()=>z(s),title:"Change role to Approver",testId:"downgrade-to-approver-confirmation-modal",continueButtonText:"Yes",message:(0,i.jsx)(n.xvT,{children:"Switching to an approver role will remove all assigned systems. Do you wish to proceed?"})})]})}})},R=t(61038),z=t(55484),I=t(25980),q=t(40324),E=t(96006);let{useGetEmailInviteStatusQuery:O}=t(78780).u.injectEndpoints({endpoints:e=>({getEmailInviteStatus:e.query({query:()=>({url:"/messaging/email-invite/status"}),providesTags:()=>["Email Invite Status"]})})});var N=t(76174),Z=t(91317),U=t(31883);let W=z.Ry().shape({password:E.a.label("Password"),passwordConfirmation:z.Z_().required().oneOf([z.iH("password")],"Passwords must match").label("Password confirmation")}),D={password:"",passwordConfirmation:""},K=e=>{let s=(0,n.qY0)(),t=(0,n.pmc)(),[i]=(0,v.ls)(),a=async n=>{let a=await i({id:e,new_password:n.password});(0,U.D4)(a)?t((0,g.Vo)((0,u.e$)(a.error))):(t((0,g.t5)("Successfully reset user's password. Please inform the user of their new password.")),s.onClose())};return{...s,handleResetPassword:a}};var V=e=>{let{id:s}=e,{handleResetPassword:t,isOpen:a,onClose:l,onOpen:r}=K(s);return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.wpx,{onClick:r,"data-testid":"reset-password-btn",children:"Reset password"}),(0,i.jsxs)(n.u_l,{isCentered:!0,isOpen:a,onClose:l,children:[(0,i.jsx)(n.ZAr,{}),(0,i.jsx)(n.hzk,{children:(0,i.jsx)(h.J9,{initialValues:D,validationSchema:W,onSubmit:t,children:e=>{let{isSubmitting:s,dirty:t,isValid:a}=e;return(0,i.jsxs)(h.l0,{children:[(0,i.jsx)(n.xBx,{children:"Reset Password"}),(0,i.jsx)(n.olH,{}),(0,i.jsx)(n.fef,{children:(0,i.jsxs)(n.Kqy,{direction:"column",spacing:4,children:[(0,i.jsx)(n.xvT,{mb:2,children:"Choose a new password for this user."}),(0,i.jsx)(q.j0,{name:"password",label:"Password",placeholder:"********",type:"password",tooltip:"Password must contain at least 8 characters, 1 number, 1 capital letter, 1 lowercase letter, and at least 1 symbol.",autoComplete:"new-password"}),(0,i.jsx)(q.j0,{name:"passwordConfirmation",label:"Confirm Password",placeholder:"********",type:"password",tooltip:"Must match above password.",autoComplete:"confirm-password"})]})}),(0,i.jsx)(n.mzw,{children:(0,i.jsxs)("div",{className:"w-full gap-2",children:[(0,i.jsx)(n.wpx,{onClick:l,className:"w-1/2",children:"Cancel"}),(0,i.jsx)(n.wpx,{type:"primary",disabled:!t||!a,loading:s,htmlType:"submit",className:"w-1/2","data-testid":"submit-btn",children:"Change Password"})]})})]})}})})]})]})};let Y=e=>{let s=(0,n.qY0)(),t=(0,n.pmc)(),[i,a]=(0,m.useState)(""),[l,r]=(0,m.useState)(""),[d,{isLoading:o}]=(0,v.ev)(),c=!!(e&&l&&i),h=async()=>{c&&d({id:e,old_password:i,new_password:l}).unwrap().then(()=>{t((0,g.t5)("Password updated")),s.onClose()})};return{...s,changePasswordValidation:c,handleChange:e=>{"oldPassword"===e.target.name?a(e.target.value):r(e.target.value)},handleChangePassword:h,isLoading:o,newPasswordValue:l,oldPasswordValue:i}};var B=e=>{let{id:s}=e,{changePasswordValidation:t,handleChange:a,handleChangePassword:l,isLoading:r,isOpen:d,newPasswordValue:o,oldPasswordValue:c,onClose:h,onOpen:x}=Y(s);return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.wpx,{onClick:x,"data-testid":"update-password-btn",children:"Update password"}),(0,i.jsxs)(n.u_l,{isCentered:!0,isOpen:d,onClose:h,children:[(0,i.jsx)(n.ZAr,{}),(0,i.jsxs)(n.hzk,{children:[(0,i.jsx)(n.xBx,{children:"Update Password"}),(0,i.jsx)(n.olH,{}),(0,i.jsx)(n.fef,{pb:6,children:(0,i.jsxs)(n.Kqy,{direction:"column",spacing:"15px",children:[(0,i.jsx)(n.NIc,{children:(0,i.jsx)(n.IIB,{isRequired:!0,name:"oldPassword",onChange:a,placeholder:"Old Password",type:"password",value:c,"data-testid":"input-oldPassword"})}),(0,i.jsx)(n.NIc,{children:(0,i.jsx)(n.IIB,{isRequired:!0,name:"newPassword",onChange:a,placeholder:"New Password",type:"password",value:o,"data-testid":"input-newPassword"})})]})}),(0,i.jsxs)(n.mzw,{children:[(0,i.jsx)(n.wpx,{onClick:h,className:"mr-2 w-1/2",children:"Cancel"}),(0,i.jsx)(n.wpx,{type:"primary",disabled:!t,loading:r,onClick:l,htmlType:"submit",className:"mr-3 w-1/2","data-testid":"submit-btn",children:"Change Password"})]})]})]})]})},L=()=>{let e=(0,r.C)(v.Ux),s=(0,r.C)(Z.dy),t=!!s&&s.id===e;return(0,i.jsx)(n.xuv,{children:e?(0,i.jsxs)(n.Ugi,{children:[t?(0,i.jsx)(B,{id:e}):null,(0,i.jsx)(c.ZP,{scopes:[o.Sh.USER_PASSWORD_RESET],children:(0,i.jsx)(V,{id:e})})]}):null})};let F={username:"",first_name:"",email_address:"",last_name:"",password:""},M=z.Ry().shape({username:z.Z_().required().label("Username"),email_address:z.Z_().email().required().label("Email address"),first_name:z.Z_().label("First name"),last_name:z.Z_().label("Last name"),password:E.a.label("Password")});var $=e=>{let{onSubmit:s,initialValues:t,canEditNames:a}=e,l=(0,x.useRouter)(),d=(0,n.pmc)(),o=(0,r.T)(),c=(0,n.qY0)(),m=(0,r.C)(v.ZC),{data:p}=O(),w=null==p?void 0:p.enabled,{flags:f}=(0,I.Vb)(),b=!m,C=!b&&!a,S=b&&!w,k=async e=>{let{password:t,...i}=e,n=await s(S?e:i);if((0,u.D4)(n)){d((0,g.Vo)((0,u.e$)(n.error)));return}d((0,g.t5)("".concat(b?"User created. By default, new users are set to the Viewer role. To change the role, please go to the Permissions tab.":"User updated."))),(null==n?void 0:n.data)&&o((0,v.Vv)(n.data.id))},_=M,{data:P}=(0,N.qv)(),A=!f.ssoAuthentication||!(null==P?void 0:P.length);return A||(_=M.shape({password:E.a.optional().label("Password")})),_=S?_:_.omit(["password"]),(0,i.jsx)(h.J9,{onSubmit:k,initialValues:null!=t?t:F,validationSchema:_,children:e=>{let{dirty:s,isSubmitting:t,isValid:a}=e;return(0,i.jsx)(h.l0,{children:(0,i.jsxs)(n.Kqy,{maxW:["xs","xs","100%"],width:"100%",spacing:7,children:[(0,i.jsxs)(n.Kqy,{spacing:6,maxWidth:"55%",children:[(0,i.jsxs)(n.kCb,{children:[(0,i.jsxs)(n.xvT,{display:"flex",alignItems:"center",fontSize:"sm",fontWeight:"semibold",children:["Profile"," ",(null==m?void 0:m.disabled)&&(0,i.jsx)(n.j8w,{color:"success",className:"ml-2","data-testid":"invite-sent-badge",children:"Invite sent"})]}),(0,i.jsx)(n.xuv,{marginLeft:"auto",children:(0,i.jsxs)(n.Ugi,{children:[(0,i.jsx)(L,{}),b?null:(0,i.jsxs)(n.xuv,{children:[(0,i.jsx)(n.wpx,{"aria-label":"delete",icon:(0,i.jsx)(y.l,{}),onClick:c.onOpen,"data-testid":"delete-user-btn"}),(0,i.jsx)(R.Z,{user:m,...c})]})]})})]}),(0,i.jsx)(q.j0,{name:"username",label:"Username",variant:"block",placeholder:"Enter new username",disabled:!b,isRequired:!0}),(0,i.jsx)(q.j0,{name:"email_address",label:"Email address",variant:"block",placeholder:"Enter email of user",isRequired:!0}),(0,i.jsx)(q.j0,{name:"first_name",label:"First Name",variant:"block",placeholder:"Enter first name of user",disabled:C}),(0,i.jsx)(q.j0,{name:"last_name",label:"Last Name",variant:"block",placeholder:"Enter last name of user",disabled:C}),S?(0,i.jsx)(q.j0,{name:"password",label:"Password",variant:"block",placeholder:"********",type:"password",tooltip:"Password must contain at least 8 characters, 1 number, 1 capital letter, 1 lowercase letter, and at least 1 symbol.",isRequired:A}):null]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(n.wpx,{onClick:()=>l.push(j.e3),className:"mr-3",children:"Cancel"}),(0,i.jsx)(n.wpx,{htmlType:"submit",type:"primary",disabled:!s||!a,loading:t,"data-testid":"save-user-btn",children:"Save"})]})]})})}})},H=e=>{let{onSubmit:s,initialValues:t,...a}=e,h=(0,r.C)(v.Ux);(0,v.Fk)(h,{skip:!h});let x=(0,c.Tg)([o.Sh.USER_PERMISSION_UPDATE]),m=[{label:"Profile",content:(0,i.jsx)($,{onSubmit:s,initialValues:t,...a})},{label:"Permissions",content:(0,i.jsxs)(n.kCb,{gap:"97px",children:[(0,i.jsx)(n.xuv,{w:{base:"100%",md:"50%",xl:"50%"},children:(0,i.jsx)(T,{})}),(0,i.jsx)(n.xuv,{position:"absolute",top:"96px",right:6,height:"calc(100% + 100px)",overflowY:"scroll",padding:6,w:"35%",borderLeftWidth:"1px",children:(0,i.jsx)(l,{})})]}),isDisabled:!h||!x}];return(0,i.jsx)(d.Z,{data:m})}},31883:function(e,s,t){t.d(s,{Bw:function(){return i.Bw},D4:function(){return i.D4}});var i=t(19043)}}]);
1
+ "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1150],{7617:function(e,s,t){t.d(s,{q:function(){return a}});var i=t(24246),n=t(77181);let a=e=>{let{label:s,isDisabled:t,...a}=e;return(0,i.jsx)(n.OK9,{"data-testid":"tab-".concat(s),_selected:{fontWeight:"600",color:"complimentary.500",borderColor:"complimentary.500"},fontSize:a.fontSize,fontWeight:"500",color:"gray.500",isDisabled:t||!1,children:s})};s.Z=e=>{let{data:s,border:t="partial",...l}=e;return(0,i.jsxs)(n.mQc,{colorScheme:"complimentary",...l,children:[(0,i.jsx)(n.tdY,{width:"partial"===t?"max-content":void 0,children:s.map(e=>(0,i.jsx)(a,{label:e.label,isDisabled:e.isDisabled,fontSize:l.fontSize},e.label))}),(0,i.jsx)(n.nPR,{children:s.map(e=>(0,i.jsx)(n.x45,{px:0,"data-testid":"tab-panel-".concat(e.label),children:e.content},e.label))})]})}},58452:function(e,s,t){var i=t(24246),n=t(77181);s.Z=e=>{let{isOpen:s,onClose:t,onConfirm:a,onCancel:l,title:r,message:d,cancelButtonText:o,continueButtonText:c,isLoading:h,returnFocusOnClose:x,isCentered:m,testId:u="confirmation-modal",icon:p}=e;return(0,i.jsxs)(n.u_l,{isOpen:s,onClose:t,size:"lg",returnFocusOnClose:null==x||x,isCentered:m,children:[(0,i.jsx)(n.ZAr,{}),(0,i.jsxs)(n.hzk,{textAlign:"center",p:6,"data-testid":u,children:[p?(0,i.jsx)(n.M5Y,{mb:2,children:p}):null,r?(0,i.jsx)(n.xBx,{fontWeight:"medium",pb:0,children:r}):null,d?(0,i.jsx)(n.fef,{children:d}):null,(0,i.jsx)(n.mzw,{children:(0,i.jsxs)(n.MIq,{columns:2,width:"100%",children:[(0,i.jsx)(n.wpx,{onClick:()=>{l&&l(),t()},size:"large",className:"mr-3","data-testid":"cancel-btn",disabled:h,children:o||"Cancel"}),(0,i.jsx)(n.wpx,{type:"primary",size:"large",onClick:a,"data-testid":"continue-btn",loading:h,children:c||"Continue"})]})})]})]})}},41150:function(e,s,t){t.d(s,{Z:function(){return H}});var i=t(24246),n=t(77181),a=t(91437),l=()=>(0,i.jsxs)(n.xuv,{children:[(0,i.jsx)(n.xuv,{pb:4,fontSize:"18px",fontWeight:"semibold",children:"Role Description"}),(0,i.jsx)(n.gCW,{spacing:4,children:a.K.map(e=>(0,i.jsxs)(n.xuv,{width:"100%",padding:4,borderRadius:"md",backgroundColor:"gray.75",fontSize:"14px",children:[(0,i.jsx)(n.xuv,{fontWeight:"semibold",children:e.label}),(0,i.jsx)(n.xuv,{color:"gray.700",children:e.description})]},e.roleKey))})]}),r=t(16134),d=t(7617),o=t(28120),c=t(19904),h=t(34090),x=t(86677),m=t(27378),u=t(812),p=t(58452),j=t(77830),w=t(91613),g=t(46628),f=t(86780),b=t(1315),y=t(95492),v=t(65497);let C=e=>{let{assignedSystems:s,onAssignedSystemChange:t}=e,a=(0,r.C)(v.Ux);if((0,v.d6)(a,{skip:!a}),0===s.length)return null;let l=e=>{t(s.filter(s=>s.fides_key!==e.fides_key))};return(0,i.jsxs)(n.iA_,{size:"sm","data-testid":"assign-systems-delete-table",children:[(0,i.jsx)(n.hrZ,{children:(0,i.jsxs)(n.Tr,{children:[(0,i.jsx)(n.Th,{children:"System"}),(0,i.jsx)(n.Th,{})]})}),(0,i.jsx)(n.p3B,{children:s.map(e=>(0,i.jsxs)(n.Tr,{_hover:{bg:"gray.50"},"data-testid":"row-".concat(e.fides_key),children:[(0,i.jsx)(n.Td,{children:e.name}),(0,i.jsx)(n.Td,{textAlign:"end",children:(0,i.jsx)(n.wpx,{"aria-label":"Unassign system from user",icon:(0,i.jsx)(y.l,{}),onClick:()=>l(e),"data-testid":"unassign-btn"})})]},e.fides_key))})]})};var S=e=>{let{allSystems:s,assignedSystems:t,onChange:a}=e,l=e=>{t.find(s=>s.fides_key===e.fides_key)?a(t.filter(s=>s.fides_key!==e.fides_key)):a([...t,e])};return(0,i.jsx)(n.xuv,{overflowY:"auto",maxHeight:"300px",children:(0,i.jsxs)(n.iA_,{size:"sm","data-testid":"assign-systems-table",maxHeight:"50vh",overflowY:"scroll",children:[(0,i.jsx)(n.hrZ,{position:"sticky",top:0,background:"white",zIndex:1,children:(0,i.jsxs)(n.Tr,{children:[(0,i.jsx)(n.Th,{children:"System"}),(0,i.jsx)(n.Th,{children:"Assign"})]})}),(0,i.jsx)(n.p3B,{children:s.map(e=>{let s=!!t.find(s=>s.fides_key===e.fides_key);return(0,i.jsxs)(n.Tr,{_hover:{bg:"gray.50"},"data-testid":"row-".concat(e.fides_key),children:[(0,i.jsx)(n.Td,{children:e.name}),(0,i.jsx)(n.Td,{children:(0,i.jsx)(n.rAg,{checked:s,onChange:()=>l(e),"data-testid":"assign-switch"})})]},e.fides_key)})})]})})};let k=(e,s)=>{var t,i;return(null===(t=e.name)||void 0===t?void 0:t.toLocaleLowerCase().includes(s.toLocaleLowerCase()))||(null===(i=e.description)||void 0===i?void 0:i.toLocaleLowerCase().includes(s.toLocaleLowerCase()))};var _=e=>{let{isOpen:s,onClose:t,assignedSystems:a,onAssignedSystemChange:l}=e,{data:r}=(0,b.K3)(),[d,o]=(0,m.useState)(""),[c,h]=(0,m.useState)(a),x=async()=>{l(c),t()},u=!r||0===r.length,p=(0,m.useMemo)(()=>r?r.filter(e=>k(e,d)):[],[r,d]),j=(0,m.useMemo)(()=>{let e=new Set(c.map(e=>e.fides_key));return p.every(s=>e.has(s.fides_key))},[p,c]);return(0,i.jsxs)(n.u_l,{isOpen:s,onClose:t,size:"2xl",children:[(0,i.jsx)(n.ZAr,{}),(0,i.jsxs)(n.hzk,{p:8,"data-testid":"confirmation-modal",children:[(0,i.jsxs)(n.xBx,{fontWeight:"medium",display:"flex",justifyContent:"space-between",alignItems:"center",children:[(0,i.jsx)(n.xvT,{children:"Assign systems"}),(0,i.jsxs)(n.j8w,{color:"success",children:["Assigned to ",a.length," systems"]})]}),(0,i.jsx)(n.fef,{"data-testid":"assign-systems-modal-body",children:u?(0,i.jsx)(n.xvT,{children:"No systems found"}):(0,i.jsxs)(n.Kqy,{spacing:4,children:[(0,i.jsxs)(n.kCb,{justifyContent:"space-between",children:[(0,i.jsx)(n.xvT,{fontSize:"sm",flexGrow:1,fontWeight:"medium",children:"Assign systems in your organization to this user"}),(0,i.jsx)(n.xuv,{children:(0,i.jsxs)(n.NIc,{display:"flex",alignItems:"center",children:[(0,i.jsx)(n.lXp,{fontSize:"sm",htmlFor:"assign-all-systems",mb:"0",children:"Assign all systems"}),(0,i.jsx)(n.rAg,{size:"small",id:"assign-all-systems",checked:j,onChange:e=>{e&&r?h(p):h(r?r.filter(e=>!p.includes(e)):[])},"data-testid":"assign-all-systems-toggle"})]})})]}),(0,i.jsx)(f.Z,{search:d,onChange:o,placeholder:"Search for systems","data-testid":"system-search",withIcon:!0}),(0,i.jsx)(S,{allSystems:p,assignedSystems:c,onChange:h})]})}),(0,i.jsx)(n.mzw,{justifyContent:"flex-start",children:(0,i.jsxs)("div",{children:[(0,i.jsx)(n.wpx,{onClick:t,className:"mr-2","data-testid":"cancel-btn",children:"Cancel"}),u?null:(0,i.jsx)(n.wpx,{type:"primary",onClick:x,"data-testid":"confirm-btn",children:"Confirm"})]})})]})]})},P=e=>{let{label:s,roleKey:t,isSelected:a,isDisabled:l,assignedSystems:r,onAssignedSystemChange:d}=e,{setFieldValue:c}=(0,h.u6)(),x=(0,n.qY0)(),m=l?"You do not have sufficient permissions to assign this role.":void 0;return a?(0,i.jsxs)(n.Kqy,{borderRadius:"md",border:"1px solid",borderColor:"gray.200",p:4,backgroundColor:"gray.50","aria-selected":"true",spacing:4,"data-testid":"selected",children:[(0,i.jsxs)(n.kCb,{alignItems:"center",justifyContent:"space-between",children:[(0,i.jsx)(n.xvT,{fontSize:"md",fontWeight:"semibold",children:s}),(0,i.jsx)(n.StI,{})]}),t!==o.A7.APPROVER?(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(n.kCb,{alignItems:"center",children:[(0,i.jsx)(n.xvT,{fontSize:"sm",fontWeight:"semibold",mr:1,children:"Assigned systems"}),(0,i.jsx)(w.Z,{label:"Assigned systems refer to those systems that have been specifically allocated to a user for management purposes. Users assigned to a system possess full edit permissions and are listed as the Data Steward for the respective system."})]}),(0,i.jsx)(n.wpx,{disabled:l,title:m,type:"primary",size:"small",onClick:x.onOpen,"data-testid":"assign-systems-btn",children:"Assign systems +"}),(0,i.jsx)(C,{assignedSystems:r,onAssignedSystemChange:d}),x.isOpen?(0,i.jsx)(_,{isOpen:x.isOpen,onClose:x.onClose,assignedSystems:r,onAssignedSystemChange:d}):null]}):null]}):(0,i.jsx)(n.wpx,{onClick:()=>{c("roles",[t])},"data-testid":"role-option-".concat(s),title:m,disabled:l,children:s})};let A={roles:[]};var T=()=>{var e;let s=(0,n.pmc)(),t=(0,x.useRouter)(),l=(0,r.C)(v.Ux);(0,v.d6)(l,{skip:!l});let{isOpen:d,onOpen:f,onClose:b}=(0,n.qY0)(),y=(0,r.C)(v.R$),[C,S]=(0,m.useState)(y),[k]=(0,v.G$)();(0,m.useEffect)(()=>{S(y)},[y]);let{data:_,isLoading:T}=(0,v.gU)(null!=l?l:"",{skip:!l}),[R]=(0,v.lD)(),z=async e=>{if(d&&b(),!l)return;let t=e.roles.includes(o.A7.APPROVER),i=await R({user_id:l,payload:{roles:e.roles}});if((0,u.D4)(i)){s((0,g.Vo)((0,u.e$)(i.error)));return}if(!t){let e=C.map(e=>e.fides_key),t=await k({userId:l,fidesKeys:e});if((0,u.D4)(t)){s((0,g.Vo)((0,u.e$)(t.error)));return}}s((0,g.t5)("Permissions updated"))},I=async e=>{l&&(C.length>0&&e.roles.includes(o.A7.APPROVER)?f():await z(e))},q=(0,c.Tg)([o.Sh.USER_PERMISSION_ASSIGN_OWNERS]);if(!l)return null;if(T)return(0,i.jsx)(n.$jN,{});if(!q&&(null==_?void 0:null===(e=_.roles)||void 0===e?void 0:e.includes(o.A7.OWNER)))return(0,i.jsx)(n.xvT,{"data-testid":"insufficient-access",children:"You do not have sufficient access to change this user's permissions."});let E=(null==_?void 0:_.roles)?{roles:_.roles}:A;return(0,i.jsx)(h.J9,{onSubmit:I,initialValues:E,enableReinitialize:!0,children:e=>{let{values:s,isSubmitting:l,dirty:r}=e;return(0,i.jsxs)(h.l0,{children:[(0,i.jsxs)(n.Kqy,{spacing:7,children:[(0,i.jsxs)(n.Kqy,{spacing:3,"data-testid":"role-options",children:[(0,i.jsxs)(n.kCb,{alignItems:"center",children:[(0,i.jsx)(n.xvT,{fontSize:"sm",fontWeight:"semibold",mr:1,children:"User role"}),(0,i.jsx)(w.Z,{label:"A user's role in the organization determines what parts of the UI they can access and which functions are available to them."})]}),a.K.map(e=>{let t=s.roles.indexOf(e.roleKey)>=0;return(0,i.jsx)(P,{isSelected:t,isDisabled:e.roleKey===o.A7.OWNER&&!q,assignedSystems:C,onAssignedSystemChange:S,...e},e.roleKey)})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(n.wpx,{onClick:()=>t.push(j.e3),children:"Cancel"}),(0,i.jsx)(n.wpx,{type:"primary",htmlType:"submit",loading:l,disabled:!r&&C===y,"data-testid":"save-btn",children:"Save"})]})]}),(0,i.jsx)(p.Z,{isOpen:d,onClose:b,onConfirm:()=>z(s),title:"Change role to Approver",testId:"downgrade-to-approver-confirmation-modal",continueButtonText:"Yes",message:(0,i.jsx)(n.xvT,{children:"Switching to an approver role will remove all assigned systems. Do you wish to proceed?"})})]})}})},R=t(61038),z=t(55484),I=t(25980),q=t(40324),E=t(96006);let{useGetEmailInviteStatusQuery:O}=t(78780).u.injectEndpoints({endpoints:e=>({getEmailInviteStatus:e.query({query:()=>({url:"/messaging/email-invite/status"}),providesTags:()=>["Email Invite Status"]})})});var N=t(76174),Z=t(91317),U=t(31883);let W=z.Ry().shape({password:E.a.label("Password"),passwordConfirmation:z.Z_().required().oneOf([z.iH("password")],"Passwords must match").label("Password confirmation")}),D={password:"",passwordConfirmation:""},K=e=>{let s=(0,n.qY0)(),t=(0,n.pmc)(),[i]=(0,v.ls)(),a=async n=>{let a=await i({id:e,new_password:n.password});(0,U.D4)(a)?t((0,g.Vo)((0,u.e$)(a.error))):(t((0,g.t5)("Successfully reset user's password. Please inform the user of their new password.")),s.onClose())};return{...s,handleResetPassword:a}};var V=e=>{let{id:s}=e,{handleResetPassword:t,isOpen:a,onClose:l,onOpen:r}=K(s);return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.wpx,{onClick:r,"data-testid":"reset-password-btn",children:"Reset password"}),(0,i.jsxs)(n.u_l,{isCentered:!0,isOpen:a,onClose:l,children:[(0,i.jsx)(n.ZAr,{}),(0,i.jsx)(n.hzk,{children:(0,i.jsx)(h.J9,{initialValues:D,validationSchema:W,onSubmit:t,children:e=>{let{isSubmitting:s,dirty:t,isValid:a}=e;return(0,i.jsxs)(h.l0,{children:[(0,i.jsx)(n.xBx,{children:"Reset Password"}),(0,i.jsx)(n.olH,{}),(0,i.jsx)(n.fef,{children:(0,i.jsxs)(n.Kqy,{direction:"column",spacing:4,children:[(0,i.jsx)(n.xvT,{mb:2,children:"Choose a new password for this user."}),(0,i.jsx)(q.j0,{name:"password",label:"Password",placeholder:"********",type:"password",tooltip:"Password must contain at least 8 characters, 1 number, 1 capital letter, 1 lowercase letter, and at least 1 symbol.",autoComplete:"new-password"}),(0,i.jsx)(q.j0,{name:"passwordConfirmation",label:"Confirm Password",placeholder:"********",type:"password",tooltip:"Must match above password.",autoComplete:"confirm-password"})]})}),(0,i.jsx)(n.mzw,{children:(0,i.jsxs)("div",{className:"w-full gap-2",children:[(0,i.jsx)(n.wpx,{onClick:l,className:"w-1/2",children:"Cancel"}),(0,i.jsx)(n.wpx,{type:"primary",disabled:!t||!a,loading:s,htmlType:"submit",className:"w-1/2","data-testid":"submit-btn",children:"Change Password"})]})})]})}})})]})]})};let Y=e=>{let s=(0,n.qY0)(),t=(0,n.pmc)(),[i,a]=(0,m.useState)(""),[l,r]=(0,m.useState)(""),[d,{isLoading:o}]=(0,v.ev)(),c=!!(e&&l&&i),h=async()=>{c&&d({id:e,old_password:i,new_password:l}).unwrap().then(()=>{t((0,g.t5)("Password updated")),s.onClose()})};return{...s,changePasswordValidation:c,handleChange:e=>{"oldPassword"===e.target.name?a(e.target.value):r(e.target.value)},handleChangePassword:h,isLoading:o,newPasswordValue:l,oldPasswordValue:i}};var B=e=>{let{id:s}=e,{changePasswordValidation:t,handleChange:a,handleChangePassword:l,isLoading:r,isOpen:d,newPasswordValue:o,oldPasswordValue:c,onClose:h,onOpen:x}=Y(s);return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.wpx,{onClick:x,"data-testid":"update-password-btn",children:"Update password"}),(0,i.jsxs)(n.u_l,{isCentered:!0,isOpen:d,onClose:h,children:[(0,i.jsx)(n.ZAr,{}),(0,i.jsxs)(n.hzk,{children:[(0,i.jsx)(n.xBx,{children:"Update Password"}),(0,i.jsx)(n.olH,{}),(0,i.jsx)(n.fef,{pb:6,children:(0,i.jsxs)(n.Kqy,{direction:"column",spacing:"15px",children:[(0,i.jsx)(n.NIc,{children:(0,i.jsx)(n.IIB,{isRequired:!0,name:"oldPassword",onChange:a,placeholder:"Old Password",type:"password",value:c,"data-testid":"input-oldPassword"})}),(0,i.jsx)(n.NIc,{children:(0,i.jsx)(n.IIB,{isRequired:!0,name:"newPassword",onChange:a,placeholder:"New Password",type:"password",value:o,"data-testid":"input-newPassword"})})]})}),(0,i.jsxs)(n.mzw,{children:[(0,i.jsx)(n.wpx,{onClick:h,className:"mr-2 w-1/2",children:"Cancel"}),(0,i.jsx)(n.wpx,{type:"primary",disabled:!t,loading:r,onClick:l,htmlType:"submit",className:"mr-3 w-1/2","data-testid":"submit-btn",children:"Change Password"})]})]})]})]})},L=()=>{let e=(0,r.C)(v.Ux),s=(0,r.C)(Z.dy),t=!!s&&s.id===e;return(0,i.jsx)(n.xuv,{children:e?(0,i.jsxs)(n.Ugi,{children:[t?(0,i.jsx)(B,{id:e}):null,(0,i.jsx)(c.ZP,{scopes:[o.Sh.USER_PASSWORD_RESET],children:(0,i.jsx)(V,{id:e})})]}):null})};let F={username:"",first_name:"",email_address:"",last_name:"",password:""},M=z.Ry().shape({username:z.Z_().required().label("Username"),email_address:z.Z_().email().required().label("Email address"),first_name:z.Z_().label("First name"),last_name:z.Z_().label("Last name"),password:E.a.label("Password")});var $=e=>{let{onSubmit:s,initialValues:t,canEditNames:a}=e,l=(0,x.useRouter)(),d=(0,n.pmc)(),o=(0,r.T)(),c=(0,n.qY0)(),m=(0,r.C)(v.ZC),{data:p}=O(),w=null==p?void 0:p.enabled,{flags:f}=(0,I.Vb)(),b=!m,C=!b&&!a,S=b&&!w,k=async e=>{let{password:t,...i}=e,n=await s(S?e:i);if((0,u.D4)(n)){d((0,g.Vo)((0,u.e$)(n.error)));return}d((0,g.t5)("".concat(b?"User created. By default, new users are set to the Viewer role. To change the role, please go to the Permissions tab.":"User updated."))),(null==n?void 0:n.data)&&o((0,v.Vv)(n.data.id))},_=M,{data:P}=(0,N.qv)(),A=!f.ssoAuthentication||!(null==P?void 0:P.length);return A||(_=M.shape({password:E.a.optional().label("Password")})),_=S?_:_.omit(["password"]),(0,i.jsx)(h.J9,{onSubmit:k,initialValues:null!=t?t:F,validationSchema:_,children:e=>{let{dirty:s,isSubmitting:t,isValid:a}=e;return(0,i.jsx)(h.l0,{children:(0,i.jsxs)(n.Kqy,{maxW:["xs","xs","100%"],width:"100%",spacing:7,children:[(0,i.jsxs)(n.Kqy,{spacing:6,maxWidth:"55%",children:[(0,i.jsxs)(n.kCb,{children:[(0,i.jsxs)(n.xvT,{display:"flex",alignItems:"center",fontSize:"sm",fontWeight:"semibold",children:["Profile"," ",(null==m?void 0:m.disabled)&&(0,i.jsx)(n.j8w,{color:"success",className:"ml-2","data-testid":"invite-sent-badge",children:"Invite sent"})]}),(0,i.jsx)(n.xuv,{marginLeft:"auto",children:(0,i.jsxs)(n.Ugi,{children:[(0,i.jsx)(L,{}),b?null:(0,i.jsxs)(n.xuv,{children:[(0,i.jsx)(n.wpx,{"aria-label":"delete",icon:(0,i.jsx)(y.l,{}),onClick:c.onOpen,"data-testid":"delete-user-btn"}),(0,i.jsx)(R.Z,{user:m,...c})]})]})})]}),(0,i.jsx)(q.j0,{name:"username",label:"Username",variant:"block",placeholder:"Enter new username",disabled:!b,isRequired:!0}),(0,i.jsx)(q.j0,{name:"email_address",label:"Email address",variant:"block",placeholder:"Enter email of user",isRequired:!0}),(0,i.jsx)(q.j0,{name:"first_name",label:"First Name",variant:"block",placeholder:"Enter first name of user",disabled:C}),(0,i.jsx)(q.j0,{name:"last_name",label:"Last Name",variant:"block",placeholder:"Enter last name of user",disabled:C}),S?(0,i.jsx)(q.j0,{name:"password",label:"Password",variant:"block",placeholder:"********",type:"password",tooltip:"Password must contain at least 8 characters, 1 number, 1 capital letter, 1 lowercase letter, and at least 1 symbol.",isRequired:A}):null]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(n.wpx,{onClick:()=>l.push(j.e3),className:"mr-3",children:"Cancel"}),(0,i.jsx)(n.wpx,{htmlType:"submit",type:"primary",disabled:!s||!a,loading:t,"data-testid":"save-user-btn",children:"Save"})]})]})})}})},H=e=>{let{onSubmit:s,initialValues:t,...a}=e,h=(0,r.C)(v.Ux);(0,v.Fk)(h,{skip:!h});let x=(0,c.Tg)([o.Sh.USER_PERMISSION_UPDATE]),m=[{label:"Profile",content:(0,i.jsx)($,{onSubmit:s,initialValues:t,...a})},{label:"Permissions",content:(0,i.jsxs)(n.kCb,{gap:"97px",children:[(0,i.jsx)(n.xuv,{w:{base:"100%",md:"50%",xl:"50%"},children:(0,i.jsx)(T,{})}),(0,i.jsx)(n.xuv,{position:"absolute",top:"96px",right:6,height:"calc(100% + 100px)",overflowY:"scroll",padding:6,w:"35%",borderLeftWidth:"1px",children:(0,i.jsx)(l,{})})]}),isDisabled:!h||!x}];return(0,i.jsx)(d.Z,{data:m})}},31883:function(e,s,t){t.d(s,{Bw:function(){return i.Bw},D4:function(){return i.D4}});var i=t(19043)}}]);