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

Email the "contact us" form data instead of sending to Freshdesk for sensitive services #2416

Merged
merged 21 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 35 additions & 21 deletions app/clients/freshdesk.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,31 +136,45 @@ def send_ticket(self) -> int:
return 201
except requests.RequestException as e:
current_app.logger.error(f"Failed to create Freshdesk ticket: {e}")
self.email_freshdesk_ticket(self._generate_ticket())
self.email_freshdesk_ticket_freshdesk_down()
return 201

def email_freshdesk_ticket(self, content: dict) -> None:
def email_freshdesk_ticket_freshdesk_down(self):
if current_app.config["CONTACT_FORM_EMAIL_ADDRESS"] is None:
current_app.logger.info("Cannot email contact us form, CONTACT_FORM_EMAIL_ADDRESS is empty")
self.email_freshdesk_ticket(
current_app.config["CONTACT_FORM_EMAIL_ADDRESS"], current_app.config["CONTACT_FORM_DIRECT_EMAIL_TEMPLATE_ID"]
)

def email_freshdesk_ticket_pt_service(self):
email_address = current_app.config.get("SENSITIVE_SERVICE_EMAIL")
template_id = current_app.config.get("CONTACT_FORM_SENSITIVE_SERVICE_EMAIL_TEMPLATE_ID")
if not email_address:
current_app.logger.error("SENSITIVE_SERVICE_EMAIL not set")
self.email_freshdesk_ticket(email_address, template_id)

def email_freshdesk_ticket(self, email_address, template_id) -> None:
content = self._generate_ticket()
try:
template = dao_get_template_by_id(current_app.config["CONTACT_FORM_DIRECT_EMAIL_TEMPLATE_ID"])
template = dao_get_template_by_id(template_id)
notify_service = dao_fetch_service_by_id(current_app.config["NOTIFY_SERVICE_ID"])

if current_app.config["CONTACT_FORM_EMAIL_ADDRESS"] is None:
current_app.logger.info("Cannot email contact us form, CONTACT_FORM_EMAIL_ADDRESS is empty")
else:
current_app.logger.info("Emailing contact us form to {}".format(current_app.config["CONTACT_FORM_EMAIL_ADDRESS"]))
saved_notification = persist_notification(
template_id=template.id,
template_version=template.version,
recipient=current_app.config["CONTACT_FORM_EMAIL_ADDRESS"],
service=notify_service,
personalisation={
"contact_us_content": json.dumps(content, indent=4),
},
notification_type=template.template_type,
api_key_id=None,
key_type=KEY_TYPE_NORMAL,
reply_to_text=notify_service.get_default_reply_to_email_address(),
)
send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY)
saved_notification = persist_notification(
template_id=template.id,
template_version=template.version,
recipient=email_address,
service=notify_service,
# This email will be badly formatted, but this allows us to re-use the
# _generate_description fn without having to duplicate all of that logic to get the
# description in plain text.
personalisation={
"contact_us_content": json.dumps(content, indent=4),
},
notification_type=template.template_type,
api_key_id=None,
key_type=KEY_TYPE_NORMAL,
reply_to_text=notify_service.get_default_reply_to_email_address(),
)
send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY)
except Exception as e:
current_app.logger.exception(f"Failed to email contact form {json.dumps(content, indent=4)}, error: {e}")
3 changes: 3 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ class Config(object):
REACHED_DAILY_SMS_LIMIT_TEMPLATE_ID = "a646e614-c527-4f94-a955-ed7185d577f4"
DAILY_SMS_LIMIT_UPDATED_TEMPLATE_ID = "6ec12dd0-680a-4073-8d58-91d17cc8442f"
CONTACT_FORM_DIRECT_EMAIL_TEMPLATE_ID = "b04beb4a-8408-4280-9a5c-6a046b6f7704"
CONTACT_FORM_SENSITIVE_SERVICE_EMAIL_TEMPLATE_ID = "4bf8c15b-7393-463f-b6fe-e3fd1e99a03d"
NEAR_DAILY_EMAIL_LIMIT_TEMPLATE_ID = "9aa60ad7-2d7f-46f0-8cbe-2bac3d4d77d8"
REACHED_DAILY_EMAIL_LIMIT_TEMPLATE_ID = "ee036547-e51b-49f1-862b-10ea982cfceb"
DAILY_EMAIL_LIMIT_UPDATED_TEMPLATE_ID = "97dade64-ea8d-460f-8a34-900b74ee5eb0"
Expand Down Expand Up @@ -559,6 +560,7 @@ class Config(object):
AWS_SEND_SMS_BOTO_CALL_LATENCY = os.getenv("AWS_SEND_SMS_BOTO_CALL_LATENCY", 0.06) # average delay in production

