mirror of
https://github.com/GSA/notifications-api.git
synced 2026-02-02 17:31:14 -05:00
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:
@@ -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):
|
||||
|
||||
|
||||
10
app/v2/broadcast/__init__.py
Normal file
10
app/v2/broadcast/__init__.py
Normal 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)
|
||||
37
app/v2/broadcast/post_broadcast.py
Normal file
37
app/v2/broadcast/post_broadcast.py
Normal 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 it’s
|
||||
# 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
|
||||
0
tests/app/v2/broadcast/__init__.py
Normal file
0
tests/app/v2/broadcast/__init__.py
Normal file
65
tests/app/v2/broadcast/test_post_broadcast.py
Normal file
65
tests/app/v2/broadcast/test_post_broadcast.py
Normal 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 can’t 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
|
||||
Reference in New Issue
Block a user