ethyca-fides 2.58.2b2__py2.py3-none-any.whl → 2.58.2b5__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 (130) hide show
  1. {ethyca_fides-2.58.2b2.dist-info → ethyca_fides-2.58.2b5.dist-info}/METADATA +20 -11
  2. {ethyca_fides-2.58.2b2.dist-info → ethyca_fides-2.58.2b5.dist-info}/RECORD +118 -115
  3. {ethyca_fides-2.58.2b2.dist-info → ethyca_fides-2.58.2b5.dist-info}/WHEEL +1 -1
  4. {ethyca_fides-2.58.2b2.dist-info → ethyca_fides-2.58.2b5.dist-info}/entry_points.txt +0 -1
  5. fides/_version.py +3 -3
  6. fides/api/alembic/migrations/versions/9288f729cac4_add_tcf_configuration_fk_to_experience_.py +62 -0
  7. fides/api/alembic/migrations/versions/99c603c1b8f9_add_password_login_enabled_and_totp_secret_to_fidesuser.py +45 -0
  8. fides/api/api/v1/endpoints/user_endpoints.py +8 -12
  9. fides/api/models/detection_discovery.py +31 -0
  10. fides/api/models/fides_user.py +26 -9
  11. fides/api/models/fides_user_invite.py +2 -0
  12. fides/api/models/privacy_experience.py +26 -0
  13. fides/api/models/tcf_publisher_restrictions.py +209 -48
  14. fides/api/schemas/user.py +5 -1
  15. fides/api/service/deps.py +9 -0
  16. fides/api/util/collection_util.py +48 -9
  17. fides/cli/commands/pull.py +77 -13
  18. fides/core/api.py +2 -1
  19. fides/core/pull.py +38 -7
  20. fides/service/user/__init__.py +0 -0
  21. fides/service/user/user_service.py +140 -0
  22. fides/ui-build/static/admin/404.html +1 -1
  23. fides/ui-build/static/admin/_next/static/{F-2Pz9ByzGwcvQtVLstwR → _o6WH0hDzNEhnUJyvLex7}/_buildManifest.js +1 -1
  24. fides/ui-build/static/admin/_next/static/chunks/1376-87058e04584cff20.js +1 -0
  25. fides/ui-build/static/admin/_next/static/chunks/4121-4d5273d7a354994d.js +1 -0
  26. fides/ui-build/static/admin/_next/static/chunks/{4450-6a8aa0d7358ac26f.js → 4450-9c3086ccb55c66aa.js} +1 -1
  27. fides/ui-build/static/admin/_next/static/chunks/6315-24a0483ee1cab6cc.js +1 -0
  28. fides/ui-build/static/admin/_next/static/chunks/{9046-8a5fdd335a76d224.js → 9046-a69fa8f99c414570.js} +1 -1
  29. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-4a5be35cd8f832c0.js → _app-0c1548ca3b158123.js} +1 -1
  30. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-17d1525551d8904f.js +1 -0
  31. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-7d2cb947eee11262.js +1 -0
  32. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-9ef6b422ae7bc2a8.js → [id]-b75ab4ee677f118d.js} +1 -1
  33. fides/ui-build/static/admin/_next/static/chunks/pages/messaging-26407674949bcbc4.js +1 -0
  34. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-28d4bdf060ec8cb2.js +1 -0
  35. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-208e49ef43361d6f.js +1 -0
  36. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-ff1985f72d50ef47.js +1 -0
  37. fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-5a0b10ec955097d4.js +1 -0
  38. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-f8bca2e322ddf252.js → new-082c3156175f9267.js} +1 -1
  39. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/[id]-af83245e9373a064.js +1 -0
  40. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  41. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  42. fides/ui-build/static/admin/add-systems.html +1 -1
  43. fides/ui-build/static/admin/ant-poc.html +1 -1
  44. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  45. fides/ui-build/static/admin/consent/configure.html +1 -1
  46. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  47. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  48. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  49. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  50. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  51. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  52. fides/ui-build/static/admin/consent/properties.html +1 -1
  53. fides/ui-build/static/admin/consent/reporting.html +1 -1
  54. fides/ui-build/static/admin/consent.html +1 -1
  55. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  56. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  57. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  58. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  59. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  60. fides/ui-build/static/admin/data-catalog.html +1 -1
  61. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  62. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  63. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  64. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  65. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  66. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  67. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  68. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  69. fides/ui-build/static/admin/datamap.html +1 -1
  70. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  71. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  72. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  73. fides/ui-build/static/admin/dataset/new.html +1 -1
  74. fides/ui-build/static/admin/dataset.html +1 -1
  75. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  76. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  77. fides/ui-build/static/admin/datastore-connection.html +1 -1
  78. fides/ui-build/static/admin/index.html +1 -1
  79. fides/ui-build/static/admin/integrations/[id].html +1 -1
  80. fides/ui-build/static/admin/integrations.html +1 -1
  81. fides/ui-build/static/admin/lib/fides-tcf.js +1 -1
  82. fides/ui-build/static/admin/login/[provider].html +1 -1
  83. fides/ui-build/static/admin/login.html +1 -1
  84. fides/ui-build/static/admin/messaging/[id].html +1 -1
  85. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  86. fides/ui-build/static/admin/messaging.html +1 -1
  87. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  88. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  89. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  90. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  91. fides/ui-build/static/admin/privacy-requests.html +1 -1
  92. fides/ui-build/static/admin/properties/[id].html +1 -1
  93. fides/ui-build/static/admin/properties/add-property.html +1 -1
  94. fides/ui-build/static/admin/properties.html +1 -1
  95. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  96. fides/ui-build/static/admin/settings/about.html +1 -1
  97. fides/ui-build/static/admin/settings/consent.html +1 -1
  98. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  99. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  100. fides/ui-build/static/admin/settings/domains.html +1 -1
  101. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  102. fides/ui-build/static/admin/settings/locations.html +1 -1
  103. fides/ui-build/static/admin/settings/organization.html +1 -1
  104. fides/ui-build/static/admin/settings/regulations.html +1 -1
  105. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  106. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  107. fides/ui-build/static/admin/systems.html +1 -1
  108. fides/ui-build/static/admin/taxonomy.html +1 -1
  109. fides/ui-build/static/admin/user-management/new.html +1 -1
  110. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  111. fides/ui-build/static/admin/user-management.html +1 -1
  112. fides/api/service/user/fides_user_service.py +0 -128
  113. fides/ui-build/static/admin/_next/static/chunks/1150-2642cd9cdc8a52f6.js +0 -1
  114. fides/ui-build/static/admin/_next/static/chunks/1376-03e7f50e708b7589.js +0 -1
  115. fides/ui-build/static/admin/_next/static/chunks/6315-1adb10a8b98b4a13.js +0 -1
  116. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-719949074f10bd6e.js +0 -1
  117. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-4892603e743cd6ab.js +0 -1
  118. fides/ui-build/static/admin/_next/static/chunks/pages/messaging-1e60754abec1ee6b.js +0 -1
  119. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-5e2687ab5ab10275.js +0 -1
  120. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-2914aade73dcaecc.js +0 -1
  121. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-3ac1e5d3de5dd4a7.js +0 -1
  122. fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-24cba38685dc872c.js +0 -1
  123. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/[id]-c0378fd1a26a71da.js +0 -1
  124. {ethyca_fides-2.58.2b2.dist-info → ethyca_fides-2.58.2b5.dist-info/licenses}/LICENSE +0 -0
  125. {ethyca_fides-2.58.2b2.dist-info → ethyca_fides-2.58.2b5.dist-info}/top_level.txt +0 -0
  126. /fides/ui-build/static/admin/_next/static/{F-2Pz9ByzGwcvQtVLstwR → _o6WH0hDzNEhnUJyvLex7}/_ssgManifest.js +0 -0
  127. /fides/ui-build/static/admin/_next/static/chunks/{1817-f82105a9608bba1a.js → 1817-48e1c9d3504e18f0.js} +0 -0
  128. /fides/ui-build/static/admin/_next/static/chunks/{6954-baa1d873abfe8b77.js → 6954-ec5276bb464d42b2.js} +0 -0
  129. /fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-b0f801d66e79a31a.js → privacy-requests-fd81714d811db7b3.js} +0 -0
  130. /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-3ca3c687e72d1364.js → user-management-a1db56f1cbfba373.js} +0 -0