CONTACT_FORM_EMAIL_ADDRESS = os.getenv("CONTACT_FORM_EMAIL_ADDRESS", "[email protected]")
SENSITIVE_SERVICE_EMAIL = os.getenv("SENSITIVE_SERVICE_EMAIL", "[email protected]")

FROM_NUMBER = "development"

Expand Down Expand Up @@ -635,6 +637,7 @@ class Config(object):
FF_CLOUDWATCH_METRICS_ENABLED = env.bool("FF_CLOUDWATCH_METRICS_ENABLED", False)
FF_SALESFORCE_CONTACT = env.bool("FF_SALESFORCE_CONTACT", False)
FF_ANNUAL_LIMIT = env.bool("FF_ANNUAL_LIMIT", False)
FF_PT_SERVICE_SKIP_FRESHDESK = env.bool("FF_PT_SERVICE_SKIP_FRESHDESK", False)

# SRE Tools auth keys
SRE_USER_NAME = "SRE_CLIENT_USER"
Expand Down
7 changes: 7 additions & 0 deletions app/user/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,13 @@ def send_contact_request(user_id):
except Exception as e:
current_app.logger.exception(e)

# Check if user is member of any ptm services
if current_app.config.get("FF_PT_SERVICE_SKIP_FRESHDESK", False) and user:
if "province_or_territory" in [service.organisation_type for service in user.services]:
Copy link
Member

Choose a reason for hiding this comment

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

😍

# Send to secure email instead of Freshdesk
Freshdesk(contact).email_freshdesk_ticket_pt_service()
return jsonify({"status_code": 201}), 201

status_code = Freshdesk(contact).send_ticket()
return jsonify({"status_code": status_code}), 204

Expand Down
90 changes: 90 additions & 0 deletions migrations/versions/0472_add_direct_email_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""

Revision ID: 0472_add_direct_email_2
Revises: 0471_edit_limit_emails2
Create Date: 2025-01-13 00:00:00

