diff --git a/app/__init__.py b/app/__init__.py index 4346301da..c8224e21e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -150,8 +150,14 @@ def _csp(config): "https://js-agent.newrelic.com", "https://gov-bam.nr-data.net", "https://www.googletagmanager.com", + "https://www.google-analytics.com", + "https://dap.digitalgov.gov", + ], + "connect-src": [ + "'self'", + "https://gov-bam.nr-data.net", + "https://www.google-analytics.com", ], - "connect-src": ["'self'", "https://gov-bam.nr-data.net"], "style-src": ["'self'", asset_domain], "img-src": ["'self'", asset_domain, logo_domain], } diff --git a/app/main/forms.py b/app/main/forms.py index dd77a8f1a..2f18b3b88 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -1196,7 +1196,7 @@ class AdminServiceSMSAllowanceForm(StripWhitespaceForm): class AdminServiceMessageLimitForm(StripWhitespaceForm): message_limit = GovukIntegerField( - "Number of messages the service is allowed to send each day", + "Max number of messages the service has per send", validators=[DataRequired(message="Cannot be empty")], ) diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py index d23c27d2f..3daaa38d2 100644 --- a/app/main/views/manage_users.py +++ b/app/main/views/manage_users.py @@ -21,7 +21,7 @@ from app.main.forms import ( SearchUsersForm, ) from app.models.user import InvitedUser, User -from app.utils.user import is_gov_user, user_has_permissions, user_is_platform_admin +from app.utils.user import is_gov_user, user_has_permissions from app.utils.user_permissions import permission_options @@ -42,7 +42,7 @@ def manage_users(service_id): @main.route( "/services//users/invite/", methods=["GET", "POST"] ) -@user_is_platform_admin +@user_has_permissions("manage_service") def invite_user(service_id, user_id=None): form_class = InviteUserForm form = form_class( diff --git a/app/templates/components/table.html b/app/templates/components/table.html index 199b9083c..dc1460a20 100644 --- a/app/templates/components/table.html +++ b/app/templates/components/table.html @@ -69,7 +69,7 @@ 'table-field-right-aligned' if align == 'right' else 'table-field-center-aligned' if align == 'center' else 'table-field-left-aligned' - %} + %} {% set border = '' if border else 'table-field-noborder' %} {% set wrap = 'table-field-wrap-text' if wrap else '' %} @@ -169,7 +169,7 @@ {% if notification.status|format_notification_status_as_url(notification.notification_type) %} {% endif %} -

+

