diff --git a/app/notify_client/models.py b/app/notify_client/models.py index eacd0decb..19a7d1246 100644 --- a/app/notify_client/models.py +++ b/app/notify_client/models.py @@ -85,10 +85,15 @@ class User(UserMixin): def has_permissions(self, permissions, service_id=None, or_=False): if service_id is None: service_id = session.get('service_id', '') + #print(permissions) + #print(service_id) + #print(self._permissions) + if service_id in self._permissions: if or_: return any([x in self._permissions[service_id] for x in permissions]) - return set(self._permissions[service_id]) > set(permissions) + print(set(self._permissions[service_id]) >= set(permissions)) + return set(self._permissions[service_id]) >= set(permissions) return False @property diff --git a/tests/__init__.py b/tests/__init__.py index 9fb7b5ede..e5b211bd7 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,38 @@ 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)) 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..18debf7f3 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,100 @@ 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): + 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): + 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): + 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 e3169f966..3877cfb10 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -3,6 +3,7 @@ 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'] @@ -300,3 +301,78 @@ def test_check_messages_should_revalidate_file_when_uploading_file( response = client.post(url, data=upload_data, follow_redirects=True) assert response.status_code == 200 assert 'Your CSV file contained missing or invalid data' in response.get_data(as_text=True) + + +def test_route_permissions(mocker, + app_, + api_user_active, + service_one, + mock_get_service_template, + 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_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) 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)