@@ -0,0 +1,62 @@
1
+ """Add tcf_configuration FK to experience config
2
+
3
+ Revision ID: 9288f729cac4
4
+ Revises: 99c603c1b8f9
5
+ Create Date: 2025-04-07 18:49:31.843362
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "9288f729cac4"
14
+ down_revision = "99c603c1b8f9"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ op.add_column(
22
+ "privacyexperienceconfig",
23
+ sa.Column("tcf_configuration_id", sa.String(), nullable=True),
24
+ )
25
+ op.create_foreign_key(
26
+ "privacyexperienceconfig_tcf_configuration_fkey",
27
+ "privacyexperienceconfig",
28
+ "tcf_configuration",
29
+ ["tcf_configuration_id"],
30
+ ["id"],
31
+ ondelete="SET NULL",
32
+ )
33
+ op.add_column(
34
+ "privacyexperienceconfighistory",
35
+ sa.Column("tcf_configuration_id", sa.String(), nullable=True),
36
+ )
37
+ op.create_foreign_key(
38
+ "privacyexperienceconfighistory_tcf_configuration_fkey",
39
+ "privacyexperienceconfighistory",
40
+ "tcf_configuration",
41
+ ["tcf_configuration_id"],
42
+ ["id"],
43
+ ondelete="SET NULL",
44
+ )
45
+ # ### end Alembic commands ###
46
+
47
+
48
+ def downgrade():
49
+ # ### commands auto generated by Alembic - please adjust! ###
50
+ op.drop_constraint(
51
+ "privacyexperienceconfighistory_tcf_configuration_fkey",
52
+ "privacyexperienceconfighistory",
53
+ type_="foreignkey",
54
+ )
55
+ op.drop_column("privacyexperienceconfighistory", "tcf_configuration_id")
56
+ op.drop_constraint(
57
+ "privacyexperienceconfig_tcf_configuration_fkey",
58
+ "privacyexperienceconfig",
59
+ type_="foreignkey",
60
+ )
61
+ op.drop_column("privacyexperienceconfig", "tcf_configuration_id")
62
+ # ### end Alembic commands ###
@@ -0,0 +1,45 @@
1
+ """add password login enabled and totp secret to fidesuser
2
+
3
+ Revision ID: 99c603c1b8f9
4
+ Revises: 6e565c16dae1
5
+ Create Date: 2025-04-02 01:55:57.890545
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ import sqlalchemy_utils
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "99c603c1b8f9"
15
+ down_revision = "6e565c16dae1"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ op.add_column(
22
+ "fidesuser",
23
+ sa.Column(
24
+ "password_login_enabled",
25
+ sa.Boolean(),
26
+ nullable=True,
27
+ ),
28
+ )
29
+ op.add_column(
30
+ "fidesuser",
31
+ sa.Column(
32
+ "totp_secret",
33
+ sqlalchemy_utils.types.encrypted.encrypted_type.StringEncryptedType(),
34
+ nullable=True,
35
+ ),
36
+ )
37
+ op.alter_column("fidesuser", "hashed_password", nullable=True)
38
+ op.alter_column("fidesuser", "salt", nullable=True)
39
+
40
+
41
+ def downgrade():
42
+ op.alter_column("fidesuser", "hashed_password", nullable=False)
43
+ op.alter_column("fidesuser", "salt", nullable=False)
44
+ op.drop_column("fidesuser", "totp_secret")
45
+ op.drop_column("fidesuser", "password_login_enabled")
@@ -54,11 +54,7 @@ from fides.api.schemas.user import (
54
54
  UserResponse,
55
55
  UserUpdate,
56
56
  )
