diff --git a/app/main/views/send.py b/app/main/views/send.py index 39c6effe1..d885a9800 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -93,6 +93,16 @@ def send_messages(service_id, template_id): db_template = service_api_client.get_service_template(service_id, template_id)['data'] + if (db_template['template_type'] in ['email', 'sms']) \ + and (db_template['template_type'] not in current_service['permissions']): + return redirect(url_for( + '.action_blocked', + service_id=service_id, + notification_type=db_template['template_type'], + return_to='view_template', + template_id=template_id + )) + template = get_template( db_template, current_service, @@ -163,6 +173,18 @@ def send_test(service_id, template_id): session['recipient'] = None session['placeholders'] = {} session['send_test_letter_page_count'] = None + + db_template = service_api_client.get_service_template(service_id, template_id)['data'] + + if (db_template['template_type'] in ['email', 'sms']) \ + and (db_template['template_type'] not in current_service['permissions']): + return redirect(url_for( + '.action_blocked', + service_id=service_id, + notification_type=db_template['template_type'], + return_to='view_template', + template_id=template_id)) + return redirect(url_for( { 'main.send_test': '.send_test_step', diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 6c392187b..276931df8 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -232,15 +232,42 @@ def add_template_by_type(service_id): template_id=blank_letter['data']['id'], )) - return redirect(url_for( - '.add_service_template', - service_id=service_id, - template_type=form.template_type.data, - )) + if form.template_type.data in current_service['permissions']: + return redirect(url_for( + '.add_service_template', + service_id=service_id, + template_type=form.template_type.data, + )) + else: + return redirect(url_for( + '.action_blocked', + service_id=service_id, + notification_type=form.template_type.data, + return_to='add_new_template', + template_id='0' + )) return render_template('views/templates/add.html', form=form) +@main.route("/services//templates/action-blocked///") +@login_required +@user_has_permissions('manage_templates', admin_override=True) +def action_blocked(service_id, notification_type, return_to, template_id): + if notification_type == 'sms': + notification_type = 'text messages' + elif notification_type == 'email': + notification_type = 'emails' + + return render_template( + 'views/templates/action_blocked.html', + service_id=service_id, + notification_type=notification_type, + return_to=return_to, + template_id=template_id + ) + + @main.route("/services//templates/add-", methods=['GET', 'POST']) @login_required @user_has_permissions('manage_templates', admin_override=True) @@ -277,13 +304,21 @@ def add_service_template(service_id, template_type): return redirect( url_for('.view_template', service_id=service_id, template_id=new_template['data']['id']) ) - - return render_template( - 'views/edit-{}-template.html'.format(template_type), - form=form, - template_type=template_type, - heading_action='Add' - ) + if (template_type in ['email', 'sms']) and (template_type not in current_service['permissions']): + return redirect(url_for( + '.action_blocked', + service_id=service_id, + notification_type=template_type, + return_to='templates', + template_id='0' + )) + else: + return render_template( + 'views/edit-{}-template.html'.format(template_type), + form=form, + template_type=template_type, + heading_action='Add', + ) def abort_403_if_not_admin_user(): @@ -354,13 +389,26 @@ def edit_service_template(service_id, template_id): service_id=service_id, template_id=template_id )) - return render_template( - 'views/edit-{}-template.html'.format(template['template_type']), - form=form, - template_id=template_id, - template_type=template['template_type'], - heading_action='Edit' - ) + + db_template = service_api_client.get_service_template(service_id, template_id)['data'] + + if (db_template['template_type'] in ['email', 'sms']) \ + and (db_template['template_type'] not in current_service['permissions']): + return redirect(url_for( + '.action_blocked', + service_id=service_id, + notification_type=db_template['template_type'], + return_to='view_template', + template_id=template_id + )) + else: + return render_template( + 'views/edit-{}-template.html'.format(template['template_type']), + form=form, + template_id=template_id, + template_type=template['template_type'], + heading_action='Edit' + ) @main.route("/services//templates//delete", methods=['GET', 'POST']) diff --git a/app/templates/views/templates/action_blocked.html b/app/templates/views/templates/action_blocked.html new file mode 100644 index 000000000..8a01b6701 --- /dev/null +++ b/app/templates/views/templates/action_blocked.html @@ -0,0 +1,47 @@ +{% extends "withnav_template.html" %} +{% from "components/textbox.html" import textbox %} +{% from "components/page-footer.html" import page_footer %} + +{% block service_page_title %} + Emails +{% endblock %} + +{% block maincolumn_content %} + +
+
+

{{ notification_type.capitalize() }}

+

+ Sending {{ notification_type }} is an invitation‑only feature. +

+

+ If you want to try it out, + get in touch with the GOV.UK Notify team. +

