From d94d0bc8d746a723c15a1bb7f5085c63e8354f8d Mon Sep 17 00:00:00 2001 From: Leo Hemsted Date: Fri, 5 Mar 2021 20:13:56 +0000 Subject: [PATCH 1/5] rename invite blueprints nb: the routes are not changing as part of this, only file paths and blueprint names. invite -> service_invite this blueprint handles fetching invites for a service, creating invites, etc. accept_invite -> global_invite this blueprint handles accepting invites for now, but will also involve retrieving service/org user invite data without knowing the service/org id associated. i'm not in love with this name and open to suggestions, but i wanted to contrast it from service_invite and organisation/invite_rest.py. --- app/__init__.py | 14 ++++++++------ app/commands.py | 2 +- app/{accept_invite => global_invite}/__init__.py | 0 app/{accept_invite => global_invite}/rest.py | 6 +++--- app/{invite => service_invite}/__init__.py | 0 app/{invite => service_invite}/rest.py | 12 ++++++------ .../{accept_invite => global_invite}/__init__.py | 0 .../test_global_invite_rest.py} | 0 tests/app/{invite => service_invite}/__init__.py | 0 .../test_service_invite_rest.py} | 10 +++++----- 10 files changed, 23 insertions(+), 21 deletions(-) rename app/{accept_invite => global_invite}/__init__.py (100%) rename app/{accept_invite => global_invite}/rest.py (90%) rename app/{invite => service_invite}/__init__.py (100%) rename app/{invite => service_invite}/rest.py (90%) rename tests/app/{accept_invite => global_invite}/__init__.py (100%) rename tests/app/{accept_invite/test_accept_invite_rest.py => global_invite/test_global_invite_rest.py} (100%) rename tests/app/{invite => service_invite}/__init__.py (100%) rename tests/app/{invite/test_invite_rest.py => service_invite/test_service_invite_rest.py} (97%) diff --git a/app/__init__.py b/app/__init__.py index 54f92a655..3c94d0581 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -136,7 +136,6 @@ def create_app(application): def register_blueprint(application): - from app.accept_invite.rest import accept_invite from app.authentication.auth import ( requires_admin_auth, requires_auth, @@ -147,9 +146,9 @@ def register_blueprint(application): from app.complaint.complaint_rest import complaint_blueprint from app.email_branding.rest import email_branding_blueprint from app.events.rest import events as events_blueprint + from app.global_invite.rest import global_invite_blueprint from app.inbound_number.rest import inbound_number_blueprint from app.inbound_sms.rest import inbound_sms as inbound_sms_blueprint - from app.invite.rest import invite as invite_blueprint from app.job.rest import job_blueprint from app.letter_branding.letter_branding_rest import ( letter_branding_blueprint, @@ -174,6 +173,9 @@ def register_blueprint(application): ) from app.service.callback_rest import service_callback_blueprint from app.service.rest import service_blueprint + from app.service_invite.rest import ( + service_invite as service_invite_blueprint, + ) from app.status.healthcheck import status as status_blueprint from app.template.rest import template_blueprint from app.template_folder.rest import template_folder_blueprint @@ -210,8 +212,8 @@ def register_blueprint(application): job_blueprint.before_request(requires_admin_auth) application.register_blueprint(job_blueprint) - invite_blueprint.before_request(requires_admin_auth) - application.register_blueprint(invite_blueprint) + service_invite_blueprint.before_request(requires_admin_auth) + application.register_blueprint(service_invite_blueprint) inbound_number_blueprint.before_request(requires_admin_auth) application.register_blueprint(inbound_number_blueprint) @@ -219,8 +221,8 @@ def register_blueprint(application): inbound_sms_blueprint.before_request(requires_admin_auth) application.register_blueprint(inbound_sms_blueprint) - accept_invite.before_request(requires_admin_auth) - application.register_blueprint(accept_invite, url_prefix='/invite') + global_invite_blueprint.before_request(requires_admin_auth) + application.register_blueprint(global_invite_blueprint, url_prefix='/invite') template_statistics_blueprint.before_request(requires_admin_auth) application.register_blueprint(template_statistics_blueprint) diff --git a/app/commands.py b/app/commands.py index b8e680083..a73f7aed9 100644 --- a/app/commands.py +++ b/app/commands.py @@ -570,7 +570,7 @@ def bulk_invite_user_to_service(file_name, service_id, user_id, auth_type, permi # platform_admin # view_activity # "send_texts,send_emails,send_letters,view_activity" - from app.invite.rest import create_invited_user + from app.service_invite.rest import create_invited_user file = open(file_name) for email_address in file: data = { diff --git a/app/accept_invite/__init__.py b/app/global_invite/__init__.py similarity index 100% rename from app/accept_invite/__init__.py rename to app/global_invite/__init__.py diff --git a/app/accept_invite/rest.py b/app/global_invite/rest.py similarity index 90% rename from app/accept_invite/rest.py rename to app/global_invite/rest.py index 5bb34c3fe..f205f93fc 100644 --- a/app/accept_invite/rest.py +++ b/app/global_invite/rest.py @@ -7,11 +7,11 @@ from app.dao.organisation_dao import dao_get_invited_organisation_user from app.errors import InvalidRequest, register_errors from app.schemas import invited_user_schema -accept_invite = Blueprint('accept_invite', __name__) -register_errors(accept_invite) +global_invite_blueprint = Blueprint('global_invite', __name__) +register_errors(global_invite_blueprint) -@accept_invite.route('//', methods=['GET']) +@global_invite_blueprint.route('//', methods=['GET']) def validate_invitation_token(invitation_type, token): max_age_seconds = 60 * 60 * 24 * current_app.config['INVITATION_EXPIRATION_DAYS'] diff --git a/app/invite/__init__.py b/app/service_invite/__init__.py similarity index 100% rename from app/invite/__init__.py rename to app/service_invite/__init__.py diff --git a/app/invite/rest.py b/app/service_invite/rest.py similarity index 90% rename from app/invite/rest.py rename to app/service_invite/rest.py index a5af73fe0..2bc71cf57 100644 --- a/app/invite/rest.py +++ b/app/service_invite/rest.py @@ -15,12 +15,12 @@ from app.notifications.process_notifications import ( ) from app.schemas import invited_user_schema -invite = Blueprint('invite', __name__, url_prefix='/service//invite') +service_invite = Blueprint('service_invite', __name__, url_prefix='/service//invite') -register_errors(invite) +register_errors(service_invite) -@invite.route('', methods=['POST']) +@service_invite.route('', methods=['POST']) def create_invited_user(service_id): request_json = request.get_json() invited_user, errors = invited_user_schema.load(request_json) @@ -58,19 +58,19 @@ def create_invited_user(service_id): return jsonify(data=invited_user_schema.dump(invited_user).data), 201 -@invite.route('', methods=['GET']) +@service_invite.route('', methods=['GET']) def get_invited_users_by_service(service_id): invited_users = get_invited_users_for_service(service_id) return jsonify(data=invited_user_schema.dump(invited_users, many=True).data), 200 -@invite.route('/', methods=['GET']) +@service_invite.route('/', methods=['GET']) def get_invited_user_by_service(service_id, invited_user_id): invited_user = get_invited_user(service_id, invited_user_id) return jsonify(data=invited_user_schema.dump(invited_user).data), 200 -@invite.route('/', methods=['POST']) +@service_invite.route('/', methods=['POST']) def update_invited_user(service_id, invited_user_id): fetched = get_invited_user(service_id=service_id, invited_user_id=invited_user_id) diff --git a/tests/app/accept_invite/__init__.py b/tests/app/global_invite/__init__.py similarity index 100% rename from tests/app/accept_invite/__init__.py rename to tests/app/global_invite/__init__.py diff --git a/tests/app/accept_invite/test_accept_invite_rest.py b/tests/app/global_invite/test_global_invite_rest.py similarity index 100% rename from tests/app/accept_invite/test_accept_invite_rest.py rename to tests/app/global_invite/test_global_invite_rest.py diff --git a/tests/app/invite/__init__.py b/tests/app/service_invite/__init__.py similarity index 100% rename from tests/app/invite/__init__.py rename to tests/app/service_invite/__init__.py diff --git a/tests/app/invite/test_invite_rest.py b/tests/app/service_invite/test_service_invite_rest.py similarity index 97% rename from tests/app/invite/test_invite_rest.py rename to tests/app/service_invite/test_service_invite_rest.py index 58ff4a41a..4d69f879b 100644 --- a/tests/app/invite/test_invite_rest.py +++ b/tests/app/service_invite/test_service_invite_rest.py @@ -41,7 +41,7 @@ def test_create_invited_user( ) json_resp = admin_request.post( - 'invite.create_invited_user', + 'service_invite.create_invited_user', service_id=sample_service.id, _data=data, _expected_status=201 @@ -102,7 +102,7 @@ def test_invited_user_for_broadcast_service_receives_broadcast_invite_email( ) admin_request.post( - 'invite.create_invited_user', + 'service_invite.create_invited_user', service_id=sample_broadcast_service.id, _data=data, _expected_status=201 @@ -136,7 +136,7 @@ def test_create_invited_user_without_auth_type(admin_request, sample_service, mo } json_resp = admin_request.post( - 'invite.create_invited_user', + 'service_invite.create_invited_user', service_id=sample_service.id, _data=data, _expected_status=201 @@ -218,7 +218,7 @@ def test_get_invited_users_by_service_with_no_invites(client, notify_db, notify_ def test_get_invited_user_by_service(admin_request, sample_invited_user): json_resp = admin_request.get( - 'invite.get_invited_user_by_service', + 'service_invite.get_invited_user_by_service', service_id=sample_invited_user.service.id, invited_user_id=sample_invited_user.id ) @@ -231,7 +231,7 @@ def test_get_invited_user_by_service_when_user_does_not_belong_to_the_service( fake_uuid, ): json_resp = admin_request.get( - 'invite.get_invited_user_by_service', + 'service_invite.get_invited_user_by_service', service_id=fake_uuid, invited_user_id=sample_invited_user.id, _expected_status=404 From 4471189b877d2d5a1fd264acef9a982405bbc466 Mon Sep 17 00:00:00 2001 From: Leo Hemsted Date: Fri, 5 Mar 2021 20:54:25 +0000 Subject: [PATCH 2/5] add endpoints to fetch a user based on their ID only this functions the same as `validate_invitation_token`, but without having the signed token, instead just the ID. This is so later endpoints within the invite flow can also fetch the invited user --- app/global_invite/rest.py | 12 ++++++ .../global_invite/test_global_invite_rest.py | 39 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/app/global_invite/rest.py b/app/global_invite/rest.py index f205f93fc..7727c44f3 100644 --- a/app/global_invite/rest.py +++ b/app/global_invite/rest.py @@ -38,3 +38,15 @@ def validate_invitation_token(invitation_type, token): return jsonify(data=invited_user.serialize()), 200 else: raise InvalidRequest("Unrecognised invitation type: {}".format(invitation_type)) + + +@global_invite_blueprint.route('/service/', methods=['GET']) +def get_invited_user(invited_user_id): + invited_user = get_invited_user_by_id(invited_user_id) + return jsonify(data=invited_user_schema.dump(invited_user).data), 200 + + +@global_invite_blueprint.route('/organisation/', methods=['GET']) +def get_invited_org_user(invited_org_user_id): + invited_user = dao_get_invited_organisation_user(invited_org_user_id) + return jsonify(data=invited_user.serialize()), 200 diff --git a/tests/app/global_invite/test_global_invite_rest.py b/tests/app/global_invite/test_global_invite_rest.py index 98c3887f6..41f125f38 100644 --- a/tests/app/global_invite/test_global_invite_rest.py +++ b/tests/app/global_invite/test_global_invite_rest.py @@ -83,3 +83,42 @@ def test_validate_invitation_token_returns_400_when_token_is_malformed(client, i assert json_resp['message'] == { 'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.' } + + +def test_get_invited_user(admin_request, sample_invited_user): + json_resp = admin_request.get( + 'global_invite.get_invited_user', + invited_user_id=sample_invited_user.id + ) + assert json_resp['data']['id'] == str(sample_invited_user.id) + assert json_resp['data']['email_address'] == sample_invited_user.email_address + assert json_resp['data']['service'] == str(sample_invited_user.service_id) + assert json_resp['data']['permissions'] == sample_invited_user.permissions + + +def test_get_invited_user_404s_if_invite_doesnt_exist(admin_request, sample_invited_user, fake_uuid): + json_resp = admin_request.get( + 'global_invite.get_invited_user', + invited_user_id=fake_uuid, + _expected_status=404 + ) + assert json_resp['result'] == 'error' + + +def test_get_invited_org_user(admin_request, sample_invited_org_user): + json_resp = admin_request.get( + 'global_invite.get_invited_org_user', + invited_org_user_id=sample_invited_org_user.id + ) + assert json_resp['data']['id'] == str(sample_invited_org_user.id) + assert json_resp['data']['email_address'] == sample_invited_org_user.email_address + assert json_resp['data']['organisation'] == str(sample_invited_org_user.organisation_id) + + +def test_get_invited_org_user_404s_if_invite_doesnt_exist(admin_request, sample_invited_org_user, fake_uuid): + json_resp = admin_request.get( + 'global_invite.get_invited_org_user', + invited_org_user_id=fake_uuid, + _expected_status=404 + ) + assert json_resp['result'] == 'error' From eab4a75e29a2cf7d96d5c2143d9d294d106e0b09 Mon Sep 17 00:00:00 2001 From: Leo Hemsted Date: Thu, 11 Mar 2021 20:26:44 +0000 Subject: [PATCH 3/5] remove the global_invite (accept_invite) endpoint move the endpoints into service_invite/rest.py and organisation/invite_rest.py respectively. Remove the prefix from all the blueprints to allow this. --- app/__init__.py | 10 +- app/global_invite/__init__.py | 0 app/global_invite/rest.py | 52 -------- app/organisation/invite_rest.py | 61 +++++++-- app/service_invite/rest.py | 51 +++++-- tests/app/global_invite/__init__.py | 0 .../global_invite/test_global_invite_rest.py | 124 ------------------ tests/app/organisation/test_invite_rest.py | 85 ++++++++++++ .../test_service_invite_rest.py | 89 +++++++++++++ 9 files changed, 267 insertions(+), 205 deletions(-) delete mode 100644 app/global_invite/__init__.py delete mode 100644 app/global_invite/rest.py delete mode 100644 tests/app/global_invite/__init__.py delete mode 100644 tests/app/global_invite/test_global_invite_rest.py diff --git a/app/__init__.py b/app/__init__.py index 3c94d0581..3752cfba4 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -146,7 +146,6 @@ def register_blueprint(application): from app.complaint.complaint_rest import complaint_blueprint from app.email_branding.rest import email_branding_blueprint from app.events.rest import events as events_blueprint - from app.global_invite.rest import global_invite_blueprint from app.inbound_number.rest import inbound_number_blueprint from app.inbound_sms.rest import inbound_sms as inbound_sms_blueprint from app.job.rest import job_blueprint @@ -215,15 +214,15 @@ def register_blueprint(application): service_invite_blueprint.before_request(requires_admin_auth) application.register_blueprint(service_invite_blueprint) + organisation_invite_blueprint.before_request(requires_admin_auth) + application.register_blueprint(organisation_invite_blueprint) + inbound_number_blueprint.before_request(requires_admin_auth) application.register_blueprint(inbound_number_blueprint) inbound_sms_blueprint.before_request(requires_admin_auth) application.register_blueprint(inbound_sms_blueprint) - global_invite_blueprint.before_request(requires_admin_auth) - application.register_blueprint(global_invite_blueprint, url_prefix='/invite') - template_statistics_blueprint.before_request(requires_admin_auth) application.register_blueprint(template_statistics_blueprint) @@ -251,9 +250,6 @@ def register_blueprint(application): organisation_blueprint.before_request(requires_admin_auth) application.register_blueprint(organisation_blueprint, url_prefix='/organisations') - organisation_invite_blueprint.before_request(requires_admin_auth) - application.register_blueprint(organisation_invite_blueprint) - complaint_blueprint.before_request(requires_admin_auth) application.register_blueprint(complaint_blueprint) diff --git a/app/global_invite/__init__.py b/app/global_invite/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/global_invite/rest.py b/app/global_invite/rest.py deleted file mode 100644 index 7727c44f3..000000000 --- a/app/global_invite/rest.py +++ /dev/null @@ -1,52 +0,0 @@ -from flask import Blueprint, current_app, jsonify -from itsdangerous import BadData, SignatureExpired -from notifications_utils.url_safe_token import check_token - -from app.dao.invited_user_dao import get_invited_user_by_id -from app.dao.organisation_dao import dao_get_invited_organisation_user -from app.errors import InvalidRequest, register_errors -from app.schemas import invited_user_schema - -global_invite_blueprint = Blueprint('global_invite', __name__) -register_errors(global_invite_blueprint) - - -@global_invite_blueprint.route('//', methods=['GET']) -def validate_invitation_token(invitation_type, token): - - max_age_seconds = 60 * 60 * 24 * current_app.config['INVITATION_EXPIRATION_DAYS'] - - try: - invited_user_id = check_token(token, - current_app.config['SECRET_KEY'], - current_app.config['DANGEROUS_SALT'], - max_age_seconds) - except SignatureExpired: - errors = {'invitation': - 'Your invitation to GOV.UK Notify has expired. ' - 'Please ask the person that invited you to send you another one'} - raise InvalidRequest(errors, status_code=400) - except BadData: - errors = {'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.'} - raise InvalidRequest(errors, status_code=400) - - if invitation_type == 'service': - invited_user = get_invited_user_by_id(invited_user_id) - return jsonify(data=invited_user_schema.dump(invited_user).data), 200 - elif invitation_type == 'organisation': - invited_user = dao_get_invited_organisation_user(invited_user_id) - return jsonify(data=invited_user.serialize()), 200 - else: - raise InvalidRequest("Unrecognised invitation type: {}".format(invitation_type)) - - -@global_invite_blueprint.route('/service/', methods=['GET']) -def get_invited_user(invited_user_id): - invited_user = get_invited_user_by_id(invited_user_id) - return jsonify(data=invited_user_schema.dump(invited_user).data), 200 - - -@global_invite_blueprint.route('/organisation/', methods=['GET']) -def get_invited_org_user(invited_org_user_id): - invited_user = dao_get_invited_organisation_user(invited_org_user_id) - return jsonify(data=invited_user.serialize()), 200 diff --git a/app/organisation/invite_rest.py b/app/organisation/invite_rest.py index d6454643d..0e30ab6c8 100644 --- a/app/organisation/invite_rest.py +++ b/app/organisation/invite_rest.py @@ -1,14 +1,18 @@ from flask import Blueprint, current_app, jsonify, request -from notifications_utils.url_safe_token import generate_token +from itsdangerous import BadData, SignatureExpired +from notifications_utils.url_safe_token import check_token, generate_token from app.config import QueueNames from app.dao.invited_org_user_dao import ( - get_invited_org_user, + get_invited_org_user as dao_get_invited_org_user, +) +from app.dao.invited_org_user_dao import ( get_invited_org_users_for_organisation, save_invited_org_user, ) +from app.dao.organisation_dao import dao_get_invited_organisation_user from app.dao.templates_dao import dao_get_template_by_id -from app.errors import register_errors +from app.errors import InvalidRequest, register_errors from app.models import EMAIL_TYPE, KEY_TYPE_NORMAL, InvitedOrganisationUser from app.notifications.process_notifications import ( persist_notification, @@ -20,14 +24,12 @@ from app.organisation.organisation_schema import ( ) from app.schema_validation import validate -organisation_invite_blueprint = Blueprint( - 'organisation_invite', __name__, - url_prefix='/organisation//invite') +organisation_invite_blueprint = Blueprint('organisation_invite', __name__) register_errors(organisation_invite_blueprint) -@organisation_invite_blueprint.route('', methods=['POST']) +@organisation_invite_blueprint.route('/organisation//invite', methods=['POST']) def invite_user_to_org(organisation_id): data = request.get_json() validate(data, post_create_invited_org_user_status_schema) @@ -69,21 +71,27 @@ def invite_user_to_org(organisation_id): return jsonify(data=invited_org_user.serialize()), 201 -@organisation_invite_blueprint.route('', methods=['GET']) +@organisation_invite_blueprint.route('/organisation//invite', methods=['GET']) def get_invited_org_users_by_organisation(organisation_id): invited_org_users = get_invited_org_users_for_organisation(organisation_id) return jsonify(data=[x.serialize() for x in invited_org_users]), 200 -@organisation_invite_blueprint.route('/', methods=['GET']) +@organisation_invite_blueprint.route( + '/organisation//invite/', + methods=['GET'] +) def get_invited_org_user_by_organisation(organisation_id, invited_org_user_id): - invited_org_user = get_invited_org_user(organisation_id, invited_org_user_id) + invited_org_user = dao_get_invited_org_user(organisation_id, invited_org_user_id) return jsonify(data=invited_org_user.serialize()), 200 -@organisation_invite_blueprint.route('/', methods=['POST']) +@organisation_invite_blueprint.route( + '/organisation//invite/', + methods=['POST'] +) def update_org_invite_status(organisation_id, invited_org_user_id): - fetched = get_invited_org_user(organisation_id=organisation_id, invited_org_user_id=invited_org_user_id) + fetched = dao_get_invited_org_user(organisation_id=organisation_id, invited_org_user_id=invited_org_user_id) data = request.get_json() validate(data, post_update_invited_org_user_status_schema) @@ -105,3 +113,32 @@ def invited_org_user_url(invited_org_user_id, invite_link_host=None): invite_link_host = current_app.config['ADMIN_BASE_URL'] return '{0}/organisation-invitation/{1}'.format(invite_link_host, token) + + +@organisation_invite_blueprint.route('/invite/organisation/', methods=['GET']) +def get_invited_org_user(invited_org_user_id): + invited_user = dao_get_invited_organisation_user(invited_org_user_id) + return jsonify(data=invited_user.serialize()), 200 + + +@organisation_invite_blueprint.route('/invite/organisation/', methods=['GET']) +def validate_invitation_token(token): + + max_age_seconds = 60 * 60 * 24 * current_app.config['INVITATION_EXPIRATION_DAYS'] + + try: + invited_user_id = check_token(token, + current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT'], + max_age_seconds) + except SignatureExpired: + errors = {'invitation': + 'Your invitation to GOV.UK Notify has expired. ' + 'Please ask the person that invited you to send you another one'} + raise InvalidRequest(errors, status_code=400) + except BadData: + errors = {'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.'} + raise InvalidRequest(errors, status_code=400) + + invited_user = dao_get_invited_organisation_user(invited_user_id) + return jsonify(data=invited_user.serialize()), 200 diff --git a/app/service_invite/rest.py b/app/service_invite/rest.py index 2bc71cf57..f29f62344 100644 --- a/app/service_invite/rest.py +++ b/app/service_invite/rest.py @@ -1,13 +1,16 @@ from flask import Blueprint, current_app, jsonify, request +from itsdangerous import BadData, SignatureExpired +from notifications_utils.url_safe_token import check_token, generate_token from app.config import QueueNames +from app.dao.invited_user_dao import get_invited_user as dao_get_invited_user from app.dao.invited_user_dao import ( - get_invited_user, + get_invited_user_by_id, get_invited_users_for_service, save_invited_user, ) from app.dao.templates_dao import dao_get_template_by_id -from app.errors import register_errors +from app.errors import InvalidRequest, register_errors from app.models import BROADCAST_TYPE, EMAIL_TYPE, KEY_TYPE_NORMAL, Service from app.notifications.process_notifications import ( persist_notification, @@ -15,12 +18,12 @@ from app.notifications.process_notifications import ( ) from app.schemas import invited_user_schema -service_invite = Blueprint('service_invite', __name__, url_prefix='/service//invite') +service_invite = Blueprint('service_invite', __name__) register_errors(service_invite) -@service_invite.route('', methods=['POST']) +@service_invite.route('/service//invite', methods=['POST']) def create_invited_user(service_id): request_json = request.get_json() invited_user, errors = invited_user_schema.load(request_json) @@ -58,21 +61,21 @@ def create_invited_user(service_id): return jsonify(data=invited_user_schema.dump(invited_user).data), 201 -@service_invite.route('', methods=['GET']) +@service_invite.route('/service//invite', methods=['GET']) def get_invited_users_by_service(service_id): invited_users = get_invited_users_for_service(service_id) return jsonify(data=invited_user_schema.dump(invited_users, many=True).data), 200 -@service_invite.route('/', methods=['GET']) +@service_invite.route('/service//invite/', methods=['GET']) def get_invited_user_by_service(service_id, invited_user_id): - invited_user = get_invited_user(service_id, invited_user_id) + invited_user = dao_get_invited_user(service_id, invited_user_id) return jsonify(data=invited_user_schema.dump(invited_user).data), 200 -@service_invite.route('/', methods=['POST']) +@service_invite.route('/service//invite/', methods=['POST']) def update_invited_user(service_id, invited_user_id): - fetched = get_invited_user(service_id=service_id, invited_user_id=invited_user_id) + fetched = dao_get_invited_user(service_id=service_id, invited_user_id=invited_user_id) current_data = dict(invited_user_schema.dump(fetched).data.items()) current_data.update(request.get_json()) @@ -82,10 +85,38 @@ def update_invited_user(service_id, invited_user_id): def invited_user_url(invited_user_id, invite_link_host=None): - from notifications_utils.url_safe_token import generate_token token = generate_token(str(invited_user_id), current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT']) if invite_link_host is None: invite_link_host = current_app.config['ADMIN_BASE_URL'] return '{0}/invitation/{1}'.format(invite_link_host, token) + + +@service_invite.route('/invite/service/', methods=['GET']) +def get_invited_user(invited_user_id): + invited_user = get_invited_user_by_id(invited_user_id) + return jsonify(data=invited_user_schema.dump(invited_user).data), 200 + + +@service_invite.route('/invite/service/', methods=['GET']) +def validate_service_invitation_token(token): + + max_age_seconds = 60 * 60 * 24 * current_app.config['INVITATION_EXPIRATION_DAYS'] + + try: + invited_user_id = check_token(token, + current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT'], + max_age_seconds) + except SignatureExpired: + errors = {'invitation': + 'Your invitation to GOV.UK Notify has expired. ' + 'Please ask the person that invited you to send you another one'} + raise InvalidRequest(errors, status_code=400) + except BadData: + errors = {'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.'} + raise InvalidRequest(errors, status_code=400) + + invited_user = get_invited_user_by_id(invited_user_id) + return jsonify(data=invited_user_schema.dump(invited_user).data), 200 diff --git a/tests/app/global_invite/__init__.py b/tests/app/global_invite/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/app/global_invite/test_global_invite_rest.py b/tests/app/global_invite/test_global_invite_rest.py deleted file mode 100644 index 41f125f38..000000000 --- a/tests/app/global_invite/test_global_invite_rest.py +++ /dev/null @@ -1,124 +0,0 @@ -import uuid - -import pytest -from flask import current_app, json -from freezegun import freeze_time -from notifications_utils.url_safe_token import generate_token - -from tests import create_authorization_header - - -@pytest.mark.parametrize('invitation_type', ['service', 'organisation']) -def test_validate_invitation_token_for_expired_token_returns_400(client, invitation_type): - with freeze_time('2016-01-01T12:00:00'): - token = generate_token(str(uuid.uuid4()), current_app.config['SECRET_KEY'], - current_app.config['DANGEROUS_SALT']) - url = '/invite/{}/{}'.format(invitation_type, token) - auth_header = create_authorization_header() - response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) - - assert response.status_code == 400 - json_resp = json.loads(response.get_data(as_text=True)) - assert json_resp['result'] == 'error' - assert json_resp['message'] == { - 'invitation': 'Your invitation to GOV.UK Notify has expired. ' - 'Please ask the person that invited you to send you another one'} - - -@pytest.mark.parametrize('invitation_type', ['service', 'organisation']) -def test_validate_invitation_token_returns_200_when_token_valid( - client, invitation_type, sample_invited_user, sample_invited_org_user -): - invited_user = sample_invited_user if invitation_type == 'service' else sample_invited_org_user - - token = generate_token(str(invited_user.id), current_app.config['SECRET_KEY'], - current_app.config['DANGEROUS_SALT']) - url = '/invite/{}/{}'.format(invitation_type, token) - auth_header = create_authorization_header() - response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) - - assert response.status_code == 200 - json_resp = json.loads(response.get_data(as_text=True)) - if invitation_type == 'service': - assert json_resp['data']['id'] == str(sample_invited_user.id) - assert json_resp['data']['email_address'] == sample_invited_user.email_address - assert json_resp['data']['from_user'] == str(sample_invited_user.user_id) - assert json_resp['data']['service'] == str(sample_invited_user.service_id) - assert json_resp['data']['status'] == sample_invited_user.status - assert json_resp['data']['permissions'] == sample_invited_user.permissions - assert json_resp['data']['folder_permissions'] == sample_invited_user.folder_permissions - if invitation_type == 'organisation': - assert json_resp['data'] == sample_invited_org_user.serialize() - - -@pytest.mark.parametrize('invitation_type', ['service', 'organisation']) -def test_validate_invitation_token_returns_400_when_invited_user_does_not_exist(client, invitation_type): - token = generate_token(str(uuid.uuid4()), current_app.config['SECRET_KEY'], - current_app.config['DANGEROUS_SALT']) - url = '/invite/{}/{}'.format(invitation_type, token) - auth_header = create_authorization_header() - response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) - - assert response.status_code == 404 - json_resp = json.loads(response.get_data(as_text=True)) - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'No result found' - - -@pytest.mark.parametrize('invitation_type', ['service', 'organisation']) -def test_validate_invitation_token_returns_400_when_token_is_malformed(client, invitation_type): - token = generate_token( - str(uuid.uuid4()), - current_app.config['SECRET_KEY'], - current_app.config['DANGEROUS_SALT'] - )[:-2] - - url = '/invite/{}/{}'.format(invitation_type, token) - auth_header = create_authorization_header() - response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) - - assert response.status_code == 400 - json_resp = json.loads(response.get_data(as_text=True)) - assert json_resp['result'] == 'error' - assert json_resp['message'] == { - 'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.' - } - - -def test_get_invited_user(admin_request, sample_invited_user): - json_resp = admin_request.get( - 'global_invite.get_invited_user', - invited_user_id=sample_invited_user.id - ) - assert json_resp['data']['id'] == str(sample_invited_user.id) - assert json_resp['data']['email_address'] == sample_invited_user.email_address - assert json_resp['data']['service'] == str(sample_invited_user.service_id) - assert json_resp['data']['permissions'] == sample_invited_user.permissions - - -def test_get_invited_user_404s_if_invite_doesnt_exist(admin_request, sample_invited_user, fake_uuid): - json_resp = admin_request.get( - 'global_invite.get_invited_user', - invited_user_id=fake_uuid, - _expected_status=404 - ) - assert json_resp['result'] == 'error' - - -def test_get_invited_org_user(admin_request, sample_invited_org_user): - json_resp = admin_request.get( - 'global_invite.get_invited_org_user', - invited_org_user_id=sample_invited_org_user.id - ) - assert json_resp['data']['id'] == str(sample_invited_org_user.id) - assert json_resp['data']['email_address'] == sample_invited_org_user.email_address - assert json_resp['data']['organisation'] == str(sample_invited_org_user.organisation_id) - - -def test_get_invited_org_user_404s_if_invite_doesnt_exist(admin_request, sample_invited_org_user, fake_uuid): - json_resp = admin_request.get( - 'global_invite.get_invited_org_user', - invited_org_user_id=fake_uuid, - _expected_status=404 - ) - assert json_resp['result'] == 'error' diff --git a/tests/app/organisation/test_invite_rest.py b/tests/app/organisation/test_invite_rest.py index eae278e0e..0656d1ea8 100644 --- a/tests/app/organisation/test_invite_rest.py +++ b/tests/app/organisation/test_invite_rest.py @@ -1,6 +1,12 @@ +import uuid + import pytest +from flask import current_app, json +from freezegun import freeze_time +from notifications_utils.url_safe_token import generate_token from app.models import INVITE_PENDING, Notification +from tests import create_authorization_header from tests.app.db import create_invited_org_user @@ -175,3 +181,82 @@ def test_update_org_invited_user_for_invalid_data_returns_400(admin_request, sam ) assert len(json_resp['errors']) == 1 assert json_resp['errors'][0]['message'] == 'status garbage is not one of [pending, accepted, cancelled]' + + +def test_validate_invitation_token_returns_200_when_token_valid(client, sample_invited_org_user): + token = generate_token(str(sample_invited_org_user.id), current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT']) + url = '/invite/organisation/{}'.format(token) + auth_header = create_authorization_header() + response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 200 + json_resp = json.loads(response.get_data(as_text=True)) + assert json_resp['data'] == sample_invited_org_user.serialize() + + +def test_validate_invitation_token_for_expired_token_returns_400(client): + with freeze_time('2016-01-01T12:00:00'): + token = generate_token(str(uuid.uuid4()), current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT']) + url = '/invite/organisation/{}'.format(token) + auth_header = create_authorization_header() + response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 400 + json_resp = json.loads(response.get_data(as_text=True)) + assert json_resp['result'] == 'error' + assert json_resp['message'] == { + 'invitation': 'Your invitation to GOV.UK Notify has expired. ' + 'Please ask the person that invited you to send you another one'} + + +def test_validate_invitation_token_returns_400_when_invited_user_does_not_exist(client): + token = generate_token(str(uuid.uuid4()), current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT']) + url = '/invite/organisation/{}'.format(token) + auth_header = create_authorization_header() + response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 404 + json_resp = json.loads(response.get_data(as_text=True)) + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'No result found' + + +def test_validate_invitation_token_returns_400_when_token_is_malformed(client): + token = generate_token( + str(uuid.uuid4()), + current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT'] + )[:-2] + + url = '/invite/organisation/{}'.format(token) + auth_header = create_authorization_header() + response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 400 + json_resp = json.loads(response.get_data(as_text=True)) + assert json_resp['result'] == 'error' + assert json_resp['message'] == { + 'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.' + } + + +def test_get_invited_org_user(admin_request, sample_invited_org_user): + json_resp = admin_request.get( + 'organisation_invite.get_invited_org_user', + invited_org_user_id=sample_invited_org_user.id + ) + assert json_resp['data']['id'] == str(sample_invited_org_user.id) + assert json_resp['data']['email_address'] == sample_invited_org_user.email_address + assert json_resp['data']['organisation'] == str(sample_invited_org_user.organisation_id) + + +def test_get_invited_org_user_404s_if_invite_doesnt_exist(admin_request, sample_invited_org_user, fake_uuid): + json_resp = admin_request.get( + 'organisation_invite.get_invited_org_user', + invited_org_user_id=fake_uuid, + _expected_status=404 + ) + assert json_resp['result'] == 'error' diff --git a/tests/app/service_invite/test_service_invite_rest.py b/tests/app/service_invite/test_service_invite_rest.py index 4d69f879b..cddbc7d31 100644 --- a/tests/app/service_invite/test_service_invite_rest.py +++ b/tests/app/service_invite/test_service_invite_rest.py @@ -1,7 +1,10 @@ import json +import uuid import pytest from flask import current_app +from freezegun import freeze_time +from notifications_utils.url_safe_token import generate_token from app.models import EMAIL_AUTH_TYPE, SMS_AUTH_TYPE, Notification from tests import create_authorization_header @@ -270,3 +273,89 @@ def test_update_invited_user_for_invalid_data_returns_400(client, sample_invited response = client.post(url, data=json.dumps(data), headers=[('Content-Type', 'application/json'), auth_header]) assert response.status_code == 400 + + +def test_validate_invitation_token_returns_200_when_token_valid(client, sample_invited_user): + token = generate_token(str(sample_invited_user.id), current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT']) + url = '/invite/service/{}'.format(token) + auth_header = create_authorization_header() + response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 200 + json_resp = json.loads(response.get_data(as_text=True)) + assert json_resp['data']['id'] == str(sample_invited_user.id) + assert json_resp['data']['email_address'] == sample_invited_user.email_address + assert json_resp['data']['from_user'] == str(sample_invited_user.user_id) + assert json_resp['data']['service'] == str(sample_invited_user.service_id) + assert json_resp['data']['status'] == sample_invited_user.status + assert json_resp['data']['permissions'] == sample_invited_user.permissions + assert json_resp['data']['folder_permissions'] == sample_invited_user.folder_permissions + + +def test_validate_invitation_token_for_expired_token_returns_400(client): + with freeze_time('2016-01-01T12:00:00'): + token = generate_token(str(uuid.uuid4()), current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT']) + url = '/invite/service/{}'.format(token) + auth_header = create_authorization_header() + response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 400 + json_resp = json.loads(response.get_data(as_text=True)) + assert json_resp['result'] == 'error' + assert json_resp['message'] == { + 'invitation': 'Your invitation to GOV.UK Notify has expired. ' + 'Please ask the person that invited you to send you another one'} + + +def test_validate_invitation_token_returns_400_when_invited_user_does_not_exist(client): + token = generate_token(str(uuid.uuid4()), current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT']) + url = '/invite/service/{}'.format(token) + auth_header = create_authorization_header() + response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 404 + json_resp = json.loads(response.get_data(as_text=True)) + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'No result found' + + +def test_validate_invitation_token_returns_400_when_token_is_malformed(client): + token = generate_token( + str(uuid.uuid4()), + current_app.config['SECRET_KEY'], + current_app.config['DANGEROUS_SALT'] + )[:-2] + + url = '/invite/service/{}'.format(token) + auth_header = create_authorization_header() + response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 400 + json_resp = json.loads(response.get_data(as_text=True)) + assert json_resp['result'] == 'error' + assert json_resp['message'] == { + 'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.' + } + + +def test_get_invited_user(admin_request, sample_invited_user): + json_resp = admin_request.get( + 'service_invite.get_invited_user', + invited_user_id=sample_invited_user.id + ) + assert json_resp['data']['id'] == str(sample_invited_user.id) + assert json_resp['data']['email_address'] == sample_invited_user.email_address + assert json_resp['data']['service'] == str(sample_invited_user.service_id) + assert json_resp['data']['permissions'] == sample_invited_user.permissions + + +def test_get_invited_user_404s_if_invite_doesnt_exist(admin_request, sample_invited_user, fake_uuid): + json_resp = admin_request.get( + 'service_invite.get_invited_user', + invited_user_id=fake_uuid, + _expected_status=404 + ) + assert json_resp['result'] == 'error' From 58896e194dfa8105c0cf6b3bd2c6edd21316af72 Mon Sep 17 00:00:00 2001 From: Leo Hemsted Date: Thu, 11 Mar 2021 20:47:24 +0000 Subject: [PATCH 4/5] add new `invite//check/` endpoint having `/invite/service/` and `/invite/service/` as two separate routes (the first to validate an invite token, the second to retrieve invite metadata) technically works. Routes are matched from first to last until a match is found. The metadata endpoint only accepts UUIDs, so requests with a UUID will be picked up by the correct endpoint, while requests that don't look like a UUID will carry on searching for an endpoint, and will find the token validation endpoint. So while this works correctly for our normal expected input, it only does so _because the UUID endpoint is first in the file_. This isn't great, and it makes it harder to reason about the URLs when looking at them. To solve this, create the new `invite/service/check/` endpoint. For backwards compatibility, assign this in parallel with the existing route - once the admin uses the new route we can remove the old route and make better guarantees about what endpoint is being hit. --- app/organisation/invite_rest.py | 1 + app/service_invite/rest.py | 1 + tests/app/organisation/test_invite_rest.py | 9 +++++++-- tests/app/service_invite/test_service_invite_rest.py | 8 ++++++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/organisation/invite_rest.py b/app/organisation/invite_rest.py index 0e30ab6c8..0894ceffc 100644 --- a/app/organisation/invite_rest.py +++ b/app/organisation/invite_rest.py @@ -122,6 +122,7 @@ def get_invited_org_user(invited_org_user_id): @organisation_invite_blueprint.route('/invite/organisation/', methods=['GET']) +@organisation_invite_blueprint.route('/invite/organisation/check/', methods=['GET']) def validate_invitation_token(token): max_age_seconds = 60 * 60 * 24 * current_app.config['INVITATION_EXPIRATION_DAYS'] diff --git a/app/service_invite/rest.py b/app/service_invite/rest.py index f29f62344..00b2ac05f 100644 --- a/app/service_invite/rest.py +++ b/app/service_invite/rest.py @@ -100,6 +100,7 @@ def get_invited_user(invited_user_id): @service_invite.route('/invite/service/', methods=['GET']) +@service_invite.route('/invite/service/check/', methods=['GET']) def validate_service_invitation_token(token): max_age_seconds = 60 * 60 * 24 * current_app.config['INVITATION_EXPIRATION_DAYS'] diff --git a/tests/app/organisation/test_invite_rest.py b/tests/app/organisation/test_invite_rest.py index 0656d1ea8..b057272eb 100644 --- a/tests/app/organisation/test_invite_rest.py +++ b/tests/app/organisation/test_invite_rest.py @@ -183,10 +183,15 @@ def test_update_org_invited_user_for_invalid_data_returns_400(admin_request, sam assert json_resp['errors'][0]['message'] == 'status garbage is not one of [pending, accepted, cancelled]' -def test_validate_invitation_token_returns_200_when_token_valid(client, sample_invited_org_user): +@pytest.mark.parametrize('endpoint_format_str', [ + '/invite/organisation/{}', + '/invite/organisation/check/{}', +]) +def test_validate_invitation_token_returns_200_when_token_valid(client, sample_invited_org_user, endpoint_format_str): token = generate_token(str(sample_invited_org_user.id), current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT']) - url = '/invite/organisation/{}'.format(token) + + url = endpoint_format_str.format(token) auth_header = create_authorization_header() response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) diff --git a/tests/app/service_invite/test_service_invite_rest.py b/tests/app/service_invite/test_service_invite_rest.py index cddbc7d31..f7c939ae3 100644 --- a/tests/app/service_invite/test_service_invite_rest.py +++ b/tests/app/service_invite/test_service_invite_rest.py @@ -275,10 +275,14 @@ def test_update_invited_user_for_invalid_data_returns_400(client, sample_invited assert response.status_code == 400 -def test_validate_invitation_token_returns_200_when_token_valid(client, sample_invited_user): +@pytest.mark.parametrize('endpoint_format_str', [ + '/invite/service/{}', + '/invite/service/check/{}', +]) +def test_validate_invitation_token_returns_200_when_token_valid(client, sample_invited_user, endpoint_format_str): token = generate_token(str(sample_invited_user.id), current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT']) - url = '/invite/service/{}'.format(token) + url = endpoint_format_str.format(token) auth_header = create_authorization_header() response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header]) From ebd4eda8bd9e27974bf48bac2c87358173ffc0ff Mon Sep 17 00:00:00 2001 From: Leo Hemsted Date: Fri, 12 Mar 2021 12:50:07 +0000 Subject: [PATCH 5/5] remove duplicate dao invite fns and improve naming --- app/dao/invited_user_dao.py | 2 +- app/dao/organisation_dao.py | 12 +----------- app/organisation/invite_rest.py | 6 +++--- app/service_invite/rest.py | 6 +++--- tests/app/dao/test_invited_user_dao.py | 6 +++--- tests/app/dao/test_organisation_dao.py | 11 ----------- 6 files changed, 11 insertions(+), 32 deletions(-) diff --git a/app/dao/invited_user_dao.py b/app/dao/invited_user_dao.py index d44cd3343..b78b9b90c 100644 --- a/app/dao/invited_user_dao.py +++ b/app/dao/invited_user_dao.py @@ -9,7 +9,7 @@ def save_invited_user(invited_user): db.session.commit() -def get_invited_user(service_id, invited_user_id): +def get_invited_user_by_service_and_id(service_id, invited_user_id): return InvitedUser.query.filter_by(service_id=service_id, id=invited_user_id).one() diff --git a/app/dao/organisation_dao.py b/app/dao/organisation_dao.py index f97ee779a..877f8af30 100644 --- a/app/dao/organisation_dao.py +++ b/app/dao/organisation_dao.py @@ -2,13 +2,7 @@ from sqlalchemy.sql.expression import func from app import db from app.dao.dao_utils import VersionOptions, transactional, version_class -from app.models import ( - Domain, - InvitedOrganisationUser, - Organisation, - Service, - User, -) +from app.models import Domain, Organisation, Service, User def dao_get_organisations(): @@ -125,10 +119,6 @@ def dao_add_service_to_organisation(service, organisation_id): db.session.add(service) -def dao_get_invited_organisation_user(user_id): - return InvitedOrganisationUser.query.filter_by(id=user_id).one() - - def dao_get_users_for_organisation(organisation_id): return db.session.query( User diff --git a/app/organisation/invite_rest.py b/app/organisation/invite_rest.py index 0894ceffc..153a48ca8 100644 --- a/app/organisation/invite_rest.py +++ b/app/organisation/invite_rest.py @@ -7,10 +7,10 @@ from app.dao.invited_org_user_dao import ( get_invited_org_user as dao_get_invited_org_user, ) from app.dao.invited_org_user_dao import ( + get_invited_org_user_by_id, get_invited_org_users_for_organisation, save_invited_org_user, ) -from app.dao.organisation_dao import dao_get_invited_organisation_user from app.dao.templates_dao import dao_get_template_by_id from app.errors import InvalidRequest, register_errors from app.models import EMAIL_TYPE, KEY_TYPE_NORMAL, InvitedOrganisationUser @@ -117,7 +117,7 @@ def invited_org_user_url(invited_org_user_id, invite_link_host=None): @organisation_invite_blueprint.route('/invite/organisation/', methods=['GET']) def get_invited_org_user(invited_org_user_id): - invited_user = dao_get_invited_organisation_user(invited_org_user_id) + invited_user = get_invited_org_user_by_id(invited_org_user_id) return jsonify(data=invited_user.serialize()), 200 @@ -141,5 +141,5 @@ def validate_invitation_token(token): errors = {'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.'} raise InvalidRequest(errors, status_code=400) - invited_user = dao_get_invited_organisation_user(invited_user_id) + invited_user = get_invited_org_user_by_id(invited_user_id) return jsonify(data=invited_user.serialize()), 200 diff --git a/app/service_invite/rest.py b/app/service_invite/rest.py index 00b2ac05f..2c812d8c6 100644 --- a/app/service_invite/rest.py +++ b/app/service_invite/rest.py @@ -3,9 +3,9 @@ from itsdangerous import BadData, SignatureExpired from notifications_utils.url_safe_token import check_token, generate_token from app.config import QueueNames -from app.dao.invited_user_dao import get_invited_user as dao_get_invited_user from app.dao.invited_user_dao import ( get_invited_user_by_id, + get_invited_user_by_service_and_id, get_invited_users_for_service, save_invited_user, ) @@ -69,13 +69,13 @@ def get_invited_users_by_service(service_id): @service_invite.route('/service//invite/', methods=['GET']) def get_invited_user_by_service(service_id, invited_user_id): - invited_user = dao_get_invited_user(service_id, invited_user_id) + invited_user = get_invited_user_by_service_and_id(service_id, invited_user_id) return jsonify(data=invited_user_schema.dump(invited_user).data), 200 @service_invite.route('/service//invite/', methods=['POST']) def update_invited_user(service_id, invited_user_id): - fetched = dao_get_invited_user(service_id=service_id, invited_user_id=invited_user_id) + fetched = get_invited_user_by_service_and_id(service_id=service_id, invited_user_id=invited_user_id) current_data = dict(invited_user_schema.dump(fetched).data.items()) current_data.update(request.get_json()) diff --git a/tests/app/dao/test_invited_user_dao.py b/tests/app/dao/test_invited_user_dao.py index e0abd39c5..1711f4595 100644 --- a/tests/app/dao/test_invited_user_dao.py +++ b/tests/app/dao/test_invited_user_dao.py @@ -7,8 +7,8 @@ from sqlalchemy.orm.exc import NoResultFound from app import db from app.dao.invited_user_dao import ( delete_invitations_created_more_than_two_days_ago, - get_invited_user, get_invited_user_by_id, + get_invited_user_by_service_and_id, get_invited_users_for_service, save_invited_user, ) @@ -65,7 +65,7 @@ def test_create_invited_user_sets_default_folder_permissions_of_empty_list( def test_get_invited_user_by_service_and_id(notify_db, notify_db_session, sample_invited_user): - from_db = get_invited_user(sample_invited_user.service.id, sample_invited_user.id) + from_db = get_invited_user_by_service_and_id(sample_invited_user.service.id, sample_invited_user.id) assert from_db == sample_invited_user @@ -78,7 +78,7 @@ def test_get_unknown_invited_user_returns_none(notify_db, notify_db_session, sam unknown_id = uuid.uuid4() with pytest.raises(NoResultFound) as e: - get_invited_user(sample_service.id, unknown_id) + get_invited_user_by_service_and_id(sample_service.id, unknown_id) assert 'No row was found for one()' in str(e.value) diff --git a/tests/app/dao/test_organisation_dao.py b/tests/app/dao/test_organisation_dao.py index 95dc6b8d6..91136de8a 100644 --- a/tests/app/dao/test_organisation_dao.py +++ b/tests/app/dao/test_organisation_dao.py @@ -8,7 +8,6 @@ from app import db from app.dao.organisation_dao import ( dao_add_service_to_organisation, dao_add_user_to_organisation, - dao_get_invited_organisation_user, dao_get_organisation_by_email_address, dao_get_organisation_by_id, dao_get_organisation_by_service_id, @@ -275,16 +274,6 @@ def test_get_organisation_by_service_id(sample_service, sample_organisation): assert organisation_2 == another_org -def test_dao_get_invited_organisation_user(sample_invited_org_user): - invited_org_user = dao_get_invited_organisation_user(sample_invited_org_user.id) - assert invited_org_user == sample_invited_org_user - - -def test_dao_get_invited_organisation_user_returns_none(notify_db): - with pytest.raises(expected_exception=SQLAlchemyError): - dao_get_invited_organisation_user(uuid.uuid4()) - - def test_dao_get_users_for_organisation(sample_organisation): first = create_user(email='first@invited.com') second = create_user(email='another@invited.com')