57
- from fides.api.service.user.fides_user_service import (
58
- accept_invite,
59
- invite_user,
60
- perform_login,
61
- )
57
+ from fides.api.service.deps import get_user_service
62
58
  from fides.api.util.api_router import APIRouter
63
59
  from fides.common.api.scope_registry import (
64
60
  SCOPE_REGISTRY,
@@ -75,6 +71,7 @@ from fides.common.api.v1 import urn_registry as urls
75
71
  from fides.common.api.v1.urn_registry import V1_URL_PREFIX
76
72
  from fides.config import CONFIG, FidesConfig, get_config
77
73
  from fides.config.config_proxy import ConfigProxy
74
+ from fides.service.user.user_service import UserService
78
75
 
79
76
  router = APIRouter(tags=["Users"], prefix=V1_URL_PREFIX)
80
77
 
@@ -423,6 +420,7 @@ def create_user(
423
420
  db: Session = Depends(get_db),
424
421
  user_data: UserCreate,
425
422
  config_proxy: ConfigProxy = Depends(get_config_proxy),
423
+ user_service: UserService = Depends(get_user_service),
426
424
  ) -> FidesUser:
427
425
  """
428
426
  Create a user given a username and password.
@@ -461,7 +459,7 @@ def create_user(
461
459
  user = FidesUser.create(db=db, data=user_data.model_dump(mode="json"))
462
460
 
463
461
  # invite user via email
464
- invite_user(db=db, config_proxy=config_proxy, user=user)
462
+ user_service.invite_user(user)
465
463
 
466
464
  logger.info("Created user with id: '{}'.", user.id)
467
465
  FidesUserPermissions.create(
@@ -544,6 +542,7 @@ def user_login(
544
542
  db: Session = Depends(get_db),
545
543
  config: FidesConfig = Depends(get_config),
546
544
  user_data: UserLogin,
545
+ user_service: UserService = Depends(get_user_service),
547
546
  ) -> UserLoginResponse:
548
547
  """Login the user by creating a client if it doesn't exist, and have that client
549
548
  generate a token."""
@@ -602,8 +601,7 @@ def user_login(
602
601
  # from complaining.
603
602
  user = user_check
604
603
 
605
- client = perform_login(
606
- db,
604
+ client = user_service.perform_login(
607
605
  config.security.oauth_client_id_length_bytes,
608
606
  config.security.oauth_client_secret_length_bytes,
609
607
  user,
@@ -666,9 +664,9 @@ def verify_invite_code(
666
664
  def accept_user_invite(
667
665
  *,
668
666
  db: Session = Depends(get_db),
669
- config: FidesConfig = Depends(get_config),
670
667
  user_data: UserForcePasswordReset,
671
668
  verified_invite: FidesUserInvite = Depends(verify_invite_code),
669
+ user_service: UserService = Depends(get_user_service),
672
670
  ) -> UserLoginResponse:
673
671
  """Sets the password and enables the user if a valid username and invite code are provided."""
674
672
 
@@ -681,9 +679,7 @@ def accept_user_invite(
681
679
  detail=f"User with username {verified_invite.username} does not exist.",
682
680
  )
683
681
 
684
- user, access_code = accept_invite(
685
- db=db, config=config, user=user, new_password=user_data.new_password
686
- )
682
+ user, access_code = user_service.accept_invite(user, user_data.new_password)
687
683
 
688
684
  return UserLoginResponse(
689
685
  user_data=user,
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from datetime import datetime
4
4
  from enum import Enum
5
+ from re import match
5
6
  from typing import Any, Dict, Iterable, List, Optional, Type
6
7
 
7
8
  from loguru import logger
@@ -35,9 +36,17 @@ class MonitorFrequency(Enum):
35
36
  DAILY = "Daily"
36
37
  WEEKLY = "Weekly"
37
38
  MONTHLY = "Monthly"
39
+ QUARTERLY = "Quarterly"
40
+ YEARLY = "Yearly"
38
41
  NOT_SCHEDULED = "Not scheduled"
39
42
 
40
43
 
44
+ # pattern for a string of 4 comma-separated integers,
45
+ # used to represent the months of the year that the monitor will run
46
+ # on quarterly basis, in cron format
47
+ QUARTERLY_MONTH_PATTERN = r"^\d+,\d+,\d+,\d+$"
48
+
49
+
41
50
  class MonitorConfig(Base):
42
51
  """
43
52
  Monitor configuration used for data detection and discovery.
@@ -138,6 +147,13 @@ class MonitorConfig(Base):
138
147
  or self.monitor_execution_trigger.get("hour", None) is None
139
148
  ):
