Skip to content
This repository was archived by the owner on Nov 30, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cb93b9b
initial infra for email notification subject request completion
eastandwestwind Sep 2, 2022
9b6fdc1
adding new config var for ttl
eastandwestwind Sep 6, 2022
19fed19
changes to accomodate new config var for ttl
eastandwestwind Sep 7, 2022
fcebda0
explicit err messages
eastandwestwind Sep 7, 2022
ff45764
pull latest, fix conflicts
eastandwestwind Sep 7, 2022
b4d68e0
update docs
eastandwestwind Sep 7, 2022
34d119f
gets unit tests working, adds new test
eastandwestwind Sep 8, 2022
a1e93fd
formatting, types, respect config var for sending email notification
eastandwestwind Sep 8, 2022
514755c
isort, and update changelog
eastandwestwind Sep 8, 2022
c362b39
format
eastandwestwind Sep 8, 2022
03eb704
fix logic to handle access vs deletion
eastandwestwind Sep 8, 2022
50a1119
pull latest, resolve conflicts
eastandwestwind Sep 8, 2022
629b220
sort
eastandwestwind Sep 8, 2022
115fc04
turn off email complete notification in tests by default, override fo…
eastandwestwind Sep 8, 2022
ead2eb1
pull latest, fix conflicts
eastandwestwind Sep 8, 2022
6fb8fd2
fix integration test
eastandwestwind Sep 8, 2022
a1e2070
cr changes, notably better testing, default to not send emails in dev…
eastandwestwind Sep 9, 2022
29b83de
pull latest, fix conflicts
eastandwestwind Sep 9, 2022
12526ae
remove unneeded test
eastandwestwind Sep 9, 2022
851fdd9
move fixture locally
eastandwestwind Sep 9, 2022
1dcf9ac
move fixture to class level
eastandwestwind Sep 9, 2022
c8697b2
method signature
eastandwestwind Sep 9, 2022
50b6a0c
annotate fixture properly
eastandwestwind Sep 9, 2022
4e5326f
get unit test passing locally
eastandwestwind Sep 12, 2022
f165598
use Any matcher for integration test
eastandwestwind Sep 12, 2022
13d5a0b
sort
eastandwestwind Sep 12, 2022
040fcf4
fix params in assert
eastandwestwind Sep 12, 2022
0e36740
pull latest, fix conflicts
eastandwestwind Sep 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ The types of changes are:
* Data seeding for Datadog access tests [#1269](https://github.com/ethyca/fidesops/pull/1269)
* Added support for one-to-many relationships for param_values in SaaS configs [#1253](https://github.com/ethyca/fidesops/pull/1253)
* Added erasure endpoints for Shopify connector [#1289](https://github.com/ethyca/fidesops/pull/1289)
* Adds ability to send email notification upon privacy request completion [#1282](https://github.com/ethyca/fidesops/pull/1282)

### Docs

* Fix analytics opt out environment variable name [#1170](https://github.com/ethyca/fidesops/pull/1170)
Expand Down
3 changes: 3 additions & 0 deletions data/config/fidesops.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ analytics_id = "internal"

[admin_ui]
enabled = true

[notifications]
send_request_completion_notification = true
8 changes: 8 additions & 0 deletions docs/fidesops/docs/guides/configuration_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ The `fidesops.toml` file should specify the following variables:
| `root_username` | `FIDESOPS__SECURITY__ROOT_USERNAME` | string | root_user | None | If set this can be used in conjunction with `root_password` to log in as a root user without first needing to create a user in the database. |
| `root_password` | `FIDESOPS__SECURITY__ROOT_PASSWORD` | string | apassword | None | If set this can be used in conjunction with `root_username` to log in as a root user without first needing to create a user in the database. |
| `root_user_scopes` | `FIDESOPS__SECURITY__ROOT_USER_SCOPES` | list of strings | ["client:create", "client:update"] | All available scopes | The scopes granted to the root user when logging in with `root_username` and `root_password`. |
| `subject_request_download_link_ttl_seconds` | `FIDESOPS__SECURITY__SUBJECT_REQUEST_DOWNLOAD_LINK_TTL_SECONDS` | int | 86400 | 86400 | Time in seconds for a subject data package download link to remain valid, default to 1 day. |
| Execution Variables |---|---|---|---|---|
|`privacy_request_delay_timeout` | `FIDESOPS__EXECUTION__PRIVACY_REQUEST_DELAY_TIMEOUT` | int | 3600 | 3600 | The amount of time to wait for actions delaying privacy requests, for example pre and post processing webhooks.
|`task_retry_count` | `FIDESOPS__EXECUTION__TASK_RETRY_COUNT` | int | 5 | 0 | The number of times a failed request will be retried
Expand All @@ -61,6 +62,9 @@ The `fidesops.toml` file should specify the following variables:
|`analytics_opt_out` | `FIDESOPS__ROOT_USER__ANALYTICS_OPT_OUT` | bool | False | False | Opt out of sending anonymous usage data to Ethyca to improve the product experience.
| Admin UI Variables|---|---|---|---|---|
|`enabled` | `FIDESOPS__ADMIN_UI__ENABLED` | bool | False | True | Toggle whether the Admin UI is served from `/`
| Fidesops Notification Variables|---|---|---|---|---|
|`send_request_completion_notification` | `FIDESOPS__NOTIFICATIONS__SEND_REQUEST_COMPLETION_NOTIFICATION` | bool | True | True | Whether a notification will be sent to data subjects upon privacy request completion


### An example `fidesops.toml` configuration file

Expand Down Expand Up @@ -93,6 +97,7 @@ oauth_root_client_secret = "fidesopsadminsecret"
log_level = "INFO"
root_username = "root_user"
root_password = "Testpassword1!"
subject_request_download_link_ttl_seconds = 86400

[execution]
masking_strict = true
Expand All @@ -110,6 +115,9 @@ analytics_opt_out = false

[admin_ui]
enabled = true

[notifications]
send_request_completion_notification = true
```

Note: The configuration is case-sensitive, so the variables must be specified in `lowercase`.
Expand Down
3 changes: 2 additions & 1 deletion docs/fidesops/docs/guides/email_communications.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ Fidesops supports configuring third party email servers to handle outbound commu

Supported modes of use:

- Subject Identity Verification - sends a verification code to the user's email address prior to processing a subject request. for more information on identity verification, see the [Privacy Requests](privacy_requests.md#subject-identity-verification) guide.
- Subject Identity Verification - sends a verification code to the user's email address prior to processing a subject request. For more information on identity verification, see the [Privacy Requests](privacy_requests.md#subject-identity-verification) guide.
- Erasure Request Email Fulfillment - sends an email to configured third parties to process erasures for a given data subject. See [creating email Connectors](#email-third-party-services) for more information.
- Privacy Request Completion Notification - sends an email to user's email address with privacy request completion notification, including a download link to data package, for access requests. For more information on request completion notification, see the [Privacy Requests](privacy_requests.md#request-completion-notification) guide.

## Prerequisites

Expand Down
11 changes: 11 additions & 0 deletions docs/fidesops/docs/guides/privacy_requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ to continue privacy request execution. Until the Privacy Request identity is ve
```


## Request Completion Notification

By default, a request completion email will be sent to users, along with a link to download their data, if applicable. To change this behavior, set the `send_request_completion_notification`
variable in your `fidesops.toml`. You must also set up an [EmailConfig](./email_communications.md) that lets fidesops send automated emails
to your users. If using a custom privacy center, ensure that you intake an email identity, which is required for email notifications throughout fidesops.

!!! Note
For security purposes, the data package download link is a one-time link and expires in 24 hrs by default. To change TTL, update the `subject_request_download_link_ttl_seconds`
variable in your `fidesops.toml`.


## Approve and deny Privacy Requests

To review Privacy Requests before they are executed, set the `require_manual_request_approval` variable in your `fidesops.toml` to `TRUE`.
Expand Down
4 changes: 4 additions & 0 deletions fidesops.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ drp_jwt_secret = "secret"
log_level = "INFO"
root_username = "root_user"
root_password = "Testpassword1!"
subject_request_download_link_ttl_seconds = 86400

[execution]
masking_strict = true
Expand All @@ -44,3 +45,6 @@ analytics_opt_out = false

[admin_ui]
enabled = true

[notifications]
send_request_completion_notification = false
4 changes: 4 additions & 0 deletions src/fidesops/ops/common_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ class StorageConfigNotFoundException(BaseException):
"""Custom Exception - StorageConfig Not Found"""


class IdentityNotFoundException(BaseException):
"""Identity Not Found"""


class WebhookOrderException(BaseException):
"""Custom Exception - Issue with webhooks order"""

Expand Down
13 changes: 13 additions & 0 deletions src/fidesops/ops/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class FidesopsSecuritySettings(SecuritySettings):

log_level: str = "INFO"
root_user_scopes: Optional[List[str]] = SCOPE_REGISTRY
subject_request_download_link_ttl_seconds: Optional[int] = 86400

@validator("log_level", pre=True)
def validate_log_level(cls, value: str) -> str:
Expand Down Expand Up @@ -149,6 +150,15 @@ class Config:
env_prefix = "FIDESOPS__ADMIN_UI__"


class FidesopsNotificationSettings(FidesSettings):
"""Configuration settings for data subject and/or data processor notifications"""

send_request_completion_notification: Optional[bool] = True

class Config:
env_prefix = "FIDESOPS__NOTIFICATIONS__"


class FidesopsConfig(FidesSettings):
"""Configuration variables for the FastAPI project"""

Expand All @@ -158,6 +168,7 @@ class FidesopsConfig(FidesSettings):
execution: ExecutionSettings
root_user: RootUserSettings
admin_ui: AdminUiSettings
notifications: FidesopsNotificationSettings

port: int
is_test_mode: bool = os.getenv("TESTING", "").lower() == "true"
Expand Down Expand Up @@ -214,6 +225,7 @@ def log_all_config_values(self) -> None:
"cors_origins",
"encoding",
"oauth_access_token_expire_minutes",
"subject_request_download_link_ttl_seconds",
],
"execution": [
"task_retry_count",
Expand All @@ -222,6 +234,7 @@ def log_all_config_values(self) -> None:
"require_manual_request_approval",
"subject_identity_verification_required",
],
"notifications": ["send_request_completion_notification"],
}


Expand Down
6 changes: 6 additions & 0 deletions src/fidesops/ops/email_templates/get_email_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from fidesops.ops.common_exceptions import EmailTemplateUnhandledActionType
from fidesops.ops.email_templates.template_names import (
EMAIL_ERASURE_REQUEST_FULFILLMENT,
PRIVACY_REQUEST_COMPLETE_ACCESS_TEMPLATE,
PRIVACY_REQUEST_COMPLETE_DELETION_TEMPLATE,
SUBJECT_IDENTITY_VERIFICATION_TEMPLATE,
)
from fidesops.ops.schemas.email.email import EmailActionType
Expand All @@ -25,6 +27,10 @@ def get_email_template(action_type: EmailActionType) -> Template:
return template_env.get_template(SUBJECT_IDENTITY_VERIFICATION_TEMPLATE)
if action_type == EmailActionType.EMAIL_ERASURE_REQUEST_FULFILLMENT:
return template_env.get_template(EMAIL_ERASURE_REQUEST_FULFILLMENT)
if action_type == EmailActionType.PRIVACY_REQUEST_COMPLETE_ACCESS:
return template_env.get_template(PRIVACY_REQUEST_COMPLETE_ACCESS_TEMPLATE)
if action_type == EmailActionType.PRIVACY_REQUEST_COMPLETE_DELETION:
return template_env.get_template(PRIVACY_REQUEST_COMPLETE_DELETION_TEMPLATE)

logger.error("No corresponding template linked to the %s", action_type)
raise EmailTemplateUnhandledActionType(
Expand Down
2 changes: 2 additions & 0 deletions src/fidesops/ops/email_templates/template_names.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
SUBJECT_IDENTITY_VERIFICATION_TEMPLATE = "subject_identity_verification.html"
EMAIL_ERASURE_REQUEST_FULFILLMENT = "erasure_request_email_fulfillment.html"
PRIVACY_REQUEST_COMPLETE_DELETION_TEMPLATE = "privacy_request_complete_deletion.html"
PRIVACY_REQUEST_COMPLETE_ACCESS_TEMPLATE = "privacy_request_complete_access.html"
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Privacy Request Complete</title>
</head>
<body>
<main>
{% if download_links|length > 1 %}
<p>
Your data access has been completed and can be downloaded at the below links. For security purposes, these secret links will expire in 24 hours.
</p>
<ul>
{% for link in download_links %}
<li> {{link}} </li>
{% endfor %}
</ul>
{% else %}
<p>
Your data access has been completed and can be downloaded at {{download_links[0]}}. For security purposes, this secret link will expire in 24 hours.
</p>

{% endif %}
</main>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Privacy Request Complete</title>
</head>
<body>
<main>
<p>
Your privacy request for deletion has been completed.
</p>
</main>
</body>
</html>
10 changes: 9 additions & 1 deletion src/fidesops/ops/schemas/email/email.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from enum import Enum
from typing import Any, Dict, Optional, Union
from typing import Any, Dict, List, Optional, Union

from pydantic import BaseModel, Extra

Expand All @@ -20,6 +20,8 @@ class EmailActionType(Enum):
# verify email upon acct creation
SUBJECT_IDENTITY_VERIFICATION = "subject_identity_verification"
EMAIL_ERASURE_REQUEST_FULFILLMENT = "email_erasure_fulfillment"
PRIVACY_REQUEST_COMPLETE_ACCESS = "privacy_request_complete_access"
PRIVACY_REQUEST_COMPLETE_DELETION = "privacy_request_complete_deletion"


class EmailTemplateBodyParams(Enum):
Expand All @@ -41,6 +43,12 @@ def get_verification_code_ttl_minutes(self) -> int:
return self.verification_code_ttl_seconds // 60


class AccessRequestCompleteBodyParams(BaseModel):
"""Body params required for privacy request completion access email template"""

download_links: List[str]


class EmailForActionType(BaseModel):
"""Email details that depend on action type"""

Expand Down
25 changes: 18 additions & 7 deletions src/fidesops/ops/service/email/email_dispatch_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Any, Optional, Union
from typing import Any, Optional

import requests
from requests import Response
Expand All @@ -8,14 +8,12 @@
from fidesops.ops.common_exceptions import EmailDispatchException
from fidesops.ops.email_templates import get_email_template
from fidesops.ops.models.email import EmailConfig
from fidesops.ops.models.privacy_request import EmailRequestFulfillmentBodyParams
from fidesops.ops.schemas.email.email import (
EmailActionType,
EmailForActionType,
EmailServiceDetails,
EmailServiceSecrets,
EmailServiceType,
SubjectIdentityVerificationBodyParams,
)
from fidesops.ops.util.logger import Pii

Expand All @@ -26,10 +24,7 @@ def dispatch_email(
db: Session,
action_type: EmailActionType,
to_email: Optional[str],
email_body_params: Union[
SubjectIdentityVerificationBodyParams,
EmailRequestFulfillmentBodyParams,
],
email_body_params: Any,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still having issues with mypy errors here

) -> None:
if not to_email:
raise EmailDispatchException("No email supplied.")
Expand Down Expand Up @@ -82,6 +77,22 @@ def _build_email(
{"dataset_collection_action_required": body_params}
),
)
if action_type == EmailActionType.PRIVACY_REQUEST_COMPLETE_ACCESS:
base_template = get_email_template(action_type)
return EmailForActionType(
subject="Your data is ready to be downloaded",
body=base_template.render(
{
"download_links": body_params.download_links,
}
),
)
if action_type == EmailActionType.PRIVACY_REQUEST_COMPLETE_DELETION:
base_template = get_email_template(action_type)
return EmailForActionType(
subject="Your data has been deleted",
body=base_template.render(),
)
logger.error("Email action type %s is not implemented", action_type)
raise EmailDispatchException(f"Email action type {action_type} is not implemented")

Expand Down
Loading