ethyca-fides 2.56.3b0__py2.py3-none-any.whl → 2.56.3b2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/METADATA +1 -2
- {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/RECORD +106 -102
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/1152c1717849_fix_monitorexecution_started_default.py +57 -0
- fides/api/alembic/migrations/versions/69ad6d844e21_add_comments_and_comment_references.py +84 -0
- fides/api/alembic/migrations/versions/6ea2171c544f_change_attachment_storage_key_to_.py +77 -0
- fides/api/custom_types.py +24 -1
- fides/api/db/base.py +1 -0
- fides/api/models/attachment.py +109 -49
- fides/api/models/comment.py +109 -0
- fides/api/models/detection_discovery.py +4 -2
- fides/api/service/connectors/query_configs/saas_query_config.py +21 -15
- fides/api/service/storage/storage_uploader_service.py +4 -10
- fides/api/task/graph_task.py +84 -43
- fides/api/tasks/storage.py +106 -15
- fides/api/util/aws_util.py +19 -0
- fides/api/util/collection_util.py +117 -0
- fides/api/util/consent_util.py +20 -5
- fides/api/util/saas_util.py +32 -56
- fides/config/security_settings.py +7 -13
- fides/data/language/languages.yml +2 -0
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-54e64129c5f3042a.js → _app-3b7bbcdb61d952e7.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{index-c9fa68dc0fa42c81.js → index-94e6d589c4edf360.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-4769f55b138073f7.js +1 -0
- fides/ui-build/static/admin/_next/static/{o6oSu0mrMicc3b7f8nyq5 → n4uO6TqGfiKHQ-X5XYkoy}/_buildManifest.js +1 -1
- fides/ui-build/static/admin/add-systems/manual.html +1 -1
- fides/ui-build/static/admin/add-systems/multiple.html +1 -1
- fides/ui-build/static/admin/add-systems.html +1 -1
- fides/ui-build/static/admin/ant-poc.html +1 -1
- fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
- fides/ui-build/static/admin/consent/configure.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
- fides/ui-build/static/admin/consent/properties.html +1 -1
- fides/ui-build/static/admin/consent/reporting.html +1 -1
- fides/ui-build/static/admin/consent.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
- fides/ui-build/static/admin/data-catalog.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
- fides/ui-build/static/admin/data-discovery/activity.html +1 -1
- fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/detection.html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
- fides/ui-build/static/admin/datamap.html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
- fides/ui-build/static/admin/dataset/new.html +1 -1
- fides/ui-build/static/admin/dataset.html +1 -1
- fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
- fides/ui-build/static/admin/datastore-connection/new.html +1 -1
- fides/ui-build/static/admin/datastore-connection.html +1 -1
- fides/ui-build/static/admin/index.html +1 -1
- fides/ui-build/static/admin/integrations/[id].html +1 -1
- fides/ui-build/static/admin/integrations.html +1 -1
- fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
- fides/ui-build/static/admin/lib/fides-headless.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +3 -3
- fides/ui-build/static/admin/lib/fides.js +2 -2
- fides/ui-build/static/admin/login/[provider].html +1 -1
- fides/ui-build/static/admin/login.html +1 -1
- fides/ui-build/static/admin/messaging/[id].html +1 -1
- fides/ui-build/static/admin/messaging/add-template.html +1 -1
- fides/ui-build/static/admin/messaging.html +1 -1
- fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
- fides/ui-build/static/admin/privacy-requests.html +1 -1
- fides/ui-build/static/admin/properties/[id].html +1 -1
- fides/ui-build/static/admin/properties/add-property.html +1 -1
- fides/ui-build/static/admin/properties.html +1 -1
- fides/ui-build/static/admin/reporting/datamap.html +1 -1
- fides/ui-build/static/admin/settings/about.html +1 -1
- fides/ui-build/static/admin/settings/consent.html +1 -1
- fides/ui-build/static/admin/settings/custom-fields.html +1 -1
- fides/ui-build/static/admin/settings/domain-records.html +1 -1
- fides/ui-build/static/admin/settings/domains.html +1 -1
- fides/ui-build/static/admin/settings/email-templates.html +1 -1
- fides/ui-build/static/admin/settings/locations.html +1 -1
- fides/ui-build/static/admin/settings/organization.html +1 -1
- fides/ui-build/static/admin/settings/regulations.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id].html +1 -1
- fides/ui-build/static/admin/systems.html +1 -1
- fides/ui-build/static/admin/taxonomy.html +1 -1
- fides/ui-build/static/admin/user-management/new.html +1 -1
- fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
- fides/ui-build/static/admin/user-management.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-815497f4f12600ec.js +0 -1
- {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/LICENSE +0 -0
- {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{o6oSu0mrMicc3b7f8nyq5 → n4uO6TqGfiKHQ-X5XYkoy}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Change Attachment.storage_key to foreign key
|
|
2
|
+
|
|
3
|
+
Revision ID: 6ea2171c544f
|
|
4
|
+
Revises: 1152c1717849
|
|
5
|
+
Create Date: 2025-03-10 17:36:31.504831
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
from alembic import op
|
|
11
|
+
from sqlalchemy.dialects import postgresql
|
|
12
|
+
|
|
13
|
+
# revision identifiers, used by Alembic.
|
|
14
|
+
revision = "6ea2171c544f"
|
|
15
|
+
down_revision = "1152c1717849"
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade():
|
|
21
|
+
|
|
22
|
+
# Alter the column type and add the new foreign key constraint
|
|
23
|
+
op.alter_column(
|
|
24
|
+
"attachment",
|
|
25
|
+
"storage_key",
|
|
26
|
+
existing_type=sa.String(),
|
|
27
|
+
type_=sa.String(),
|
|
28
|
+
nullable=False,
|
|
29
|
+
)
|
|
30
|
+
op.create_foreign_key(
|
|
31
|
+
"fk_attachment_storage_key",
|
|
32
|
+
"attachment",
|
|
33
|
+
"storageconfig",
|
|
34
|
+
["storage_key"],
|
|
35
|
+
["key"],
|
|
36
|
+
ondelete="CASCADE",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Add index on attachment_reference.reference_id
|
|
40
|
+
op.create_index(
|
|
41
|
+
"ix_attachment_reference_reference_id",
|
|
42
|
+
"attachment_reference",
|
|
43
|
+
["reference_id"],
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Add index on attachment_reference.reference_type
|
|
47
|
+
op.create_index(
|
|
48
|
+
"ix_attachment_reference_reference_type",
|
|
49
|
+
"attachment_reference",
|
|
50
|
+
["reference_type"],
|
|
51
|
+
)
|
|
52
|
+
# ### end Alembic commands ###
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def downgrade():
|
|
56
|
+
# Drop the index on attachment_reference.reference_id
|
|
57
|
+
op.drop_index(
|
|
58
|
+
"ix_attachment_reference_reference_id", table_name="attachment_reference"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Drop the index on attachment_reference.reference_type
|
|
62
|
+
op.drop_index(
|
|
63
|
+
"ix_attachment_reference_reference_type", table_name="attachment_reference"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Drop the new foreign key constraint
|
|
67
|
+
op.drop_constraint("fk_attachment_storage_key", "attachment", type_="foreignkey")
|
|
68
|
+
|
|
69
|
+
# Revert the column type change
|
|
70
|
+
op.alter_column(
|
|
71
|
+
"attachment",
|
|
72
|
+
"storage_key",
|
|
73
|
+
existing_type=sa.String(),
|
|
74
|
+
type_=sa.String(),
|
|
75
|
+
nullable=True,
|
|
76
|
+
)
|
|
77
|
+
# ### end Alembic commands ###
|
fides/api/custom_types.py
CHANGED
|
@@ -116,6 +116,23 @@ def validate_path_of_url(value: AnyUrl) -> str:
|
|
|
116
116
|
|
|
117
117
|
We perform the basic URL validation, but also prevent URLs with paths,
|
|
118
118
|
as paths are not part of an origin.
|
|
119
|
+
|
|
120
|
+
As this is meant to be used as a validator _after_ the AnyUrl validator,
|
|
121
|
+
we implicitly disallow `*` to be specified as an origin value, since it is
|
|
122
|
+
rejected by the AnyUrl validator.
|
|
123
|
+
|
|
124
|
+
Note that `*` _is_ considered a valid origin value
|
|
125
|
+
(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin),
|
|
126
|
+
but we specifically disallow it to be set on the `cors_origins` security setting
|
|
127
|
+
in our application. This is because non-owner users (contributors)
|
|
128
|
+
are able to edit the `cors_origins` security setting (via API), and we do not
|
|
129
|
+
want to allow them to set a wildcard origin.
|
|
130
|
+
|
|
131
|
+
Instead, `.*` can be set via the `cors_origin_regex` security setting
|
|
132
|
+
to effectively allow all origins. This is roughly equivalent to setting
|
|
133
|
+
`"*"` as a `cors_origin`, but there is a slight behavioral difference:
|
|
134
|
+
the `Access-Control-Allow-Origin` in the response will be set to the
|
|
135
|
+
`Access-Control-Allow-Origin` specified in the request rather than `"*"`.
|
|
119
136
|
"""
|
|
120
137
|
if value.path and value.path != "/":
|
|
121
138
|
raise ValueError("URL origin values cannot contain a path.")
|
|
@@ -125,7 +142,13 @@ def validate_path_of_url(value: AnyUrl) -> str:
|
|
|
125
142
|
return str(value).rstrip("/")
|
|
126
143
|
|
|
127
144
|
|
|
128
|
-
|
|
145
|
+
# This custom type is used to validate HTTP origins, e.g. CORS origins
|
|
146
|
+
# used in SecuritySettings. See docstring for `validate_path_of_url`
|
|
147
|
+
# for more details.
|
|
148
|
+
URLOriginString = Annotated[
|
|
149
|
+
AnyUrl,
|
|
150
|
+
AfterValidator(validate_path_of_url),
|
|
151
|
+
]
|
|
129
152
|
|
|
130
153
|
|
|
131
154
|
def validate_css_str(value: str) -> str:
|
fides/api/db/base.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
from fides.api.db.base_class import Base
|
|
5
5
|
from fides.api.models.application_config import ApplicationConfig
|
|
6
6
|
from fides.api.models.asset import Asset
|
|
7
|
+
from fides.api.models.attachment import Attachment, AttachmentReference
|
|
7
8
|
from fides.api.models.audit_log import AuditLog
|
|
8
9
|
from fides.api.models.authentication_request import AuthenticationRequest
|
|
9
10
|
from fides.api.models.client import ClientDetail
|
fides/api/models/attachment.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from enum import Enum as EnumType
|
|
2
3
|
from typing import Any, Optional
|
|
3
4
|
|
|
5
|
+
from loguru import logger as log
|
|
4
6
|
from sqlalchemy import Column
|
|
5
7
|
from sqlalchemy import Enum as EnumColumn
|
|
6
8
|
from sqlalchemy import ForeignKey, String, UniqueConstraint
|
|
@@ -9,6 +11,15 @@ from sqlalchemy.orm import Session, relationship
|
|
|
9
11
|
|
|
10
12
|
from fides.api.db.base_class import Base
|
|
11
13
|
from fides.api.models.fides_user import FidesUser # pylint: disable=unused-import
|
|
14
|
+
from fides.api.models.storage import StorageConfig # pylint: disable=unused-import
|
|
15
|
+
from fides.api.schemas.storage.storage import StorageDetails, StorageType
|
|
16
|
+
from fides.api.tasks.storage import (
|
|
17
|
+
LOCAL_FIDES_UPLOAD_DIRECTORY,
|
|
18
|
+
generic_delete_from_s3,
|
|
19
|
+
generic_retrieve_from_s3,
|
|
20
|
+
get_local_filename,
|
|
21
|
+
upload_to_s3,
|
|
22
|
+
)
|
|
12
23
|
|
|
13
24
|
|
|
14
25
|
class AttachmentType(str, EnumType):
|
|
@@ -74,7 +85,9 @@ class Attachment(Base):
|
|
|
74
85
|
)
|
|
75
86
|
file_name = Column(String, nullable=False)
|
|
76
87
|
attachment_type = Column(EnumColumn(AttachmentType), nullable=False)
|
|
77
|
-
storage_key = Column(
|
|
88
|
+
storage_key = Column(
|
|
89
|
+
String, ForeignKey("storageconfig.key", ondelete="CASCADE"), nullable=False
|
|
90
|
+
)
|
|
78
91
|
|
|
79
92
|
user = relationship(
|
|
80
93
|
"FidesUser",
|
|
@@ -90,64 +103,111 @@ class Attachment(Base):
|
|
|
90
103
|
uselist=True,
|
|
91
104
|
)
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
106
|
+
config = relationship(
|
|
107
|
+
"StorageConfig",
|
|
108
|
+
lazy="selectin",
|
|
109
|
+
uselist=False,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def upload(self, attachment: bytes) -> None:
|
|
113
|
+
"""Uploads an attachment to S3 or local storage."""
|
|
114
|
+
if self.config.type == StorageType.s3:
|
|
115
|
+
bucket_name = f"{self.config.details[StorageDetails.BUCKET.value]}"
|
|
116
|
+
auth_method = self.config.details[StorageDetails.AUTH_METHOD.value]
|
|
117
|
+
upload_to_s3(
|
|
118
|
+
storage_secrets=self.config.secrets,
|
|
119
|
+
data={},
|
|
120
|
+
bucket_name=bucket_name,
|
|
121
|
+
file_key=self.id,
|
|
122
|
+
resp_format=self.config.format,
|
|
123
|
+
privacy_request=None,
|
|
124
|
+
document=attachment,
|
|
125
|
+
auth_method=auth_method,
|
|
126
|
+
)
|
|
127
|
+
log.info(f"Uploaded {self.file_name} to S3 bucket {bucket_name}/{self.id}")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if self.config.type == StorageType.local:
|
|
131
|
+
filename = get_local_filename(self.id)
|
|
132
|
+
with open(filename, "wb") as file:
|
|
133
|
+
file.write(attachment)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
raise ValueError(f"Unsupported storage type: {self.config.type}")
|
|
137
|
+
|
|
138
|
+
def retrieve_attachment(self) -> Optional[bytes]:
|
|
139
|
+
"""Returns the attachment from S3 in bytes form."""
|
|
140
|
+
if self.config.type == StorageType.s3:
|
|
141
|
+
bucket_name = f"{self.config.details[StorageDetails.BUCKET.value]}"
|
|
142
|
+
auth_method = self.config.details[StorageDetails.AUTH_METHOD.value]
|
|
143
|
+
return generic_retrieve_from_s3(
|
|
144
|
+
storage_secrets=self.config.secrets,
|
|
145
|
+
bucket_name=bucket_name,
|
|
146
|
+
file_key=self.id,
|
|
147
|
+
auth_method=auth_method,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if self.config.type == StorageType.local:
|
|
151
|
+
filename = f"{LOCAL_FIDES_UPLOAD_DIRECTORY}/{self.id}"
|
|
152
|
+
with open(filename, "rb") as file:
|
|
153
|
+
return file.read()
|
|
154
|
+
|
|
155
|
+
raise ValueError(f"Unsupported storage type: {self.config.type}")
|
|
156
|
+
|
|
157
|
+
def delete_attachment_from_storage(self) -> None:
|
|
158
|
+
"""Deletes an attachment from S3 or local storage."""
|
|
159
|
+
if self.config.type == StorageType.s3:
|
|
160
|
+
bucket_name = f"{self.config.details[StorageDetails.BUCKET.value]}"
|
|
161
|
+
auth_method = self.config.details[StorageDetails.AUTH_METHOD.value]
|
|
162
|
+
generic_delete_from_s3(
|
|
163
|
+
storage_secrets=self.config.secrets,
|
|
164
|
+
bucket_name=bucket_name,
|
|
165
|
+
file_key=self.id,
|
|
166
|
+
auth_method=auth_method,
|
|
167
|
+
)
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
if self.config.type == StorageType.local:
|
|
171
|
+
filename = f"{LOCAL_FIDES_UPLOAD_DIRECTORY}/{self.id}"
|
|
172
|
+
os.remove(filename)
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
raise ValueError(f"Unsupported storage type: {self.config.type}")
|
|
131
176
|
|
|
132
177
|
@classmethod
|
|
133
|
-
def
|
|
178
|
+
def create_and_upload(
|
|
134
179
|
cls,
|
|
135
180
|
db: Session,
|
|
136
181
|
*,
|
|
137
182
|
data: dict[str, Any],
|
|
183
|
+
attachment_file: bytes,
|
|
138
184
|
check_name: bool = False,
|
|
139
|
-
attachment: Optional[
|
|
140
|
-
bytes
|
|
141
|
-
] = None, # This will not be optional once the upload method is implemented.
|
|
142
185
|
) -> "Attachment":
|
|
143
186
|
"""Creates a new attachment record in the database and uploads the attachment to S3."""
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
187
|
+
if attachment_file is None:
|
|
188
|
+
raise ValueError("Attachment is required")
|
|
189
|
+
attachment_model = super().create(db=db, data=data, check_name=check_name)
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
attachment_model.upload(attachment_file)
|
|
193
|
+
return attachment_model
|
|
194
|
+
except Exception as e:
|
|
195
|
+
log.error(f"Failed to upload attachment: {e}")
|
|
196
|
+
attachment_model.delete(db)
|
|
197
|
+
raise e
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def create(
|
|
201
|
+
cls,
|
|
202
|
+
db: Session,
|
|
203
|
+
*,
|
|
204
|
+
data: dict[str, Any],
|
|
205
|
+
check_name: bool = False,
|
|
206
|
+
) -> "Attachment":
|
|
207
|
+
"""Raises Error, provides information for user to create with upload instead."""
|
|
208
|
+
raise NotImplementedError("Please use create_and_upload method for Attachment")
|
|
147
209
|
|
|
148
210
|
def delete(self, db: Session) -> None:
|
|
149
211
|
"""Deletes an attachment record from the database and deletes the attachment from S3."""
|
|
150
|
-
|
|
151
|
-
# attachment_record.delete_attachment_from_s3(db)
|
|
152
|
-
# log.info(f"Deleted attachment {attachment_record.id} from S3")
|
|
212
|
+
self.delete_attachment_from_storage()
|
|
153
213
|
super().delete(db=db)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from enum import Enum as EnumType
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import Column
|
|
5
|
+
from sqlalchemy import Enum as EnumColumn
|
|
6
|
+
from sqlalchemy import ForeignKey, String, UniqueConstraint
|
|
7
|
+
from sqlalchemy.ext.declarative import declared_attr
|
|
8
|
+
from sqlalchemy.orm import Session, relationship
|
|
9
|
+
|
|
10
|
+
from fides.api.db.base_class import Base
|
|
11
|
+
from fides.api.models.attachment import Attachment, AttachmentReference
|
|
12
|
+
from fides.api.models.fides_user import FidesUser # pylint: disable=unused-import
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CommentType(str, EnumType):
|
|
16
|
+
"""
|
|
17
|
+
Enum for comment types. Indicates comment usage.
|
|
18
|
+
|
|
19
|
+
- notes are internal comments.
|
|
20
|
+
- reply comments are public and may cause an email or other communciation to be sent
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
note = "note"
|
|
24
|
+
reply = "reply"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CommentReferenceType(str, EnumType):
|
|
28
|
+
"""
|
|
29
|
+
Enum for comment reference types. Indicates where the comment is referenced.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
manual_step = "manual_step"
|
|
33
|
+
privacy_request = "privacy_request"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CommentReference(Base):
|
|
37
|
+
"""
|
|
38
|
+
Stores information about a comment and any other element which may reference that comment.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@declared_attr
|
|
42
|
+
def __tablename__(cls) -> str:
|
|
43
|
+
"""Overriding base class method to set the table name."""
|
|
44
|
+
return "comment_reference"
|
|
45
|
+
|
|
46
|
+
comment_id = Column(String, ForeignKey("comment.id"), nullable=False)
|
|
47
|
+
reference_id = Column(String, nullable=False)
|
|
48
|
+
reference_type = Column(EnumColumn(CommentReferenceType), nullable=False)
|
|
49
|
+
|
|
50
|
+
__table_args__ = (
|
|
51
|
+
UniqueConstraint("comment_id", "reference_id", name="comment_reference_uc"),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
comment = relationship(
|
|
55
|
+
"Comment",
|
|
56
|
+
back_populates="references",
|
|
57
|
+
uselist=False,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def create(
|
|
62
|
+
cls, db: Session, *, data: dict[str, Any], check_name: bool = False
|
|
63
|
+
) -> "CommentReference":
|
|
64
|
+
"""Creates a new comment reference record in the database."""
|
|
65
|
+
return super().create(db=db, data=data, check_name=check_name)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Comment(Base):
|
|
69
|
+
"""
|
|
70
|
+
Stores information about a Comment.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
user_id = Column(
|
|
74
|
+
String, ForeignKey("fidesuser.id", ondelete="SET NULL"), nullable=True
|
|
75
|
+
)
|
|
76
|
+
comment_text = Column(String, nullable=False)
|
|
77
|
+
comment_type = Column(EnumColumn(CommentType), nullable=False)
|
|
78
|
+
|
|
79
|
+
user = relationship(
|
|
80
|
+
"FidesUser",
|
|
81
|
+
backref="comments",
|
|
82
|
+
lazy="selectin",
|
|
83
|
+
uselist=False,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
references = relationship(
|
|
87
|
+
"CommentReference",
|
|
88
|
+
back_populates="comment",
|
|
89
|
+
cascade="all, delete",
|
|
90
|
+
uselist=True,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def get_attachments(self, db: Session) -> list[Attachment]:
|
|
94
|
+
"""Retrieve all attachments associated with this comment."""
|
|
95
|
+
stmt = (
|
|
96
|
+
db.query(Attachment)
|
|
97
|
+
.join(
|
|
98
|
+
AttachmentReference, Attachment.id == AttachmentReference.attachment_id
|
|
99
|
+
)
|
|
100
|
+
.where(AttachmentReference.reference_id == self.id)
|
|
101
|
+
)
|
|
102
|
+
return db.execute(stmt).scalars().all()
|
|
103
|
+
|
|
104
|
+
def delete(self, db: Session) -> None:
|
|
105
|
+
"""Delete the comment and all associated references."""
|
|
106
|
+
attachments = self.get_attachments(db)
|
|
107
|
+
for attachment in attachments:
|
|
108
|
+
attachment.delete(db)
|
|
109
|
+
db.delete(self)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from typing import Any, Dict, Iterable, List, Optional, Type
|
|
6
6
|
|
|
@@ -388,7 +388,9 @@ class MonitorExecution(Base):
|
|
|
388
388
|
)
|
|
389
389
|
status = Column(String, nullable=True)
|
|
390
390
|
started = Column(
|
|
391
|
-
DateTime(timezone=True),
|
|
391
|
+
DateTime(timezone=True),
|
|
392
|
+
nullable=True,
|
|
393
|
+
server_default=func.now(),
|
|
392
394
|
)
|
|
393
395
|
completed = Column(DateTime(timezone=True), nullable=True)
|
|
394
396
|
classification_instances = Column(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pylint: disable=too-many-instance-attributes
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
from copy import deepcopy
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from itertools import product
|
|
6
7
|
from typing import Any, Dict, List, Literal, Optional, Tuple, TypeVar
|
|
@@ -12,7 +13,6 @@ from loguru import logger
|
|
|
12
13
|
from sqlalchemy.orm import Session
|
|
13
14
|
|
|
14
15
|
from fides.api.common_exceptions import FidesopsException
|
|
15
|
-
from fides.api.graph.config import ScalarField
|
|
16
16
|
from fides.api.graph.execution import ExecutionNode
|
|
17
17
|
from fides.api.models.policy import Policy
|
|
18
18
|
from fides.api.models.privacy_request import (
|
|
@@ -33,7 +33,12 @@ from fides.api.task.refine_target_path import (
|
|
|
33
33
|
join_detailed_path,
|
|
34
34
|
)
|
|
35
35
|
from fides.api.util import saas_util
|
|
36
|
-
from fides.api.util.collection_util import
|
|
36
|
+
from fides.api.util.collection_util import (
|
|
37
|
+
Row,
|
|
38
|
+
flatten_dict,
|
|
39
|
+
merge_dicts,
|
|
40
|
+
unflatten_dict,
|
|
41
|
+
)
|
|
37
42
|
from fides.api.util.saas_util import (
|
|
38
43
|
ALL_OBJECT_FIELDS,
|
|
39
44
|
CUSTOM_PRIVACY_REQUEST_FIELDS,
|
|
@@ -45,7 +50,6 @@ from fides.api.util.saas_util import (
|
|
|
45
50
|
REPLY_TO_TOKEN,
|
|
46
51
|
UUID,
|
|
47
52
|
get_identities,
|
|
48
|
-
unflatten_dict,
|
|
49
53
|
)
|
|
50
54
|
from fides.common.api.v1.urn_registry import REQUEST_TASK_CALLBACK, V1_URL_PREFIX
|
|
51
55
|
from fides.config.config_proxy import ConfigProxy
|
|
@@ -510,22 +514,24 @@ class SaaSQueryConfig(QueryConfig[SaaSRequestParams]):
|
|
|
510
514
|
|
|
511
515
|
def all_value_map(self, row: Row) -> Dict[str, Any]:
|
|
512
516
|
"""
|
|
513
|
-
Takes a row and preserves only the fields that are defined in the
|
|
517
|
+
Takes a row and preserves only the fields that are defined in the collection.
|
|
514
518
|
Used for scenarios when an update endpoint has required fields other than
|
|
515
519
|
just the fields being updated.
|
|
516
520
|
"""
|
|
521
|
+
flattened_row = flatten_dict(deepcopy(row))
|
|
517
522
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
523
|
+
# Get root field names defined in the collection
|
|
524
|
+
collection_fields = {
|
|
525
|
+
field_path.string_path.split(".")[0]
|
|
526
|
+
for field_path, _ in self.field_map().items()
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
# Only keep the field values defined in the collection
|
|
530
|
+
return {
|
|
531
|
+
path: value
|
|
532
|
+
for path, value in flattened_row.items()
|
|
533
|
+
if path.split(".")[0] in collection_fields
|
|
534
|
+
}
|
|
529
535
|
|
|
530
536
|
def query_to_str(self, t: T, input_data: Dict[str, List[Any]]) -> str:
|
|
531
537
|
"""Convert query to string"""
|
|
@@ -41,9 +41,7 @@ def upload(
|
|
|
41
41
|
logger.warning("Storage type not found: {}", storage_key)
|
|
42
42
|
raise StorageUploadError(f"Storage type not found: {storage_key}")
|
|
43
43
|
uploader: Any = _get_uploader_from_config_type(config.type) # type: ignore
|
|
44
|
-
return uploader(
|
|
45
|
-
db, config, data, privacy_request, data_category_field_mapping, data_use_map
|
|
46
|
-
)
|
|
44
|
+
return uploader(db, config, data, privacy_request)
|
|
47
45
|
|
|
48
46
|
|
|
49
47
|
def get_extension(resp_format: ResponseFormat) -> str:
|
|
@@ -88,14 +86,13 @@ def _s3_uploader(
|
|
|
88
86
|
config: StorageConfig,
|
|
89
87
|
data: Dict,
|
|
90
88
|
privacy_request: PrivacyRequest,
|
|
91
|
-
data_category_field_mapping: Optional[DataCategoryFieldMapping] = None,
|
|
92
|
-
data_use_map: Optional[Dict[str, Set[str]]] = None,
|
|
93
89
|
) -> str:
|
|
94
90
|
"""Constructs necessary info needed for s3 before calling upload"""
|
|
95
91
|
file_key: str = _construct_file_key(privacy_request.id, config)
|
|
96
92
|
|
|
97
93
|
bucket_name = config.details[StorageDetails.BUCKET.value]
|
|
98
94
|
auth_method = config.details[StorageDetails.AUTH_METHOD.value]
|
|
95
|
+
document = None
|
|
99
96
|
|
|
100
97
|
return upload_to_s3(
|
|
101
98
|
config.secrets, # type: ignore
|
|
@@ -104,9 +101,8 @@ def _s3_uploader(
|
|
|
104
101
|
file_key,
|
|
105
102
|
config.format.value, # type: ignore
|
|
106
103
|
privacy_request,
|
|
104
|
+
document,
|
|
107
105
|
auth_method,
|
|
108
|
-
data_category_field_mapping,
|
|
109
|
-
data_use_map,
|
|
110
106
|
)
|
|
111
107
|
|
|
112
108
|
|
|
@@ -115,9 +111,7 @@ def _local_uploader(
|
|
|
115
111
|
config: StorageConfig,
|
|
116
112
|
data: Dict,
|
|
117
113
|
privacy_request: PrivacyRequest,
|
|
118
|
-
data_category_field_mapping: Optional[DataCategoryFieldMapping] = None,
|
|
119
|
-
data_use_map: Optional[Dict[str, Set[str]]] = None,
|
|
120
114
|
) -> str:
|
|
121
115
|
"""Uploads data to local storage, used for quick-start/demo purposes"""
|
|
122
116
|
file_key: str = _construct_file_key(privacy_request.id, config)
|
|
123
|
-
return upload_to_local(data, file_key, privacy_request, config.format.value
|
|
117
|
+
return upload_to_local(data, file_key, privacy_request, config.format.value) # type: ignore
|