+ + {% set + back_link_dict = { + 'add_new_template': { + 'url' : '.add_template_by_type', 'text': 'Back to add new template' + }, + 'templates': { + 'url' : '.choose_template', 'text' : 'Back to templates' + }, + 'view_template': { + 'url' :'.view_template', 'text' : 'Back to the template' + } + } + %} + + {{ page_footer( + back_link=url_for( + back_link_dict[return_to]['url'], + service_id=current_service.id, + template_id=template_id), + back_link_text=back_link_dict[return_to]['text'] + ) }} + +
+
+ +{% endblock %} diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index acbb0ac73..39a0d3671 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -38,6 +38,32 @@ def test_that_test_files_exist(): assert len(test_non_spreadsheet_files) == 6 +def test_should_not_allow_files_to_be_uploaded_without_the_correct_permission( + logged_in_client, + mock_get_service_template, + service_one, + fake_uuid, +): + template_id = fake_uuid + service_one['permissions'] = [] + + response = logged_in_client.get(url_for( + '.send_messages', + service_id=service_one['id'], + template_id=template_id), + follow_redirects=True) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + + assert response.status_code == 200 + assert page.select('main p')[0].text.strip() == "Sending text messages is an invitation‑only feature." + assert page.select(".page-footer-back-link")[0].text == "Back to the template" + assert page.select(".page-footer-back-link")[0]['href'] == url_for( + '.view_template', + service_id=service_one['id'], + template_id=template_id, + ) + + @pytest.mark.parametrize( "filename, acceptable_file", list(zip( @@ -310,6 +336,32 @@ def test_send_test_step_redirects_if_session_not_setup( assert session['recipient'] == expected_recipient +def test_send_one_off_does_not_send_without_the_correct_permissions( + logged_in_client, + mock_get_service_template, + service_one, + fake_uuid, +): + template_id = fake_uuid + service_one['permissions'] = [] + + response = logged_in_client.get(url_for( + '.send_one_off', + service_id=service_one['id'], + template_id=template_id), + follow_redirects=True) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + + assert response.status_code == 200 + assert page.select('main p')[0].text.strip() == "Sending text messages is an invitation‑only feature." + assert page.select(".page-footer-back-link")[0].text == "Back to the template" + assert page.select(".page-footer-back-link")[0]['href'] == url_for( + '.view_template', + service_id=service_one['id'], + template_id=template_id, + ) + + @pytest.mark.parametrize('template_mock, partial_url, expected_h1, tour_shown', [ ( mock_get_service_template_with_placeholders, @@ -542,11 +594,15 @@ def test_send_test_redirects_to_start_if_index_out_of_bounds_and_some_placeholde ]) def _redirects_with_help_argument( logged_in_client, + mocker, service_one, fake_uuid, endpoint, expected_redirect, ): + template = {'data': {'template_type': 'sms'}} + mocker.patch('app.service_api_client.get_service_template', return_value=template) + response = logged_in_client.get( url_for(endpoint, service_id=service_one['id'], template_id=fake_uuid, help=1) ) @@ -848,6 +904,8 @@ def test_send_test_clears_session( service_one, fake_uuid, ): + template = {'data': {'template_type': 'sms'}} + mocker.patch('app.service_api_client.get_service_template', return_value=template) with logged_in_client.session_transaction() as session: session['recipient'] = '07700900001' diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index 38446df14..dc24f7258 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -266,6 +266,64 @@ def test_dont_show_preview_letter_templates_for_bad_filetype( assert mock_get_service_template.called is False +@pytest.mark.parametrize('type_of_template', ['email', 'sms']) +def test_should_not_allow_creation_of_template_through_form_without_correct_permission( + logged_in_client, + service_one, + mocker, + type_of_template, +): + service_one['permissions'] = [] + template_description = {'sms': 'text messages', 'email': 'emails'} + + response = logged_in_client.post(url_for( + '.add_template_by_type', + service_id=service_one['id']), + data={'template_type': type_of_template}, + follow_redirects=True) + + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + + assert response.status_code == 200 + assert page.select('main p')[0].text.strip() == \ + "Sending {} is an invitation‑only feature.".format(template_description[type_of_template]) + assert page.select(".page-footer-back-link")[0].text == "Back to add new template" + assert page.select(".page-footer-back-link")[0]['href'] == url_for( + '.add_template_by_type', + service_id=service_one['id'], + template_id='0', + ) + + +@pytest.mark.parametrize('type_of_template', ['email', 'sms']) +def test_should_not_allow_creation_of_a_template_without_correct_permission( + logged_in_client, + service_one, + mocker, + type_of_template, +): + service_one['permissions'] = [] + template_description = {'sms': 'text messages', 'email': 'emails'} + + response = logged_in_client.get(url_for( + '.add_service_template', + service_id=service_one['id'], + template_type=type_of_template), + follow_redirects=True) + + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + + assert response.status_code == 200 + assert page.select('main p')[0].text.strip() == \ + "Sending {} is an invitation‑only feature.".format(template_description[type_of_template]) + assert page.select(".page-footer-back-link")[0].text == "Back to templates" + assert page.select(".page-footer-back-link")[0]['href'] == url_for( + '.choose_template', + service_id=service_one['id'], + template_id='0', + ) + + def test_should_redirect_when_saving_a_template( logged_in_client, active_user_with_permissions, @@ -336,6 +394,32 @@ def test_should_edit_content_when_process_type_is_priority_not_platform_admin( ) +def test_should_not_allow_template_edits_without_correct_permission( + logged_in_client, + mock_get_service_template, + service_one, + fake_uuid, +): + template_id = fake_uuid + service_one['permissions'] = ['email'] + + response = logged_in_client.get(url_for( + '.edit_service_template', + service_id=service_one['id'], + template_id=template_id), + follow_redirects=True) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + + assert response.status_code == 200 + assert page.select('main p')[0].text.strip() == "Sending text messages is an invitation‑only feature." + assert page.select(".page-footer-back-link")[0].text == "Back to the template" + assert page.select(".page-footer-back-link")[0]['href'] == url_for( + '.view_template', + service_id=service_one['id'], + template_id=template_id, + ) + + def test_should_403_when_edit_template_with_process_type_of_priority_for_non_platform_admin( client, active_user_with_permissions, diff --git a/tests/conftest.py b/tests/conftest.py index 2afa1880e..1f1d2534f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -78,7 +78,7 @@ def mock_get_service(mocker, api_user_active): @pytest.fixture(scope='function') def mock_get_international_service(mocker, api_user_active): def _get(service_id): - service = service_json(service_id, users=[api_user_active.id], permissions=['international_sms']) + service = service_json(service_id, users=[api_user_active.id], permissions=['sms', 'international_sms']) return {'data': service} return mocker.patch('app.service_api_client.get_service', side_effect=_get)