2020-10-20 14:00:53 +01:00
|
|
|
import json
|
2020-12-24 15:19:46 +00:00
|
|
|
from abc import ABC, abstractmethod
|
2020-10-20 14:00:53 +01:00
|
|
|
|
2020-10-20 13:33:51 +01:00
|
|
|
import boto3
|
2020-11-17 12:35:22 +00:00
|
|
|
from flask import current_app
|
2020-12-24 15:09:41 +00:00
|
|
|
from notifications_utils.template import non_gsm_characters
|
2020-10-20 13:33:51 +01:00
|
|
|
|
2020-11-16 12:47:38 +00:00
|
|
|
from app.config import BroadcastProvider
|
2020-12-18 17:39:35 +00:00
|
|
|
from app.utils import DATETIME_FORMAT, format_sequential_number
|
2020-10-29 11:12:28 +00:00
|
|
|
|
2020-10-22 12:19:25 +01:00
|
|
|
# The variable names in this file have specific meaning in a CAP message
|
|
|
|
|
#
|
|
|
|
|
# identifier is a unique field for each CAP message
|
|
|
|
|
#
|
|
|
|
|
# headline is a field which we are not sure if we will use
|
|
|
|
|
#
|
|
|
|
|
# description is the body of the message
|
2020-10-23 16:44:11 +01:00
|
|
|
|
|
|
|
|
# areas is a list of dicts, with the following items
|
|
|
|
|
# * description is a string which populates the areaDesc field
|
|
|
|
|
# * polygon is a list of lat/long pairs
|
2020-10-22 12:19:25 +01:00
|
|
|
#
|
2020-11-17 12:35:22 +00:00
|
|
|
# previous_provider_messages is a list of previous events (models.py::BroadcastProviderMessage)
|
|
|
|
|
# ie a Cancel message would have a unique event but have the event of
|
2020-11-17 12:35:10 +00:00
|
|
|
# the preceeding Alert message in the previous_provider_messages field
|
2020-10-22 12:19:25 +01:00
|
|
|
|
2020-10-20 15:18:24 +01:00
|
|
|
|
2020-10-29 10:22:50 +00:00
|
|
|
class CBCProxyException(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2020-11-17 12:35:22 +00:00
|
|
|
class CBCProxyClient:
|
|
|
|
|
_lambda_client = None
|
2020-10-20 11:18:46 +01:00
|
|
|
|
|
|
|
|
def init_app(self, app):
|
2020-11-25 17:39:32 +00:00
|
|
|
if app.config.get('CBC_PROXY_ENABLED'):
|
2020-11-17 12:35:22 +00:00
|
|
|
self._lambda_client = boto3.client(
|
|
|
|
|
'lambda',
|
|
|
|
|
region_name='eu-west-2',
|
|
|
|
|
aws_access_key_id=app.config['CBC_PROXY_AWS_ACCESS_KEY_ID'],
|
|
|
|
|
aws_secret_access_key=app.config['CBC_PROXY_AWS_SECRET_ACCESS_KEY'],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def get_proxy(self, provider):
|
|
|
|
|
proxy_classes = {
|
|
|
|
|
'canary': CBCProxyCanary,
|
|
|
|
|
BroadcastProvider.EE: CBCProxyEE,
|
2020-12-04 15:49:50 +00:00
|
|
|
BroadcastProvider.VODAFONE: CBCProxyVodafone,
|
2020-11-17 12:35:22 +00:00
|
|
|
}
|
|
|
|
|
return proxy_classes[provider](self._lambda_client)
|
|
|
|
|
|
|
|
|
|
|
2020-12-24 15:19:46 +00:00
|
|
|
class CBCProxyClientBase(ABC):
|
2020-11-17 12:35:22 +00:00
|
|
|
lambda_name = None
|
|
|
|
|
|
2020-12-24 15:19:46 +00:00
|
|
|
@property
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def LANGUAGE_ENGLISH(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def LANGUAGE_WELSH(self):
|
|
|
|
|
pass
|
2020-12-24 15:09:41 +00:00
|
|
|
|
2020-11-17 12:35:22 +00:00
|
|
|
def __init__(self, lambda_client):
|
|
|
|
|
self._lambda_client = lambda_client
|
2020-10-20 11:18:46 +01:00
|
|
|
|
2020-10-26 17:14:08 +00:00
|
|
|
def send_canary(
|
|
|
|
|
self,
|
|
|
|
|
identifier,
|
|
|
|
|
):
|
|
|
|
|
pass
|
|
|
|
|
|
2020-10-27 14:44:04 +00:00
|
|
|
def send_link_test(
|
|
|
|
|
self,
|
|
|
|
|
identifier,
|
2020-12-11 18:52:54 +00:00
|
|
|
sequential_number
|
2020-10-27 14:44:04 +00:00
|
|
|
):
|
2020-12-09 11:13:50 +00:00
|
|
|
pass
|
2020-10-27 14:44:04 +00:00
|
|
|
|
2020-10-20 11:18:46 +01:00
|
|
|
def create_and_send_broadcast(
|
2020-12-11 18:52:54 +00:00
|
|
|
self, identifier, headline, description, areas, sent, expires, message_number=None
|
2020-10-20 11:18:46 +01:00
|
|
|
):
|
2020-12-09 11:13:50 +00:00
|
|
|
pass
|
2020-10-20 11:18:46 +01:00
|
|
|
|
|
|
|
|
# We have not implementated updating a broadcast
|
|
|
|
|
def update_and_send_broadcast(
|
|
|
|
|
self,
|
2020-11-17 12:35:10 +00:00
|
|
|
identifier, previous_provider_messages, headline, description, areas,
|
2020-12-11 18:52:54 +00:00
|
|
|
sent, expires, message_number=None
|
2020-10-20 11:18:46 +01:00
|
|
|
):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# We have not implemented cancelling a broadcast
|
|
|
|
|
def cancel_broadcast(
|
|
|
|
|
self,
|
2020-11-17 12:35:10 +00:00
|
|
|
identifier, previous_provider_messages, headline, description, areas,
|
2020-12-11 18:52:54 +00:00
|
|
|
sent, expires, message_number=None
|
2020-10-20 11:18:46 +01:00
|
|
|
):
|
|
|
|
|
pass
|
|
|
|
|
|
2020-11-17 12:35:22 +00:00
|
|
|
def _invoke_lambda(self, payload):
|
|
|
|
|
if not self.lambda_name:
|
|
|
|
|
current_app.logger.warning(
|
|
|
|
|
'{self.__class__.__name__} tried to send {payload} but cbc proxy aws env vars not set'
|
|
|
|
|
)
|
|
|
|
|
return
|
2020-10-20 11:18:46 +01:00
|
|
|
|
2020-10-29 10:22:50 +00:00
|
|
|
payload_bytes = bytes(json.dumps(payload), encoding='utf8')
|
2020-10-26 17:14:08 +00:00
|
|
|
|
|
|
|
|
result = self._lambda_client.invoke(
|
2020-11-17 12:35:22 +00:00
|
|
|
FunctionName=self.lambda_name,
|
2020-10-26 17:14:08 +00:00
|
|
|
InvocationType='RequestResponse',
|
|
|
|
|
Payload=payload_bytes,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if result['StatusCode'] > 299:
|
2020-10-29 10:22:50 +00:00
|
|
|
raise CBCProxyException('Could not invoke lambda')
|
2020-10-26 17:14:08 +00:00
|
|
|
|
|
|
|
|
if 'FunctionError' in result:
|
2020-10-29 10:22:50 +00:00
|
|
|
raise CBCProxyException('Function exited with unhandled exception')
|
2020-10-26 17:14:08 +00:00
|
|
|
|
2020-10-29 10:22:50 +00:00
|
|
|
return result
|
|
|
|
|
|
2020-12-24 15:09:41 +00:00
|
|
|
def infer_language_from(self, content):
|
|
|
|
|
if non_gsm_characters(content):
|
|
|
|
|
return self.LANGUAGE_WELSH
|
|
|
|
|
return self.LANGUAGE_ENGLISH
|
|
|
|
|
|
2020-11-17 12:35:22 +00:00
|
|
|
|
|
|
|
|
class CBCProxyCanary(CBCProxyClientBase):
|
|
|
|
|
"""
|
|
|
|
|
The canary is a lambda which tests notify's connectivity to the Cell Broadcast AWS infrastructure. It calls the
|
|
|
|
|
canary, a specific lambda that does not open a vpn or connect to a provider but just responds from within AWS.
|
|
|
|
|
"""
|
|
|
|
|
lambda_name = 'canary'
|
|
|
|
|
|
2020-12-24 15:19:46 +00:00
|
|
|
LANGUAGE_ENGLISH = None
|
|
|
|
|
LANGUAGE_WELSH = None
|
|
|
|
|
|
2020-10-29 10:22:50 +00:00
|
|
|
def send_canary(
|
2020-10-27 14:44:04 +00:00
|
|
|
self,
|
|
|
|
|
identifier,
|
|
|
|
|
):
|
2020-11-17 12:35:22 +00:00
|
|
|
self._invoke_lambda(payload={'identifier': identifier})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CBCProxyEE(CBCProxyClientBase):
|
|
|
|
|
lambda_name = 'bt-ee-1-proxy'
|
2020-10-27 14:44:04 +00:00
|
|
|
|
2020-12-24 15:19:46 +00:00
|
|
|
LANGUAGE_ENGLISH = 'en-GB'
|
|
|
|
|
LANGUAGE_WELSH = 'cy-GB'
|
|
|
|
|
|
2020-12-09 11:13:50 +00:00
|
|
|
def send_link_test(
|
|
|
|
|
self,
|
|
|
|
|
identifier,
|
|
|
|
|
sequential_number=None,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
link test - open up a connection to a specific provider, and send them an xml payload with a <msgType> of
|
|
|
|
|
test.
|
|
|
|
|
"""
|
|
|
|
|
payload = {
|
|
|
|
|
'message_type': 'test',
|
|
|
|
|
'identifier': identifier,
|
2020-12-09 17:10:40 +00:00
|
|
|
'message_format': 'cap'
|
2020-12-09 11:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self._invoke_lambda(payload=payload)
|
|
|
|
|
|
|
|
|
|
def create_and_send_broadcast(
|
|
|
|
|
self, identifier, headline, description, areas, sent, expires, message_number=None
|
|
|
|
|
):
|
|
|
|
|
payload = {
|
|
|
|
|
'message_type': 'alert',
|
|
|
|
|
'identifier': identifier,
|
2020-12-09 17:10:40 +00:00
|
|
|
'message_format': 'cap',
|
2020-12-09 11:13:50 +00:00
|
|
|
'headline': headline,
|
|
|
|
|
'description': description,
|
|
|
|
|
'areas': areas,
|
|
|
|
|
'sent': sent,
|
|
|
|
|
'expires': expires,
|
2020-12-24 15:09:41 +00:00
|
|
|
'language': self.infer_language_from(description),
|
2020-12-09 11:13:50 +00:00
|
|
|
}
|
|
|
|
|
self._invoke_lambda(payload=payload)
|
|
|
|
|
|
2020-12-11 18:52:54 +00:00
|
|
|
def cancel_broadcast(
|
|
|
|
|
self,
|
|
|
|
|
identifier, previous_provider_messages,
|
|
|
|
|
sent, message_number=None
|
|
|
|
|
):
|
|
|
|
|
payload = {
|
|
|
|
|
'message_type': 'cancel',
|
|
|
|
|
'identifier': identifier,
|
|
|
|
|
'message_format': 'cap',
|
|
|
|
|
"references": [
|
2020-12-18 17:22:11 +00:00
|
|
|
{
|
|
|
|
|
"message_id": str(message.id),
|
|
|
|
|
"sent": message.created_at.strftime(DATETIME_FORMAT)
|
|
|
|
|
} for message in previous_provider_messages
|
2020-12-11 18:52:54 +00:00
|
|
|
],
|
|
|
|
|
'sent': sent,
|
|
|
|
|
}
|
|
|
|
|
self._invoke_lambda(payload=payload)
|
|
|
|
|
|
2020-12-04 15:49:50 +00:00
|
|
|
|
|
|
|
|
class CBCProxyVodafone(CBCProxyClientBase):
|
2020-12-04 17:29:12 +00:00
|
|
|
lambda_name = 'vodafone-1-proxy'
|
2020-12-09 11:13:50 +00:00
|
|
|
|
2020-12-24 15:09:41 +00:00
|
|
|
LANGUAGE_ENGLISH = 'English'
|
|
|
|
|
LANGUAGE_WELSH = 'Welsh'
|
|
|
|
|
|
2020-12-09 11:13:50 +00:00
|
|
|
def send_link_test(
|
|
|
|
|
self,
|
|
|
|
|
identifier,
|
|
|
|
|
sequential_number,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
link test - open up a connection to a specific provider, and send them an xml payload with a <msgType> of
|
|
|
|
|
test.
|
|
|
|
|
"""
|
|
|
|
|
payload = {
|
|
|
|
|
'message_type': 'test',
|
|
|
|
|
'identifier': identifier,
|
|
|
|
|
'message_number': sequential_number,
|
|
|
|
|
'message_format': 'ibag'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self._invoke_lambda(payload=payload)
|
|
|
|
|
|
|
|
|
|
def create_and_send_broadcast(
|
|
|
|
|
self, identifier, message_number, headline, description, areas, sent, expires,
|
|
|
|
|
):
|
|
|
|
|
payload = {
|
|
|
|
|
'message_type': 'alert',
|
|
|
|
|
'identifier': identifier,
|
|
|
|
|
'message_number': message_number,
|
|
|
|
|
'message_format': 'ibag',
|
|
|
|
|
'headline': headline,
|
|
|
|
|
'description': description,
|
|
|
|
|
'areas': areas,
|
|
|
|
|
'sent': sent,
|
|
|
|
|
'expires': expires,
|
2020-12-24 15:09:41 +00:00
|
|
|
'language': self.infer_language_from(description),
|
2020-12-09 11:13:50 +00:00
|
|
|
}
|
|
|
|
|
self._invoke_lambda(payload=payload)
|
2020-12-11 18:52:54 +00:00
|
|
|
|
|
|
|
|
def cancel_broadcast(
|
|
|
|
|
self, identifier, previous_provider_messages, sent, message_number
|
|
|
|
|
):
|
2020-12-14 17:52:08 +00:00
|
|
|
|
2020-12-11 18:52:54 +00:00
|
|
|
payload = {
|
|
|
|
|
'message_type': 'cancel',
|
|
|
|
|
'identifier': identifier,
|
|
|
|
|
'message_number': message_number,
|
|
|
|
|
'message_format': 'ibag',
|
|
|
|
|
"references": [
|
|
|
|
|
{
|
|
|
|
|
"message_id": str(message.id),
|
2020-12-14 17:52:08 +00:00
|
|
|
"message_number": format_sequential_number(message.message_number),
|
2020-12-18 17:22:11 +00:00
|
|
|
"sent": message.created_at.strftime(DATETIME_FORMAT)
|
2020-12-11 18:52:54 +00:00
|
|
|
} for message in previous_provider_messages
|
|
|
|
|
],
|
|
|
|
|
'sent': sent,
|
|
|
|
|
}
|
|
|
|
|
self._invoke_lambda(payload=payload)
|