140
149
  return MonitorFrequency.NOT_SCHEDULED
150
+ month_trigger = self.monitor_execution_trigger.get("month", None)
151
+ if month_trigger is not None:
152
+ if isinstance(month_trigger, str) and match(
153
+ QUARTERLY_MONTH_PATTERN, month_trigger
154
+ ):
155
+ return MonitorFrequency.QUARTERLY
156
+ return MonitorFrequency.YEARLY
141
157
  if self.monitor_execution_trigger.get("day", None) is not None:
142
158
  return MonitorFrequency.MONTHLY
143
159
  if self.monitor_execution_trigger.get("day_of_week", None) is not None:
@@ -201,6 +217,10 @@ class MonitorConfig(Base):
201
217
  a Tuesday.
202
218
  - with an `execution_frequency` of "monthly", it will result in monthly
203
219
  execution at 12:00:00+00:00 on the 14th day of every month.
220
+ - with an `execution_frequency` of "quarterly", it will result in quarterly
221
+ execution at 12:00:00+00:00 on the 14th day of the first month of each quarter.
222
+ - with an `execution_frequency` of "yearly", it will result in yearly
223
+ execution at 12:00:00+00:00 on May 14th of each year.
204
224
 
205
225
  See https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html
206
226
  for more information about the cron trigger parameters.
