From 2ba369100c1bde2d02980d08df0b58fd99cfecf2 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Mon, 18 Nov 2024 13:35:24 -0800 Subject: [PATCH 1/8] content --- app/main/views/index.py | 8 +++ app/main/views/sub_navigation_dictionaries.py | 4 ++ app/templates/views/about/security.html | 61 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 app/templates/views/about/security.html diff --git a/app/main/views/index.py b/app/main/views/index.py index 974e29211..79d4365f4 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -295,6 +295,14 @@ def about_notify(): ) +@main.route("/about/security") +def about_security(): + return render_template( + "views/about/security.html", + navigation_links=about_notify_nav(), + ) + + @main.route("/using-notify/guidance/create-and-send-messages") @user_is_logged_in def create_and_send_messages(): diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index b9fb7f8ae..f043955de 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -113,4 +113,8 @@ def about_notify_nav(): "name": "About notify", "link": "main.about_notify", }, + { + "name": "Security", + "link": "main.about_security", + }, ] diff --git a/app/templates/views/about/security.html b/app/templates/views/about/security.html new file mode 100644 index 000000000..b03e593a4 --- /dev/null +++ b/app/templates/views/about/security.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} + +{% set page_title = "Security" %} + +{% block per_page_title %} +{{page_title}} +{% endblock %} + +{% block content_column_content %} + +
+

{{page_title}}

+

Notify.gov is built for the needs of government agencies with fundamental system + security processes in place to: +

+ +

+ Notify.gov operates under a full three-year Authority-to-Operate (ATO). This federal security authorization process leverages security + controls provided by National Institute of Standards and Technology (NIST). +

+ +

+ Our infrastructure runs on cloud.gov and utilizes several + services through Amazon Web + Services (AWS), including AWS SNS for sending SMS + messages. +

+

For more information about the Notify.gov infrastructure, contact us at notify-support@gsa.gov.

+

Data

+

+ On Notify.gov, data is encrypted both in transit and at rest. To send a message, agencies upload a spreadsheet of + phone numbers and other necessary data from their existing data management system. +

+

+ Notify.gov is not a system of record, so it does not have a System of Records Notice (SORN). Agencies are + responsible for managing their data outside of Notify.gov. +

+

Data retention

+

+ Any data uploads that have recipient data are held for seven calendar days; personally identifiable information + (PII) is never stored in Notify’s database. +

+

Multi-Factor Authentication

+

+ Notify.gov uses Login.gov for enhanced security. + Login.gov is an extra layer of security created by the government that uses multi-factor authentication and stronger + passwords to protect your account. +

+

+ To access Notify.gov, users will use a Login.gov account associated with their agency (.gov) email with one of the + multi-factor authentication + methods offered through Login.gov. +

