mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-23 17:01:35 -05:00
Merge pull request #765 from alphagov/use-notify-queue-to-deliver
Send Notify's 2FA codes via only one queue
This commit is contained in:
@@ -70,19 +70,22 @@ def persist_notification(template_id,
|
|||||||
return notification
|
return notification
|
||||||
|
|
||||||
|
|
||||||
def send_notification_to_queue(notification, research_mode):
|
def send_notification_to_queue(notification, research_mode, queue=None):
|
||||||
try:
|
if research_mode or notification.key_type == KEY_TYPE_TEST:
|
||||||
research_mode = research_mode or notification.key_type == KEY_TYPE_TEST
|
queue = 'research-mode'
|
||||||
|
elif not queue:
|
||||||
if notification.notification_type == SMS_TYPE:
|
if notification.notification_type == SMS_TYPE:
|
||||||
provider_tasks.deliver_sms.apply_async(
|
queue = 'send-sms'
|
||||||
[str(notification.id)],
|
|
||||||
queue='send-sms' if not research_mode else 'research-mode'
|
|
||||||
)
|
|
||||||
if notification.notification_type == EMAIL_TYPE:
|
if notification.notification_type == EMAIL_TYPE:
|
||||||
provider_tasks.deliver_email.apply_async(
|
queue = 'send-email'
|
||||||
[str(notification.id)],
|
|
||||||
queue='send-email' if not research_mode else 'research-mode'
|
if notification.notification_type == SMS_TYPE:
|
||||||
)
|
deliver_task = provider_tasks.deliver_sms
|
||||||
|
if notification.notification_type == EMAIL_TYPE:
|
||||||
|
deliver_task = provider_tasks.deliver_email
|
||||||
|
|
||||||
|
try:
|
||||||
|
deliver_task.apply_async([str(notification.id)], queue=queue)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.exception(e)
|
current_app.logger.exception(e)
|
||||||
dao_delete_notifications_and_history_by_id(notification.id)
|
dao_delete_notifications_and_history_by_id(notification.id)
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ from app.dao.users_dao import (
|
|||||||
from app.dao.permissions_dao import permission_dao
|
from app.dao.permissions_dao import permission_dao
|
||||||
from app.dao.services_dao import dao_fetch_service_by_id
|
from app.dao.services_dao import dao_fetch_service_by_id
|
||||||
from app.dao.templates_dao import dao_get_template_by_id
|
from app.dao.templates_dao import dao_get_template_by_id
|
||||||
from app.models import SMS_TYPE
|
from app.models import SMS_TYPE, KEY_TYPE_NORMAL
|
||||||
|
from app.notifications.process_notifications import (
|
||||||
|
persist_notification,
|
||||||
|
send_notification_to_queue
|
||||||
|
)
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
email_data_request_schema,
|
email_data_request_schema,
|
||||||
user_schema,
|
user_schema,
|
||||||
@@ -143,20 +147,22 @@ def send_user_sms_code(user_id):
|
|||||||
mobile = user_to_send_to.mobile_number if verify_code.get('to', None) is None else verify_code.get('to')
|
mobile = user_to_send_to.mobile_number if verify_code.get('to', None) is None else verify_code.get('to')
|
||||||
sms_code_template_id = current_app.config['SMS_CODE_TEMPLATE_ID']
|
sms_code_template_id = current_app.config['SMS_CODE_TEMPLATE_ID']
|
||||||
sms_code_template = dao_get_template_by_id(sms_code_template_id)
|
sms_code_template = dao_get_template_by_id(sms_code_template_id)
|
||||||
verification_message = encryption.encrypt({
|
notify_service_id = current_app.config['NOTIFY_SERVICE_ID']
|
||||||
'template': sms_code_template_id,
|
|
||||||
'template_version': sms_code_template.version,
|
|
||||||
'to': mobile,
|
|
||||||
'personalisation': {
|
|
||||||
'verify_code': secret_code
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
saved_notification = persist_notification(
|
||||||
send_sms.apply_async([current_app.config['NOTIFY_SERVICE_ID'],
|
template_id=sms_code_template_id,
|
||||||
str(uuid.uuid4()),
|
template_version=sms_code_template.version,
|
||||||
verification_message,
|
recipient=mobile,
|
||||||
datetime.utcnow().strftime(DATETIME_FORMAT)
|
service_id=notify_service_id,
|
||||||
], queue='notify')
|
personalisation={'verify_code': secret_code},
|
||||||
|
notification_type=SMS_TYPE,
|
||||||
|
api_key_id=None,
|
||||||
|
key_type=KEY_TYPE_NORMAL
|
||||||
|
)
|
||||||
|
# Assume that we never want to observe the Notify service's research mode
|
||||||
|
# setting for this notification - we still need to be able to log into the
|
||||||
|
# admin even if we're doing user research using this service:
|
||||||
|
send_notification_to_queue(saved_notification, False, queue='notify')
|
||||||
|
|
||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import pytest
|
|||||||
from boto3.exceptions import Boto3Error
|
from boto3.exceptions import Boto3Error
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from app.models import Template, Notification, NotificationHistory
|
from app.models import Template, Notification, NotificationHistory
|
||||||
from app.notifications import SendNotificationToQueueError
|
from app.notifications import SendNotificationToQueueError
|
||||||
@@ -135,24 +136,33 @@ def test_persist_notification_with_optionals(sample_job, sample_api_key, mocker)
|
|||||||
assert persisted_notification.reference is None
|
assert persisted_notification.reference is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('research_mode, queue, notification_type, key_type',
|
@pytest.mark.parametrize('research_mode, requested_queue, expected_queue, notification_type, key_type',
|
||||||
[(True, 'research-mode', 'sms', 'normal'),
|
[(True, None, 'research-mode', 'sms', 'normal'),
|
||||||
(True, 'research-mode', 'email', 'normal'),
|
(True, None, 'research-mode', 'email', 'normal'),
|
||||||
(True, 'research-mode', 'email', 'team'),
|
(True, None, 'research-mode', 'email', 'team'),
|
||||||
(False, 'send-sms', 'sms', 'normal'),
|
(False, None, 'send-sms', 'sms', 'normal'),
|
||||||
(False, 'send-email', 'email', 'normal'),
|
(False, None, 'send-email', 'email', 'normal'),
|
||||||
(False, 'send-sms', 'sms', 'team'),
|
(False, None, 'send-sms', 'sms', 'team'),
|
||||||
(False, 'research-mode', 'sms', 'test')])
|
(False, None, 'research-mode', 'sms', 'test'),
|
||||||
|
(True, 'notify', 'research-mode', 'email', 'normal'),
|
||||||
|
(False, 'notify', 'notify', 'sms', 'normal'),
|
||||||
|
(False, 'notify', 'notify', 'email', 'normal'),
|
||||||
|
(False, 'notify', 'research-mode', 'sms', 'test')])
|
||||||
def test_send_notification_to_queue(notify_db, notify_db_session,
|
def test_send_notification_to_queue(notify_db, notify_db_session,
|
||||||
research_mode, notification_type,
|
research_mode, requested_queue, expected_queue,
|
||||||
queue, key_type, mocker):
|
notification_type, key_type, mocker):
|
||||||
mocked = mocker.patch('app.celery.provider_tasks.deliver_{}.apply_async'.format(notification_type))
|
mocked = mocker.patch('app.celery.provider_tasks.deliver_{}.apply_async'.format(notification_type))
|
||||||
template = sample_template(notify_db, notify_db_session) if notification_type == 'sms' \
|
Notification = namedtuple('Notification', ['id', 'key_type', 'notification_type', 'created_at'])
|
||||||
else sample_email_template(notify_db, notify_db_session)
|
notification = Notification(
|
||||||
notification = sample_notification(notify_db, notify_db_session, template=template, key_type=key_type)
|
id=uuid.uuid4(),
|
||||||
send_notification_to_queue(notification=notification, research_mode=research_mode)
|
key_type=key_type,
|
||||||
|
notification_type=notification_type,
|
||||||
|
created_at=datetime.datetime(2016, 11, 11, 16, 8, 18),
|
||||||
|
)
|
||||||
|
|
||||||
mocked.assert_called_once_with([str(notification.id)], queue=queue)
|
send_notification_to_queue(notification=notification, research_mode=research_mode, queue=requested_queue)
|
||||||
|
|
||||||
|
mocked.assert_called_once_with([str(notification.id)], queue=expected_queue)
|
||||||
|
|
||||||
|
|
||||||
def test_send_notification_to_queue_throws_exception_deletes_notification(sample_notification, mocker):
|
def test_send_notification_to_queue_throws_exception_deletes_notification(sample_notification, mocker):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import moto
|
import moto
|
||||||
|
import pytest
|
||||||
|
|
||||||
from datetime import (
|
from datetime import (
|
||||||
datetime,
|
datetime,
|
||||||
@@ -7,9 +8,11 @@ from datetime import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from flask import url_for, current_app
|
from flask import url_for, current_app
|
||||||
|
from app.dao.services_dao import dao_update_service, dao_fetch_service_by_id
|
||||||
from app.models import (
|
from app.models import (
|
||||||
VerifyCode,
|
VerifyCode,
|
||||||
User
|
User,
|
||||||
|
Notification
|
||||||
)
|
)
|
||||||
|
|
||||||
from app import db, encryption
|
from app import db, encryption
|
||||||
@@ -214,41 +217,47 @@ def test_user_verify_password_missing_password(notify_api,
|
|||||||
assert 'Required field missing data' in json_resp['message']['password']
|
assert 'Required field missing data' in json_resp['message']['password']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('research_mode', [True, False])
|
||||||
@freeze_time("2016-01-01 11:09:00.061258")
|
@freeze_time("2016-01-01 11:09:00.061258")
|
||||||
def test_send_user_sms_code(notify_api,
|
def test_send_user_sms_code(notify_api,
|
||||||
sample_user,
|
sample_user,
|
||||||
sms_code_template,
|
sms_code_template,
|
||||||
mocker):
|
mocker,
|
||||||
|
research_mode):
|
||||||
"""
|
"""
|
||||||
Tests POST endpoint /user/<user_id>/sms-code
|
Tests POST endpoint /user/<user_id>/sms-code
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with notify_api.test_request_context():
|
with notify_api.test_request_context():
|
||||||
with notify_api.test_client() as client:
|
with notify_api.test_client() as client:
|
||||||
|
if research_mode:
|
||||||
|
notify_service = dao_fetch_service_by_id(current_app.config['NOTIFY_SERVICE_ID'])
|
||||||
|
notify_service.research_mode = True
|
||||||
|
dao_update_service(notify_service)
|
||||||
|
|
||||||
data = json.dumps({})
|
data = json.dumps({})
|
||||||
auth_header = create_authorization_header()
|
auth_header = create_authorization_header()
|
||||||
mocked = mocker.patch('app.user.rest.create_secret_code', return_value='11111')
|
mocked = mocker.patch('app.user.rest.create_secret_code', return_value='11111')
|
||||||
mocker.patch('app.celery.tasks.send_sms.apply_async')
|
mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
|
||||||
mocker.patch('uuid.uuid4', return_value='some_uuid') # for the notification id
|
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
url_for('user.send_user_sms_code', user_id=sample_user.id),
|
url_for('user.send_user_sms_code', user_id=sample_user.id),
|
||||||
data=data,
|
data=data,
|
||||||
headers=[('Content-Type', 'application/json'), auth_header])
|
headers=[('Content-Type', 'application/json'), auth_header])
|
||||||
assert resp.status_code == 204
|
assert resp.status_code == 204
|
||||||
|
|
||||||
assert mocked.call_count == 1
|
assert mocked.call_count == 1
|
||||||
encrypted = encryption.encrypt({
|
assert VerifyCode.query.count() == 1
|
||||||
'template': current_app.config['SMS_CODE_TEMPLATE_ID'],
|
assert VerifyCode.query.first().check_code('11111')
|
||||||
'template_version': 1,
|
|
||||||
'to': sample_user.mobile_number,
|
assert Notification.query.count() == 1
|
||||||
'personalisation': {
|
notification = Notification.query.first()
|
||||||
'verify_code': '11111'
|
assert notification.personalisation == {'verify_code': '11111'}
|
||||||
}
|
assert notification.to == sample_user.mobile_number
|
||||||
})
|
assert str(notification.service_id) == current_app.config['NOTIFY_SERVICE_ID']
|
||||||
app.celery.tasks.send_sms.apply_async.assert_called_once_with(
|
|
||||||
([current_app.config['NOTIFY_SERVICE_ID'],
|
app.celery.provider_tasks.deliver_sms.apply_async.assert_called_once_with(
|
||||||
"some_uuid",
|
([str(notification.id)]),
|
||||||
encrypted,
|
|
||||||
"2016-01-01T11:09:00.061258Z"]),
|
|
||||||
queue="notify"
|
queue="notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -257,38 +266,29 @@ def test_send_user_sms_code(notify_api,
|
|||||||
def test_send_user_code_for_sms_with_optional_to_field(notify_api,
|
def test_send_user_code_for_sms_with_optional_to_field(notify_api,
|
||||||
sample_user,
|
sample_user,
|
||||||
sms_code_template,
|
sms_code_template,
|
||||||
mock_encryption,
|
|
||||||
mocker):
|
mocker):
|
||||||
"""
|
"""
|
||||||
Tests POST endpoint '/<user_id>/code' successful sms with optional to field
|
Tests POST endpoint /user/<user_id>/sms-code with optional to field
|
||||||
"""
|
"""
|
||||||
with notify_api.test_request_context():
|
with notify_api.test_request_context():
|
||||||
with notify_api.test_client() as client:
|
with notify_api.test_client() as client:
|
||||||
|
to_number = '+441119876757'
|
||||||
mocked = mocker.patch('app.user.rest.create_secret_code', return_value='11111')
|
mocked = mocker.patch('app.user.rest.create_secret_code', return_value='11111')
|
||||||
mocker.patch('uuid.uuid4', return_value='some_uuid') # for the notification id
|
mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
|
||||||
mocker.patch('app.celery.tasks.send_sms.apply_async')
|
data = json.dumps({'to': to_number})
|
||||||
data = json.dumps({'to': '+441119876757'})
|
|
||||||
auth_header = create_authorization_header()
|
auth_header = create_authorization_header()
|
||||||
|
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
url_for('user.send_user_sms_code', user_id=sample_user.id),
|
url_for('user.send_user_sms_code', user_id=sample_user.id),
|
||||||
data=data,
|
data=data,
|
||||||
headers=[('Content-Type', 'application/json'), auth_header])
|
headers=[('Content-Type', 'application/json'), auth_header])
|
||||||
|
|
||||||
assert resp.status_code == 204
|
assert resp.status_code == 204
|
||||||
encrypted = encryption.encrypt({
|
|
||||||
'template': current_app.config['SMS_CODE_TEMPLATE_ID'],
|
|
||||||
'template_version': 1,
|
|
||||||
'to': '+441119876757',
|
|
||||||
'personalisation': {
|
|
||||||
'verify_code': '11111'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
assert mocked.call_count == 1
|
assert mocked.call_count == 1
|
||||||
app.celery.tasks.send_sms.apply_async.assert_called_once_with(
|
notification = Notification.query.first()
|
||||||
([current_app.config['NOTIFY_SERVICE_ID'],
|
assert notification.to == to_number
|
||||||
"some_uuid",
|
app.celery.provider_tasks.deliver_sms.apply_async.assert_called_once_with(
|
||||||
encrypted,
|
([str(notification.id)]),
|
||||||
"2016-01-01T11:09:00.061258Z"]),
|
|
||||||
queue="notify"
|
queue="notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user