{{ notification.status|format_notification_status_as_time( notification.created_at|format_datetime_short, (notification.updated_at or notification.created_at)|format_datetime_short @@ -180,6 +180,42 @@ {% endif %} {% endmacro %} +{% macro notification_carrier_field(notification) %} + + + + {% if not notification %} + {% call field(align='right') %}{% endcall %} + {% else %} + + {% call field( + align='right' + ) %} +

+ {{ notification.carrier}} +

+ {% if displayed_on_single_line %}{% endif %} + {% endcall %} + {% endif %} +{% endmacro %} + +{% macro notification_carrier_message_field(notification) %} + + {% if not notification %} + {% call field(align='right') %}{% endcall %} + {% else %} + + {% call field( + status=status, + align='right' + ) %} +

+ {{notification.provider_response}} +

+ {% if displayed_on_single_line %}{% endif %} + {% endcall %} + {% endif %} +{% endmacro %} {% macro spark_bar_field( count, @@ -189,7 +225,7 @@ {% call field(align='right') %} - {{ '{:,.0f}'.format(count) }} + {{ '{:,.0f}'.format(count) }} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/templates/views/templates/_template_list.html b/app/templates/views/templates/_template_list.html index 49a23a0ee..ee4450106 100644 --- a/app/templates/views/templates/_template_list.html +++ b/app/templates/views/templates/_template_list.html @@ -38,18 +38,12 @@ {% for item in template_list %} {% set item_link_content %} - {% for ancestor in item.ancestors %} - - {{- format_item_name(ancestor.name) -}} - - {% endfor %} {% if item.is_folder %} {{- format_item_name(item.name) -}} - {% else %} + {% elif not item.ancestors %} @@ -70,9 +64,11 @@ {% endset %} {% set item_meta %} + {% if not item.ancestors %} {{ item.hint }} + {% endif %} {% endset %} {# create the item config now to include the label content -#} @@ -112,4 +108,4 @@ }) }} {% endif %} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/tests/app/main/views/service_settings/test_service_settings.py b/tests/app/main/views/service_settings/test_service_settings.py index ab467a1f9..ede9f1e92 100644 --- a/tests/app/main/views/service_settings/test_service_settings.py +++ b/tests/app/main/views/service_settings/test_service_settings.py @@ -81,7 +81,7 @@ def _mock_get_service_settings_page_common( "Notes None Change the notes for the service", "Organization Test organization Federal government Change organization for service", "Rate limit 3,000 per minute Change rate limit", - "Message limit 1,000 per day Change daily message limit", + "Message batch limit 1,000 per send Change message batch limit", "Free text message allowance 250,000 per year Change free text message allowance", "Email branding GOV.UK Change email branding (admin view)", "Custom data retention Email – 7 days Change data retention", @@ -3171,7 +3171,7 @@ def test_should_show_page_to_set_message_limit( client_request.login(platform_admin_user) page = client_request.get("main.set_message_limit", service_id=SERVICE_ONE_ID) assert normalize_spaces(page.select_one("label").text) == ( - "Number of messages the service is allowed to send each day" + "Max number of messages the service has per send" ) assert normalize_spaces(page.select_one("input[type=text]")["value"]) == ("1000") diff --git a/tests/app/main/views/test_headers.py b/tests/app/main/views/test_headers.py index de0cef7d2..211601d16 100644 --- a/tests/app/main/views/test_headers.py +++ b/tests/app/main/views/test_headers.py @@ -18,9 +18,15 @@ def test_owasp_useful_headers_set( assert search( r"script-src 'self' static\.example\.com 'unsafe-eval' https:\/\/js-agent\.new" r"relic\.com https:\/\/gov-bam\.nr-data\.net https:\/\/www\.googletagmanager\." - r"com 'nonce-.*';", + r"com https:\/\/www\.google-analytics\." + r"com https:\/\/dap\.digitalgov\." + r"gov 'nonce-.*';", + csp, + ) + assert search( + r"connect-src 'self' https:\/\/gov-bam.nr-data\.net https:\/\/www\.google-analytics\." + r"com;", csp, ) - assert search(r"connect-src 'self' https:\/\/gov-bam.nr-data\.net;", csp) assert search(r"style-src 'self' static\.example\.com 'nonce-.*';", csp) assert search(r"img-src 'self' static\.example\.com static-logos\.test\.com", csp) diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index e9ea4dcf0..0b584962a 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -15,7 +15,7 @@ def test_non_logged_in_user_can_see_homepage( client_request.logout() page = client_request.get("main.index", _test_page_title=False) - assert page.h1.text.strip() == ("Send text messages to your participants") + assert page.h1.text.strip() == ("Reach people where they are with government-powered text messages") assert page.select_one("a.usa-button.usa-button--big")["href"] == url_for( "main.sign_in", diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py index 2a93dbef0..c5d97bd03 100644 --- a/tests/app/main/views/test_manage_users.py +++ b/tests/app/main/views/test_manage_users.py @@ -762,9 +762,9 @@ def test_edit_user_permissions_shows_authentication_for_email_auth_service( def test_should_show_page_for_inviting_user( client_request, mock_get_template_folders, - platform_admin_user, + active_user_with_permissions, ): - client_request.login(platform_admin_user) + client_request.login(active_user_with_permissions) page = client_request.get( "main.invite_user", service_id=SERVICE_ONE_ID, @@ -774,6 +774,17 @@ def test_should_show_page_for_inviting_user( assert not page.find("div", class_="checkboxes-nested") +def test_should_not_show_page_for_inviting_user_without_permissions( + client_request, mock_get_template_folders, active_user_empty_permissions +): + client_request.login(active_user_empty_permissions) + page = client_request.get( + "main.invite_user", service_id=SERVICE_ONE_ID, _expected_status=403 + ) + + assert "not allowed to see this page" in page.h1.string.strip() + + def test_should_show_page_for_inviting_user_with_email_prefilled( client_request, mocker, @@ -801,9 +812,12 @@ def test_should_show_page_for_inviting_user_with_email_prefilled( # We have the user’s name in the H1 but don’t want it duplicated # in the page title _test_page_title=False, - _expected_status=403, ) - assert "not allowed to see this page" in page.h1.string.strip() + assert normalize_spaces(page.select_one("title").text).startswith( + "Invite a team member" + ) + assert normalize_spaces(page.select_one("h1").text) == ("Invite Service Two User") + assert not page.select("input#email_address") or page.select("input[type=email]") def test_should_show_page_if_prefilled_user_is_already_a_team_member( @@ -813,9 +827,8 @@ def test_should_show_page_if_prefilled_user_is_already_a_team_member( fake_uuid, active_user_with_permissions, active_caseworking_user, - platform_admin_user, ): - client_request.login(platform_admin_user) + client_request.login(active_user_with_permissions) mocker.patch( "app.models.user.user_api_client.get_user", side_effect=[ @@ -1280,9 +1293,11 @@ def test_user_cant_invite_themselves( "permissions_field": ["send_messages", "manage_service", "manage_api_keys"], }, _follow_redirects=True, - _expected_status=403, + _expected_status=200, ) - assert "not allowed to see this page" in page.h1.string.strip() + assert page.h1.string.strip() == "Invite a team member" + form_error = page.find("span", class_="usa-error-message").text.strip() + assert form_error == "Error: You cannot send an invitation to yourself" assert not mock_create_invite.called diff --git a/tests/app/main/views/test_template_folders.py b/tests/app/main/views/test_template_folders.py index c4c2da4c1..fae020e58 100644 --- a/tests/app/main/views/test_template_folders.py +++ b/tests/app/main/views/test_template_folders.py @@ -58,18 +58,10 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): ["Email", "Text message"], [ "folder_one folder_one 2 folders", - ("folder_one folder_one_one " "folder_one folder_one_one " "1 folder"), - ( - "folder_one folder_one_one folder_one_one_one " - "folder_one folder_one_one folder_one_one_one " - "1 template" - ), - ( - "folder_one folder_one_one folder_one_one_one sms_template_nested " - "folder_one folder_one_one folder_one_one_one sms_template_nested " - "Text message template" - ), - ("folder_one folder_one_two " "folder_one folder_one_two " "Empty"), + ("folder_one folder_one_one " "folder_one_one"), + ("folder_one folder_one_one folder_one_one_one " "folder_one_one_one"), + ("folder_one folder_one_one folder_one_one_one sms_template_nested"), + ("folder_one folder_one_two " "folder_one_two"), "folder_two folder_two Empty", ("sms_template_one " "sms_template_one " "Text message template"), ("sms_template_two " "sms_template_two " "Text message template"), @@ -88,7 +80,6 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): "folder_one", "folder_one_one", "folder_one_one_one", - "sms_template_nested", "folder_one_two", "folder_two", "sms_template_one", @@ -106,18 +97,10 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): ["Email", "Text message"], [ "folder_one folder_one 2 folders", - ("folder_one folder_one_one " "folder_one folder_one_one " "1 folder"), - ( - "folder_one folder_one_one folder_one_one_one " - "folder_one folder_one_one folder_one_one_one " - "1 template" - ), - ( - "folder_one folder_one_one folder_one_one_one sms_template_nested " - "folder_one folder_one_one folder_one_one_one sms_template_nested " - "Text message template" - ), - ("folder_one folder_one_two " "folder_one folder_one_two " "Empty"), + ("folder_one folder_one_one " "folder_one_one"), + ("folder_one folder_one_one folder_one_one_one " "folder_one_one_one"), + ("folder_one folder_one_one folder_one_one_one sms_template_nested"), + ("folder_one folder_one_two " "folder_one_two"), "folder_two folder_two Empty", "sms_template_one sms_template_one Text message template", "sms_template_two sms_template_two Text message template", @@ -136,7 +119,6 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): "folder_one", "folder_one_one", "folder_one_one_one", - "sms_template_nested", "folder_one_two", "folder_two", "sms_template_one", @@ -154,17 +136,9 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): ["All", "Email"], [ "folder_one folder_one 1 folder", - ("folder_one folder_one_one " "folder_one folder_one_one " "1 folder"), - ( - "folder_one folder_one_one folder_one_one_one " - "folder_one folder_one_one folder_one_one_one " - "1 template" - ), - ( - "folder_one folder_one_one folder_one_one_one sms_template_nested " - "folder_one folder_one_one folder_one_one_one sms_template_nested " - "Text message template" - ), + ("folder_one folder_one_one " "folder_one_one"), + ("folder_one folder_one_one folder_one_one_one " "folder_one_one_one"), + ("folder_one folder_one_one folder_one_one_one sms_template_nested"), "sms_template_one sms_template_one Text message template", "sms_template_two sms_template_two Text message template", ], @@ -177,7 +151,6 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): "folder_one", "folder_one_one", "folder_one_one_one", - "sms_template_nested", "sms_template_one", "sms_template_two", ], @@ -191,16 +164,8 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): ["Email", "Text message"], [ "folder_one_one folder_one_one 1 folder", - ( - "folder_one_one folder_one_one_one " - "folder_one_one folder_one_one_one " - "1 template" - ), - ( - "folder_one_one folder_one_one_one sms_template_nested " - "folder_one_one folder_one_one_one sms_template_nested " - "Text message template" - ), + ("folder_one_one folder_one_one_one " "folder_one_one_one"), + ("folder_one_one folder_one_one_one sms_template_nested"), "folder_one_two folder_one_two Empty", ], [ @@ -210,7 +175,6 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): [ "folder_one_one", "folder_one_one_one", - "sms_template_nested", "folder_one_two", ], None, @@ -223,25 +187,13 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): ["All", "Email"], [ "folder_one_one folder_one_one 1 folder", - ( - "folder_one_one folder_one_one_one " - "folder_one_one folder_one_one_one " - "1 template" - ), - ( - "folder_one_one folder_one_one_one sms_template_nested " - "folder_one_one folder_one_one_one sms_template_nested " - "Text message template" - ), + ("folder_one_one folder_one_one_one " "folder_one_one_one"), + ("folder_one_one folder_one_one_one sms_template_nested"), ], [ "folder_one_one folder_one_one 1 folder", ], - [ - "folder_one_one", - "folder_one_one_one", - "sms_template_nested", - ], + ["folder_one_one", "folder_one_one_one"], None, ), ( @@ -266,19 +218,12 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): ["Email", "Text message"], [ "folder_one_one_one folder_one_one_one 1 template", - ( - "folder_one_one_one sms_template_nested " - "folder_one_one_one sms_template_nested " - "Text message template" - ), + ("folder_one_one_one sms_template_nested"), ], [ "folder_one_one_one folder_one_one_one 1 template", ], - [ - "folder_one_one_one", - "sms_template_nested", - ], + ["folder_one_one_one"], None, ), ( @@ -502,7 +447,6 @@ def test_template_id_is_searchable_for_services_with_api_keys( ) ] == [ "folder one", - f'{template_2["id"]} template two', f'{template_1["id"]} template one', ] @@ -523,10 +467,7 @@ def test_template_id_is_searchable_for_services_with_api_keys( # Text which should be hidden from all users r".template-list-item .display-none" ) - ] == [ - template_2["id"], - template_1["id"], - ] + ] == [template_1["id"]] mock_get_api_keys.assert_called_once_with(SERVICE_ONE_ID) @@ -1707,21 +1648,10 @@ def test_show_custom_error_message( ], [ ["folder_A", "folder_A", "1 template, 2 folders"], - ["folder_A folder_C", "folder_A", "folder_C", "1 template"], - [ - "folder_A folder_C sms_template_C", - "folder_A", - "folder_C", - "sms_template_C", - "Text message template", - ], - ["folder_A folder_D", "folder_A", "folder_D", "Empty"], - [ - "folder_A sms_template_A", - "folder_A", - "sms_template_A", - "Text message template", - ], + ["folder_A folder_C", "folder_C"], + ["folder_A folder_C sms_template_C"], + ["folder_A folder_D", "folder_D"], + ["folder_A sms_template_A"], [ "folder_E folder_F folder_G", "folder_E", @@ -1729,14 +1659,7 @@ def test_show_custom_error_message( "folder_G", "1 template", ], - [ - "folder_E folder_F folder_G email_template_G", - "folder_E", - "folder_F", - "folder_G", - "email_template_G", - "Email template", - ], + ["folder_E folder_F folder_G email_template_G"], ["email_template_root", "email_template_root", "Email template"], ], None, @@ -1763,11 +1686,6 @@ def test_show_custom_error_message( ], [ "folder_E folder_F folder_G email_template_G", - "folder_E", - "folder_F", - "folder_G", - "email_template_G", - "Email template", ], ["email_template_root", "email_template_root", "Email template"], ], @@ -1780,20 +1698,9 @@ def test_show_custom_error_message( ], [ ["folder_A", "folder_A", "1 template, 1 folder"], - ["folder_A folder_C", "folder_A", "folder_C", "1 template"], - [ - "folder_A folder_C sms_template_C", - "folder_A", - "folder_C", - "sms_template_C", - "Text message template", - ], - [ - "folder_A sms_template_A", - "folder_A", - "sms_template_A", - "Text message template", - ], + ["folder_A folder_C", "folder_C"], + ["folder_A folder_C sms_template_C"], + ["folder_A sms_template_A"], ], None, ), diff --git a/tests/conftest.py b/tests/conftest.py index 0c43770d3..9d55b796b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1067,6 +1067,11 @@ def active_user_with_permissions(fake_uuid): return create_active_user_with_permissions() +@pytest.fixture() +def active_user_empty_permissions(fake_uuid): + return create_active_user_empty_permissions() + + @pytest.fixture() def active_user_with_permission_to_two_services(fake_uuid): permissions = [