diff --git a/app/config.py b/app/config.py index 4f824040d..bec56ba49 100644 --- a/app/config.py +++ b/app/config.py @@ -69,6 +69,7 @@ class Config(object): STATSD_PORT = 8125 NOTIFY_ENVIRONMENT = 'development' LOGO_UPLOAD_BUCKET_NAME = 'public-logos-local' + MOU_BUCKET_NAME = 'local-mou' ROUTE_SECRET_KEY_1 = os.environ.get('ROUTE_SECRET_KEY_1', '') ROUTE_SECRET_KEY_2 = os.environ.get('ROUTE_SECRET_KEY_2', '') CHECK_PROXY_HEADER = False @@ -82,6 +83,7 @@ class Development(Config): STATSD_ENABLED = False CSV_UPLOAD_BUCKET_NAME = 'development-notifications-csv-upload' LOGO_UPLOAD_BUCKET_NAME = 'public-logos-tools' + MOU_BUCKET_NAME = 'notify.tools-mou' ADMIN_CLIENT_SECRET = 'dev-notify-secret-key' API_HOST_NAME = 'http://localhost:6011' @@ -98,6 +100,7 @@ class Test(Development): WTF_CSRF_ENABLED = False CSV_UPLOAD_BUCKET_NAME = 'test-notifications-csv-upload' LOGO_UPLOAD_BUCKET_NAME = 'public-logos-test' + MOU_BUCKET_NAME = 'test-mou' NOTIFY_ENVIRONMENT = 'test' API_HOST_NAME = 'http://you-forgot-to-mock-an-api-call-to' TEMPLATE_PREVIEW_API_HOST = 'http://localhost:9999' @@ -109,6 +112,7 @@ class Preview(Config): STATSD_ENABLED = True CSV_UPLOAD_BUCKET_NAME = 'preview-notifications-csv-upload' LOGO_UPLOAD_BUCKET_NAME = 'public-logos-preview' + MOU_BUCKET_NAME = 'notify.works-mou' NOTIFY_ENVIRONMENT = 'preview' CHECK_PROXY_HEADER = True @@ -120,6 +124,7 @@ class Staging(Config): STATSD_ENABLED = True CSV_UPLOAD_BUCKET_NAME = 'staging-notify-csv-upload' LOGO_UPLOAD_BUCKET_NAME = 'public-logos-staging' + MOU_BUCKET_NAME = 'staging-notify.works-mou' NOTIFY_ENVIRONMENT = 'staging' CHECK_PROXY_HEADER = True @@ -131,6 +136,7 @@ class Live(Config): STATSD_ENABLED = True CSV_UPLOAD_BUCKET_NAME = 'live-notifications-csv-upload' LOGO_UPLOAD_BUCKET_NAME = 'public-logos-production' + MOU_BUCKET_NAME = 'notifications.service.gov.uk-mou' NOTIFY_ENVIRONMENT = 'live' CHECK_PROXY_HEADER = False diff --git a/app/domains.yml b/app/domains.yml index 15e368867..823337680 100644 --- a/app/domains.yml +++ b/app/domains.yml @@ -21,6 +21,7 @@ marinemanagement.org.uk: agreement_signed: true cabinet-office.gov.uk: owner: Cabinet Office + crown: true agreement_signed: true cica.gsi.gov.uk: cica.gov.uk cica.gov.uk: diff --git a/app/main/__init__.py b/app/main/__init__.py index a255c4600..7510e236a 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -32,5 +32,6 @@ from app.main.views import ( # noqa conversation, organisations, notifications, - inbound_number + inbound_number, + agreement, ) diff --git a/app/main/s3_client.py b/app/main/s3_client.py index a3cd1a691..cbcbb0357 100644 --- a/app/main/s3_client.py +++ b/app/main/s3_client.py @@ -61,6 +61,26 @@ def s3download(service_id, upload_id): return contents +def get_mou(organisation_is_crown): + bucket = current_app.config['MOU_BUCKET_NAME'] + filename = 'crown.pdf' if organisation_is_crown else 'non-crown.pdf' + attachment_filename = 'GOV.UK Notify data sharing and financial agreement{}.pdf'.format( + '' if organisation_is_crown else ' (non-crown)' + ) + try: + key = get_s3_object(bucket, filename) + return { + 'filename_or_fp': key.get()['Body'], + 'attachment_filename': attachment_filename, + 'as_attachment': True, + } + except botocore.exceptions.ClientError as exception: + current_app.logger.error("Unable to download s3 file {}/{}".format( + bucket, filename + )) + raise exception + + def upload_logo(filename, filedata, region, user_id): upload_file_name = LOGO_LOCATION_STRUCTURE.format( temp=TEMP_TAG.format(user_id=user_id), diff --git a/app/main/views/agreement.py b/app/main/views/agreement.py new file mode 100644 index 000000000..9985bd032 --- /dev/null +++ b/app/main/views/agreement.py @@ -0,0 +1,26 @@ +from flask import render_template, send_file +from flask_login import login_required + +from app.main import main +from app.main.views.sub_navigation_dictionaries import features_nav +from app.main.s3_client import get_mou +from app.utils import AgreementInfo + + + +@main.route('/agreement') +@login_required +def agreement(): + return render_template( + 'views/agreement.html', + crown_status=AgreementInfo.from_current_user().crown_status_or_404, + navigation_links=features_nav(), + ) + + +@main.route('/agreement.pdf') +@login_required +def download_agreement(): + return send_file(**get_mou( + AgreementInfo.from_current_user().crown_status_or_404 + )) diff --git a/app/templates/views/agreement.html b/app/templates/views/agreement.html new file mode 100644 index 000000000..9a1c0e5af --- /dev/null +++ b/app/templates/views/agreement.html @@ -0,0 +1,27 @@ +{% extends "withoutnav_template.html" %} +{% from "components/sub-navigation.html" import sub_navigation %} + +{% block per_page_title %} + GOV.UK Notify data sharing and financial agreement +{% endblock %} + +{% block maincolumn_content %} + +
+
+ {{ sub_navigation(navigation_links) }} +
+
+ +

+ GOV.UK Notify data sharing and financial agreement +

+ +

+ Download. +

+ +
+
+ +{% endblock %} diff --git a/app/utils.py b/app/utils.py index 75a5a2138..5466416ef 100644 --- a/app/utils.py +++ b/app/utils.py @@ -464,6 +464,12 @@ class AgreementInfo: else: return 'Can’t tell' + @property + def crown_status_or_404(self): + if self.crown_status is None: + abort(404) + return self.crown_status + def as_request_for_agreement(self, with_owner=False): if with_owner and self.owner: return ( diff --git a/tests/app/main/views/test_agreement.py b/tests/app/main/views/test_agreement.py new file mode 100644 index 000000000..1376e8770 --- /dev/null +++ b/tests/app/main/views/test_agreement.py @@ -0,0 +1,103 @@ +from io import BytesIO + +import pytest +from flask import url_for +from tests.conftest import active_user_with_permissions + + +class _MockS3Object(): + + def __init__(self, data=None): + self.data = data or b'' + + def get(self): + return {'Body': BytesIO(self.data)} + + +@pytest.mark.parametrize('email_address, expected_status', [ + ('test@cabinet-office.gov.uk', 200), + ('test@aylesburytowncouncil.gov.uk', 200), + ('test@unknown.gov.uk', 404), +]) +def test_show_agreement_page( + client_request, + mocker, + fake_uuid, + email_address, + expected_status, +): + user = active_user_with_permissions(fake_uuid) + user.email_address = email_address + mocker.patch('app.user_api_client.get_user', return_value=user) + client_request.get( + 'main.agreement', + _expected_status=expected_status, + ) + + +@pytest.mark.parametrize('email_address, expected_file_fetched, expected_file_served', [ + ( + 'test@cabinet-office.gov.uk', + 'crown.pdf', + 'GOV.UK Notify data sharing and financial agreement.pdf', + ), + ( + 'test@aylesburytowncouncil.gov.uk', + 'non-crown.pdf', + 'GOV.UK Notify data sharing and financial agreement (non-crown).pdf', + ), +]) +def test_downloading_agreement( + logged_in_client, + mocker, + fake_uuid, + email_address, + expected_file_fetched, + expected_file_served, +): + mock_get_s3_object = mocker.patch( + 'app.main.s3_client.get_s3_object', + return_value=_MockS3Object(b'foo') + ) + user = active_user_with_permissions(fake_uuid) + user.email_address = email_address + mocker.patch('app.user_api_client.get_user', return_value=user) + response = logged_in_client.get(url_for('main.download_agreement')) + assert response.status_code == 200 + assert response.get_data() == b'foo' + assert response.headers['Content-Type'] == 'application/pdf' + assert response.headers['Content-Disposition'] == ( + 'attachment; filename="{}"'.format(expected_file_served) + ) + mock_get_s3_object.assert_called_once_with('test-mou', expected_file_fetched) + + +def test_agreement_cant_be_downloaded_unknown_crown_status( + logged_in_client, + mocker, + fake_uuid, +): + mock_get_s3_object = mocker.patch( + 'app.main.s3_client.get_s3_object', + return_value=_MockS3Object() + ) + user = active_user_with_permissions(fake_uuid) + user.email_address = 'test@unknown.gov.uk' + mocker.patch('app.user_api_client.get_user', return_value=user) + response = logged_in_client.get(url_for('main.download_agreement')) + assert response.status_code == 404 + assert mock_get_s3_object.call_args_list == [] + + +def test_agreement_requires_login( + client, + mocker, +): + mock_get_s3_object = mocker.patch( + 'app.main.s3_client.get_s3_object', + return_value=_MockS3Object() + ) + response = client.get(url_for('main.download_agreement')) + assert response.status_code == 302 + assert response.location == 'http://localhost/sign-in?next=%2Fagreement.pdf' + assert mock_get_s3_object.call_args_list == []