mirror of
https://github.com/GSA/notifications-api.git
synced 2026-09-08 16:53:12 -04:00
Register a before_request event for all blueprints, that defines the authentication requirement.
There are three authentication methods: - requires_no_auth - public endpoint that does not require an Authorisation header - requires_auth - public endpoints that need an API key in the Authorisation header - requires_admin_auth - private endpoint that requires an Authorisation header which contains the API key for the defined as the client admin user
This commit is contained in:
@@ -2,7 +2,7 @@ import os
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from flask import Flask, _request_ctx_stack
|
from flask import Flask, _request_ctx_stack
|
||||||
from flask import request, url_for, g, jsonify
|
from flask import request, g, jsonify
|
||||||
from flask.ext.sqlalchemy import SQLAlchemy
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
from flask_marshmallow import Marshmallow
|
from flask_marshmallow import Marshmallow
|
||||||
from monotonic import monotonic
|
from monotonic import monotonic
|
||||||
@@ -20,7 +20,6 @@ from app.clients.sms.mmg import MMGClient
|
|||||||
from app.clients.performance_platform.performance_platform_client import PerformancePlatformClient
|
from app.clients.performance_platform.performance_platform_client import PerformancePlatformClient
|
||||||
from app.encryption import Encryption
|
from app.encryption import Encryption
|
||||||
|
|
||||||
|
|
||||||
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
||||||
DATE_FORMAT = "%Y-%m-%d"
|
DATE_FORMAT = "%Y-%m-%d"
|
||||||
|
|
||||||
@@ -77,10 +76,10 @@ def create_app(app_name=None):
|
|||||||
|
|
||||||
def register_blueprint(application):
|
def register_blueprint(application):
|
||||||
from app.service.rest import service_blueprint
|
from app.service.rest import service_blueprint
|
||||||
from app.user.rest import user as user_blueprint
|
from app.user.rest import user_blueprint
|
||||||
from app.template.rest import template as template_blueprint
|
from app.template.rest import template_blueprint
|
||||||
from app.status.healthcheck import status as status_blueprint
|
from app.status.healthcheck import status as status_blueprint
|
||||||
from app.job.rest import job as job_blueprint
|
from app.job.rest import job_blueprint
|
||||||
from app.notifications.rest import notifications as notifications_blueprint
|
from app.notifications.rest import notifications as notifications_blueprint
|
||||||
from app.invite.rest import invite as invite_blueprint
|
from app.invite.rest import invite as invite_blueprint
|
||||||
from app.accept_invite.rest import accept_invite
|
from app.accept_invite.rest import accept_invite
|
||||||
@@ -90,49 +89,100 @@ def register_blueprint(application):
|
|||||||
from app.spec.rest import spec as spec_blueprint
|
from app.spec.rest import spec as spec_blueprint
|
||||||
from app.organisation.rest import organisation_blueprint
|
from app.organisation.rest import organisation_blueprint
|
||||||
from app.delivery.rest import delivery_blueprint
|
from app.delivery.rest import delivery_blueprint
|
||||||
|
from app.notifications.receive_notifications import receive_notifications_blueprint
|
||||||
|
from app.notifications.notifications_ses_callback import ses_callback_blueprint
|
||||||
|
from app.notifications.notifications_sms_callback import sms_callback_blueprint
|
||||||
|
from app.status.healthcheck import status
|
||||||
|
from app.authentication.auth import requires_admin_auth, requires_auth, requires_no_auth
|
||||||
|
|
||||||
|
service_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(service_blueprint, url_prefix='/service')
|
application.register_blueprint(service_blueprint, url_prefix='/service')
|
||||||
|
|
||||||
|
user_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(user_blueprint, url_prefix='/user')
|
application.register_blueprint(user_blueprint, url_prefix='/user')
|
||||||
|
|
||||||
|
template_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(template_blueprint)
|
application.register_blueprint(template_blueprint)
|
||||||
|
|
||||||
|
status_blueprint.before_request(requires_no_auth)
|
||||||
application.register_blueprint(status_blueprint)
|
application.register_blueprint(status_blueprint)
|
||||||
|
|
||||||
|
notifications_blueprint.before_request(requires_auth)
|
||||||
|
receive_notifications_blueprint.before_request(requires_no_auth)
|
||||||
|
|
||||||
|
ses_callback_blueprint.before_request(requires_no_auth)
|
||||||
|
application.register_blueprint(ses_callback_blueprint)
|
||||||
|
|
||||||
|
sms_callback_blueprint.before_request(requires_no_auth)
|
||||||
|
application.register_blueprint(sms_callback_blueprint)
|
||||||
|
|
||||||
|
receive_notifications_blueprint.before_request(requires_no_auth)
|
||||||
|
application.register_blueprint(receive_notifications_blueprint)
|
||||||
|
|
||||||
|
notifications_blueprint.before_request(requires_auth)
|
||||||
application.register_blueprint(notifications_blueprint)
|
application.register_blueprint(notifications_blueprint)
|
||||||
|
|
||||||
|
job_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(job_blueprint)
|
application.register_blueprint(job_blueprint)
|
||||||
|
|
||||||
|
invite_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(invite_blueprint)
|
application.register_blueprint(invite_blueprint)
|
||||||
|
|
||||||
|
delivery_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(delivery_blueprint)
|
application.register_blueprint(delivery_blueprint)
|
||||||
|
|
||||||
|
accept_invite.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(accept_invite, url_prefix='/invite')
|
application.register_blueprint(accept_invite, url_prefix='/invite')
|
||||||
|
|
||||||
|
template_statistics_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(template_statistics_blueprint)
|
application.register_blueprint(template_statistics_blueprint)
|
||||||
|
|
||||||
|
events_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(events_blueprint)
|
application.register_blueprint(events_blueprint)
|
||||||
|
|
||||||
|
provider_details_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(provider_details_blueprint, url_prefix='/provider-details')
|
application.register_blueprint(provider_details_blueprint, url_prefix='/provider-details')
|
||||||
|
|
||||||
|
spec_blueprint.before_request(requires_no_auth)
|
||||||
application.register_blueprint(spec_blueprint, url_prefix='/spec')
|
application.register_blueprint(spec_blueprint, url_prefix='/spec')
|
||||||
|
|
||||||
|
organisation_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(organisation_blueprint, url_prefix='/organisation')
|
application.register_blueprint(organisation_blueprint, url_prefix='/organisation')
|
||||||
|
|
||||||
|
status.before_request(requires_no_auth)
|
||||||
|
application.register_blueprint(status)
|
||||||
|
|
||||||
|
|
||||||
def register_v2_blueprints(application):
|
def register_v2_blueprints(application):
|
||||||
from app.v2.notifications.post_notifications import notification_blueprint as post_notifications
|
from app.v2.notifications.post_notifications import v2_notification_blueprint as post_notifications
|
||||||
from app.v2.notifications.get_notifications import notification_blueprint as get_notifications
|
from app.v2.notifications.get_notifications import v2_notification_blueprint as get_notifications
|
||||||
|
from app.authentication.auth import requires_auth
|
||||||
|
|
||||||
|
post_notifications.before_request(requires_auth)
|
||||||
application.register_blueprint(post_notifications)
|
application.register_blueprint(post_notifications)
|
||||||
|
|
||||||
|
get_notifications.before_request(requires_auth)
|
||||||
application.register_blueprint(get_notifications)
|
application.register_blueprint(get_notifications)
|
||||||
|
|
||||||
|
|
||||||
def init_app(app):
|
def init_app(app):
|
||||||
@app.before_request
|
# @app.before_request
|
||||||
def required_authentication():
|
# def required_authentication():
|
||||||
no_auth_req = [
|
# no_auth_req = [
|
||||||
url_for('status.show_status'),
|
# url_for('status.show_status'),
|
||||||
url_for('notifications.process_ses_response'),
|
# url_for('notifications.process_ses_response'),
|
||||||
url_for('notifications.process_firetext_response'),
|
# url_for('notifications.process_firetext_response'),
|
||||||
url_for('notifications.process_mmg_response'),
|
# url_for('notifications.process_mmg_response'),
|
||||||
url_for('notifications.receive_mmg_sms'),
|
# url_for('notifications.receive_mmg_sms'),
|
||||||
url_for('status.show_delivery_status'),
|
# url_for('status.show_delivery_status'),
|
||||||
url_for('spec.get_spec')
|
# url_for('spec.get_spec')
|
||||||
]
|
# ]
|
||||||
|
#
|
||||||
if request.path not in no_auth_req:
|
# if request.path not in no_auth_req:
|
||||||
from app.authentication import auth
|
# from app.authentication import auth
|
||||||
error = auth.requires_auth()
|
# error = auth.requires_auth()
|
||||||
if error:
|
# if error:
|
||||||
return error
|
# return error
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def record_user_agent():
|
def record_user_agent():
|
||||||
|
|||||||
@@ -39,14 +39,13 @@ def get_auth_token(req):
|
|||||||
return auth_header[7:]
|
return auth_header[7:]
|
||||||
|
|
||||||
|
|
||||||
|
def requires_no_auth():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def requires_admin_auth():
|
def requires_admin_auth():
|
||||||
auth_token = get_auth_token(request)
|
auth_token = get_auth_token(request)
|
||||||
try:
|
client = __get_token_issuer(auth_token)
|
||||||
client = get_token_issuer(auth_token)
|
|
||||||
except TokenDecodeError as e:
|
|
||||||
raise AuthError(e.message, 403)
|
|
||||||
except TokenIssuerError:
|
|
||||||
raise AuthError("Invalid token: iss not provided", 403)
|
|
||||||
|
|
||||||
if client == current_app.config.get('ADMIN_CLIENT_USER_NAME'):
|
if client == current_app.config.get('ADMIN_CLIENT_USER_NAME'):
|
||||||
g.service_id = current_app.config.get('ADMIN_CLIENT_USER_NAME')
|
g.service_id = current_app.config.get('ADMIN_CLIENT_USER_NAME')
|
||||||
@@ -57,16 +56,7 @@ def requires_admin_auth():
|
|||||||
|
|
||||||
def requires_auth():
|
def requires_auth():
|
||||||
auth_token = get_auth_token(request)
|
auth_token = get_auth_token(request)
|
||||||
try:
|
client = __get_token_issuer(auth_token)
|
||||||
client = get_token_issuer(auth_token)
|
|
||||||
except TokenDecodeError as e:
|
|
||||||
raise AuthError(e.message, 403)
|
|
||||||
except TokenIssuerError:
|
|
||||||
raise AuthError("Invalid token: iss not provided", 403)
|
|
||||||
|
|
||||||
if client == current_app.config.get('ADMIN_CLIENT_USER_NAME'):
|
|
||||||
g.service_id = current_app.config.get('ADMIN_CLIENT_USER_NAME')
|
|
||||||
return handle_admin_key(auth_token, current_app.config.get('ADMIN_CLIENT_SECRET'))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
service = dao_fetch_service_by_id(client)
|
service = dao_fetch_service_by_id(client)
|
||||||
@@ -98,12 +88,22 @@ def requires_auth():
|
|||||||
raise AuthError("Invalid token: signature, api token is not valid", 403)
|
raise AuthError("Invalid token: signature, api token is not valid", 403)
|
||||||
|
|
||||||
|
|
||||||
|
def __get_token_issuer(auth_token):
|
||||||
|
try:
|
||||||
|
client = get_token_issuer(auth_token)
|
||||||
|
except TokenIssuerError:
|
||||||
|
raise AuthError("Invalid token: iss field not provided", 403)
|
||||||
|
except TokenDecodeError as e:
|
||||||
|
raise AuthError("Invalid token: signature, api token is not valid", 403)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
def handle_admin_key(auth_token, secret):
|
def handle_admin_key(auth_token, secret):
|
||||||
try:
|
try:
|
||||||
get_decode_errors(auth_token, secret)
|
get_decode_errors(auth_token, secret)
|
||||||
return
|
return
|
||||||
except TokenDecodeError as e:
|
except TokenDecodeError as e:
|
||||||
raise AuthError(e.message, 403)
|
raise AuthError("Invalid token: signature, api token is not valid", 403)
|
||||||
|
|
||||||
|
|
||||||
def get_decode_errors(auth_token, unsigned_secret):
|
def get_decode_errors(auth_token, unsigned_secret):
|
||||||
|
|||||||
@@ -34,17 +34,17 @@ from app.models import JOB_STATUS_SCHEDULED, JOB_STATUS_PENDING, JOB_STATUS_CANC
|
|||||||
|
|
||||||
from app.utils import pagination_links
|
from app.utils import pagination_links
|
||||||
|
|
||||||
job = Blueprint('job', __name__, url_prefix='/service/<uuid:service_id>/job')
|
job_blueprint = Blueprint('job', __name__, url_prefix='/service/<uuid:service_id>/job')
|
||||||
|
|
||||||
from app.errors import (
|
from app.errors import (
|
||||||
register_errors,
|
register_errors,
|
||||||
InvalidRequest
|
InvalidRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
register_errors(job)
|
register_errors(job_blueprint)
|
||||||
|
|
||||||
|
|
||||||
@job.route('/<job_id>', methods=['GET'])
|
@job_blueprint.route('/<job_id>', methods=['GET'])
|
||||||
def get_job_by_service_and_job_id(service_id, job_id):
|
def get_job_by_service_and_job_id(service_id, job_id):
|
||||||
job = dao_get_job_by_service_id_and_job_id(service_id, job_id)
|
job = dao_get_job_by_service_id_and_job_id(service_id, job_id)
|
||||||
statistics = dao_get_notification_outcomes_for_job(service_id, job_id)
|
statistics = dao_get_notification_outcomes_for_job(service_id, job_id)
|
||||||
@@ -55,7 +55,7 @@ def get_job_by_service_and_job_id(service_id, job_id):
|
|||||||
return jsonify(data=data)
|
return jsonify(data=data)
|
||||||
|
|
||||||
|
|
||||||
@job.route('/<job_id>/cancel', methods=['POST'])
|
@job_blueprint.route('/<job_id>/cancel', methods=['POST'])
|
||||||
def cancel_job(service_id, job_id):
|
def cancel_job(service_id, job_id):
|
||||||
job = dao_get_future_scheduled_job_by_id_and_service_id(job_id, service_id)
|
job = dao_get_future_scheduled_job_by_id_and_service_id(job_id, service_id)
|
||||||
job.job_status = JOB_STATUS_CANCELLED
|
job.job_status = JOB_STATUS_CANCELLED
|
||||||
@@ -64,7 +64,7 @@ def cancel_job(service_id, job_id):
|
|||||||
return get_job_by_service_and_job_id(service_id, job_id)
|
return get_job_by_service_and_job_id(service_id, job_id)
|
||||||
|
|
||||||
|
|
||||||
@job.route('/<job_id>/notifications', methods=['GET'])
|
@job_blueprint.route('/<job_id>/notifications', methods=['GET'])
|
||||||
def get_all_notifications_for_service_job(service_id, job_id):
|
def get_all_notifications_for_service_job(service_id, job_id):
|
||||||
data = notifications_filter_schema.load(request.args).data
|
data = notifications_filter_schema.load(request.args).data
|
||||||
page = data['page'] if 'page' in data else 1
|
page = data['page'] if 'page' in data else 1
|
||||||
@@ -100,7 +100,7 @@ def get_all_notifications_for_service_job(service_id, job_id):
|
|||||||
), 200
|
), 200
|
||||||
|
|
||||||
|
|
||||||
@job.route('', methods=['GET'])
|
@job_blueprint.route('', methods=['GET'])
|
||||||
def get_jobs_by_service(service_id):
|
def get_jobs_by_service(service_id):
|
||||||
if request.args.get('limit_days'):
|
if request.args.get('limit_days'):
|
||||||
try:
|
try:
|
||||||
@@ -117,7 +117,7 @@ def get_jobs_by_service(service_id):
|
|||||||
return jsonify(**get_paginated_jobs(service_id, limit_days, statuses, page))
|
return jsonify(**get_paginated_jobs(service_id, limit_days, statuses, page))
|
||||||
|
|
||||||
|
|
||||||
@job.route('', methods=['POST'])
|
@job_blueprint.route('', methods=['POST'])
|
||||||
def create_job(service_id):
|
def create_job(service_id):
|
||||||
service = dao_fetch_service_by_id(service_id)
|
service = dao_fetch_service_by_id(service_id)
|
||||||
if not service.active:
|
if not service.active:
|
||||||
|
|||||||
101
app/notifications/notifications_ses_callback.py
Normal file
101
app/notifications/notifications_ses_callback.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from flask import (
|
||||||
|
Blueprint,
|
||||||
|
jsonify,
|
||||||
|
request,
|
||||||
|
current_app,
|
||||||
|
json
|
||||||
|
)
|
||||||
|
|
||||||
|
from app import statsd_client
|
||||||
|
from app.clients.email.aws_ses import get_aws_responses
|
||||||
|
from app.dao import (
|
||||||
|
notifications_dao
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.notifications.process_client_response import validate_callback_data
|
||||||
|
|
||||||
|
ses_callback_blueprint = Blueprint('notifications_ses_callback_', __name__)
|
||||||
|
|
||||||
|
from app.errors import (
|
||||||
|
register_errors,
|
||||||
|
InvalidRequest
|
||||||
|
)
|
||||||
|
register_errors(ses_callback_blueprint)
|
||||||
|
|
||||||
|
|
||||||
|
@ses_callback_blueprint.route('/notifications/email/ses', methods=['POST'])
|
||||||
|
def process_ses_response():
|
||||||
|
client_name = 'SES'
|
||||||
|
try:
|
||||||
|
ses_request = json.loads(request.data)
|
||||||
|
|
||||||
|
if 'Type' in ses_request and ses_request['Type'] == 'SubscriptionConfirmation':
|
||||||
|
current_app.logger.info("SNS subscription confirmation url: {}".format(ses_request['SubscribeURL']))
|
||||||
|
|
||||||
|
errors = validate_callback_data(data=ses_request, fields=['Message'], client_name=client_name)
|
||||||
|
if errors:
|
||||||
|
raise InvalidRequest(errors, status_code=400)
|
||||||
|
|
||||||
|
ses_message = json.loads(ses_request['Message'])
|
||||||
|
errors = validate_callback_data(data=ses_message, fields=['notificationType'], client_name=client_name)
|
||||||
|
if errors:
|
||||||
|
raise InvalidRequest(errors, status_code=400)
|
||||||
|
|
||||||
|
notification_type = ses_message['notificationType']
|
||||||
|
if notification_type == 'Bounce':
|
||||||
|
if ses_message['bounce']['bounceType'] == 'Permanent':
|
||||||
|
notification_type = ses_message['bounce']['bounceType'] # permanent or not
|
||||||
|
else:
|
||||||
|
notification_type = 'Temporary'
|
||||||
|
try:
|
||||||
|
aws_response_dict = get_aws_responses(notification_type)
|
||||||
|
except KeyError:
|
||||||
|
error = "{} callback failed: status {} not found".format(client_name, notification_type)
|
||||||
|
raise InvalidRequest(error, status_code=400)
|
||||||
|
|
||||||
|
notification_status = aws_response_dict['notification_status']
|
||||||
|
|
||||||
|
try:
|
||||||
|
reference = ses_message['mail']['messageId']
|
||||||
|
notification = notifications_dao.update_notification_status_by_reference(
|
||||||
|
reference,
|
||||||
|
notification_status
|
||||||
|
)
|
||||||
|
if not notification:
|
||||||
|
error = "SES callback failed: notification either not found or already updated " \
|
||||||
|
"from sending. Status {} for notification reference {}".format(notification_status, reference)
|
||||||
|
raise InvalidRequest(error, status_code=404)
|
||||||
|
|
||||||
|
if not aws_response_dict['success']:
|
||||||
|
current_app.logger.info(
|
||||||
|
"SES delivery failed: notification id {} and reference {} has error found. Status {}".format(
|
||||||
|
notification.id,
|
||||||
|
reference,
|
||||||
|
aws_response_dict['message']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
current_app.logger.info('{} callback return status of {} for notification: {}'.format(
|
||||||
|
client_name,
|
||||||
|
notification_status,
|
||||||
|
notification.id))
|
||||||
|
statsd_client.incr('callback.ses.{}'.format(notification_status))
|
||||||
|
if notification.sent_at:
|
||||||
|
statsd_client.timing_with_dates(
|
||||||
|
'callback.ses.elapsed-time'.format(client_name.lower()),
|
||||||
|
datetime.utcnow(),
|
||||||
|
notification.sent_at
|
||||||
|
)
|
||||||
|
return jsonify(
|
||||||
|
result="success", message="SES callback succeeded"
|
||||||
|
), 200
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
message = "SES callback failed: messageId missing"
|
||||||
|
raise InvalidRequest(message, status_code=400)
|
||||||
|
|
||||||
|
except ValueError as ex:
|
||||||
|
error = "{} callback failed: invalid json".format(client_name)
|
||||||
|
raise InvalidRequest(error, status_code=400)
|
||||||
47
app/notifications/notifications_sms_callback.py
Normal file
47
app/notifications/notifications_sms_callback.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
from flask import json
|
||||||
|
from flask import request, jsonify
|
||||||
|
|
||||||
|
from app.errors import InvalidRequest, register_errors
|
||||||
|
from app.notifications.process_client_response import validate_callback_data, process_sms_client_response
|
||||||
|
|
||||||
|
sms_callback_blueprint = Blueprint("sms_callback_blueprint", __name__, url_prefix="/notifications/sms")
|
||||||
|
register_errors(sms_callback_blueprint)
|
||||||
|
|
||||||
|
|
||||||
|
@sms_callback_blueprint.route('/mmg', methods=['POST'])
|
||||||
|
def process_mmg_response():
|
||||||
|
client_name = 'MMG'
|
||||||
|
data = json.loads(request.data)
|
||||||
|
errors = validate_callback_data(data=data,
|
||||||
|
fields=['status', 'CID'],
|
||||||
|
client_name=client_name)
|
||||||
|
if errors:
|
||||||
|
raise InvalidRequest(errors, status_code=400)
|
||||||
|
|
||||||
|
success, errors = process_sms_client_response(status=str(data.get('status')),
|
||||||
|
reference=data.get('CID'),
|
||||||
|
client_name=client_name)
|
||||||
|
if errors:
|
||||||
|
raise InvalidRequest(errors, status_code=400)
|
||||||
|
else:
|
||||||
|
return jsonify(result='success', message=success), 200
|
||||||
|
|
||||||
|
|
||||||
|
@sms_callback_blueprint.route('/firetext', methods=['POST'])
|
||||||
|
def process_firetext_response():
|
||||||
|
client_name = 'Firetext'
|
||||||
|
errors = validate_callback_data(data=request.form,
|
||||||
|
fields=['status', 'reference'],
|
||||||
|
client_name=client_name)
|
||||||
|
if errors:
|
||||||
|
raise InvalidRequest(errors, status_code=400)
|
||||||
|
|
||||||
|
status = request.form.get('status')
|
||||||
|
success, errors = process_sms_client_response(status=status,
|
||||||
|
reference=request.form.get('reference'),
|
||||||
|
client_name=client_name)
|
||||||
|
if errors:
|
||||||
|
raise InvalidRequest(errors, status_code=400)
|
||||||
|
else:
|
||||||
|
return jsonify(result='success', message=success), 200
|
||||||
17
app/notifications/receive_notifications.py
Normal file
17
app/notifications/receive_notifications.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
from flask import current_app
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from app.errors import register_errors
|
||||||
|
|
||||||
|
receive_notifications_blueprint = Blueprint('receive_notifications', __name__)
|
||||||
|
register_errors(receive_notifications_blueprint)
|
||||||
|
|
||||||
|
|
||||||
|
@receive_notifications_blueprint.route('/notifications/sms/receive/mmg', methods=['POST'])
|
||||||
|
def receive_mmg_sms():
|
||||||
|
post_data = request.get_json()
|
||||||
|
post_data.pop('MSISDN', None)
|
||||||
|
current_app.logger.info("Recieve notification form data: {}".format(post_data))
|
||||||
|
|
||||||
|
return "RECEIVED"
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
jsonify,
|
jsonify,
|
||||||
@@ -8,8 +6,7 @@ from flask import (
|
|||||||
json
|
json
|
||||||
)
|
)
|
||||||
|
|
||||||
from app import api_user, statsd_client
|
from app import api_user
|
||||||
from app.clients.email.aws_ses import get_aws_responses
|
|
||||||
from app.dao import (
|
from app.dao import (
|
||||||
templates_dao,
|
templates_dao,
|
||||||
services_dao,
|
services_dao,
|
||||||
@@ -49,129 +46,6 @@ from app.errors import (
|
|||||||
register_errors(notifications)
|
register_errors(notifications)
|
||||||
|
|
||||||
|
|
||||||
@notifications.route('/notifications/sms/receive/mmg', methods=['POST'])
|
|
||||||
def receive_mmg_sms():
|
|
||||||
post_data = request.get_json()
|
|
||||||
post_data.pop('MSISDN', None)
|
|
||||||
current_app.logger.info("Recieve notification form data: {}".format(post_data))
|
|
||||||
|
|
||||||
return "RECEIVED"
|
|
||||||
|
|
||||||
|
|
||||||
@notifications.route('/notifications/email/ses', methods=['POST'])
|
|
||||||
def process_ses_response():
|
|
||||||
client_name = 'SES'
|
|
||||||
try:
|
|
||||||
ses_request = json.loads(request.data)
|
|
||||||
|
|
||||||
if 'Type' in ses_request and ses_request['Type'] == 'SubscriptionConfirmation':
|
|
||||||
current_app.logger.info("SNS subscription confirmation url: {}".format(ses_request['SubscribeURL']))
|
|
||||||
|
|
||||||
errors = validate_callback_data(data=ses_request, fields=['Message'], client_name=client_name)
|
|
||||||
if errors:
|
|
||||||
raise InvalidRequest(errors, status_code=400)
|
|
||||||
|
|
||||||
ses_message = json.loads(ses_request['Message'])
|
|
||||||
errors = validate_callback_data(data=ses_message, fields=['notificationType'], client_name=client_name)
|
|
||||||
if errors:
|
|
||||||
raise InvalidRequest(errors, status_code=400)
|
|
||||||
|
|
||||||
notification_type = ses_message['notificationType']
|
|
||||||
if notification_type == 'Bounce':
|
|
||||||
if ses_message['bounce']['bounceType'] == 'Permanent':
|
|
||||||
notification_type = ses_message['bounce']['bounceType'] # permanent or not
|
|
||||||
else:
|
|
||||||
notification_type = 'Temporary'
|
|
||||||
try:
|
|
||||||
aws_response_dict = get_aws_responses(notification_type)
|
|
||||||
except KeyError:
|
|
||||||
error = "{} callback failed: status {} not found".format(client_name, notification_type)
|
|
||||||
raise InvalidRequest(error, status_code=400)
|
|
||||||
|
|
||||||
notification_status = aws_response_dict['notification_status']
|
|
||||||
|
|
||||||
try:
|
|
||||||
reference = ses_message['mail']['messageId']
|
|
||||||
notification = notifications_dao.update_notification_status_by_reference(
|
|
||||||
reference,
|
|
||||||
notification_status
|
|
||||||
)
|
|
||||||
if not notification:
|
|
||||||
error = "SES callback failed: notification either not found or already updated " \
|
|
||||||
"from sending. Status {} for notification reference {}".format(notification_status, reference)
|
|
||||||
raise InvalidRequest(error, status_code=404)
|
|
||||||
|
|
||||||
if not aws_response_dict['success']:
|
|
||||||
current_app.logger.info(
|
|
||||||
"SES delivery failed: notification id {} and reference {} has error found. Status {}".format(
|
|
||||||
notification.id,
|
|
||||||
reference,
|
|
||||||
aws_response_dict['message']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
current_app.logger.info('{} callback return status of {} for notification: {}'.format(
|
|
||||||
client_name,
|
|
||||||
notification_status,
|
|
||||||
notification.id))
|
|
||||||
statsd_client.incr('callback.ses.{}'.format(notification_status))
|
|
||||||
if notification.sent_at:
|
|
||||||
statsd_client.timing_with_dates(
|
|
||||||
'callback.ses.elapsed-time'.format(client_name.lower()),
|
|
||||||
datetime.utcnow(),
|
|
||||||
notification.sent_at
|
|
||||||
)
|
|
||||||
return jsonify(
|
|
||||||
result="success", message="SES callback succeeded"
|
|
||||||
), 200
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
message = "SES callback failed: messageId missing"
|
|
||||||
raise InvalidRequest(message, status_code=400)
|
|
||||||
|
|
||||||
except ValueError as ex:
|
|
||||||
error = "{} callback failed: invalid json".format(client_name)
|
|
||||||
raise InvalidRequest(error, status_code=400)
|
|
||||||
|
|
||||||
|
|
||||||
@notifications.route('/notifications/sms/mmg', methods=['POST'])
|
|
||||||
def process_mmg_response():
|
|
||||||
client_name = 'MMG'
|
|
||||||
data = json.loads(request.data)
|
|
||||||
errors = validate_callback_data(data=data,
|
|
||||||
fields=['status', 'CID'],
|
|
||||||
client_name=client_name)
|
|
||||||
if errors:
|
|
||||||
raise InvalidRequest(errors, status_code=400)
|
|
||||||
|
|
||||||
success, errors = process_sms_client_response(status=str(data.get('status')),
|
|
||||||
reference=data.get('CID'),
|
|
||||||
client_name=client_name)
|
|
||||||
if errors:
|
|
||||||
raise InvalidRequest(errors, status_code=400)
|
|
||||||
else:
|
|
||||||
return jsonify(result='success', message=success), 200
|
|
||||||
|
|
||||||
|
|
||||||
@notifications.route('/notifications/sms/firetext', methods=['POST'])
|
|
||||||
def process_firetext_response():
|
|
||||||
client_name = 'Firetext'
|
|
||||||
errors = validate_callback_data(data=request.form,
|
|
||||||
fields=['status', 'reference'],
|
|
||||||
client_name=client_name)
|
|
||||||
if errors:
|
|
||||||
raise InvalidRequest(errors, status_code=400)
|
|
||||||
|
|
||||||
status = request.form.get('status')
|
|
||||||
success, errors = process_sms_client_response(status=status,
|
|
||||||
reference=request.form.get('reference'),
|
|
||||||
client_name=client_name)
|
|
||||||
if errors:
|
|
||||||
raise InvalidRequest(errors, status_code=400)
|
|
||||||
else:
|
|
||||||
return jsonify(result='success', message=success), 200
|
|
||||||
|
|
||||||
|
|
||||||
@notifications.route('/notifications/<uuid:notification_id>', methods=['GET'])
|
@notifications.route('/notifications/<uuid:notification_id>', methods=['GET'])
|
||||||
def get_notification_by_id(notification_id):
|
def get_notification_by_id(notification_id):
|
||||||
notification = notifications_dao.get_notification_with_personalisation(str(api_user.service_id),
|
notification = notifications_dao.get_notification_with_personalisation(str(api_user.service_id),
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ from app.dao import notifications_dao
|
|||||||
from app.dao.provider_statistics_dao import get_fragment_count
|
from app.dao.provider_statistics_dao import get_fragment_count
|
||||||
from app.dao.users_dao import get_user_by_id
|
from app.dao.users_dao import get_user_by_id
|
||||||
from app.errors import (
|
from app.errors import (
|
||||||
InvalidRequest,
|
InvalidRequest, register_errors)
|
||||||
register_errors)
|
|
||||||
from app.service import statistics
|
from app.service import statistics
|
||||||
from app.service.utils import get_whitelist_objects
|
from app.service.utils import get_whitelist_objects
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
@@ -57,11 +56,8 @@ from app.schemas import (
|
|||||||
from app.utils import pagination_links, get_london_midnight_in_utc
|
from app.utils import pagination_links, get_london_midnight_in_utc
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
from app.authentication.auth import requires_admin_auth
|
|
||||||
|
|
||||||
service_blueprint = Blueprint('service', __name__)
|
service_blueprint = Blueprint('service', __name__)
|
||||||
|
|
||||||
service_blueprint.before_request(requires_admin_auth)
|
|
||||||
register_errors(service_blueprint)
|
register_errors(service_blueprint)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from app.dao.services_dao import dao_fetch_service_by_id
|
|||||||
from app.models import SMS_TYPE
|
from app.models import SMS_TYPE
|
||||||
from app.schemas import (template_schema, template_history_schema)
|
from app.schemas import (template_schema, template_history_schema)
|
||||||
|
|
||||||
template = Blueprint('template', __name__, url_prefix='/service/<uuid:service_id>/template')
|
template_blueprint = Blueprint('template', __name__, url_prefix='/service/<uuid:service_id>/template')
|
||||||
|
|
||||||
from app.errors import (
|
from app.errors import (
|
||||||
register_errors,
|
register_errors,
|
||||||
@@ -25,7 +25,7 @@ from app.errors import (
|
|||||||
)
|
)
|
||||||
from app.utils import get_template_instance
|
from app.utils import get_template_instance
|
||||||
|
|
||||||
register_errors(template)
|
register_errors(template_blueprint)
|
||||||
|
|
||||||
|
|
||||||
def _content_count_greater_than_limit(content, template_type):
|
def _content_count_greater_than_limit(content, template_type):
|
||||||
@@ -35,7 +35,7 @@ def _content_count_greater_than_limit(content, template_type):
|
|||||||
return template.content_count > current_app.config.get('SMS_CHAR_COUNT_LIMIT')
|
return template.content_count > current_app.config.get('SMS_CHAR_COUNT_LIMIT')
|
||||||
|
|
||||||
|
|
||||||
@template.route('', methods=['POST'])
|
@template_blueprint.route('', methods=['POST'])
|
||||||
def create_template(service_id):
|
def create_template(service_id):
|
||||||
fetched_service = dao_fetch_service_by_id(service_id=service_id)
|
fetched_service = dao_fetch_service_by_id(service_id=service_id)
|
||||||
new_template = template_schema.load(request.get_json()).data
|
new_template = template_schema.load(request.get_json()).data
|
||||||
@@ -51,7 +51,7 @@ def create_template(service_id):
|
|||||||
return jsonify(data=template_schema.dump(new_template).data), 201
|
return jsonify(data=template_schema.dump(new_template).data), 201
|
||||||
|
|
||||||
|
|
||||||
@template.route('/<uuid:template_id>', methods=['POST'])
|
@template_blueprint.route('/<uuid:template_id>', methods=['POST'])
|
||||||
def update_template(service_id, template_id):
|
def update_template(service_id, template_id):
|
||||||
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
||||||
|
|
||||||
@@ -73,21 +73,21 @@ def update_template(service_id, template_id):
|
|||||||
return jsonify(data=template_schema.dump(update_dict).data), 200
|
return jsonify(data=template_schema.dump(update_dict).data), 200
|
||||||
|
|
||||||
|
|
||||||
@template.route('', methods=['GET'])
|
@template_blueprint.route('', methods=['GET'])
|
||||||
def get_all_templates_for_service(service_id):
|
def get_all_templates_for_service(service_id):
|
||||||
templates = dao_get_all_templates_for_service(service_id=service_id)
|
templates = dao_get_all_templates_for_service(service_id=service_id)
|
||||||
data = template_schema.dump(templates, many=True).data
|
data = template_schema.dump(templates, many=True).data
|
||||||
return jsonify(data=data)
|
return jsonify(data=data)
|
||||||
|
|
||||||
|
|
||||||
@template.route('/<uuid:template_id>', methods=['GET'])
|
@template_blueprint.route('/<uuid:template_id>', methods=['GET'])
|
||||||
def get_template_by_id_and_service_id(service_id, template_id):
|
def get_template_by_id_and_service_id(service_id, template_id):
|
||||||
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
||||||
data = template_schema.dump(fetched_template).data
|
data = template_schema.dump(fetched_template).data
|
||||||
return jsonify(data=data)
|
return jsonify(data=data)
|
||||||
|
|
||||||
|
|
||||||
@template.route('/<uuid:template_id>/preview', methods=['GET'])
|
@template_blueprint.route('/<uuid:template_id>/preview', methods=['GET'])
|
||||||
def preview_template_by_id_and_service_id(service_id, template_id):
|
def preview_template_by_id_and_service_id(service_id, template_id):
|
||||||
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
||||||
data = template_schema.dump(fetched_template).data
|
data = template_schema.dump(fetched_template).data
|
||||||
@@ -106,7 +106,7 @@ def preview_template_by_id_and_service_id(service_id, template_id):
|
|||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
||||||
|
|
||||||
@template.route('/<uuid:template_id>/version/<int:version>')
|
@template_blueprint.route('/<uuid:template_id>/version/<int:version>')
|
||||||
def get_template_version(service_id, template_id, version):
|
def get_template_version(service_id, template_id, version):
|
||||||
data = template_history_schema.dump(
|
data = template_history_schema.dump(
|
||||||
dao_get_template_by_id_and_service_id(
|
dao_get_template_by_id_and_service_id(
|
||||||
@@ -118,7 +118,7 @@ def get_template_version(service_id, template_id, version):
|
|||||||
return jsonify(data=data)
|
return jsonify(data=data)
|
||||||
|
|
||||||
|
|
||||||
@template.route('/<uuid:template_id>/versions')
|
@template_blueprint.route('/<uuid:template_id>/versions')
|
||||||
def get_template_versions(service_id, template_id):
|
def get_template_versions(service_id, template_id):
|
||||||
data = template_history_schema.dump(
|
data = template_history_schema.dump(
|
||||||
dao_get_template_versions(service_id=service_id, template_id=template_id),
|
dao_get_template_versions(service_id=service_id, template_id=template_id),
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ from app.errors import (
|
|||||||
)
|
)
|
||||||
from app.utils import url_with_token
|
from app.utils import url_with_token
|
||||||
|
|
||||||
user = Blueprint('user', __name__)
|
user_blueprint = Blueprint('user', __name__)
|
||||||
register_errors(user)
|
register_errors(user_blueprint)
|
||||||
|
|
||||||
|
|
||||||
@user.route('', methods=['POST'])
|
@user_blueprint.route('', methods=['POST'])
|
||||||
def create_user():
|
def create_user():
|
||||||
user_to_create, errors = user_schema.load(request.get_json())
|
user_to_create, errors = user_schema.load(request.get_json())
|
||||||
req_json = request.get_json()
|
req_json = request.get_json()
|
||||||
@@ -56,7 +56,7 @@ def create_user():
|
|||||||
return jsonify(data=user_schema.dump(user_to_create).data), 201
|
return jsonify(data=user_schema.dump(user_to_create).data), 201
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>', methods=['PUT'])
|
@user_blueprint.route('/<uuid:user_id>', methods=['PUT'])
|
||||||
def update_user(user_id):
|
def update_user(user_id):
|
||||||
user_to_update = get_user_by_id(user_id=user_id)
|
user_to_update = get_user_by_id(user_id=user_id)
|
||||||
req_json = request.get_json()
|
req_json = request.get_json()
|
||||||
@@ -73,7 +73,7 @@ def update_user(user_id):
|
|||||||
return jsonify(data=user_schema.dump(user_to_update).data), 200
|
return jsonify(data=user_schema.dump(user_to_update).data), 200
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>', methods=['POST'])
|
||||||
def update_user_attribute(user_id):
|
def update_user_attribute(user_id):
|
||||||
user_to_update = get_user_by_id(user_id=user_id)
|
user_to_update = get_user_by_id(user_id=user_id)
|
||||||
req_json = request.get_json()
|
req_json = request.get_json()
|
||||||
@@ -84,14 +84,14 @@ def update_user_attribute(user_id):
|
|||||||
return jsonify(data=user_schema.dump(user_to_update).data), 200
|
return jsonify(data=user_schema.dump(user_to_update).data), 200
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/reset-failed-login-count', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/reset-failed-login-count', methods=['POST'])
|
||||||
def user_reset_failed_login_count(user_id):
|
def user_reset_failed_login_count(user_id):
|
||||||
user_to_update = get_user_by_id(user_id=user_id)
|
user_to_update = get_user_by_id(user_id=user_id)
|
||||||
reset_failed_login_count(user_to_update)
|
reset_failed_login_count(user_to_update)
|
||||||
return jsonify(data=user_schema.dump(user_to_update).data), 200
|
return jsonify(data=user_schema.dump(user_to_update).data), 200
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/verify/password', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/verify/password', methods=['POST'])
|
||||||
def verify_user_password(user_id):
|
def verify_user_password(user_id):
|
||||||
user_to_verify = get_user_by_id(user_id=user_id)
|
user_to_verify = get_user_by_id(user_id=user_id)
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ def verify_user_password(user_id):
|
|||||||
raise InvalidRequest(errors, status_code=400)
|
raise InvalidRequest(errors, status_code=400)
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/verify/code', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/verify/code', methods=['POST'])
|
||||||
def verify_user_code(user_id):
|
def verify_user_code(user_id):
|
||||||
user_to_verify = get_user_by_id(user_id=user_id)
|
user_to_verify = get_user_by_id(user_id=user_id)
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ def verify_user_code(user_id):
|
|||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/sms-code', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/sms-code', methods=['POST'])
|
||||||
def send_user_sms_code(user_id):
|
def send_user_sms_code(user_id):
|
||||||
user_to_send_to = get_user_by_id(user_id=user_id)
|
user_to_send_to = get_user_by_id(user_id=user_id)
|
||||||
verify_code, errors = request_verify_code_schema.load(request.get_json())
|
verify_code, errors = request_verify_code_schema.load(request.get_json())
|
||||||
@@ -187,7 +187,7 @@ def send_user_sms_code(user_id):
|
|||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/change-email-verification', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/change-email-verification', methods=['POST'])
|
||||||
def send_user_confirm_new_email(user_id):
|
def send_user_confirm_new_email(user_id):
|
||||||
user_to_send_to = get_user_by_id(user_id=user_id)
|
user_to_send_to = get_user_by_id(user_id=user_id)
|
||||||
email, errors = email_data_request_schema.load(request.get_json())
|
email, errors = email_data_request_schema.load(request.get_json())
|
||||||
@@ -216,7 +216,7 @@ def send_user_confirm_new_email(user_id):
|
|||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/email-verification', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/email-verification', methods=['POST'])
|
||||||
def send_user_email_verification(user_id):
|
def send_user_email_verification(user_id):
|
||||||
user_to_send_to = get_user_by_id(user_id=user_id)
|
user_to_send_to = get_user_by_id(user_id=user_id)
|
||||||
secret_code = create_secret_code()
|
secret_code = create_secret_code()
|
||||||
@@ -244,7 +244,7 @@ def send_user_email_verification(user_id):
|
|||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/email-already-registered', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/email-already-registered', methods=['POST'])
|
||||||
def send_already_registered_email(user_id):
|
def send_already_registered_email(user_id):
|
||||||
to, errors = email_data_request_schema.load(request.get_json())
|
to, errors = email_data_request_schema.load(request.get_json())
|
||||||
template = dao_get_template_by_id(current_app.config['ALREADY_REGISTERED_EMAIL_TEMPLATE_ID'])
|
template = dao_get_template_by_id(current_app.config['ALREADY_REGISTERED_EMAIL_TEMPLATE_ID'])
|
||||||
@@ -270,15 +270,15 @@ def send_already_registered_email(user_id):
|
|||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>', methods=['GET'])
|
@user_blueprint.route('/<uuid:user_id>', methods=['GET'])
|
||||||
@user.route('', methods=['GET'])
|
@user_blueprint.route('', methods=['GET'])
|
||||||
def get_user(user_id=None):
|
def get_user(user_id=None):
|
||||||
users = get_user_by_id(user_id=user_id)
|
users = get_user_by_id(user_id=user_id)
|
||||||
result = user_schema.dump(users, many=True) if isinstance(users, list) else user_schema.dump(users)
|
result = user_schema.dump(users, many=True) if isinstance(users, list) else user_schema.dump(users)
|
||||||
return jsonify(data=result.data)
|
return jsonify(data=result.data)
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/service/<uuid:service_id>/permission', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/service/<uuid:service_id>/permission', methods=['POST'])
|
||||||
def set_permissions(user_id, service_id):
|
def set_permissions(user_id, service_id):
|
||||||
# TODO fix security hole, how do we verify that the user
|
# TODO fix security hole, how do we verify that the user
|
||||||
# who is making this request has permission to make the request.
|
# who is making this request has permission to make the request.
|
||||||
@@ -293,7 +293,7 @@ def set_permissions(user_id, service_id):
|
|||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
@user.route('/email', methods=['GET'])
|
@user_blueprint.route('/email', methods=['GET'])
|
||||||
def get_by_email():
|
def get_by_email():
|
||||||
email = request.args.get('email')
|
email = request.args.get('email')
|
||||||
if not email:
|
if not email:
|
||||||
@@ -305,7 +305,7 @@ def get_by_email():
|
|||||||
return jsonify(data=result.data)
|
return jsonify(data=result.data)
|
||||||
|
|
||||||
|
|
||||||
@user.route('/reset-password', methods=['POST'])
|
@user_blueprint.route('/reset-password', methods=['POST'])
|
||||||
def send_user_reset_password():
|
def send_user_reset_password():
|
||||||
email, errors = email_data_request_schema.load(request.get_json())
|
email, errors = email_data_request_schema.load(request.get_json())
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ def send_user_reset_password():
|
|||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
@user.route('/<uuid:user_id>/update-password', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/update-password', methods=['POST'])
|
||||||
def update_password(user_id):
|
def update_password(user_id):
|
||||||
user = get_user_by_id(user_id=user_id)
|
user = get_user_by_id(user_id=user_id)
|
||||||
req_json = request.get_json()
|
req_json = request.get_json()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
from app.v2.errors import register_errors
|
from app.v2.errors import register_errors
|
||||||
|
|
||||||
notification_blueprint = Blueprint("v2_notifications", __name__, url_prefix='/v2/notifications')
|
v2_notification_blueprint = Blueprint("v2_notifications", __name__, url_prefix='/v2/notifications')
|
||||||
|
|
||||||
register_errors(notification_blueprint)
|
register_errors(v2_notification_blueprint)
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ from werkzeug.exceptions import abort
|
|||||||
from app import api_user
|
from app import api_user
|
||||||
from app.dao import notifications_dao
|
from app.dao import notifications_dao
|
||||||
from app.schema_validation import validate
|
from app.schema_validation import validate
|
||||||
from app.v2.notifications import notification_blueprint
|
from app.v2.notifications import v2_notification_blueprint
|
||||||
from app.v2.notifications.notification_schemas import get_notifications_request
|
from app.v2.notifications.notification_schemas import get_notifications_request
|
||||||
|
|
||||||
|
|
||||||
@notification_blueprint.route("/<id>", methods=['GET'])
|
@v2_notification_blueprint.route("/<id>", methods=['GET'])
|
||||||
def get_notification_by_id(id):
|
def get_notification_by_id(id):
|
||||||
try:
|
try:
|
||||||
casted_id = uuid.UUID(id)
|
casted_id = uuid.UUID(id)
|
||||||
@@ -23,7 +23,7 @@ def get_notification_by_id(id):
|
|||||||
return jsonify(notification.serialize()), 200
|
return jsonify(notification.serialize()), 200
|
||||||
|
|
||||||
|
|
||||||
@notification_blueprint.route("", methods=['GET'])
|
@v2_notification_blueprint.route("", methods=['GET'])
|
||||||
def get_notifications():
|
def get_notifications():
|
||||||
_data = request.args.to_dict(flat=False)
|
_data = request.args.to_dict(flat=False)
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ from app.notifications.validators import (check_service_message_limit,
|
|||||||
validate_and_format_recipient)
|
validate_and_format_recipient)
|
||||||
from app.schema_validation import validate
|
from app.schema_validation import validate
|
||||||
from app.v2.errors import BadRequestError
|
from app.v2.errors import BadRequestError
|
||||||
from app.v2.notifications import notification_blueprint
|
from app.v2.notifications import v2_notification_blueprint
|
||||||
from app.v2.notifications.notification_schemas import (post_sms_request,
|
from app.v2.notifications.notification_schemas import (post_sms_request,
|
||||||
create_post_sms_response_from_notification, post_email_request,
|
create_post_sms_response_from_notification, post_email_request,
|
||||||
create_post_email_response_from_notification)
|
create_post_email_response_from_notification)
|
||||||
|
|
||||||
|
|
||||||
@notification_blueprint.route('/<notification_type>', methods=['POST'])
|
@v2_notification_blueprint.route('/<notification_type>', methods=['POST'])
|
||||||
def post_notification(notification_type):
|
def post_notification(notification_type):
|
||||||
if notification_type == EMAIL_TYPE:
|
if notification_type == EMAIL_TYPE:
|
||||||
form = validate(request.get_json(), post_email_request)
|
form = validate(request.get_json(), post_email_request)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ def test_should_not_allow_request_with_incorrect_token(client, url):
|
|||||||
response = client.get(url, headers={'Authorization': 'Bearer 1234'})
|
response = client.get(url, headers={'Authorization': 'Bearer 1234'})
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
data = json.loads(response.get_data())
|
data = json.loads(response.get_data())
|
||||||
assert data['message'] == {"token": ['Invalid token: signature']}
|
assert data['message'] == {"token": ['Invalid token: signature, api token is not valid']}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('url', ['/service', '/notifications'])
|
@pytest.mark.parametrize('url', ['/service', '/notifications'])
|
||||||
@@ -59,8 +59,14 @@ def test_should_not_allow_request_with_no_iss(client, url):
|
|||||||
assert data['message'] == {"token": ['Invalid token: iss field not provided']}
|
assert data['message'] == {"token": ['Invalid token: iss field not provided']}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('url', ['/service', '/notifications'])
|
@pytest.mark.parametrize('url, auth_method',
|
||||||
def test_should_not_allow_request_with_no_iat(client, sample_api_key, url):
|
[('/service', 'requires_admin_auth'),
|
||||||
|
('/notifications', 'requires_auth')])
|
||||||
|
def test_should_not_allow_request_with_no_iat(client, sample_api_key, url, auth_method):
|
||||||
|
if auth_method == 'requires_admin_auth':
|
||||||
|
iss = current_app.config['ADMIN_CLIENT_USER_NAME']
|
||||||
|
if auth_method == 'requires_auth':
|
||||||
|
iss = str(sample_api_key.service_id)
|
||||||
# code copied from notifications_python_client.authentication.py::create_jwt_token
|
# code copied from notifications_python_client.authentication.py::create_jwt_token
|
||||||
headers = {
|
headers = {
|
||||||
"typ": 'JWT',
|
"typ": 'JWT',
|
||||||
@@ -68,7 +74,7 @@ def test_should_not_allow_request_with_no_iat(client, sample_api_key, url):
|
|||||||
}
|
}
|
||||||
|
|
||||||
claims = {
|
claims = {
|
||||||
'iss': str(sample_api_key.service_id)
|
'iss': iss
|
||||||
# 'iat': not provided
|
# 'iat': not provided
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,13 +86,12 @@ def test_should_not_allow_request_with_no_iat(client, sample_api_key, url):
|
|||||||
assert data['message'] == {"token": ['Invalid token: signature, api token is not valid']}
|
assert data['message'] == {"token": ['Invalid token: signature, api token is not valid']}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('url', ['/service', '/notifications'])
|
def test_should_not_allow_invalid_secret(client, sample_api_key):
|
||||||
def test_should_not_allow_invalid_secret(client, sample_api_key, url):
|
|
||||||
token = create_jwt_token(
|
token = create_jwt_token(
|
||||||
secret="not-so-secret",
|
secret="not-so-secret",
|
||||||
client_id=str(sample_api_key.service_id))
|
client_id=str(sample_api_key.service_id))
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url,
|
'/notifications',
|
||||||
headers={'Authorization': "Bearer {}".format(token)}
|
headers={'Authorization': "Bearer {}".format(token)}
|
||||||
)
|
)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
@@ -101,12 +106,11 @@ def test_should_allow_valid_token(client, sample_api_key, scheme):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('url', ['/service', '/notifications'])
|
def test_should_not_allow_service_id_that_is_not_the_wrong_data_type(client, sample_api_key):
|
||||||
def test_should_not_allow_service_id_that_is_not_the_wrong_data_type(client, sample_api_key, url):
|
|
||||||
token = create_jwt_token(secret=get_unsigned_secrets(sample_api_key.service_id)[0],
|
token = create_jwt_token(secret=get_unsigned_secrets(sample_api_key.service_id)[0],
|
||||||
client_id=str('not-a-valid-id'))
|
client_id=str('not-a-valid-id'))
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url,
|
'/notifications',
|
||||||
headers={'Authorization': "Bearer {}".format(token)}
|
headers={'Authorization': "Bearer {}".format(token)}
|
||||||
)
|
)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
@@ -208,7 +212,23 @@ def test_authentication_returns_error_when_admin_client_has_no_secrets(client):
|
|||||||
headers={'Authorization': 'Bearer {}'.format(token)})
|
headers={'Authorization': 'Bearer {}'.format(token)})
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
error_message = json.loads(response.get_data())
|
error_message = json.loads(response.get_data())
|
||||||
assert error_message['message'] == {"token": ['Invalid token: signature']}
|
assert error_message['message'] == {"token": ["Invalid token: signature, api token is not valid"]}
|
||||||
|
current_app.config['ADMIN_CLIENT_SECRET'] = api_secret
|
||||||
|
|
||||||
|
|
||||||
|
def test_authentication_returns_error_when_admin_client_secret_is_invalid(client):
|
||||||
|
api_secret = current_app.config.get('ADMIN_CLIENT_SECRET')
|
||||||
|
token = create_jwt_token(
|
||||||
|
secret=api_secret,
|
||||||
|
client_id=current_app.config.get('ADMIN_CLIENT_USER_NAME')
|
||||||
|
)
|
||||||
|
current_app.config['ADMIN_CLIENT_SECRET'] = 'something-wrong'
|
||||||
|
response = client.get(
|
||||||
|
'/service',
|
||||||
|
headers={'Authorization': 'Bearer {}'.format(token)})
|
||||||
|
assert response.status_code == 403
|
||||||
|
error_message = json.loads(response.get_data())
|
||||||
|
assert error_message['message'] == {"token": ["Invalid token: signature, api token is not valid"]}
|
||||||
current_app.config['ADMIN_CLIENT_SECRET'] = api_secret
|
current_app.config['ADMIN_CLIENT_SECRET'] = api_secret
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ from app.dao.templates_dao import dao_update_template
|
|||||||
from app.models import NOTIFICATION_STATUS_TYPES, JOB_STATUS_TYPES, JOB_STATUS_PENDING
|
from app.models import NOTIFICATION_STATUS_TYPES, JOB_STATUS_TYPES, JOB_STATUS_PENDING
|
||||||
|
|
||||||
|
|
||||||
def test_get_job_with_invalid_service_id_returns404(notify_api, sample_api_key, sample_service):
|
def test_get_job_with_invalid_service_id_returns404(notify_api, sample_service):
|
||||||
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:
|
||||||
path = '/service/{}/job'.format(sample_service.id)
|
path = '/service/{}/job'.format(sample_service.id)
|
||||||
auth_header = create_authorization_header(service_id=sample_service.id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -33,7 +33,7 @@ def test_get_job_with_invalid_job_id_returns404(notify_api, sample_template):
|
|||||||
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:
|
||||||
path = '/service/{}/job/{}'.format(service_id, "bad-id")
|
path = '/service/{}/job/{}'.format(service_id, "bad-id")
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -46,7 +46,7 @@ def test_get_job_with_unknown_id_returns404(notify_api, sample_template, fake_uu
|
|||||||
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:
|
||||||
path = '/service/{}/job/{}'.format(service_id, fake_uuid)
|
path = '/service/{}/job/{}'.format(service_id, fake_uuid)
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -62,7 +62,7 @@ def test_get_job_by_id(notify_api, sample_job):
|
|||||||
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:
|
||||||
path = '/service/{}/job/{}'.format(service_id, job_id)
|
path = '/service/{}/job/{}'.format(service_id, job_id)
|
||||||
auth_header = create_authorization_header(service_id=sample_job.service.id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -75,7 +75,7 @@ def test_cancel_job(notify_api, sample_scheduled_job):
|
|||||||
service_id = sample_scheduled_job.service.id
|
service_id = sample_scheduled_job.service.id
|
||||||
with notify_api.test_request_context(), notify_api.test_client() as client:
|
with notify_api.test_request_context(), notify_api.test_client() as client:
|
||||||
path = '/service/{}/job/{}/cancel'.format(service_id, job_id)
|
path = '/service/{}/job/{}/cancel'.format(service_id, job_id)
|
||||||
auth_header = create_authorization_header(service_id=service_id)
|
auth_header = create_authorization_header()
|
||||||
response = client.post(path, headers=[auth_header])
|
response = client.post(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -89,7 +89,7 @@ def test_cant_cancel_normal_job(notify_api, sample_job, mocker):
|
|||||||
with notify_api.test_request_context(), notify_api.test_client() as client:
|
with notify_api.test_request_context(), notify_api.test_client() as client:
|
||||||
mock_update = mocker.patch('app.dao.jobs_dao.dao_update_job')
|
mock_update = mocker.patch('app.dao.jobs_dao.dao_update_job')
|
||||||
path = '/service/{}/job/{}/cancel'.format(service_id, job_id)
|
path = '/service/{}/job/{}/cancel'.format(service_id, job_id)
|
||||||
auth_header = create_authorization_header(service_id=service_id)
|
auth_header = create_authorization_header()
|
||||||
response = client.post(path, headers=[auth_header])
|
response = client.post(path, headers=[auth_header])
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
assert mock_update.call_count == 0
|
assert mock_update.call_count == 0
|
||||||
@@ -108,7 +108,7 @@ def test_create_unscheduled_job(notify_api, sample_template, mocker, fake_uuid):
|
|||||||
'created_by': str(sample_template.created_by.id)
|
'created_by': str(sample_template.created_by.id)
|
||||||
}
|
}
|
||||||
path = '/service/{}/job'.format(sample_template.service.id)
|
path = '/service/{}/job'.format(sample_template.service.id)
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
headers = [('Content-Type', 'application/json'), auth_header]
|
headers = [('Content-Type', 'application/json'), auth_header]
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
@@ -149,7 +149,7 @@ def test_create_scheduled_job(notify_api, sample_template, mocker, fake_uuid):
|
|||||||
'scheduled_for': scheduled_date
|
'scheduled_for': scheduled_date
|
||||||
}
|
}
|
||||||
path = '/service/{}/job'.format(sample_template.service.id)
|
path = '/service/{}/job'.format(sample_template.service.id)
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
headers = [('Content-Type', 'application/json'), auth_header]
|
headers = [('Content-Type', 'application/json'), auth_header]
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
@@ -202,7 +202,7 @@ def test_should_not_create_scheduled_job_more_then_24_hours_hence(notify_api, sa
|
|||||||
'scheduled_for': scheduled_date
|
'scheduled_for': scheduled_date
|
||||||
}
|
}
|
||||||
path = '/service/{}/job'.format(sample_template.service.id)
|
path = '/service/{}/job'.format(sample_template.service.id)
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
headers = [('Content-Type', 'application/json'), auth_header]
|
headers = [('Content-Type', 'application/json'), auth_header]
|
||||||
|
|
||||||
print(json.dumps(data))
|
print(json.dumps(data))
|
||||||
@@ -237,7 +237,7 @@ def test_should_not_create_scheduled_job_in_the_past(notify_api, sample_template
|
|||||||
'scheduled_for': scheduled_date
|
'scheduled_for': scheduled_date
|
||||||
}
|
}
|
||||||
path = '/service/{}/job'.format(sample_template.service.id)
|
path = '/service/{}/job'.format(sample_template.service.id)
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
headers = [('Content-Type', 'application/json'), auth_header]
|
headers = [('Content-Type', 'application/json'), auth_header]
|
||||||
|
|
||||||
print(json.dumps(data))
|
print(json.dumps(data))
|
||||||
@@ -263,7 +263,7 @@ def test_create_job_returns_400_if_missing_data(notify_api, sample_template, moc
|
|||||||
'template': str(sample_template.id)
|
'template': str(sample_template.id)
|
||||||
}
|
}
|
||||||
path = '/service/{}/job'.format(sample_template.service.id)
|
path = '/service/{}/job'.format(sample_template.service.id)
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
headers = [('Content-Type', 'application/json'), auth_header]
|
headers = [('Content-Type', 'application/json'), auth_header]
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path,
|
path,
|
||||||
@@ -288,7 +288,7 @@ def test_create_job_returns_404_if_template_does_not_exist(notify_api, sample_se
|
|||||||
'template': str(sample_service.id)
|
'template': str(sample_service.id)
|
||||||
}
|
}
|
||||||
path = '/service/{}/job'.format(sample_service.id)
|
path = '/service/{}/job'.format(sample_service.id)
|
||||||
auth_header = create_authorization_header(service_id=sample_service.id)
|
auth_header = create_authorization_header()
|
||||||
headers = [('Content-Type', 'application/json'), auth_header]
|
headers = [('Content-Type', 'application/json'), auth_header]
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path,
|
path,
|
||||||
@@ -310,7 +310,7 @@ def test_create_job_returns_404_if_missing_service(notify_api, sample_template,
|
|||||||
random_id = str(uuid.uuid4())
|
random_id = str(uuid.uuid4())
|
||||||
data = {'template': str(sample_template.id)}
|
data = {'template': str(sample_template.id)}
|
||||||
path = '/service/{}/job'.format(random_id)
|
path = '/service/{}/job'.format(random_id)
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
headers = [('Content-Type', 'application/json'), auth_header]
|
headers = [('Content-Type', 'application/json'), auth_header]
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path,
|
path,
|
||||||
@@ -335,7 +335,7 @@ def test_create_job_returns_400_if_archived_template(notify_api, sample_template
|
|||||||
'template': str(sample_template.id)
|
'template': str(sample_template.id)
|
||||||
}
|
}
|
||||||
path = '/service/{}/job'.format(sample_template.service.id)
|
path = '/service/{}/job'.format(sample_template.service.id)
|
||||||
auth_header = create_authorization_header(service_id=sample_template.service.id)
|
auth_header = create_authorization_header()
|
||||||
headers = [('Content-Type', 'application/json'), auth_header]
|
headers = [('Content-Type', 'application/json'), auth_header]
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path,
|
path,
|
||||||
@@ -455,7 +455,7 @@ def test_get_job_by_id(notify_api, sample_job):
|
|||||||
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:
|
||||||
path = '/service/{}/job/{}'.format(service_id, job_id)
|
path = '/service/{}/job/{}'.format(service_id, job_id)
|
||||||
auth_header = create_authorization_header(service_id=sample_job.service.id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -480,7 +480,7 @@ def test_get_job_by_id_should_return_statistics(notify_db, notify_db_session, no
|
|||||||
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:
|
||||||
path = '/service/{}/job/{}'.format(service_id, job_id)
|
path = '/service/{}/job/{}'.format(service_id, job_id)
|
||||||
auth_header = create_authorization_header(service_id=sample_job.service.id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -514,7 +514,7 @@ def test_get_job_by_id_should_return_summed_statistics(notify_db, notify_db_sess
|
|||||||
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:
|
||||||
path = '/service/{}/job/{}'.format(service_id, job_id)
|
path = '/service/{}/job/{}'.format(service_id, job_id)
|
||||||
auth_header = create_authorization_header(service_id=sample_job.service.id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -535,7 +535,7 @@ def test_get_jobs(notify_api, notify_db, notify_db_session, sample_template):
|
|||||||
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:
|
||||||
path = '/service/{}/job'.format(service_id)
|
path = '/service/{}/job'.format(service_id)
|
||||||
auth_header = create_authorization_header(service_id=service_id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -561,7 +561,7 @@ def test_get_jobs_with_limit_days(notify_api, notify_db, notify_db_session, samp
|
|||||||
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:
|
||||||
path = '/service/{}/job'.format(service_id)
|
path = '/service/{}/job'.format(service_id)
|
||||||
auth_header = create_authorization_header(service_id=service_id)
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header], query_string={'limit_days': 5})
|
response = client.get(path, headers=[auth_header], query_string={'limit_days': 5})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -584,7 +584,7 @@ def test_get_jobs_should_return_statistics(notify_db, notify_db_session, notify_
|
|||||||
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:
|
||||||
path = '/service/{}/job'.format(sample_service.id)
|
path = '/service/{}/job'.format(sample_service.id)
|
||||||
auth_header = create_authorization_header(service_id=str(sample_service.id))
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -609,7 +609,7 @@ def test_get_jobs_should_return_no_stats_if_no_rows_in_notifications(
|
|||||||
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:
|
||||||
path = '/service/{}/job'.format(sample_service.id)
|
path = '/service/{}/job'.format(sample_service.id)
|
||||||
auth_header = create_authorization_header(service_id=str(sample_service.id))
|
auth_header = create_authorization_header()
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
resp_json = json.loads(response.get_data(as_text=True))
|
resp_json = json.loads(response.get_data(as_text=True))
|
||||||
@@ -629,7 +629,7 @@ def test_get_jobs_should_paginate(
|
|||||||
create_10_jobs(notify_db, notify_db_session, sample_template.service, sample_template)
|
create_10_jobs(notify_db, notify_db_session, sample_template.service, sample_template)
|
||||||
|
|
||||||
path = '/service/{}/job'.format(sample_template.service_id)
|
path = '/service/{}/job'.format(sample_template.service_id)
|
||||||
auth_header = create_authorization_header(service_id=str(sample_template.service_id))
|
auth_header = create_authorization_header()
|
||||||
|
|
||||||
with set_config(client.application, 'PAGE_SIZE', 2):
|
with set_config(client.application, 'PAGE_SIZE', 2):
|
||||||
response = client.get(path, headers=[auth_header])
|
response = client.get(path, headers=[auth_header])
|
||||||
@@ -654,7 +654,7 @@ def test_get_jobs_accepts_page_parameter(
|
|||||||
create_10_jobs(notify_db, notify_db_session, sample_template.service, sample_template)
|
create_10_jobs(notify_db, notify_db_session, sample_template.service, sample_template)
|
||||||
|
|
||||||
path = '/service/{}/job'.format(sample_template.service_id)
|
path = '/service/{}/job'.format(sample_template.service_id)
|
||||||
auth_header = create_authorization_header(service_id=str(sample_template.service_id))
|
auth_header = create_authorization_header()
|
||||||
|
|
||||||
with set_config(client.application, 'PAGE_SIZE', 2):
|
with set_config(client.application, 'PAGE_SIZE', 2):
|
||||||
response = client.get(path, headers=[auth_header], query_string={'page': 2})
|
response = client.get(path, headers=[auth_header], query_string={'page': 2})
|
||||||
|
|||||||
@@ -399,230 +399,6 @@ def test_mmg_callback_returns_200_when_notification_id_not_found_or_already_upda
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_not_need_auth(client):
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_notification_callback(),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
assert response.status_code == 404
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_fail_if_invalid_json(client):
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data="nonsense",
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
|
||||||
assert response.status_code == 400
|
|
||||||
assert json_resp['result'] == 'error'
|
|
||||||
assert json_resp['message'] == 'SES callback failed: invalid json'
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_fail_if_invalid_notification_type(client):
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_invalid_notification_type_callback(),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
|
||||||
assert response.status_code == 400
|
|
||||||
assert json_resp['result'] == 'error'
|
|
||||||
assert json_resp['message'] == 'SES callback failed: status Unknown not found'
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_fail_if_missing_message_id(client):
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_missing_notification_id_callback(),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
|
||||||
assert response.status_code == 400
|
|
||||||
assert json_resp['result'] == 'error'
|
|
||||||
assert json_resp['message'] == 'SES callback failed: messageId missing'
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_fail_if_notification_cannot_be_found(notify_db, notify_db_session, client):
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_invalid_notification_id_callback(),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
|
||||||
assert response.status_code == 404
|
|
||||||
assert json_resp['result'] == 'error'
|
|
||||||
assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status delivered for notification reference missing' # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_update_notification_status(
|
|
||||||
client,
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
sample_email_template,
|
|
||||||
mocker):
|
|
||||||
with freeze_time('2001-01-01T12:00:00'):
|
|
||||||
mocker.patch('app.statsd_client.incr')
|
|
||||||
mocker.patch('app.statsd_client.timing_with_dates')
|
|
||||||
notification = create_sample_notification(
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
template=sample_email_template,
|
|
||||||
reference='ref',
|
|
||||||
status='sending',
|
|
||||||
sent_at=datetime.utcnow()
|
|
||||||
)
|
|
||||||
|
|
||||||
assert get_notification_by_id(notification.id).status == 'sending'
|
|
||||||
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_notification_callback(),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert json_resp['result'] == 'success'
|
|
||||||
assert json_resp['message'] == 'SES callback succeeded'
|
|
||||||
assert get_notification_by_id(notification.id).status == 'delivered'
|
|
||||||
app.statsd_client.timing_with_dates.assert_any_call(
|
|
||||||
"callback.ses.elapsed-time", datetime.utcnow(), notification.sent_at
|
|
||||||
)
|
|
||||||
app.statsd_client.incr.assert_any_call("callback.ses.delivered")
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_update_multiple_notification_status_sent(
|
|
||||||
client,
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
sample_email_template,
|
|
||||||
mocker):
|
|
||||||
notification1 = create_sample_notification(
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
template=sample_email_template,
|
|
||||||
reference='ref1',
|
|
||||||
sent_at=datetime.utcnow(),
|
|
||||||
status='sending')
|
|
||||||
|
|
||||||
notification2 = create_sample_notification(
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
template=sample_email_template,
|
|
||||||
reference='ref2',
|
|
||||||
sent_at=datetime.utcnow(),
|
|
||||||
status='sending')
|
|
||||||
|
|
||||||
notification3 = create_sample_notification(
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
template=sample_email_template,
|
|
||||||
reference='ref3',
|
|
||||||
sent_at=datetime.utcnow(),
|
|
||||||
status='sending')
|
|
||||||
|
|
||||||
resp1 = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_notification_callback(ref='ref1'),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
resp2 = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_notification_callback(ref='ref2'),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
resp3 = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_notification_callback(ref='ref3'),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
|
|
||||||
assert resp1.status_code == 200
|
|
||||||
assert resp2.status_code == 200
|
|
||||||
assert resp3.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_set_status_to_temporary_failure(client,
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
sample_email_template):
|
|
||||||
notification = create_sample_notification(
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
template=sample_email_template,
|
|
||||||
reference='ref',
|
|
||||||
status='sending',
|
|
||||||
sent_at=datetime.utcnow()
|
|
||||||
)
|
|
||||||
assert get_notification_by_id(notification.id).status == 'sending'
|
|
||||||
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_soft_bounce_callback(),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert json_resp['result'] == 'success'
|
|
||||||
assert json_resp['message'] == 'SES callback succeeded'
|
|
||||||
assert get_notification_by_id(notification.id).status == 'temporary-failure'
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_not_set_status_once_status_is_delivered(client,
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
sample_email_template):
|
|
||||||
notification = create_sample_notification(
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
template=sample_email_template,
|
|
||||||
reference='ref',
|
|
||||||
status='delivered',
|
|
||||||
sent_at=datetime.utcnow()
|
|
||||||
)
|
|
||||||
|
|
||||||
assert get_notification_by_id(notification.id).status == 'delivered'
|
|
||||||
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_soft_bounce_callback(),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
|
||||||
assert response.status_code == 404
|
|
||||||
assert json_resp['result'] == 'error'
|
|
||||||
assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status temporary-failure for notification reference ref' # noqa
|
|
||||||
assert get_notification_by_id(notification.id).status == 'delivered'
|
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_set_status_to_permanent_failure(client,
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
sample_email_template):
|
|
||||||
notification = create_sample_notification(
|
|
||||||
notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
template=sample_email_template,
|
|
||||||
reference='ref',
|
|
||||||
status='sending',
|
|
||||||
sent_at=datetime.utcnow()
|
|
||||||
)
|
|
||||||
|
|
||||||
assert get_notification_by_id(notification.id).status == 'sending'
|
|
||||||
|
|
||||||
response = client.post(
|
|
||||||
path='/notifications/email/ses',
|
|
||||||
data=ses_hard_bounce_callback(),
|
|
||||||
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
|
||||||
)
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert json_resp['result'] == 'success'
|
|
||||||
assert json_resp['message'] == 'SES callback succeeded'
|
|
||||||
assert get_notification_by_id(notification.id).status == 'permanent-failure'
|
|
||||||
|
|
||||||
|
|
||||||
def test_process_mmg_response_records_statsd(notify_db, notify_db_session, client, mocker):
|
def test_process_mmg_response_records_statsd(notify_db, notify_db_session, client, mocker):
|
||||||
with freeze_time('2001-01-01T12:00:00'):
|
with freeze_time('2001-01-01T12:00:00'):
|
||||||
|
|
||||||
@@ -670,31 +446,5 @@ def test_firetext_callback_should_record_statsd(client, notify_db, notify_db_ses
|
|||||||
app.statsd_client.incr.assert_any_call("callback.firetext.delivered")
|
app.statsd_client.incr.assert_any_call("callback.firetext.delivered")
|
||||||
|
|
||||||
|
|
||||||
def ses_notification_callback(ref='ref'):
|
|
||||||
return str.encode(
|
|
||||||
'{\n "Type" : "Notification",\n "MessageId" : "%(ref)s",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Delivery\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"messageId\\":\\"%(ref)s\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' % {'ref': ref} # noqa
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ses_invalid_notification_id_callback():
|
|
||||||
return b'{\n "Type" : "Notification",\n "MessageId" : "missing",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Delivery\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"messageId\\":\\"missing\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def ses_missing_notification_id_callback():
|
|
||||||
return b'{\n "Type" : "Notification",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Delivery\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def ses_invalid_notification_type_callback():
|
|
||||||
return b'{\n "Type" : "Notification",\n "MessageId" : "ref",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Unknown\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def ses_hard_bounce_callback():
|
|
||||||
return b'{\n "Type" : "Notification",\n "MessageId" : "ref",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Bounce\\",\\"bounce\\":{\\"bounceType\\":\\"Permanent\\",\\"bounceSubType\\":\\"General\\"}, \\"mail\\":{\\"messageId\\":\\"ref\\",\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def ses_soft_bounce_callback():
|
|
||||||
return b'{\n "Type" : "Notification",\n "MessageId" : "ref",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Bounce\\",\\"bounce\\":{\\"bounceType\\":\\"Undetermined\\",\\"bounceSubType\\":\\"General\\"}, \\"mail\\":{\\"messageId\\":\\"ref\\",\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def get_notification_stats(service_id):
|
def get_notification_stats(service_id):
|
||||||
return NotificationStatistics.query.filter_by(service_id=service_id).one()
|
return NotificationStatistics.query.filter_by(service_id=service_id).one()
|
||||||
|
|||||||
258
tests/app/notifications/test_notifications_ses_callback.py
Normal file
258
tests/app/notifications/test_notifications_ses_callback.py
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from flask import json
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
from app import statsd_client
|
||||||
|
from app.dao.notifications_dao import get_notification_by_id
|
||||||
|
from tests.app.conftest import sample_notification as create_sample_notification
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_not_need_auth(client):
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_notification_callback(),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_fail_if_invalid_json(client):
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data="nonsense",
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert json_resp['result'] == 'error'
|
||||||
|
assert json_resp['message'] == 'SES callback failed: invalid json'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_fail_if_invalid_notification_type(client):
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_invalid_notification_type_callback(),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert json_resp['result'] == 'error'
|
||||||
|
assert json_resp['message'] == 'SES callback failed: status Unknown not found'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_fail_if_missing_message_id(client):
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_missing_notification_id_callback(),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert json_resp['result'] == 'error'
|
||||||
|
assert json_resp['message'] == 'SES callback failed: messageId missing'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_fail_if_notification_cannot_be_found(notify_db, notify_db_session, client):
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_invalid_notification_id_callback(),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert json_resp['result'] == 'error'
|
||||||
|
assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status delivered for notification reference missing' # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_update_notification_status(
|
||||||
|
client,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_email_template,
|
||||||
|
mocker):
|
||||||
|
with freeze_time('2001-01-01T12:00:00'):
|
||||||
|
mocker.patch('app.statsd_client.incr')
|
||||||
|
mocker.patch('app.statsd_client.timing_with_dates')
|
||||||
|
notification = create_sample_notification(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
template=sample_email_template,
|
||||||
|
reference='ref',
|
||||||
|
status='sending',
|
||||||
|
sent_at=datetime.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert get_notification_by_id(notification.id).status == 'sending'
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_notification_callback(),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert json_resp['result'] == 'success'
|
||||||
|
assert json_resp['message'] == 'SES callback succeeded'
|
||||||
|
assert get_notification_by_id(notification.id).status == 'delivered'
|
||||||
|
statsd_client.timing_with_dates.assert_any_call(
|
||||||
|
"callback.ses.elapsed-time", datetime.utcnow(), notification.sent_at
|
||||||
|
)
|
||||||
|
statsd_client.incr.assert_any_call("callback.ses.delivered")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_update_multiple_notification_status_sent(
|
||||||
|
client,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_email_template,
|
||||||
|
mocker):
|
||||||
|
notification1 = create_sample_notification(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
template=sample_email_template,
|
||||||
|
reference='ref1',
|
||||||
|
sent_at=datetime.utcnow(),
|
||||||
|
status='sending')
|
||||||
|
|
||||||
|
notification2 = create_sample_notification(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
template=sample_email_template,
|
||||||
|
reference='ref2',
|
||||||
|
sent_at=datetime.utcnow(),
|
||||||
|
status='sending')
|
||||||
|
|
||||||
|
notification3 = create_sample_notification(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
template=sample_email_template,
|
||||||
|
reference='ref3',
|
||||||
|
sent_at=datetime.utcnow(),
|
||||||
|
status='sending')
|
||||||
|
|
||||||
|
resp1 = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_notification_callback(ref='ref1'),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
resp2 = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_notification_callback(ref='ref2'),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
resp3 = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_notification_callback(ref='ref3'),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp1.status_code == 200
|
||||||
|
assert resp2.status_code == 200
|
||||||
|
assert resp3.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_set_status_to_temporary_failure(client,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_email_template):
|
||||||
|
notification = create_sample_notification(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
template=sample_email_template,
|
||||||
|
reference='ref',
|
||||||
|
status='sending',
|
||||||
|
sent_at=datetime.utcnow()
|
||||||
|
)
|
||||||
|
assert get_notification_by_id(notification.id).status == 'sending'
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_soft_bounce_callback(),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert json_resp['result'] == 'success'
|
||||||
|
assert json_resp['message'] == 'SES callback succeeded'
|
||||||
|
assert get_notification_by_id(notification.id).status == 'temporary-failure'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_not_set_status_once_status_is_delivered(client,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_email_template):
|
||||||
|
notification = create_sample_notification(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
template=sample_email_template,
|
||||||
|
reference='ref',
|
||||||
|
status='delivered',
|
||||||
|
sent_at=datetime.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert get_notification_by_id(notification.id).status == 'delivered'
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_soft_bounce_callback(),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert json_resp['result'] == 'error'
|
||||||
|
assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status temporary-failure for notification reference ref' # noqa
|
||||||
|
assert get_notification_by_id(notification.id).status == 'delivered'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ses_callback_should_set_status_to_permanent_failure(client,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_email_template):
|
||||||
|
notification = create_sample_notification(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
template=sample_email_template,
|
||||||
|
reference='ref',
|
||||||
|
status='sending',
|
||||||
|
sent_at=datetime.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert get_notification_by_id(notification.id).status == 'sending'
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/email/ses',
|
||||||
|
data=ses_hard_bounce_callback(),
|
||||||
|
headers=[('Content-Type', 'text/plain; charset=UTF-8')]
|
||||||
|
)
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert json_resp['result'] == 'success'
|
||||||
|
assert json_resp['message'] == 'SES callback succeeded'
|
||||||
|
assert get_notification_by_id(notification.id).status == 'permanent-failure'
|
||||||
|
|
||||||
|
|
||||||
|
def ses_notification_callback(ref='ref'):
|
||||||
|
return str.encode(
|
||||||
|
'{\n "Type" : "Notification",\n "MessageId" : "%(ref)s",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Delivery\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"messageId\\":\\"%(ref)s\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' % {'ref': ref} # noqa
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ses_invalid_notification_id_callback():
|
||||||
|
return b'{\n "Type" : "Notification",\n "MessageId" : "missing",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Delivery\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"messageId\\":\\"missing\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def ses_missing_notification_id_callback():
|
||||||
|
return b'{\n "Type" : "Notification",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Delivery\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def ses_invalid_notification_type_callback():
|
||||||
|
return b'{\n "Type" : "Notification",\n "MessageId" : "ref",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Unknown\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def ses_hard_bounce_callback():
|
||||||
|
return b'{\n "Type" : "Notification",\n "MessageId" : "ref",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Bounce\\",\\"bounce\\":{\\"bounceType\\":\\"Permanent\\",\\"bounceSubType\\":\\"General\\"}, \\"mail\\":{\\"messageId\\":\\"ref\\",\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def ses_soft_bounce_callback():
|
||||||
|
return b'{\n "Type" : "Notification",\n "MessageId" : "ref",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Bounce\\",\\"bounce\\":{\\"bounceType\\":\\"Undetermined\\",\\"bounceSubType\\":\\"General\\"}, \\"mail\\":{\\"messageId\\":\\"ref\\",\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from app.models import NotificationStatistics
|
|
||||||
from app.notifications.process_client_response import (
|
from app.notifications.process_client_response import (
|
||||||
validate_callback_data,
|
validate_callback_data,
|
||||||
process_sms_client_response
|
process_sms_client_response
|
||||||
|
|||||||
@@ -13,31 +13,41 @@ from tests import create_authorization_header
|
|||||||
from tests.app.conftest import sample_notification as create_sample_notification
|
from tests.app.conftest import sample_notification as create_sample_notification
|
||||||
|
|
||||||
|
|
||||||
def test_get_sms_notification_by_id(client, sample_notification):
|
@pytest.mark.parametrize('type', ('email', 'sms'))
|
||||||
auth_header = create_authorization_header(service_id=sample_notification.service_id)
|
def test_get_notification_by_id(client, sample_notification, sample_email_notification, type):
|
||||||
|
if type == 'email':
|
||||||
|
notification_to_get = sample_email_notification
|
||||||
|
if type == 'sms':
|
||||||
|
notification_to_get = sample_notification
|
||||||
|
|
||||||
|
auth_header = create_authorization_header(service_id=notification_to_get.service_id)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
'/notifications/{}'.format(sample_notification.id),
|
'/notifications/{}'.format(notification_to_get.id),
|
||||||
headers=[auth_header])
|
headers=[auth_header])
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
notification = json.loads(response.get_data(as_text=True))['data']['notification']
|
notification = json.loads(response.get_data(as_text=True))['data']['notification']
|
||||||
assert notification['status'] == 'created'
|
assert notification['status'] == 'created'
|
||||||
assert notification['template'] == {
|
assert notification['template'] == {
|
||||||
'id': str(sample_notification.template.id),
|
'id': str(notification_to_get.template.id),
|
||||||
'name': sample_notification.template.name,
|
'name': notification_to_get.template.name,
|
||||||
'template_type': sample_notification.template.template_type,
|
'template_type': notification_to_get.template.template_type,
|
||||||
'version': 1
|
'version': 1
|
||||||
}
|
}
|
||||||
assert notification['to'] == '+447700900855'
|
assert notification['to'] == notification_to_get.to
|
||||||
assert notification['service'] == str(sample_notification.service_id)
|
assert notification['service'] == str(notification_to_get.service_id)
|
||||||
assert notification['body'] == "This is a template:\nwith a newline"
|
assert notification['body'] == notification_to_get.template.content
|
||||||
assert not notification.get('subject')
|
assert notification.get('subject', None) == notification_to_get.subject
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("id", ["1234-badly-formatted-id-7890", "0"])
|
@pytest.mark.parametrize("id", ["1234-badly-formatted-id-7890", "0"])
|
||||||
def test_get_sms_notification_by_invalid_id(client, sample_notification, id):
|
@pytest.mark.parametrize('type', ('email', 'sms'))
|
||||||
auth_header = create_authorization_header(service_id=sample_notification.service_id)
|
def test_get_notification_by_invalid_id(client, sample_notification, sample_email_notification, id, type):
|
||||||
|
if type == 'email':
|
||||||
|
notification_to_get = sample_email_notification
|
||||||
|
if type == 'sms':
|
||||||
|
notification_to_get = sample_notification
|
||||||
|
auth_header = create_authorization_header(service_id=notification_to_get.service_id)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
'/notifications/{}'.format(id),
|
'/notifications/{}'.format(id),
|
||||||
@@ -46,37 +56,6 @@ def test_get_sms_notification_by_invalid_id(client, sample_notification, id):
|
|||||||
assert response.status_code == 405
|
assert response.status_code == 405
|
||||||
|
|
||||||
|
|
||||||
def test_get_email_notification_by_id(client, notify_db, notify_db_session, sample_email_template):
|
|
||||||
|
|
||||||
email_notification = create_sample_notification(notify_db,
|
|
||||||
notify_db_session,
|
|
||||||
to_field="sample_email@example.com",
|
|
||||||
service=sample_email_template.service,
|
|
||||||
template=sample_email_template,
|
|
||||||
status='sending')
|
|
||||||
|
|
||||||
auth_header = create_authorization_header(service_id=email_notification.service_id)
|
|
||||||
|
|
||||||
response = client.get(
|
|
||||||
'/notifications/{}'.format(email_notification.id),
|
|
||||||
headers=[auth_header])
|
|
||||||
|
|
||||||
notification = json.loads(response.get_data(as_text=True))['data']['notification']
|
|
||||||
|
|
||||||
assert notification['status'] == 'sending'
|
|
||||||
assert notification['template'] == {
|
|
||||||
'id': str(email_notification.template.id),
|
|
||||||
'name': email_notification.template.name,
|
|
||||||
'template_type': email_notification.template.template_type,
|
|
||||||
'version': 1
|
|
||||||
}
|
|
||||||
assert notification['to'] == 'sample_email@example.com'
|
|
||||||
assert notification['service'] == str(email_notification.service_id)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert notification['body'] == sample_email_template.content
|
|
||||||
assert notification['subject'] == sample_email_template.subject
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_notifications_empty_result(client, sample_api_key):
|
def test_get_notifications_empty_result(client, sample_api_key):
|
||||||
auth_header = create_authorization_header(service_id=sample_api_key.service_id)
|
auth_header = create_authorization_header(service_id=sample_api_key.service_id)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user