diff --git a/app/clients/freshdesk.py b/app/clients/freshdesk.py index 785096af5a..4b277063ab 100644 --- a/app/clients/freshdesk.py +++ b/app/clients/freshdesk.py @@ -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}") diff --git a/app/config.py b/app/config.py index 4f030eb6ef..88384d1ad0 100644 --- a/app/config.py +++ b/app/config.py @@ -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" @@ -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", "helpdesk@cds-snc.ca") + SENSITIVE_SERVICE_EMAIL = os.getenv("SENSITIVE_SERVICE_EMAIL", "ESDC.Support.CDS-SNC.Soutien.EDSC@servicecanada.gc.ca") FROM_NUMBER = "development" @@ -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" diff --git a/app/user/rest.py b/app/user/rest.py index afd1556a26..a3edb3e24e 100644 --- a/app/user/rest.py +++ b/app/user/rest.py @@ -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]: + # 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 diff --git a/migrations/versions/0472_add_direct_email_2.py b/migrations/versions/0472_add_direct_email_2.py new file mode 100644 index 0000000000..717d40d46e --- /dev/null +++ b/migrations/versions/0472_add_direct_email_2.py @@ -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)) diff --git a/tests/app/clients/test_freshdesk.py b/tests/app/clients/test_freshdesk.py index ae0ada8b7e..be9e5d5236 100644 --- a/tests/app/clients/test_freshdesk.py +++ b/tests/app/clients/test_freshdesk.py @@ -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": "contact@test.com"}): with notify_api.app_context(): freshdesk_object = freshdesk.Freshdesk(ContactRequest(email_address="test@email.com")) - 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": "sensitive@test.gov.uk", + "CONTACT_FORM_SENSITIVE_SERVICE_EMAIL_TEMPLATE_ID": "template-123", + }, + ): + with notify_api.app_context(): + freshdesk_client = freshdesk.Freshdesk(ContactRequest(email_address="user@example.com")) + freshdesk_client.email_freshdesk_ticket_pt_service() + + mock_email_ticket.assert_called_once_with("sensitive@test.gov.uk", "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="user@example.com")) + 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") diff --git a/tests/app/user/test_rest.py b/tests/app/user/test_rest.py index 45968e172a..0d800732ac 100644 --- a/tests/app/user/test_rest.py +++ b/tests/app/user/test_rest.py @@ -33,6 +33,7 @@ create_template_folder, create_user, ) +from tests.conftest import set_config def test_get_user_list(admin_request, sample_service): @@ -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,