diff --git a/app/invite/rest.py b/app/invite/rest.py index 9190ad252..1b903db8b 100644 --- a/app/invite/rest.py +++ b/app/invite/rest.py @@ -1,20 +1,20 @@ -from datetime import timedelta - +import uuid +from datetime import datetime from flask import ( Blueprint, request, jsonify, current_app) -from app import encryption +from app import encryption, DATETIME_FORMAT from app.dao.invited_user_dao import ( save_invited_user, get_invited_user, get_invited_users_for_service ) - +from app.dao.templates_dao import dao_get_template_by_id from app.schemas import invited_user_schema -from app.celery.tasks import (email_invited_user) +from app.celery.tasks import (send_email) invite = Blueprint('invite', __name__, url_prefix='/service//invite') @@ -27,9 +27,25 @@ register_errors(invite) def create_invited_user(service_id): invited_user, errors = invited_user_schema.load(request.get_json()) save_invited_user(invited_user) - invitation = _create_invitation(invited_user) - encrypted_invitation = encryption.encrypt(invitation) - email_invited_user.apply_async([encrypted_invitation], queue='email-invited-user') + + template = dao_get_template_by_id(current_app.config['INVITATION_EMAIL_TEMPLATE_ID']) + message = { + 'template': str(template.id), + 'template_version': template.version, + 'to': invited_user.email_address, + 'personalisation': { + 'user_name': invited_user.from_user.name, + 'service_name': invited_user.service.name, + 'url': invited_user_url(invited_user.id) + } + } + send_email.apply_async(( + current_app.config['NOTIFY_SERVICE_ID'], + str(uuid.uuid4()), + encryption.encrypt(message), + datetime.utcnow().strftime(DATETIME_FORMAT) + ), queue="email-invited-user") + return jsonify(data=invited_user_schema.dump(invited_user).data), 201 @@ -57,19 +73,8 @@ def update_invited_user(service_id, invited_user_id): return jsonify(data=invited_user_schema.dump(fetched).data), 200 -def _create_invitation(invited_user): +def invited_user_url(invited_user_id): 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']) - # TODO: confirm what we want to do for this - the idea is that we say expires tomorrow at midnight - # and give 48 hours as the max_age - expiration_date = (invited_user.created_at + timedelta(days=current_app.config['INVITATION_EXPIRATION_DAYS'])) \ - .replace(hour=0, minute=0, second=0, microsecond=0) + token = generate_token(str(invited_user_id), current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT']) - invitation = {'to': invited_user.email_address, - 'user_name': invited_user.from_user.name, - 'service_id': str(invited_user.service_id), - 'service_name': invited_user.service.name, - 'token': token, - 'expiry_date': str(expiration_date) - } - return invitation + return '{0}/invitation/{1}'.format(current_app.config['ADMIN_BASE_URL'], token) diff --git a/app/user/rest.py b/app/user/rest.py index 360720051..66abee32a 100644 --- a/app/user/rest.py +++ b/app/user/rest.py @@ -27,7 +27,6 @@ from app.schemas import ( from app.celery.tasks import ( send_sms, - email_reset_password, send_email ) @@ -151,8 +150,6 @@ def send_user_sms_code(user_id): @user.route('//email-verification', methods=['POST']) def send_user_email_verification(user_id): user_to_send_to = get_model_users(user_id=user_id) - verify_code, errors = request_verify_code_schema.load(request.get_json()) - secret_code = create_secret_code() create_user_code(user_to_send_to, secret_code, 'email') @@ -166,6 +163,7 @@ def send_user_email_verification(user_id): 'url': _create_verification_url(user_to_send_to, secret_code) } } + print(message) send_email.apply_async(( current_app.config['NOTIFY_SERVICE_ID'], str(uuid.uuid4()), @@ -224,7 +222,7 @@ def send_user_reset_password(): 'to': user_to_send_to.email_address, 'personalisation': { 'user_name': user_to_send_to.name, - 'url': _create_verification_url(user_to_send_to, _create_reset_password_url(user_to_send_to.email_address)) + 'url': _create_reset_password_url(user_to_send_to.email_address) } } send_email.apply_async([current_app.config['NOTIFY_SERVICE_ID'], diff --git a/tests/app/conftest.py b/tests/app/conftest.py index 70b4ddbe0..ee8d67283 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -609,7 +609,7 @@ def email_verification_template(notify_db, service = Service(**data) db.session.add(service) - template = Template.query.get(current_app.config['SMS_CODE_TEMPLATE_ID']) + template = Template.query.get(current_app.config['EMAIL_VERIFY_CODE_TEMPLATE_ID']) if not template: data = { 'id': current_app.config['EMAIL_VERIFY_CODE_TEMPLATE_ID'], @@ -623,3 +623,73 @@ def email_verification_template(notify_db, template = Template(**data) db.session.add(template) return template + + +@pytest.fixture(scope='function') +def invitation_email_template(notify_db, + notify_db_session): + user = sample_user(notify_db, notify_db_session) + service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) + if not service: + data = { + 'id': current_app.config['NOTIFY_SERVICE_ID'], + 'name': 'Notify Service', + 'message_limit': 1000, + 'active': True, + 'restricted': False, + 'email_from': 'notify.service', + 'created_by': user + } + service = Service(**data) + db.session.add(service) + + template = Template.query.get(current_app.config['INVITATION_EMAIL_TEMPLATE_ID']) + if not template: + data = { + 'id': current_app.config['INVITATION_EMAIL_TEMPLATE_ID'], + 'name': 'Invitaion template', + 'template_type': 'email', + 'content': '((user_name)) is invited to Notify by ((service_name)) ((url)) to complete registration', + 'subject': 'Invitation to ((service_name))', + 'service': service, + 'created_by': user, + 'archived': False + } + template = Template(**data) + db.session.add(template) + return template + + +@pytest.fixture(scope='function') +def password_reset_email_template(notify_db, + notify_db_session): + user = sample_user(notify_db, notify_db_session) + service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) + if not service: + data = { + 'id': current_app.config['NOTIFY_SERVICE_ID'], + 'name': 'Notify Service', + 'message_limit': 1000, + 'active': True, + 'restricted': False, + 'email_from': 'notify.service', + 'created_by': user + } + service = Service(**data) + db.session.add(service) + + template = Template.query.get(current_app.config['PASSWORD_RESET_TEMPLATE_ID']) + if not template: + data = { + 'id': current_app.config['PASSWORD_RESET_TEMPLATE_ID'], + 'name': 'Password reset template', + 'template_type': 'email', + 'content': '((user_name)) you can reset password by clicking ((url))', + 'subject': 'Reset your password', + 'service': service, + 'created_by': user, + 'archived': False + } + template = Template(**data) + db.session.add(template) + return template diff --git a/tests/app/invite/test_invite_rest.py b/tests/app/invite/test_invite_rest.py index 65a6bff5a..90b36db39 100644 --- a/tests/app/invite/test_invite_rest.py +++ b/tests/app/invite/test_invite_rest.py @@ -1,17 +1,20 @@ import json import uuid -from datetime import datetime, timedelta +from flask import current_app +from freezegun import freeze_time from app import encryption from tests import create_authorization_header import app.celery.tasks -def test_create_invited_user(notify_api, sample_service, mocker): +@freeze_time("2016-01-01T11:09:00.061258") +def test_create_invited_user(notify_api, sample_service, mocker, invitation_email_template): with notify_api.test_request_context(): with notify_api.test_client() as client: - mocker.patch('app.celery.tasks.email_invited_user.apply_async') + mocker.patch('uuid.uuid4', return_value='some_uuid') # for the notification id + mocker.patch('app.celery.tasks.send_email.apply_async') mocker.patch('notifications_utils.url_safe_token.generate_token', return_value='the-token') email_address = 'invited_user@service.gov.uk' invite_from = sample_service.users[0] @@ -22,15 +25,12 @@ def test_create_invited_user(notify_api, sample_service, mocker): 'from_user': str(invite_from.id), 'permissions': 'send_messages,manage_service,manage_api_keys' } - - data = json.dumps(data) - auth_header = create_authorization_header() response = client.post( '/service/{}/invite'.format(sample_service.id), headers=[('Content-Type', 'application/json'), auth_header], - data=data + data=json.dumps(data) ) assert response.status_code == 201 json_resp = json.loads(response.get_data(as_text=True)) @@ -40,27 +40,29 @@ def test_create_invited_user(notify_api, sample_service, mocker): assert json_resp['data']['from_user'] == str(invite_from.id) assert json_resp['data']['permissions'] == 'send_messages,manage_service,manage_api_keys' assert json_resp['data']['id'] - invitation_expiration_days = notify_api.config['INVITATION_EXPIRATION_DAYS'] - expiry_date = (datetime.utcnow() + timedelta(days=invitation_expiration_days)).replace(hour=0, - minute=0, - second=0, - microsecond=0) - encrypted_invitation = {'to': email_address, - 'user_name': invite_from.name, - 'service_id': str(sample_service.id), - 'service_name': sample_service.name, - 'token': 'the-token', - 'expiry_date': str(expiry_date) - } - app.celery.tasks.email_invited_user.apply_async.assert_called_once_with( - [encryption.encrypt(encrypted_invitation)], - queue='email-invited-user') + + message = { + 'template': str(invitation_email_template.id), + 'template_version': invitation_email_template.version, + 'to': email_address, + 'personalisation': { + 'user_name': invite_from.name, + 'service_name': sample_service.name, + 'url': '{0}/invitation/{1}'.format(current_app.config['ADMIN_BASE_URL'], 'the-token') + } + } + app.celery.tasks.send_email.apply_async.assert_called_once_with( + (str(current_app.config['NOTIFY_SERVICE_ID']), + 'some_uuid', + encryption.encrypt(message), + "2016-01-01T11:09:00.061258"), + queue="email-invited-user") def test_create_invited_user_invalid_email(notify_api, sample_service, mocker): with notify_api.test_request_context(): with notify_api.test_client() as client: - mocker.patch('app.celery.tasks.email_invited_user.apply_async') + mocker.patch('app.celery.tasks.send_email.apply_async') email_address = 'notanemail' invite_from = sample_service.users[0] @@ -84,7 +86,7 @@ def test_create_invited_user_invalid_email(notify_api, sample_service, mocker): json_resp = json.loads(response.get_data(as_text=True)) assert json_resp['result'] == 'error' assert json_resp['message'] == {'email_address': ['Not a valid email address']} - app.celery.tasks.email_invited_user.apply_async.assert_not_called() + app.celery.tasks.send_email.apply_async.assert_not_called() def test_get_all_invited_users_by_service(notify_api, notify_db, notify_db_session, sample_service): diff --git a/tests/app/user/test_rest.py b/tests/app/user/test_rest.py index 72a060e6a..193434bfb 100644 --- a/tests/app/user/test_rest.py +++ b/tests/app/user/test_rest.py @@ -396,9 +396,10 @@ def test_set_user_permissions_remove_old(notify_api, def test_send_user_reset_password_should_send_reset_password_link(notify_api, sample_user, mocker, - mock_encryption): + password_reset_email_template): with notify_api.test_request_context(): with notify_api.test_client() as client: + mocker.patch('notifications_utils.url_safe_token.generate_token', return_value='the-token') mocker.patch('uuid.uuid4', return_value='some_uuid') # for the notification id mocker.patch('app.celery.tasks.send_email.apply_async') data = json.dumps({'email': sample_user.email_address}) @@ -408,11 +409,20 @@ def test_send_user_reset_password_should_send_reset_password_link(notify_api, data=data, headers=[('Content-Type', 'application/json'), auth_header]) + message = { + 'template': str(password_reset_email_template.id), + 'template_version': password_reset_email_template.version, + 'to': sample_user.email_address, + 'personalisation': { + 'user_name': sample_user.name, + 'url': current_app.config['ADMIN_BASE_URL'] + '/new-password/' + 'the-token' + } + } assert resp.status_code == 204 app.celery.tasks.send_email.apply_async.assert_called_once_with( [str(current_app.config['NOTIFY_SERVICE_ID']), 'some_uuid', - "something_encrypted", + app.encryption.encrypt(message), "2016-01-01T11:09:00.061258"], queue="email-reset-password") diff --git a/tests/app/user/test_rest_verify.py b/tests/app/user/test_rest_verify.py index c012c0bc6..3376c3926 100644 --- a/tests/app/user/test_rest_verify.py +++ b/tests/app/user/test_rest_verify.py @@ -319,8 +319,8 @@ def test_send_user_email_verification(notify_api, with notify_api.test_client() as client: data = json.dumps({}) mocker.patch('uuid.uuid4', return_value='some_uuid') # for the notification id - mocker.patch('app.encryption.encrypt', return_value="something_encrypted") mocked = mocker.patch('app.celery.tasks.send_email.apply_async') + mocker.patch('notifications_utils.url_safe_token.generate_token', return_value='the-token') auth_header = create_authorization_header() resp = client.post( url_for('user.send_user_email_verification', user_id=str(sample_user.id)), @@ -328,10 +328,20 @@ def test_send_user_email_verification(notify_api, headers=[('Content-Type', 'application/json'), auth_header]) assert resp.status_code == 204 assert mocked.call_count == 1 + message = { + 'template': str(email_verification_template.id), + 'template_version': email_verification_template.version, + 'to': sample_user.email_address, + 'personalisation': { + 'name': sample_user.name, + 'url': current_app.config['ADMIN_BASE_URL'] + '/verify-email/' + 'the-token' + } + } + print('test message: {}'.format(message)) app.celery.tasks.send_email.apply_async.assert_called_once_with( (str(current_app.config['NOTIFY_SERVICE_ID']), 'some_uuid', - "something_encrypted", + encryption.encrypt(message), "2016-01-01T11:09:00.061258"), queue="email-registration-verification")