Add public API endpoint to create emergency alerts

We know there is at least one system which wants to integrate with
Notify to send out emergency alerts, rather than creating them manually.

This commit adds an endpoint to the public API to let them do that.

To start with we’ll just let the system create them in a single call,
meaning they still have to be approved manually. This reduces the risk
of an attacker being able to broadcast an alert via the API, should the
other system be compromised.

We’ve worked with the owners of the other system to define which fields
we should care about initially.
This commit is contained in:
Chris Hill-Scott
2021-01-18 10:01:44 +00:00
parent dfbd31cef8
commit 61c9e50ed9
5 changed files with 116 additions and 0 deletions

View File

@@ -251,6 +251,7 @@ def register_v2_blueprints(application):
from app.v2.template.get_template import v2_template_blueprint as get_template
from app.v2.templates.get_templates import v2_templates_blueprint as get_templates
from app.v2.template.post_template import v2_template_blueprint as post_template
from app.v2.broadcast.post_broadcast import v2_broadcast_blueprint as post_broadcast
from app.authentication.auth import requires_auth
post_notifications.before_request(requires_auth)
@@ -271,6 +272,9 @@ def register_v2_blueprints(application):
get_inbound_sms.before_request(requires_auth)
application.register_blueprint(get_inbound_sms)
post_broadcast.before_request(requires_auth)
application.register_blueprint(post_broadcast)
def init_app(app):

View File

@@ -0,0 +1,10 @@
from flask import Blueprint
from app.v2.errors import register_errors
v2_broadcast_blueprint = Blueprint(
"v2_broadcast_blueprint",
__name__,
url_prefix='/v2/broadcast',
)
register_errors(v2_broadcast_blueprint)

View File

@@ -0,0 +1,37 @@
from flask import jsonify, request
from app import authenticated_service, api_user
from app.dao.dao_utils import dao_save_object
from app.notifications.validators import check_service_has_permission
from app.models import BROADCAST_TYPE, BroadcastMessage, BroadcastStatusType
from app.v2.broadcast import v2_broadcast_blueprint
@v2_broadcast_blueprint.route("", methods=['POST'])
def create_broadcast():
check_service_has_permission(
BROADCAST_TYPE,
authenticated_service.permissions,
)
request_json = request.get_json()
broadcast_message = BroadcastMessage(
service_id=authenticated_service.id,
content=request_json['content'],
reference=request_json['reference'],
areas={
"areas": [],
"simple_polygons": request_json['polygons'],
},
status=BroadcastStatusType.PENDING_APPROVAL,
api_key_id=api_user.id,
# The client may pass in broadcast_json['expires'] but its
# simpler for now to ignore it and have the rules around expiry
# for broadcasts created with the API match those created from
# the admin app
)
dao_save_object(broadcast_message)
return jsonify(broadcast_message.serialize()), 201

View File

View File

@@ -0,0 +1,65 @@
from flask import json
from freezegun import freeze_time
from tests import create_authorization_header
from unittest.mock import ANY
def test_broadcast_for_service_without_permission_returns_400(
client,
sample_service,
):
auth_header = create_authorization_header(service_id=sample_service.id)
response = client.post(
path='/v2/broadcast',
data='',
headers=[('Content-Type', 'application/json'), auth_header],
)
assert response.status_code == 400
assert response.get_json()['errors'][0]['message'] == (
'Service is not allowed to send broadcast messages'
)
def test_valid_post_broadcast_returns_201(
client,
sample_broadcast_service,
):
auth_header = create_authorization_header(service_id=sample_broadcast_service.id)
response = client.post(
path='/v2/broadcast',
data=json.dumps({
'content': 'This is a test',
'reference': 'abc123',
'polygons': [[
[1, 2], [3, 4], [5, 6],
]],
}),
headers=[('Content-Type', 'application/json'), auth_header],
)
assert response.status_code == 201
response_json = json.loads(response.get_data(as_text=True))
assert response_json['approved_at'] is None
assert response_json['approved_by_id'] == None
assert response_json['areas'] == []
assert response_json['cancelled_at'] == None
assert response_json['cancelled_by_id'] == None
assert response_json['content'] == 'This is a test'
assert response_json['reference'] == 'abc123'
assert response_json['created_at'] # datetime generated by the DB so cant freeze it
assert response_json['created_by_id'] == None
assert response_json['finishes_at'] is None
assert response_json['id'] == ANY
assert response_json['personalisation'] is None
assert response_json['service_id'] == str(sample_broadcast_service.id)
assert response_json['simple_polygons'] == [[[1, 2], [3, 4], [5, 6]]]
assert response_json['starts_at'] is None
assert response_json['status'] == 'pending-approval'
assert response_json['template_id'] is None
assert response_json['template_name'] is None
assert response_json['template_version'] is None
assert response_json['updated_at'] is None