diff --git a/app/templates/views/service_dashboard.html b/app/templates/views/service_dashboard.html
index b860e7f8b..90b4ecc0d 100644
--- a/app/templates/views/service_dashboard.html
+++ b/app/templates/views/service_dashboard.html
@@ -1,4 +1,5 @@
{% extends "withnav_template.html" %}
+{% from "components/banner.html" import banner_wrapper %}
{% from "components/table.html" import list_table, field, right_aligned_field_heading %}
{% from "components/big-number.html" import big_number %}
@@ -24,33 +25,26 @@
{% if not template_count and not jobs %}
- {{ banner(
- """
-
+ {% call banner_wrapper(subhead='Get started', type="tip") %}
+
+ {% if current_user.has_permissions(['manage_templates']) %}
-
- Add a template
+ Add a template
+ {% endif %}
+ {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %}
-
- Send yourself a text message
+ Send yourself a text message
-
- """.format(
- url_for(".add_service_template", service_id=service_id, template_type="sms"),
- url_for(".choose_template", service_id=service_id, template_type="sms")
- )|safe,
- subhead='Get started',
- type="tip"
- )}}
+ {% endif %}
+
+ {% endcall %}
{% elif not jobs %}
- {{ banner(
- """
- Send yourself a text message
- """.format(
- url_for(".choose_template", service_id=service_id, template_type="sms")
- )|safe,
- subhead='Next step',
- type="tip"
- )}}
+ {% call banner_wrapper(subhead='Next step', type="tip") %}
+ {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %}
+ Send yourself a text message
+ {% endif %}
+ {% endcall %}
{% else %}
{% call(item) list_table(
jobs,
@@ -69,9 +63,11 @@
{% endcall %}
{% endcall %}
{% if more_jobs_to_show %}
-
- See all sent text messages
-
+ {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %}
+
+ See all sent text messages
+
+ {% endif %}
{% endif %}
{% endif %}
diff --git a/tests/__init__.py b/tests/__init__.py
index 9fb7b5ede..a316c4342 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,3 +1,4 @@
+import pytest
from flask.testing import FlaskClient
from flask import url_for
@@ -125,3 +126,39 @@ def notification_json():
'links': {}
}
return data
+
+
+def validate_route_permission(mocker,
+ app_,
+ method,
+ response_code,
+ route,
+ permissions,
+ usr,
+ service):
+ usr._permissions[str(service['id'])] = permissions
+ mocker.patch(
+ 'app.user_api_client.check_verify_code',
+ return_value=(True, ''))
+ mocker.patch(
+ 'app.notifications_api_client.get_services',
+ return_value={'data': []})
+ mocker.patch('app.user_api_client.get_user', return_value=usr)
+ mocker.patch('app.user_api_client.get_user_by_email', return_value=usr)
+ mocker.patch('app.notifications_api_client.get_service', return_value={'data': service})
+
+ with app_.test_request_context():
+ with app_.test_client() as client:
+ client.login(usr)
+ resp = None
+ with client.session_transaction() as session:
+ session['service_id'] = str(service['id'])
+ if method == 'GET':
+ resp = client.get(route)
+ elif method == 'POST':
+ resp = client.post(route)
+ else:
+ pytest.fail("Invalid method call {}".format(method))
+ if resp.status_code != response_code:
+ pytest.fail("Invalid permissions set for endpoint {}".format(route))
+ return resp
diff --git a/tests/app/main/test_permissions.py b/tests/app/main/test_permissions.py
index 11e681a21..62c5b5058 100644
--- a/tests/app/main/test_permissions.py
+++ b/tests/app/main/test_permissions.py
@@ -6,65 +6,73 @@ from app.main.views.index import index
from werkzeug.exceptions import Forbidden
+def _test_permissions(app_, usr, permissions, will_succeed, or_=False):
+ with app_.test_request_context():
+ with app_.test_client() as client:
+ client.login(usr)
+ decorator = user_has_permissions(*permissions, or_=or_)
+ decorated_index = decorator(index)
+ if will_succeed:
+ response = decorated_index()
+ else:
+ try:
+ response = decorated_index()
+ pytest.fail("Failed to throw a forbidden exception")
+ except Forbidden:
+ pass
+
+
def test_user_has_permissions_on_endpoint_fail(app_,
api_user_active,
mock_login,
mock_get_user_with_permissions):
- with app_.test_request_context():
- with app_.test_client() as client:
- client.login(api_user_active)
- decorator = user_has_permissions('something')
- decorated_index = decorator(index)
- try:
- response = decorated_index()
- pytest.fail("Failed to throw a forbidden exception")
- except Forbidden:
- pass
+ _test_permissions(
+ app_,
+ api_user_active,
+ ['something'],
+ False)
def test_user_has_permissions_success(app_,
api_user_active,
mock_login,
mock_get_user_with_permissions):
- with app_.test_request_context():
- with app_.test_client() as client:
- client.login(api_user_active)
- decorator = user_has_permissions('manage_users')
- decorated_index = decorator(index)
- response = decorated_index()
+ _test_permissions(
+ app_,
+ api_user_active,
+ ['manage_users'],
+ True)
def test_user_has_permissions_or(app_,
api_user_active,
mock_login,
mock_get_user_with_permissions):
- with app_.test_request_context():
- with app_.test_client() as client:
- client.login(api_user_active)
- decorator = user_has_permissions('something', 'manage_users', or_=True)
- decorated_index = decorator(index)
- response = decorated_index()
+ _test_permissions(
+ app_,
+ api_user_active,
+ ['something', 'manage_users'],
+ True,
+ or_=True)
def test_user_has_permissions_multiple(app_,
api_user_active,
mock_login,
mock_get_user_with_permissions):
- with app_.test_request_context():
- with app_.test_client() as client:
- client.login(api_user_active)
- decorator = user_has_permissions('manage_templates', 'manage_users')
- decorated_index = decorator(index)
- response = decorated_index()
+ _test_permissions(
+ app_,
+ api_user_active,
+ ['manage_templates', 'manage_users'],
+ True)
def test_exact_permissions(app_,
api_user_active,
mock_login,
mock_get_user_with_permissions):
- with app_.test_request_context():
- with app_.test_client() as client:
- client.login(api_user_active)
- decorator = user_has_permissions('manage_users', 'manage_templates', 'manage_settings')
- decorated_index = decorator(index)
- response = decorated_index()
+ _test_permissions(
+ app_,
+ api_user_active,
+ ['manage_users', 'manage_templates', 'manage_settings'],
+ True)
diff --git a/tests/app/main/views/test_api_keys.py b/tests/app/main/views/test_api_keys.py
index d22855fe6..72fd6007a 100644
--- a/tests/app/main/views/test_api_keys.py
+++ b/tests/app/main/views/test_api_keys.py
@@ -1,6 +1,7 @@
import uuid
from datetime import date
from flask import url_for
+from tests import validate_route_permission
def test_should_show_api_keys_and_documentation_page(app_,
@@ -127,3 +128,39 @@ def test_should_redirect_after_revoking_api_key(app_,
assert response.location == url_for('.api_keys', service_id=service_id, _external=True)
mock_revoke_api_key.assert_called_once_with(service_id=service_id, key_id=321)
mock_get_api_keys.assert_called_once_with(service_id=service_id, key_id=321)
+
+
+def test_route_permissions(mocker, app_, api_user_active, service_one, mock_get_api_keys):
+ routes = [
+ 'main.api_keys',
+ 'main.create_api_key',
+ 'main.revoke_api_key']
+ with app_.test_request_context():
+ for route in routes:
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 200,
+ url_for(route, service_id=service_one['id'], key_id=123),
+ ['manage_api_keys'],
+ api_user_active,
+ service_one)
+
+
+def test_route_invalid_permissions(mocker, app_, api_user_active, service_one, mock_get_api_keys):
+ routes = [
+ 'main.api_keys',
+ 'main.create_api_key',
+ 'main.revoke_api_key']
+ with app_.test_request_context():
+ for route in routes:
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 403,
+ url_for(route, service_id=service_one['id'], key_id=123),
+ ['blah'],
+ api_user_active,
+ service_one)
diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py
index d5b3386ea..4fb92a2b5 100644
--- a/tests/app/main/views/test_dashboard.py
+++ b/tests/app/main/views/test_dashboard.py
@@ -1,4 +1,5 @@
from flask import url_for, session
+from bs4 import BeautifulSoup
def test_should_show_recent_jobs_on_dashboard(app_,
@@ -18,3 +19,98 @@ def test_should_show_recent_jobs_on_dashboard(app_,
assert response.status_code == 200
text = response.get_data(as_text=True)
assert 'Test Service' in text
+
+
+def _test_dashboard_menu(mocker, app_, usr, service, permissions):
+ with app_.test_request_context():
+ with app_.test_client() as client:
+ usr._permissions[str(service['id'])] = permissions
+ mocker.patch(
+ 'app.user_api_client.check_verify_code',
+ return_value=(True, ''))
+ mocker.patch(
+ 'app.notifications_api_client.get_services',
+ return_value={'data': []})
+ mocker.patch('app.user_api_client.get_user', return_value=usr)
+ mocker.patch('app.user_api_client.get_user_by_email', return_value=usr)
+ mocker.patch('app.notifications_api_client.get_service', return_value={'data': service})
+ client.login(usr)
+ return client.get(url_for('main.service_dashboard', service_id=service['id']))
+
+
+def test_menu_send_messages(mocker, app_, api_user_active, service_one, mock_get_service_templates, mock_get_jobs):
+ with app_.test_request_context():
+ resp = _test_dashboard_menu(
+ mocker,
+ app_,
+ api_user_active,
+ service_one,
+ ['send_texts', 'send_emails', 'send_letters'])
+ page = resp.get_data(as_text=True)
+ assert url_for('main.letters_stub', service_id=service_one['id']) in page
+ assert url_for(
+ 'main.choose_template',
+ service_id=service_one['id'],
+ template_type='email')in page
+ assert url_for(
+ 'main.choose_template',
+ service_id=service_one['id'],
+ template_type='sms')in page
+
+ assert url_for('main.manage_users', service_id=service_one['id']) not in page
+ assert url_for('main.service_settings', service_id=service_one['id']) not in page
+
+ assert url_for('main.api_keys', service_id=service_one['id']) not in page
+ assert url_for('main.documentation', service_id=service_one['id']) not in page
+
+
+def test_menu_manage_service(mocker, app_, api_user_active, service_one, mock_get_service_templates, mock_get_jobs):
+ with app_.test_request_context():
+ resp = _test_dashboard_menu(
+ mocker,
+ app_,
+ api_user_active,
+ service_one,
+ ['manage_users', 'manage_templates', 'manage_settings'])
+ page = resp.get_data(as_text=True)
+ assert url_for('main.letters_stub', service_id=service_one['id'])in page
+ assert url_for(
+ 'main.choose_template',
+ service_id=service_one['id'],
+ template_type='email') in page
+ assert url_for(
+ 'main.choose_template',
+ service_id=service_one['id'],
+ template_type='sms') in page
+
+ assert url_for('main.manage_users', service_id=service_one['id']) in page
+ assert url_for('main.service_settings', service_id=service_one['id']) in page
+
+ assert url_for('main.api_keys', service_id=service_one['id']) not in page
+ assert url_for('main.documentation', service_id=service_one['id']) not in page
+
+
+def test_menu_manage_api_keys(mocker, app_, api_user_active, service_one, mock_get_service_templates, mock_get_jobs):
+ with app_.test_request_context():
+ resp = _test_dashboard_menu(
+ mocker,
+ app_,
+ api_user_active,
+ service_one,
+ ['manage_api_keys', 'access_developer_docs'])
+ page = resp.get_data(as_text=True)
+ assert url_for('main.letters_stub', service_id=service_one['id']) not in page
+ assert url_for(
+ 'main.choose_template',
+ service_id=service_one['id'],
+ template_type='email') not in page
+ assert url_for(
+ 'main.choose_template',
+ service_id=service_one['id'],
+ template_type='sms') not in page
+
+ assert url_for('main.manage_users', service_id=service_one['id']) not in page
+ assert url_for('main.service_settings', service_id=service_one['id']) not in page
+
+ assert url_for('main.api_keys', service_id=service_one['id']) in page
+ assert url_for('main.documentation', service_id=service_one['id']) in page
diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py
index 7c35c7aa2..5115d4e73 100644
--- a/tests/app/main/views/test_send.py
+++ b/tests/app/main/views/test_send.py
@@ -3,36 +3,11 @@ import pytest
from io import BytesIO
from flask import url_for
from unittest.mock import ANY
+from tests import validate_route_permission
template_types = ['email', 'sms']
-@pytest.mark.parametrize("template_type", template_types)
-def test_choose_template(
- template_type,
- app_,
- api_user_active,
- mock_login,
- mock_get_user,
- mock_get_service,
- mock_check_verify_code,
- mock_get_service_templates,
- mock_get_jobs,
- mock_has_permissions
-):
- with app_.test_request_context():
- with app_.test_client() as client:
- client.login(api_user_active)
- response = client.get(url_for('main.choose_template', template_type=template_type, service_id=12345))
-
- assert response.status_code == 200
- content = response.get_data(as_text=True)
- assert '{}_template_one'.format(template_type) in content
- assert '{} template one content'.format(template_type) in content
- assert '{}_template_two'.format(template_type) in content
- assert '{} template two content'.format(template_type) in content
-
-
def test_upload_csvfile_with_errors_shows_check_page_with_errors(
app_,
api_user_active,
@@ -247,3 +222,158 @@ def test_check_messages_should_revalidate_file_when_uploading_file(
)
assert response.status_code == 200
assert 'There was a problem with invalid.csv' in response.get_data(as_text=True)
+
+
+def test_route_permissions(mocker,
+ app_,
+ api_user_active,
+ service_one,
+ mock_get_service_template,
+ mock_get_service_templates,
+ mock_get_jobs,
+ mock_get_notifications,
+ mock_create_job,
+ mock_s3_upload):
+ routes = [
+ 'main.choose_template',
+ 'main.send_messages',
+ 'main.get_example_csv']
+ with app_.test_request_context():
+ for route in routes:
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 200,
+ url_for(
+ route,
+ service_id=service_one['id'],
+ template_type='sms',
+ template_id=123),
+ ['send_texts', 'send_emails', 'send_letters'],
+ api_user_active,
+ service_one)
+
+ with app_.test_request_context():
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 302,
+ url_for(
+ 'main.send_message_to_self',
+ service_id=service_one['id'],
+ template_type='sms',
+ template_id=123),
+ ['send_texts', 'send_emails', 'send_letters'],
+ api_user_active,
+ service_one)
+
+
+def test_route_invalid_permissions(mocker,
+ app_,
+ api_user_active,
+ service_one,
+ mock_get_service_template,
+ mock_get_service_templates,
+ mock_get_jobs,
+ mock_get_notifications,
+ mock_create_job):
+ routes = [
+ 'main.choose_template',
+ 'main.send_messages',
+ 'main.get_example_csv',
+ 'main.send_message_to_self']
+ with app_.test_request_context():
+ for route in routes:
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 403,
+ url_for(
+ route,
+ service_id=service_one['id'],
+ template_type='sms',
+ template_id=123),
+ ['blah'],
+ api_user_active,
+ service_one)
+
+
+def test_route_choose_template_manage_service_permissions(mocker,
+ app_,
+ api_user_active,
+ service_one,
+ mock_login,
+ mock_get_user,
+ mock_get_service,
+ mock_check_verify_code,
+ mock_get_service_templates,
+ mock_get_jobs):
+ with app_.test_request_context():
+ template_id = mock_get_service_templates(service_one['id'])['data'][0]['id']
+ resp = validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 200,
+ url_for(
+ 'main.choose_template',
+ service_id=service_one['id'],
+ template_type='sms'),
+ ['manage_users', 'manage_templates', 'manage_settings'],
+ api_user_active,
+ service_one)
+ page = resp.get_data(as_text=True)
+ assert url_for(
+ "main.send_messages",
+ service_id=service_one['id'],
+ template_id=template_id) not in page
+ assert url_for(
+ "main.send_message_to_self",
+ service_id=service_one['id'],
+ template_id=template_id) not in page
+ assert url_for(
+ "main.edit_service_template",
+ service_id=service_one['id'],
+ template_id=template_id) in page
+
+
+def test_route_choose_template_send_messages_permissions(mocker,
+ app_,
+ api_user_active,
+ service_one,
+ mock_login,
+ mock_get_user,
+ mock_get_service,
+ mock_check_verify_code,
+ mock_get_service_templates,
+ mock_get_jobs):
+ with app_.test_request_context():
+ template_id = mock_get_service_templates(service_one['id'])['data'][0]['id']
+ resp = validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 200,
+ url_for(
+ 'main.choose_template',
+ service_id=service_one['id'],
+ template_type='sms'),
+ ['send_texts', 'send_emails', 'send_letters'],
+ api_user_active,
+ service_one)
+ page = resp.get_data(as_text=True)
+ assert url_for(
+ "main.send_messages",
+ service_id=service_one['id'],
+ template_id=template_id) in page
+ assert url_for(
+ "main.send_message_to_self",
+ service_id=service_one['id'],
+ template_id=template_id) in page
+ assert url_for(
+ "main.edit_service_template",
+ service_id=service_one['id'],
+ template_id=template_id) not in page
diff --git a/tests/app/main/views/test_service_settings.py b/tests/app/main/views/test_service_settings.py
index 174935f2a..7d9f52822 100644
--- a/tests/app/main/views/test_service_settings.py
+++ b/tests/app/main/views/test_service_settings.py
@@ -1,4 +1,5 @@
from flask import (url_for, session)
+from tests import validate_route_permission
def test_should_show_overview(app_,
@@ -318,3 +319,49 @@ def test_should_redirect_delete_confirmation(app_,
assert choose_url == response.location
assert mock_get_service.called
assert mock_delete_service.called
+
+
+def test_route_permissions(mocker, app_, api_user_active, service_one):
+ routes = [
+ 'main.service_settings',
+ 'main.service_name_change',
+ 'main.service_name_change_confirm',
+ 'main.service_request_to_go_live',
+ 'main.service_status_change',
+ 'main.service_status_change_confirm',
+ 'main.service_delete',
+ 'main.service_delete_confirm']
+ with app_.test_request_context():
+ for route in routes:
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 200,
+ url_for(route, service_id=service_one['id']),
+ ['manage_settings'],
+ api_user_active,
+ service_one)
+
+
+def test_route_invalid_permissions(mocker, app_, api_user_active, service_one):
+ routes = [
+ 'main.service_settings',
+ 'main.service_name_change',
+ 'main.service_name_change_confirm',
+ 'main.service_request_to_go_live',
+ 'main.service_status_change',
+ 'main.service_status_change_confirm',
+ 'main.service_delete',
+ 'main.service_delete_confirm']
+ with app_.test_request_context():
+ for route in routes:
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 403,
+ url_for(route, service_id=service_one['id']),
+ ['blah'],
+ api_user_active,
+ service_one)
diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py
index cff04bde1..d8096f675 100644
--- a/tests/app/main/views/test_templates.py
+++ b/tests/app/main/views/test_templates.py
@@ -1,5 +1,6 @@
import json
import uuid
+from tests import validate_route_permission
from flask import url_for
@@ -125,3 +126,55 @@ def test_should_redirect_when_deleting_a_template(app_,
service_id, template_id)
mock_delete_service_template.assert_called_with(
service_id, template_id)
+
+
+def test_route_permissions(mocker,
+ app_,
+ api_user_active,
+ service_one,
+ mock_get_service_template):
+ routes = [
+ 'main.add_service_template',
+ 'main.edit_service_template',
+ 'main.delete_service_template']
+ with app_.test_request_context():
+ for route in routes:
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 200,
+ url_for(
+ route,
+ service_id=service_one['id'],
+ template_type='sms',
+ template_id=123),
+ ['manage_templates'],
+ api_user_active,
+ service_one)
+
+
+def test_route_invalid_permissions(mocker,
+ app_,
+ api_user_active,
+ service_one,
+ mock_get_service_template):
+ routes = [
+ 'main.add_service_template',
+ 'main.edit_service_template',
+ 'main.delete_service_template']
+ with app_.test_request_context():
+ for route in routes:
+ validate_route_permission(
+ mocker,
+ app_,
+ "GET",
+ 403,
+ url_for(
+ route,
+ service_id=service_one['id'],
+ template_type='sms',
+ template_id=123),
+ ['blah'],
+ api_user_active,
+ service_one)