Files
notifications-api/app/v2/broadcast/post_broadcast.py
Chris Hill-Scott dec16a98f6 Handle XML files that have a declaration
`lxml` wants its input in bytes:

> XML is explicitly defined as a stream of bytes. It's not Unicode text.
> […] rule number one: do not decode your XML data yourself.

– https://lxml.de/FAQ.html#why-can-t-lxml-parse-my-xml-from-unicode-strings

It will accept strings unless, unless the document contains a
declaration[1] with an `encoding` attribute. Then it will refuse to
parse the document and raises a `ValueError`[2].

We can get fix this by passing `lxml` the bytes from the request, rather
than the decoded text.

1. > XML documents may begin with an XML declaration that describes some
   > information about themselves. An example is
   > `<?xml version="1.0" encoding="UTF-8"?>`.
   – https://en.wikipedia.org/wiki/XML#XML_declaration
2. See an example of this exception being raised in production here:
   https://kibana.logit.io/s/9423a789-282c-4113-908d-0be3b1bc9d1d/app/kibana#/doc/logstash-*/logstash-2021.02.05/syslog?id=AXdzfZVz5ZSa5DKpJiYd&_g=()
2021-02-08 08:51:14 +00:00

72 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from itertools import chain
from flask import current_app, jsonify, request
from notifications_utils.polygons import Polygons
from app import authenticated_service, api_user
from app.broadcast_message.translators import cap_xml_to_dict
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.schema_validation import validate
from app.v2.broadcast import v2_broadcast_blueprint
from app.v2.broadcast.broadcast_schemas import post_broadcast_schema
from app.v2.errors import BadRequestError
from app.xml_schemas import validate_xml
@v2_broadcast_blueprint.route("", methods=['POST'])
def create_broadcast():
check_service_has_permission(
BROADCAST_TYPE,
authenticated_service.permissions,
)
if request.content_type != 'application/cap+xml':
raise BadRequestError(
message=f'Content type {request.content_type} not supported',
status_code=415,
)
cap_xml = request.get_data()
if not validate_xml(cap_xml, 'CAP-v1.2.xsd'):
raise BadRequestError(
message=f'Request data is not valid CAP XML',
status_code=400,
)
broadcast_json = cap_xml_to_dict(cap_xml)
validate(broadcast_json, post_broadcast_schema)
polygons = Polygons(list(chain.from_iterable((
area['polygons'] for area in broadcast_json['areas']
))))
broadcast_message = BroadcastMessage(
service_id=authenticated_service.id,
content=broadcast_json['content'],
reference=broadcast_json['reference'],
areas={
'areas': [
area['name'] for area in broadcast_json['areas']
],
'simple_polygons': polygons.smooth.simplify.as_coordinate_pairs_long_lat,
},
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)
current_app.logger.info(
f'Broadcast message {broadcast_message.id} created for service '
f'{authenticated_service.id} with reference {broadcast_json["reference"]}'
)
return jsonify(broadcast_message.serialize()), 201