"""
from datetime import datetime

from alembic import op
from flask import current_app

revision = "0472_add_direct_email_2"
down_revision = "0471_edit_limit_emails2"

contact_us_template_id = current_app.config["CONTACT_FORM_SENSITIVE_SERVICE_EMAIL_TEMPLATE_ID"]
template_ids = [contact_us_template_id]


def upgrade():
template_insert = """
INSERT INTO templates (id, name, template_type, created_at, content, archived, service_id, subject,
created_by_id, version, process_type, hidden)
VALUES ('{}', '{}', '{}', '{}', '{}', False, '{}', '{}', '{}', 1, '{}', false)
"""
template_history_insert = """
INSERT INTO templates_history (id, name, template_type, created_at, content, archived, service_id, subject,
created_by_id, version, process_type, hidden)
VALUES ('{}', '{}', '{}', '{}', '{}', False, '{}', '{}', '{}', 1, '{}', false)
"""

contact_us_content = "\n".join(
[
"Skipping Freshdesk: The user submitting the Contact Us form belongs to a sensitive Service. Contact us form data:",
"((contact_us_content))",
"",
"___",
"",
"[FR] Skipping Freshdesk: The user submitting the Contact Us form belongs to a sensitive Service. Contact us form data:",
"",
"((contact_us_content))",
]
)

templates = [
{
"id": contact_us_template_id,
"name": "Contact form direct email - sensitive service",
"subject": "Notify Contact us form for sensitive service",
"content": contact_us_content,
},
]

for template in templates:
op.execute(
template_insert.format(
template["id"],
template["name"],
"email",
datetime.utcnow(),
template["content"],
current_app.config["NOTIFY_SERVICE_ID"],
template["subject"],
current_app.config["NOTIFY_USER_ID"],
"priority",
)
)

op.execute(
template_history_insert.format(
template["id"],
template["name"],
"email",
datetime.utcnow(),
template["content"],
current_app.config["NOTIFY_SERVICE_ID"],
template["subject"],
current_app.config["NOTIFY_USER_ID"],
"priority",
)
)


def downgrade():
for template_id in template_ids:
op.execute("DELETE FROM notifications WHERE template_id = '{}'".format(template_id))
op.execute("DELETE FROM notification_history WHERE template_id = '{}'".format(template_id))
op.execute("DELETE FROM template_redacted WHERE template_id = '{}'".format(template_id))
op.execute("DELETE FROM templates_history WHERE id = '{}'".format(template_id))
op.execute("DELETE FROM templates WHERE id = '{}'".format(template_id))
37 changes: 35 additions & 2 deletions tests/app/clients/test_freshdesk.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,40 @@ def test_email_freshdesk_ticket(self, mocker, notify_api: Flask, contact_form_em
with set_config_values(notify_api, {"CONTACT_FORM_EMAIL_ADDRESS": "[email protected]"}):
with notify_api.app_context():
freshdesk_object = freshdesk.Freshdesk(ContactRequest(email_address="[email protected]"))
content = {"data": "data"}
freshdesk_object.email_freshdesk_ticket(content)
freshdesk_object.email_freshdesk_ticket_freshdesk_down()
mock_persist_notification.assert_called_once()
mock_send_notification_to_queue.assert_called_once()


class TestEmailFreshdeskSensitiveService:
def test_email_freshdesk_ticket_pt_service_success(self, mocker, notify_api):
"""Test successful sending of sensitive service email"""
mock_email_ticket = mocker.patch.object(freshdesk.Freshdesk, "email_freshdesk_ticket")

with set_config_values(
notify_api,
{
"SENSITIVE_SERVICE_EMAIL": "[email protected]",
"CONTACT_FORM_SENSITIVE_SERVICE_EMAIL_TEMPLATE_ID": "template-123",
},
):
with notify_api.app_context():
freshdesk_client = freshdesk.Freshdesk(ContactRequest(email_address="[email protected]"))
freshdesk_client.email_freshdesk_ticket_pt_service()

mock_email_ticket.assert_called_once_with("[email protected]", "template-123")

def test_email_freshdesk_ticket_pt_service_no_email(self, mocker, notify_api):
"""Test handling when sensitive service email not configured"""
mock_email_ticket = mocker.patch.object(freshdesk.Freshdesk, "email_freshdesk_ticket")
mock_logger = mocker.patch("app.clients.freshdesk.current_app.logger.error")

with set_config_values(
notify_api, {"SENSITIVE_SERVICE_EMAIL": None, "CONTACT_FORM_SENSITIVE_SERVICE_EMAIL_TEMPLATE_ID": "template-123"}
):
with notify_api.app_context():
freshdesk_client = freshdesk.Freshdesk(ContactRequest(email_address="[email protected]"))
freshdesk_client.email_freshdesk_ticket_pt_service()

mock_logger.assert_called_once_with("SENSITIVE_SERVICE_EMAIL not set")
mock_email_ticket.assert_called_once_with(None, "template-123")
52 changes: 52 additions & 0 deletions tests/app/user/test_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
create_template_folder,
create_user,
)
from tests.conftest import set_config


def test_get_user_list(admin_request, sample_service):
Expand Down Expand Up @@ -871,6 +872,57 @@ def test_send_contact_request_with_live_service(client, sample_service, mocker):
mocked_salesforce_client.engagement_update.assert_not_called()


def test_send_contact_request_with_central_service(client, mocker, notify_api):
with set_config(notify_api, "FF_PT_SERVICE_SKIP_FRESHDESK", True):
user = create_user()
data = {
"name": user.name,
"email_address": user.email_address,
"support_type": "ask_question",
"message": "test message",
}
mocked_freshdesk_send_ticket = mocker.patch("app.user.rest.Freshdesk.send_ticket", return_value=204)
mocked_freshdesk_email = mocker.patch("app.user.rest.Freshdesk.email_freshdesk_ticket_pt_service", return_value=204)
mocker.patch("app.user.rest.salesforce_client")

resp = client.post(
url_for("user.send_contact_request", user_id=str(user.id)),
data=json.dumps(data),
headers=[("Content-Type", "application/json"), create_authorization_header()],
)
assert resp.status_code == 204
mocked_freshdesk_send_ticket.assert_called_once_with()
mocked_freshdesk_email.assert_not_called()


def test_send_contact_request_with_pt_service(client, mocker, notify_api):
with set_config(notify_api, "FF_PT_SERVICE_SKIP_FRESHDESK", True):
user = create_user(name="user 2")
data = {
"name": user.name,
"email_address": user.email_address,
"support_type": "ask_question",
"message": "test message",
}
org = create_organisation(name="Ontario", organisation_type="province_or_territory")
service = create_service(user=user, service_name="test service 2", organisation=org)
service.organisation_type = "province_or_territory"
user.services = [service]

mocked_freshdesk_send_ticket = mocker.patch("app.user.rest.Freshdesk.send_ticket", return_value=204)
mocked_freshdesk_email = mocker.patch("app.user.rest.Freshdesk.email_freshdesk_ticket_pt_service", return_value=204)

resp = client.post(
url_for("user.send_contact_request", user_id=str(user.id)),
data=json.dumps(data),
headers=[("Content-Type", "application/json"), create_authorization_header()],
)
assert resp.status_code == 201
mocked_freshdesk_send_ticket.assert_not_called()
mocked_freshdesk_email.assert_called_once_with()
user.services = []


def test_send_contact_request_demo(client, sample_user, mocker):
data = {
"name": sample_user.name,
Expand Down
Loading