Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sovrn Email Consent Connector #2543

Merged
merged 30 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
acd724f
Initial commit: Adding the boilerplate for a new consent email connec…
pattisdr Jan 28, 2023
6da08c9
Adjust the email connector to be sovrn-specific instead of generic, b…
pattisdr Feb 3, 2023
6d6e2d9
Add new request status to the UI.
pattisdr Feb 7, 2023
5f6d996
Add new awaiting_consent_email_send_at column on privacy requests.
pattisdr Feb 7, 2023
d551bce
Refactor consent email send to batch emails. Privacy request exits w…
pattisdr Feb 7, 2023
baba199
Revert RequiredComponentsValidator and just have the base ConsentEmai…
pattisdr Feb 7, 2023
f0548b6
Test the sovrn email connector (largely the test email functionality)…
pattisdr Feb 7, 2023
302361a
Add unit tests on the batch consent email send service. Break this …
pattisdr Feb 8, 2023
5dc98d0
After email send, put privacy requests in paused state and cache chec…
pattisdr Feb 8, 2023
28c7d0b
Add tests for the request runner service exiting when privacy request…
pattisdr Feb 8, 2023
0066a9a
Swap using celery beat for APScheduler, as our default mode is "task_…
pattisdr Feb 8, 2023
4ed58ad
Merge branch 'main' into fides_2394_sovrn_connector
pattisdr Feb 8, 2023
0b1fe82
Bump downrev after main merge.
pattisdr Feb 8, 2023
8981ed6
Light cleanup.
pattisdr Feb 8, 2023
a8d1da8
Add a new email system type, that contains just sovrn for now.
pattisdr Feb 9, 2023
c4efaf9
Increase test coverage of generic email connector not yet in use and …
pattisdr Feb 9, 2023
2b871cf
Fix grammar in consent email template and also add a restriction on "…
pattisdr Feb 9, 2023
34b43cc
Add assertion that awaiting_consent_email_send_at is set.
pattisdr Feb 9, 2023
11c7438
Transform user consent preferences from a fides lang data use fides k…
pattisdr Feb 9, 2023
892d123
Merge branch 'main' into fides_2394_sovrn_connector
pattisdr Feb 10, 2023
8f0d231
Update the schema for creating a consent email connector.
pattisdr Feb 14, 2023
5e1c1b6
Merge branch 'main' into fides_2394_sovrn_connector
pattisdr Feb 14, 2023
09d98ae
Bump downrev.
pattisdr Feb 14, 2023
e56bfdf
Fix typo.
pattisdr Feb 15, 2023
db20e9c
Merge main.
pattisdr Feb 16, 2023
e4c6454
Add sovrn email address to code comment.
pattisdr Feb 16, 2023
08d3393
Sovrn connector UI (#2579)
allisonking Feb 16, 2023
0e27ea4
Add sovrn cookie to privacy center (#2582)
allisonking Feb 16, 2023
934799c
Add sovrn logo
allisonking Feb 16, 2023
aed1dff
Update CHANGELOG.
pattisdr Feb 16, 2023
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
3 changes: 3 additions & 0 deletions .fides/db_dataset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,9 @@ dataset:
- name: consent_preferences
data_categories: [ system.operations ]
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: awaiting_consent_email_send_at
data_categories: [ system.operations ]
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: privacyrequesterror
data_categories: []
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
Expand Down
4 changes: 4 additions & 0 deletions clients/admin-ui/src/features/common/RequestStatusBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const statusPropMap: {
bg: "green.500",
label: "Completed",
},
awaiting_consent_email_send: {
bg: "gray.400",
label: "Awaiting Email Send",
},
denied: {
bg: "red.500",
label: "Denied",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const CONNECTION_TYPE_FILTER_MAP = new Map<string, ItemOption>([
},
],
["3rd party integrations", { value: SystemType.SAAS.toString() }],
["Email connectors", { value: SystemType.EMAIL.toString() }],
["Show all", { value: "" }],
]);

Expand All @@ -38,6 +39,10 @@ export const CONNECTOR_PARAMETERS_OPTIONS: ConnectorParameterOption[] = [
ConfigurationSettings.DSR_CUSTOMIZATION,
],
},
{
type: SystemType.EMAIL,
options: [ConfigurationSettings.CONNECTOR_PARAMETERS],
},
pattisdr marked this conversation as resolved.
Show resolved Hide resolved
{
type: SystemType.SAAS,
options: [
Expand Down
1 change: 1 addition & 0 deletions clients/admin-ui/src/features/privacy-requests/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type PrivacyRequestStatus =
| "approved"
| "awaiting_consent_email_send"
| "complete"
| "denied"
| "error"
Expand Down
1 change: 1 addition & 0 deletions clients/admin-ui/src/types/api/models/SystemType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export enum SystemType {
SAAS = "saas",
DATABASE = "database",
MANUAL = "manual",
EMAIL = "email",
pattisdr marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* tslint:disable */
/* eslint-disable */

import type { Consent } from './Consent';
import type { Consent } from "./Consent";

/**
* Schema for consent preferences.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */

import type { Consent } from './Consent';
import type { ConsentWithExecutableStatus } from './ConsentWithExecutableStatus';
import type { Identity } from './Identity';
import type { Consent } from "./Consent";
import type { ConsentWithExecutableStatus } from "./ConsentWithExecutableStatus";
import type { Identity } from "./Identity";

/**
* Schema for consent preferences including the verification code.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Add sovrn consent connector

Revision ID: 8e198eb13802
Revises: 5d62bab40b71
Create Date: 2023-01-27 21:58:23.344582

"""
from alembic import op

# revision identifiers, used by Alembic.
revision = "8e198eb13802"
down_revision = "5d62bab40b71"
branch_labels = None
depends_on = None
import sqlalchemy as sa


def upgrade():
# Add to ConnectionType enum
op.execute("alter type connectiontype rename to connectiontype_old")
pattisdr marked this conversation as resolved.
Show resolved Hide resolved
op.execute(
"create type connectiontype as enum('postgres', 'mongodb', 'mysql', 'https', 'snowflake', 'redshift', 'mssql', 'mariadb', 'bigquery', 'saas', 'manual', 'email', 'manual_webhook', 'timescale', 'fides', 'sovrn')"
)
op.execute(
(
"alter table connectionconfig alter column connection_type type connectiontype using "
"connection_type::text::connectiontype"
)
)
op.execute("drop type connectiontype_old")

# Add new PrivacyRequest.awaiting_consent_email_send_at column
op.add_column(
"privacyrequest",
sa.Column(
"awaiting_consent_email_send_at", sa.DateTime(timezone=True), nullable=True
),
)

# Add ljt_reader_id for sovrn
op.execute("ALTER TYPE providedidentitytype ADD VALUE 'ljt_readerID'")

# Add new privacyrequeststatus
op.execute(
"alter type privacyrequeststatus add value 'awaiting_consent_email_send'"
)


def downgrade():
# Remove sovrn from the connectiontype enum
op.execute("alter type connectiontype rename to connectiontype_old")
op.execute(
"create type connectiontype as enum('postgres', 'mongodb', 'mysql', 'https', 'snowflake', 'redshift', 'mssql', 'mariadb', 'bigquery', 'saas', 'manual', 'email', 'manual_webhook', 'timescale', 'fides')"
)
op.execute(
(
"alter table connectionconfig alter column connection_type type connectiontype using "
"connection_type::text::connectiontype"
)
)
op.execute("drop type connectiontype_old")

# # Drop PrivacyRequest.awaiting_consent_email_send_at column
op.drop_column("privacyrequest", "awaiting_consent_email_send_at")

# Remove ljt_reader_id from the providedidentitytype enum
op.execute("ALTER TYPE providedidentitytype RENAME TO providedidentitytype_old")
op.execute(
"CREATE TYPE providedidentitytype AS ENUM('email', 'phone_number', 'ga_client_id')"
)
op.execute(
"ALTER TABLE providedidentity ALTER COLUMN field_name TYPE providedidentitytype USING field_name::text::providedidentitytype"
)
op.execute("DROP TYPE providedidentitytype_old")

# Removing awaiting_consent_email_send privacyrequeststatus
op.execute(
"delete from privacyrequest where status in ('awaiting_consent_email_send')"
)
op.execute("alter type privacyrequeststatus rename to privacyrequeststatus_old")
op.execute(
"create type privacyrequeststatus as enum('in_processing', 'complete', 'pending', 'error', 'paused', 'approved', 'denied', 'canceled', 'identity_unverified', 'requires_input')"
)
op.execute(
(
"alter table privacyrequest alter column status type privacyrequeststatus using "
"status::text::privacyrequeststatus"
)
)
op.execute("drop type privacyrequeststatus_old")
5 changes: 5 additions & 0 deletions src/fides/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
)

# pylint: disable=wildcard-import, unused-wildcard-import
from fides.api.ops.service.privacy_request.consent_email_batch_service import (
initiate_scheduled_batch_consent_email_send,
)
from fides.api.ops.service.saas_request.override_implementations import *
from fides.api.ops.tasks.scheduled.scheduler import scheduler
from fides.api.ops.util.cache import get_cache
Expand Down Expand Up @@ -269,6 +272,8 @@ async def setup_server() -> None:
if not scheduler.running:
scheduler.start()

initiate_scheduled_batch_consent_email_send()
pattisdr marked this conversation as resolved.
Show resolved Hide resolved

logger.debug("Sending startup analytics events...")
await send_analytics_event(
AnalyticsEvent(
Expand Down
3 changes: 3 additions & 0 deletions src/fides/api/ops/email_templates/get_email_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from fides.api.ops.common_exceptions import EmailTemplateUnhandledActionType
from fides.api.ops.email_templates.template_names import (
CONSENT_REQUEST_EMAIL_FULFILLMENT,
CONSENT_REQUEST_VERIFICATION_TEMPLATE,
EMAIL_ERASURE_REQUEST_FULFILLMENT,
PRIVACY_REQUEST_COMPLETE_ACCESS_TEMPLATE,
Expand Down Expand Up @@ -35,6 +36,8 @@ def get_email_template( # pylint: disable=too-many-return-statements
return template_env.get_template(SUBJECT_IDENTITY_VERIFICATION_TEMPLATE)
if action_type == MessagingActionType.MESSAGE_ERASURE_REQUEST_FULFILLMENT:
return template_env.get_template(EMAIL_ERASURE_REQUEST_FULFILLMENT)
if action_type == MessagingActionType.CONSENT_REQUEST_EMAIL_FULFILLMENT:
return template_env.get_template(CONSENT_REQUEST_EMAIL_FULFILLMENT)
if action_type == MessagingActionType.PRIVACY_REQUEST_RECEIPT:
return template_env.get_template(PRIVACY_REQUEST_RECEIPT_TEMPLATE)
if action_type == MessagingActionType.PRIVACY_REQUEST_COMPLETE_ACCESS:
Expand Down
1 change: 1 addition & 0 deletions src/fides/api/ops/email_templates/template_names.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
CONSENT_REQUEST_VERIFICATION_TEMPLATE = "consent_request_verification.html"
CONSENT_REQUEST_EMAIL_FULFILLMENT = "consent_request_email_fulfillment.html"
SUBJECT_IDENTITY_VERIFICATION_TEMPLATE = "subject_identity_verification.html"
EMAIL_ERASURE_REQUEST_FULFILLMENT = "message_request_email_fulfillment.html"
PRIVACY_REQUEST_RECEIPT_TEMPLATE = "privacy_request_receipt.html"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Notification of users' consent preference changes from {{body.controller}}</title>
<style>
.consent_preferences {
padding: 5px;
border-bottom: 1px solid #121439;
text-align: left;
}
.identity_column {
padding-right: 15px;
}
</style>
</head>
<body>
<main>
<p> The following users of {{body.controller}} have made changes to their consent preferences. You are notified of the changes because
{{body.third_party_vendor_name}} has been identified as a third-party processor to {{body.controller}} that processes user information. </p>

<p> Please find below the updated list of users and their consent preferences:
<table>
<tr>
{% for identity_type in body.required_identities -%}
<th class="identity_column"> {{identity_type}}</th>
{%- endfor %}
<th>Preferences</th>
</tr>
{% for requested_change in body.requested_changes -%}
<tr class="consent_preferences">
{% for identity_type in body.required_identities -%}
<td class="identity_column"> {{requested_change.identities.get(identity_type)}}</td>
{%- endfor %}
<td>
{% for consent_option in requested_change.consent_preferences -%}
{{consent_option.data_use}}: {{"Opt-in" if consent_option.opt_in else "Opt-out"}}{{", " if loop.index < requested_change.consent_preferences|length else ""}}
{%- endfor %}
</td>
</tr>
{%- endfor %}
</table>
</p>

<p> You are legally obligated to honor the users' consent preferences. </p>

</main>
</body>
</html>
2 changes: 2 additions & 0 deletions src/fides/api/ops/models/connectionconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class ConnectionType(enum.Enum):
bigquery = "bigquery"
manual = "manual" # Run as part of the traversal
email = "email"
sovrn = "sovrn"
manual_webhook = "manual_webhook" # Run before the traversal
timescale = "timescale"
fides = "fides"
Expand All @@ -74,6 +75,7 @@ def human_readable(self) -> str:
ConnectionType.manual_webhook.value: "Manual Webhook",
ConnectionType.timescale.value: "TimescaleDB",
ConnectionType.fides.value: "Fides Connector",
ConnectionType.sovrn.value: "Sovrn",
}
try:
return readable_mapping[self.value]
Expand Down
1 change: 1 addition & 0 deletions src/fides/api/ops/models/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class CurrentStep(EnumType):
erasure = "erasure"
consent = "consent"
erasure_email_post_send = "erasure_email_post_send"
consent_email_post_send = "consent_email_post_send"
post_webhooks = "post_webhooks"


Expand Down
11 changes: 11 additions & 0 deletions src/fides/api/ops/models/privacy_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
CurrentStep.erasure,
CurrentStep.consent,
CurrentStep.erasure_email_post_send,
CurrentStep.consent_email_post_send,
CurrentStep.post_webhooks,
]

Expand Down Expand Up @@ -129,6 +130,7 @@ class PrivacyRequestStatus(str, EnumType):
in_processing = "in_processing"
complete = "complete"
paused = "paused"
awaiting_consent_email_send = "awaiting_consent_email_send"
canceled = "canceled"
error = "error"

Expand Down Expand Up @@ -225,6 +227,7 @@ class PrivacyRequest(IdentityVerificationMixin, Base): # pylint: disable=R0904
paused_at = Column(DateTime(timezone=True), nullable=True)
identity_verified_at = Column(DateTime(timezone=True), nullable=True)
due_date = Column(DateTime(timezone=True), nullable=True)
awaiting_consent_email_send_at = Column(DateTime(timezone=True), nullable=True)

@property
def days_left(self: PrivacyRequest) -> Union[int, None]:
Expand Down Expand Up @@ -697,6 +700,13 @@ def pause_processing(self, db: Session) -> None:
},
)

def pause_processing_for_consent_email_send(self, db: Session) -> None:
"""Put the privacy request in a state of awaiting_consent_email_send"""
if self.awaiting_consent_email_send_at is None:
self.awaiting_consent_email_send_at = datetime.utcnow()
self.status = PrivacyRequestStatus.awaiting_consent_email_send
self.save(db=db)

def cancel_processing(self, db: Session, cancel_reason: Optional[str]) -> None:
"""Cancels a privacy request. Currently should only cancel 'pending' tasks"""
if self.canceled_at is None:
Expand Down Expand Up @@ -768,6 +778,7 @@ class ProvidedIdentityType(EnumType):
email = "email"
phone_number = "phone_number"
ga_client_id = "ga_client_id"
ljt_readerID = "ljt_readerID"


class ProvidedIdentity(Base): # pylint: disable=R0904
Expand Down
14 changes: 14 additions & 0 deletions src/fides/api/ops/schemas/connection_configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
from fides.api.ops.schemas.connection_configuration.connection_secrets_email import (
EmailSchema as EmailSchema,
)
from fides.api.ops.schemas.connection_configuration.connection_secrets_email_consent import (
SVORN_REQUIRED_IDENTITY as SVORN_REQUIRED_IDENTITY,
)
from fides.api.ops.schemas.connection_configuration.connection_secrets_email_consent import (
ConsentEmailDocsSchema as ConsentEmailDocsSchema,
)
from fides.api.ops.schemas.connection_configuration.connection_secrets_email_consent import (
ConsentEmailSchema as ConsentEmailSchema,
)
from fides.api.ops.schemas.connection_configuration.connection_secrets_fides import (
FidesConnectorSchema,
FidesDocsSchema,
Expand Down Expand Up @@ -80,6 +89,9 @@
from fides.api.ops.schemas.connection_configuration.connection_secrets_snowflake import (
SnowflakeSchema as SnowflakeSchema,
)
from fides.api.ops.schemas.connection_configuration.connection_secrets_sovrn import (
SovrnEmailSchema,
)
from fides.api.ops.schemas.connection_configuration.connection_secrets_timescale import (
TimescaleDocsSchema as TimescaleDocsSchema,
)
Expand All @@ -106,6 +118,7 @@
ConnectionType.manual_webhook.value: ManualWebhookSchema,
ConnectionType.timescale.value: TimescaleSchema,
ConnectionType.fides.value: FidesConnectorSchema,
ConnectionType.sovrn.value: SovrnEmailSchema,
}


Expand Down Expand Up @@ -149,4 +162,5 @@ def get_connection_secrets_schema(
ManualWebhookSchemaforDocs,
TimescaleDocsSchema,
FidesDocsSchema,
ConsentEmailDocsSchema,
]
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class SystemType(Enum):
saas = "saas"
database = "database"
manual = "manual"
email = "email"


class ConnectionSystemTypeMap(BaseModel):
Expand Down
Loading