@@ -221,6 +241,17 @@ class MonitorConfig(Base):
221
241
  cron_trigger_dict["day_of_week"] = execution_start_date.weekday()
222
242
  if execution_frequency == MonitorFrequency.MONTHLY:
223
243
  cron_trigger_dict["day"] = execution_start_date.day
244
+ if execution_frequency == MonitorFrequency.QUARTERLY:
245
+ cron_trigger_dict["day"] = execution_start_date.day
246
+ # Calculate which month of the quarter (0-2) this is
247
+ month_of_quarter = (execution_start_date.month - 1) % 3
248
+ # Set to run in the same month of each quarter (1, 4, 7, 10 for first month)
249
+ cron_trigger_dict["month"] = (
250
+ f"{1 + month_of_quarter},{4 + month_of_quarter},{7 + month_of_quarter},{10 + month_of_quarter}"
251
+ )
252
+ if execution_frequency == MonitorFrequency.YEARLY:
253
+ cron_trigger_dict["day"] = execution_start_date.day
254
+ cron_trigger_dict["month"] = execution_start_date.month
224
255
  data["monitor_execution_trigger"] = cron_trigger_dict
225
256
 
226
257
 
@@ -1,7 +1,6 @@
1
1
  # pylint: disable=unused-import
2
2
  from __future__ import annotations
3
3
 
4
- import uuid
5
4
  from datetime import datetime
6
5
  from typing import TYPE_CHECKING, Any, List
7
6
 
@@ -10,6 +9,10 @@ from sqlalchemy import Boolean, Column, DateTime
10
9
  from sqlalchemy import Enum as EnumColumn
11
10
  from sqlalchemy import String
12
11
  from sqlalchemy.orm import Session, relationship
12
+ from sqlalchemy_utils.types.encrypted.encrypted_type import (
13
+ AesGcmEngine,
14
+ StringEncryptedType,
15
+ )
13
16
 
14
17
  from fides.api.common_exceptions import SystemManagerException
15
18
  from fides.api.cryptography.cryptographic_util import (
@@ -20,8 +23,8 @@ from fides.api.db.base_class import Base
20
23
  from fides.api.models.audit_log import AuditLog
21
24
 
22
25
  # Intentionally importing SystemManager here to build the FidesUser.systems relationship
23
- from fides.api.models.system_manager import SystemManager # type: ignore[unused-import]
24
26
  from fides.api.schemas.user import DisabledReason
27
+ from fides.config import CONFIG
25
28
 
26
29
  if TYPE_CHECKING:
27
30
  from fides.api.models.sql_models import System # type: ignore[attr-defined]
@@ -34,12 +37,22 @@ class FidesUser(Base):
34
37
  email_address = Column(CIText, unique=True, nullable=True)
35
38
  first_name = Column(String, nullable=True)
36
39
  last_name = Column(String, nullable=True)
37
- hashed_password = Column(String, nullable=False)
38
- salt = Column(String, nullable=False)
40
+ hashed_password = Column(String, nullable=True)
41
+ salt = Column(String, nullable=True)
39
42
  disabled = Column(Boolean, nullable=False, server_default="f")
40
43
  disabled_reason = Column(EnumColumn(DisabledReason), nullable=True)
41
44
  last_login_at = Column(DateTime(timezone=True), nullable=True)
42
45
  password_reset_at = Column(DateTime(timezone=True), nullable=True)
46
+ password_login_enabled = Column(Boolean, nullable=True)
47
+ totp_secret = Column(
48
+ StringEncryptedType(
49
+ type_in=String(),
50
+ key=CONFIG.security.app_encryption_key,
51
+ engine=AesGcmEngine,
52
+ padding="pkcs5",
53
+ ),
54
+ nullable=True,
55
+ )
43
56
 
44
57
  # passive_deletes="all" prevents audit logs from having their
45
58
  # privacy_request_id set to null when a privacy_request is deleted.
@@ -79,11 +92,11 @@ class FidesUser(Base):
79
92
  """Create a FidesUser by hashing the password with a generated salt
80
93
  and storing the hashed password and the salt"""
81
94
 
82
- # we set a dummy password if one isn't provided because this means it's part of the user
83
- # invite flow and the password will be set by the user after they accept their invite
84
- hashed_password, salt = FidesUser.hash_password(
85
- data.get("password") or str(uuid.uuid4())
86
- )
95
+ if password := data.get("password"):
96
+ hashed_password, salt = FidesUser.hash_password(password)
97
+ else:
98
+ hashed_password = None
99
+ salt = None
87
100
 
88
101
  user = super().create(
89
102
  db,
@@ -96,6 +109,7 @@ class FidesUser(Base):
96
109
  "last_name": data.get("last_name"),
97
110
  "disabled": data.get("disabled") or False,
98
111
  "disabled_reason": data.get("disabled_reason"),
112
+ "password_login_enabled": data.get("password_login_enabled"),
99
113
  },
100
114
  check_name=check_name,
101
115
  )
