mirror of
https://github.com/GSA/notifications-api.git
synced 2026-01-30 14:31:57 -05:00
Validate CAP against the spec
This gives us some extra confidence that there aren’t any problems with the data we’re getting from the other service. It doesn’t address any specific problems we’ve seen, rather it seems like a sensible precaution to take.
This commit is contained in:
@@ -8,6 +8,8 @@ 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'])
|
||||
@@ -21,7 +23,13 @@ def create_broadcast():
|
||||
if request.content_type == 'application/json':
|
||||
broadcast_json = request.get_json()
|
||||
elif request.content_type == 'application/cap+xml':
|
||||
broadcast_json = cap_xml_to_dict(request.get_data(as_text=True))
|
||||
cap_xml = request.get_data(as_text=True)
|
||||
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)
|
||||
else:
|
||||
raise BadRequestError(
|
||||
message=f'Content type {request.content_type} not supported',
|
||||
|
||||
218
app/xml_schemas/CAP-v1.2.xsd
Normal file
218
app/xml_schemas/CAP-v1.2.xsd
Normal file
@@ -0,0 +1,218 @@
|
||||
<?xml version = "1.0" encoding = "UTF-8"?>
|
||||
<!-- Copyright OASIS Open 2010 All Rights Reserved -->
|
||||
<schema xmlns = "http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace = "urn:oasis:names:tc:emergency:cap:1.2"
|
||||
xmlns:cap = "urn:oasis:names:tc:emergency:cap:1.2"
|
||||
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
|
||||
elementFormDefault = "qualified"
|
||||
attributeFormDefault = "unqualified"
|
||||
version = "1.2">
|
||||
<element name = "alert">
|
||||
<annotation>
|
||||
<documentation>CAP Alert Message (version 1.2)</documentation>
|
||||
</annotation>
|
||||
<complexType>
|
||||
<sequence>
|
||||
<element name = "identifier" type = "xs:string"/>
|
||||
<element name = "sender" type = "xs:string"/>
|
||||
<element name = "sent">
|
||||
<simpleType>
|
||||
<restriction base = "xs:dateTime">
|
||||
<pattern value = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d[-,+]\d\d:\d\d"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "status">
|
||||
<simpleType>
|
||||
<restriction base = "xs:string">
|
||||
<enumeration value = "Actual"/>
|
||||
<enumeration value = "Exercise"/>
|
||||
<enumeration value = "System"/>
|
||||
<enumeration value = "Test"/>
|
||||
<enumeration value = "Draft"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "msgType">
|
||||
<simpleType>
|
||||
<restriction base = "xs:string">
|
||||
<enumeration value = "Alert"/>
|
||||
<enumeration value = "Update"/>
|
||||
<enumeration value = "Cancel"/>
|
||||
<enumeration value = "Ack"/>
|
||||
<enumeration value = "Error"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "source" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "scope">
|
||||
<simpleType>
|
||||
<restriction base = "xs:string">
|
||||
<enumeration value = "Public"/>
|
||||
<enumeration value = "Restricted"/>
|
||||
<enumeration value = "Private"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "restriction" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "addresses" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "code" type = "xs:string" minOccurs = "0" maxOccurs = "unbounded"/>
|
||||
<element name = "note" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "references" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "incidents" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "info" minOccurs = "0" maxOccurs = "unbounded">
|
||||
<complexType>
|
||||
<sequence>
|
||||
<element name = "language" type = "xs:language" default = "en-US" minOccurs = "0"/>
|
||||
<element name = "category" maxOccurs = "unbounded">
|
||||
<simpleType>
|
||||
<restriction base = "xs:string">
|
||||
<enumeration value = "Geo"/>
|
||||
<enumeration value = "Met"/>
|
||||
<enumeration value = "Safety"/>
|
||||
<enumeration value = "Security"/>
|
||||
<enumeration value = "Rescue"/>
|
||||
<enumeration value = "Fire"/>
|
||||
<enumeration value = "Health"/>
|
||||
<enumeration value = "Env"/>
|
||||
<enumeration value = "Transport"/>
|
||||
<enumeration value = "Infra"/>
|
||||
<enumeration value = "CBRNE"/>
|
||||
<enumeration value = "Other"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "event" type = "xs:string"/>
|
||||
<element name = "responseType" minOccurs = "0" maxOccurs = "unbounded">
|
||||
<simpleType>
|
||||
<restriction base = "xs:string">
|
||||
<enumeration value = "Shelter"/>
|
||||
<enumeration value = "Evacuate"/>
|
||||
<enumeration value = "Prepare"/>
|
||||
<enumeration value = "Execute"/>
|
||||
<enumeration value = "Avoid"/>
|
||||
<enumeration value = "Monitor"/>
|
||||
<enumeration value = "Assess"/>
|
||||
<enumeration value = "AllClear"/>
|
||||
<enumeration value = "None"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "urgency">
|
||||
<simpleType>
|
||||
<restriction base = "xs:string">
|
||||
<enumeration value = "Immediate"/>
|
||||
<enumeration value = "Expected"/>
|
||||
<enumeration value = "Future"/>
|
||||
<enumeration value = "Past"/>
|
||||
<enumeration value = "Unknown"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "severity">
|
||||
<simpleType>
|
||||
<restriction base = "xs:string">
|
||||
<enumeration value = "Extreme"/>
|
||||
<enumeration value = "Severe"/>
|
||||
<enumeration value = "Moderate"/>
|
||||
<enumeration value = "Minor"/>
|
||||
<enumeration value = "Unknown"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "certainty">
|
||||
<simpleType>
|
||||
<restriction base = "xs:string">
|
||||
<enumeration value = "Observed"/>
|
||||
<enumeration value = "Likely"/>
|
||||
<enumeration value = "Possible"/>
|
||||
<enumeration value = "Unlikely"/>
|
||||
<enumeration value = "Unknown"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "audience" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "eventCode" minOccurs = "0" maxOccurs = "unbounded">
|
||||
<complexType>
|
||||
<sequence>
|
||||
<element ref = "cap:valueName"/>
|
||||
<element ref = "cap:value"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
<element name = "effective" minOccurs = "0">
|
||||
<simpleType>
|
||||
<restriction base = "xs:dateTime">
|
||||
<pattern value = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d[-,+]\d\d:\d\d"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "onset" minOccurs = "0">
|
||||
<simpleType>
|
||||
<restriction base = "xs:dateTime">
|
||||
<pattern value = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d[-,+]\d\d:\d\d"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "expires" minOccurs = "0">
|
||||
<simpleType>
|
||||
<restriction base = "xs:dateTime">
|
||||
<pattern value = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d[-,+]\d\d:\d\d"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</element>
|
||||
<element name = "senderName" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "headline" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "description" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "instruction" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "web" type = "xs:anyURI" minOccurs = "0"/>
|
||||
<element name = "contact" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "parameter" minOccurs = "0" maxOccurs = "unbounded">
|
||||
<complexType>
|
||||
<sequence>
|
||||
<element ref = "cap:valueName"/>
|
||||
<element ref = "cap:value"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
<element name = "resource" minOccurs = "0" maxOccurs = "unbounded">
|
||||
<complexType>
|
||||
<sequence>
|
||||
<element name = "resourceDesc" type = "xs:string"/>
|
||||
<element name = "mimeType" type = "xs:string"/>
|
||||
<element name = "size" type = "xs:integer" minOccurs = "0"/>
|
||||
<element name = "uri" type = "xs:anyURI" minOccurs = "0"/>
|
||||
<element name = "derefUri" type = "xs:string" minOccurs = "0"/>
|
||||
<element name = "digest" type = "xs:string" minOccurs = "0"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
<element name = "area" minOccurs = "0" maxOccurs = "unbounded">
|
||||
<complexType>
|
||||
<sequence>
|
||||
<element name = "areaDesc" type = "xs:string"/>
|
||||
<element name = "polygon" type = "xs:string" minOccurs = "0" maxOccurs = "unbounded"/>
|
||||
<element name = "circle" type = "xs:string" minOccurs = "0" maxOccurs = "unbounded"/>
|
||||
<element name = "geocode" minOccurs = "0" maxOccurs = "unbounded">
|
||||
<complexType>
|
||||
<sequence>
|
||||
<element ref = "cap:valueName"/>
|
||||
<element ref = "cap:value"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
<element name = "altitude" type = "xs:decimal" minOccurs = "0"/>
|
||||
<element name = "ceiling" type = "xs:decimal" minOccurs = "0"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
<any minOccurs = "0" maxOccurs = "unbounded" namespace = "http://www.w3.org/2000/09/xmldsig#" processContents = "lax"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
<element name = "valueName" type = "xs:string"/>
|
||||
<element name = "value" type = "xs:string"/>
|
||||
</schema>
|
||||
19
app/xml_schemas/__init__.py
Normal file
19
app/xml_schemas/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from lxml import etree
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate_xml(document, schema_file_name):
|
||||
|
||||
path = Path(__file__).resolve().parent / schema_file_name
|
||||
contents = path.read_text()
|
||||
|
||||
schema_root = etree.XML(contents.encode('utf-8'))
|
||||
schema = etree.XMLSchema(schema_root)
|
||||
parser = etree.XMLParser(schema=schema)
|
||||
|
||||
try:
|
||||
etree.fromstring(document, parser)
|
||||
except etree.XMLSyntaxError:
|
||||
return False
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user