+
+{% endblock %} From de8c4b7204177f862cc35e6111d77c80819c6d99 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Mon, 18 Nov 2024 13:41:32 -0800 Subject: [PATCH 2/8] test --- tests/app/test_navigation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 15be17081..910364cee 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -18,6 +18,7 @@ EXCLUDED_ENDPOINTS = tuple( Navigation.get_endpoint_with_blueprint, { "about_notify", + "about_security", "accept_invite", "accept_org_invite", "accessibility_statement", From 942489dc57f8dce8eaaa768308fdd551f8d5a4aa Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Wed, 20 Nov 2024 09:43:12 -0800 Subject: [PATCH 3/8] updated external links --- app/main/views/sub_navigation_dictionaries.py | 10 ++++++---- app/templates/views/about/security.html | 17 +++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index f043955de..0aba6069b 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -112,9 +112,11 @@ def about_notify_nav(): { "name": "About notify", "link": "main.about_notify", - }, - { - "name": "Security", - "link": "main.about_security", + "sub_navigation_items": [ + { + "name": "Security", + "link": "main.about_security", + }, + ], }, ] diff --git a/app/templates/views/about/security.html b/app/templates/views/about/security.html index b03e593a4..9ebc0420f 100644 --- a/app/templates/views/about/security.html +++ b/app/templates/views/about/security.html @@ -19,15 +19,18 @@
  • manage risks around information
  • - Notify.gov operates under a full three-year Authority-to-Operate (ATO). This federal security authorization process leverages security + Notify.gov operates under a full three-year Authority-to-Operate (ATO). This + federal security authorization process leverages security controls provided by National Institute of Standards and Technology (NIST).

    - Our infrastructure runs on cloud.gov and utilizes several + Our infrastructure runs on cloud.gov and utilizes several services through Amazon Web - Services (AWS), including AWS SNS for sending SMS + Services (AWS), including + AWS SNS for sending SMS messages.

    For more information about the Notify.gov infrastructure, contact us at

    Multi-Factor Authentication

    - Notify.gov uses Login.gov for enhanced security. + Notify.gov uses Login.gov for enhanced security. Login.gov is an extra layer of security created by the government that uses multi-factor authentication and stronger passwords to protect your account.

    To access Notify.gov, users will use a Login.gov account associated with their agency (.gov) email with one of the - multi-factor authentication + multi-factor authentication methods offered through Login.gov.

    From 8f30064f508e2fa0acb0dad7f5115522457626d2 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 21 Nov 2024 15:08:49 -0800 Subject: [PATCH 4/8] add carrier column to reports --- .ds.baseline | 4 ++-- app/config.py | 4 +--- app/main/views/index.py | 10 ++++------ app/navigation.py | 2 +- app/utils/csv.py | 4 ++++ tests/app/utils/test_csv.py | 15 +++++++++++---- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.ds.baseline b/.ds.baseline index c64d22627..56c3afc7d 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -161,7 +161,7 @@ "filename": "app/config.py", "hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc", "is_verified": false, - "line_number": 125, + "line_number": 123, "is_secret": false } ], @@ -684,5 +684,5 @@ } ] }, - "generated_at": "2024-11-14T15:53:44Z" + "generated_at": "2024-11-21T23:08:45Z" } diff --git a/app/config.py b/app/config.py index f40b46dea..146230047 100644 --- a/app/config.py +++ b/app/config.py @@ -91,9 +91,7 @@ class Config(object): getenv("FEATURE_BEST_PRACTICES_ENABLED", "false") == "true" ) - FEATURE_ABOUT_PAGE_ENABLED = ( - getenv("FEATURE_ABOUT_PAGE_ENABLED", "false") == "true" - ) + FEATURE_ABOUT_PAGE_ENABLED = getenv("FEATURE_ABOUT_PAGE_ENABLED", "false") == "true" def _s3_credentials_from_env(bucket_prefix): diff --git a/app/main/views/index.py b/app/main/views/index.py index 9982a8b74..fbf7979a3 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -30,15 +30,13 @@ from notifications_utils.url_safe_token import generate_token # Hook to check for feature flags @main.before_request def check_feature_flags(): - if ( - request.path.startswith("/guides/best-practices") - and not current_app.config.get("FEATURE_BEST_PRACTICES_ENABLED", False) + if request.path.startswith("/guides/best-practices") and not current_app.config.get( + "FEATURE_BEST_PRACTICES_ENABLED", False ): abort(404) - if ( - request.path.startswith("/about") - and not current_app.config.get("FEATURE_ABOUT_PAGE_ENABLED", False) + if request.path.startswith("/about") and not current_app.config.get( + "FEATURE_ABOUT_PAGE_ENABLED", False ): abort(404) diff --git a/app/navigation.py b/app/navigation.py index a02df484d..271d6848b 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -53,7 +53,7 @@ class HeaderNavigation(Navigation): "establish_trust", "write_for_action", "multiple_languages", - "benchmark_performance" + "benchmark_performance", }, "using_notify": { "get_started", diff --git a/app/utils/csv.py b/app/utils/csv.py index 4ed6d16b5..79e3535c8 100644 --- a/app/utils/csv.py +++ b/app/utils/csv.py @@ -103,6 +103,7 @@ def generate_notifications_csv(**kwargs): "Carrier Response", "Status", "Time", + "Carrier", ] for header in original_column_headers: if header.lower() != "phone number": @@ -118,6 +119,7 @@ def generate_notifications_csv(**kwargs): "Carrier Response", "Status", "Time", + "Carrier", ] yield ",".join(fieldnames) + "\n" @@ -140,6 +142,7 @@ def generate_notifications_csv(**kwargs): notification["provider_response"], notification["status"], preferred_tz_created_at, + notification["carrier"], ] for header in original_column_headers: if header.lower() != "phone number": @@ -158,6 +161,7 @@ def generate_notifications_csv(**kwargs): notification["provider_response"], notification["status"], preferred_tz_created_at, + notification["carrier"], ] yield Spreadsheet.from_rows([map(str, values)]).as_csv_data diff --git a/tests/app/utils/test_csv.py b/tests/app/utils/test_csv.py index d603fcd0e..db4b6a0ec 100644 --- a/tests/app/utils/test_csv.py +++ b/tests/app/utils/test_csv.py @@ -58,6 +58,7 @@ def _get_notifications_csv( "to": recipient, "recipient": recipient, "client_reference": "ref 1234", + "carrier": "AT&T Mobility", } for i in range(rows) ], @@ -88,15 +89,15 @@ def get_notifications_csv_mock( ( None, [ - "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time\n", - "8005555555,foo,,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n", + "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time,Carrier\n", + "8005555555,foo,,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern,AT&T Mobility\r\n", ], ), ( "Anne Example", [ - "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time\n", - "8005555555,foo,Anne Example,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n", # noqa + "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time,Carrier\n", + "8005555555,foo,Anne Example,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern,AT&T Mobility\r\n", # noqa ], ), ], @@ -135,6 +136,7 @@ def test_generate_notifications_csv_without_job( "Carrier Response", "Status", "Time", + "Carrier", ], [ "8005555555", @@ -144,6 +146,7 @@ def test_generate_notifications_csv_without_job( "Did not like it", "Delivered", "1943-04-19 08:00:00 AM US/Eastern", + "AT&T Mobility", ], ), ( @@ -159,6 +162,7 @@ def test_generate_notifications_csv_without_job( "Carrier Response", "Status", "Time", + "Carrier", "a", "b", "c", @@ -171,6 +175,7 @@ def test_generate_notifications_csv_without_job( "Did not like it", "Delivered", "1943-04-19 08:00:00 AM US/Eastern", + "AT&T Mobility", "🐜", "🐝", "πŸ¦€", @@ -189,6 +194,7 @@ def test_generate_notifications_csv_without_job( "Carrier Response", "Status", "Time", + "Carrier", "a", "b", "c", @@ -201,6 +207,7 @@ def test_generate_notifications_csv_without_job( "Did not like it", "Delivered", "1943-04-19 08:00:00 AM US/Eastern", + "AT&T Mobility", "🐜,🐜", "🐝,🐝", "πŸ¦€", From d4c646a788ec66ed4d139044255b45f523cc1c58 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 26 Nov 2024 13:45:43 -0800 Subject: [PATCH 5/8] making sub nav items conditional on feature flag --- app/main/views/index.py | 1 - app/main/views/sub_navigation_dictionaries.py | 55 ++++++------------- 2 files changed, 16 insertions(+), 40 deletions(-) diff --git a/app/main/views/index.py b/app/main/views/index.py index 983099a4f..49eeae427 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -292,7 +292,6 @@ def benchmark_performance(): ) -@main.route("/using-notify/guidance") @main.route("/guides/using-notify/guidance") @user_is_logged_in def guidance_index(): diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index 3c81dc7ed..0cf436424 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -1,3 +1,7 @@ +from flask import ( + current_app, +) + def features_nav(): return [ { @@ -22,46 +26,19 @@ def features_nav(): def using_notify_nav(): - return [ - { - "name": "Get started", - "link": "main.get_started", - }, - { - "name": "Guides", - "link": "main.best_practices", - }, - { - "name": "Trial mode", - "link": "main.trial_mode_new", - }, - { - "name": "Tracking usage", - "link": "main.pricing", - }, - { - "name": "Delivery status", - "link": "main.message_status", - }, - { - "name": "Guidance", - "link": "main.guidance_index", - # "sub_navigation_items": [ - # { - # "name": "Formatting", - # "link": "main.edit_and_format_messages", - # }, - # { - # "name": "Send files by email", - # "link": "main.send_files_by_email", - # }, - # ] - # { - # "name": "API documentation", - # "link": "main.documentation", - # }, - }, + nav_items = [ + {"name": "Get started", "link": "main.get_started"}, + {"name": "Guides", "link": "main.best_practices"}, + {"name": "Trial mode", "link": "main.trial_mode_new"}, + {"name": "Tracking usage", "link": "main.pricing"}, + {"name": "Delivery Status", "link": "main.message_status"}, + {"name": "Guidance", "link": "main.guidance_index"}, + ] + if not current_app.config.get("FEATURE_BEST_PRACTICES_ENABLED"): + nav_items = [item for item in nav_items if item["link"] != "main.best_practices"] + + return nav_items def best_practices_nav(): From e638eb4ad13cf2a88a10c975674df8e5312b8122 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 26 Nov 2024 13:50:00 -0800 Subject: [PATCH 6/8] isort fix --- app/main/views/sub_navigation_dictionaries.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index 0cf436424..bd44c1319 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -1,6 +1,5 @@ -from flask import ( - current_app, -) +from flask import current_app + def features_nav(): return [ From a5c65439b601389d7e7a7325405117389f7a232e Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 26 Nov 2024 14:38:15 -0800 Subject: [PATCH 7/8] update e2e --- app/main/views/sub_navigation_dictionaries.py | 5 +++-- tests/end_to_end/test_best_practices_content_pages.py | 4 ++-- urls.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index bd44c1319..603094cd1 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -32,10 +32,11 @@ def using_notify_nav(): {"name": "Tracking usage", "link": "main.pricing"}, {"name": "Delivery Status", "link": "main.message_status"}, {"name": "Guidance", "link": "main.guidance_index"}, - ] if not current_app.config.get("FEATURE_BEST_PRACTICES_ENABLED"): - nav_items = [item for item in nav_items if item["link"] != "main.best_practices"] + nav_items = [ + item for item in nav_items if item["link"] != "main.best_practices" + ] return nav_items diff --git a/tests/end_to_end/test_best_practices_content_pages.py b/tests/end_to_end/test_best_practices_content_pages.py index 962100de3..031e4baef 100644 --- a/tests/end_to_end/test_best_practices_content_pages.py +++ b/tests/end_to_end/test_best_practices_content_pages.py @@ -11,7 +11,7 @@ E2E_TEST_URI = os.getenv("NOTIFY_E2E_TEST_URI") def test_best_practices_side_menu(authenticated_page): page = authenticated_page - page.goto(f"{E2E_TEST_URI}/best-practices") + page.goto(f"{E2E_TEST_URI}/guides/best-practices") page.wait_for_load_state("domcontentloaded") check_axe_report(page) @@ -58,7 +58,7 @@ def test_best_practices_side_menu(authenticated_page): def test_breadcrumbs_best_practices(authenticated_page): page = authenticated_page - page.goto(f"{E2E_TEST_URI}/best-practices") + page.goto(f"{E2E_TEST_URI}/guides/best-practices") page.wait_for_load_state("domcontentloaded") check_axe_report(page) diff --git a/urls.js b/urls.js index 0440430ec..2279b5a79 100644 --- a/urls.js +++ b/urls.js @@ -8,7 +8,7 @@ const sublinks = [ { label: 'Trial Mode', path: '/using-notify/trial-mode' }, { label: 'Pricing', path: '/using-notify/pricing' }, { label: 'Delivery Status', path: '/using-notify/delivery-status' }, - { label: 'Guidance', path: '/using-notify/guidance' }, + { label: 'Guidance', path: '/guides/using-notify/guidance' }, { label: 'Features', path: '/features' }, { label: 'Roadmap', path: '/features/roadmap' }, { label: 'Security', path: '/features/security' }, From 3dd8d2508977242b0593c2d9f6d1c8f2859b56c9 Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Tue, 26 Nov 2024 21:02:45 -0500 Subject: [PATCH 8/8] Bump admin production memory to 2GB per instance This changeset bumps the admin production memory to 2GB per instance. We were noticing that the admin app was consuming almost all of the 1.5GB memory currently. Signed-off-by: Carlo Costino --- deploy-config/production.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy-config/production.yml b/deploy-config/production.yml index 42a681cb4..9f5cffc89 100644 --- a/deploy-config/production.yml +++ b/deploy-config/production.yml @@ -1,6 +1,6 @@ env: production instances: 2 -memory: 1.5G +memory: 2G command: newrelic-admin run-program gunicorn -c /home/vcap/app/gunicorn_config.py application public_admin_route: beta.notify.gov cloud_dot_gov_route: notify.app.cloud.gov