@@ -104,6 +118,9 @@ class FidesUser(Base):
104
118
 
105
119
  def credentials_valid(self, password: str, encoding: str = "UTF-8") -> bool:
106
120
  """Verifies that the provided password is correct."""
121
+ if self.salt is None:
122
+ return False
123
+
107
124
  provided_password_hash = hash_credential_with_salt(
108
125
  password.encode(encoding),
109
126
  self.salt.encode(encoding),
@@ -67,6 +67,8 @@ class FidesUserInvite(Base):
67
67
 
68
68
  def invite_code_valid(self, invite_code: str, encoding: str = "UTF-8") -> bool:
69
69
  """Verifies that the provided invite code is correct."""
70
+ if self.salt is None:
71
+ return False
70
72
 
71
73
  invite_code_hash = hash_credential_with_salt(
72
74
  invite_code.encode(encoding),
@@ -20,6 +20,7 @@ from fides.api.models import (
20
20
  from fides.api.models.location_regulation_selections import PrivacyNoticeRegion
21
21
  from fides.api.models.privacy_notice import PrivacyNotice
22
22
  from fides.api.models.property import Property
23
+ from fides.api.models.tcf_publisher_restrictions import TCFConfiguration
23
24
  from fides.api.schemas.language import SupportedLanguage
24
25
 
25
26
 
@@ -228,6 +229,13 @@ class PrivacyExperienceConfig(PrivacyExperienceConfigBase, Base):
228
229
  EnumColumn(RejectAllMechanism),
229
230
  nullable=True,
230
231
  )
232
+ # Optional FK to a TCF Configuration
233
+
234
+ tcf_configuration_id = Column(
235
+ String,
236
+ ForeignKey(TCFConfiguration.id_field_path, ondelete="SET NULL"),
237
+ nullable=True,
238
+ )
231
239
 
232
240
  # Relationships
233
241
  experiences = relationship(
@@ -258,6 +266,12 @@ class PrivacyExperienceConfig(PrivacyExperienceConfigBase, Base):
258
266
  lazy="selectin",
259
267
  )
260
268
 
269
+ tcf_configuration: RelationshipProperty[Optional[TCFConfiguration]] = relationship(
270
+ "TCFConfiguration",
271
+ back_populates="privacy_experience_configs",
272
+ lazy="selectin",
273
+ )
274
+
261
275
  @property
262
276
  def regions(self) -> List[PrivacyNoticeRegion]:
263
277
  """Return the regions using this experience config"""
@@ -548,6 +562,17 @@ class PrivacyExperienceConfigHistory(
548
562
  EnumColumn(RejectAllMechanism),
549
563
  nullable=True,
550
564
  )
565
+ # Optional FK to a TCF Configuration
566
+ tcf_configuration_id = Column(
567
+ String,
568
+ ForeignKey(TCFConfiguration.id_field_path, ondelete="SET NULL"),
569
+ nullable=True,
570
+ )
571
+
572
+ tcf_configuration: RelationshipProperty[Optional[TCFConfiguration]] = relationship(
573
+ "TCFConfiguration",
574
+ lazy="selectin",
575
+ )
551
576
 
552
577
  version = Column(Float, nullable=False, default=1.0)
553
578
 
@@ -611,6 +636,7 @@ class PrivacyExperience(Base):
611
636
  tcf_special_features: List = []
612
637
  tcf_system_consents: List = []
613
638
  tcf_system_legitimate_interests: List = []
639
+ tcf_publisher_restrictions: List = []
614
640
  gvl: Optional[Dict] = {}
615
641
  # TCF Developer-Friendly Meta added at runtime as the result of build_tc_data_for_mobile
616
642
  meta: Dict = {}