mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-25 16:53:42 -04:00
Compare commits
5 Commits
672c838318
...
jim/100522
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20b4d1f6ee | ||
|
|
54ce019df6 | ||
|
|
00abfb07b6 | ||
|
|
fd7aeba4e3 | ||
|
|
209ea4646c |
@@ -42,31 +42,31 @@ class AwsSnsClient(SmsClient):
|
|||||||
"AWS.SNS.SMS.SMSType": {
|
"AWS.SNS.SMS.SMSType": {
|
||||||
"DataType": "String",
|
"DataType": "String",
|
||||||
"StringValue": "Transactional",
|
"StringValue": "Transactional",
|
||||||
|
},
|
||||||
|
"AWS.MM.SMS.OriginationNumber": {
|
||||||
|
"DataType": "String",
|
||||||
|
"StringValue": self.current_app.config["AWS_US_TOLL_FREE_NUMBER"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# If sending with a long code number, we need to use another AWS region
|
# sender is managed in the UI in settings > Text message senders
|
||||||
# and specify the phone number we want to use as the origination number
|
|
||||||
send_with_dedicated_phone_number = self._send_with_dedicated_phone_number(sender)
|
send_with_dedicated_phone_number = self._send_with_dedicated_phone_number(sender)
|
||||||
|
|
||||||
if send_with_dedicated_phone_number:
|
if send_with_dedicated_phone_number:
|
||||||
client = self._long_codes_client
|
client = self._long_codes_client
|
||||||
attributes["AWS.MM.SMS.OriginationNumber"] = {
|
attributes["AWS.MM.SMS.OriginationNumber"] = {
|
||||||
"DataType": "String",
|
"DataType": "String",
|
||||||
"StringValue": sender,
|
"StringValue": sender,
|
||||||
}
|
}
|
||||||
|
|
||||||
# If the number is US based, we must use a US Toll Free number to send the message
|
|
||||||
country = phonenumbers.region_code_for_number(match.number)
|
country = phonenumbers.region_code_for_number(match.number)
|
||||||
if country == "US":
|
|
||||||
client = self._long_codes_client
|
|
||||||
attributes["AWS.MM.SMS.OriginationNumber"] = {
|
|
||||||
"DataType": "String",
|
|
||||||
"StringValue": self.current_app.config["AWS_US_TOLL_FREE_NUMBER"],
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
start_time = monotonic()
|
start_time = monotonic()
|
||||||
response = client.publish(PhoneNumber=to, Message=content, MessageAttributes=attributes)
|
response = client.publish(PhoneNumber=to, Message=content, MessageAttributes=attributes)
|
||||||
|
self.current_app.logger.info('RESPONSE FROM AWS SNS:')
|
||||||
|
for k,v in response.items():
|
||||||
|
self.current_app.logger.info(f'{k}: {v}')
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
self.statsd_client.incr("clients.sns.error")
|
self.statsd_client.incr("clients.sns.error")
|
||||||
raise str(e)
|
raise str(e)
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ import json
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
def find_by_service_name(services, service_name):
|
|
||||||
for i in range(len(services)):
|
|
||||||
if services[i]['name'] == service_name:
|
|
||||||
return services[i]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def extract_cloudfoundry_config():
|
def extract_cloudfoundry_config():
|
||||||
vcap_services = json.loads(os.environ['VCAP_SERVICES'])
|
vcap_services = json.loads(os.environ['VCAP_SERVICES'])
|
||||||
|
|
||||||
@@ -15,19 +9,3 @@ def extract_cloudfoundry_config():
|
|||||||
os.environ['SQLALCHEMY_DATABASE_URI'] = vcap_services['aws-rds'][0]['credentials']['uri'].replace('postgres','postgresql')
|
os.environ['SQLALCHEMY_DATABASE_URI'] = vcap_services['aws-rds'][0]['credentials']['uri'].replace('postgres','postgresql')
|
||||||
# Redis config
|
# Redis config
|
||||||
os.environ['REDIS_URL'] = vcap_services['aws-elasticache-redis'][0]['credentials']['uri'].replace('redis://','rediss://')
|
os.environ['REDIS_URL'] = vcap_services['aws-elasticache-redis'][0]['credentials']['uri'].replace('redis://','rediss://')
|
||||||
|
|
||||||
# CSV Upload Bucket Name
|
|
||||||
bucket_service = find_by_service_name(vcap_services['s3'], f"notifications-api-csv-upload-bucket-{os.environ['DEPLOY_ENV']}")
|
|
||||||
if bucket_service:
|
|
||||||
os.environ['CSV_UPLOAD_BUCKET_NAME'] = bucket_service['credentials']['bucket']
|
|
||||||
os.environ['CSV_UPLOAD_ACCESS_KEY'] = bucket_service['credentials']['access_key_id']
|
|
||||||
os.environ['CSV_UPLOAD_SECRET_KEY'] = bucket_service['credentials']['secret_access_key']
|
|
||||||
os.environ['CSV_UPLOAD_REGION'] = bucket_service['credentials']['region']
|
|
||||||
|
|
||||||
# Contact List Bucket Name
|
|
||||||
bucket_service = find_by_service_name(vcap_services['s3'], f"notifications-api-contact-list-bucket-{os.environ['DEPLOY_ENV']}")
|
|
||||||
if bucket_service:
|
|
||||||
os.environ['CONTACT_LIST_BUCKET_NAME'] = bucket_service['credentials']['bucket']
|
|
||||||
os.environ['CONTACT_LIST_ACCESS_KEY'] = bucket_service['credentials']['access_key_id']
|
|
||||||
os.environ['CONTACT_LIST_SECRET_KEY'] = bucket_service['credentials']['secret_access_key']
|
|
||||||
os.environ['CONTACT_LIST_REGION'] = bucket_service['credentials']['region']
|
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ class Staging(Config):
|
|||||||
# LETTER_SANITISE_BUCKET_NAME = 'staging-letters-sanitise'
|
# LETTER_SANITISE_BUCKET_NAME = 'staging-letters-sanitise'
|
||||||
FROM_NUMBER = 'stage'
|
FROM_NUMBER = 'stage'
|
||||||
API_RATE_LIMIT_ENABLED = True
|
API_RATE_LIMIT_ENABLED = True
|
||||||
CHECK_PROXY_HEADER = True
|
CHECK_PROXY_HEADER = False
|
||||||
|
|
||||||
|
|
||||||
class Live(Config):
|
class Live(Config):
|
||||||
@@ -562,7 +562,7 @@ class Live(Config):
|
|||||||
|
|
||||||
FROM_NUMBER = 'US Notify'
|
FROM_NUMBER = 'US Notify'
|
||||||
API_RATE_LIMIT_ENABLED = True
|
API_RATE_LIMIT_ENABLED = True
|
||||||
CHECK_PROXY_HEADER = True
|
CHECK_PROXY_HEADER = False
|
||||||
SES_STUB_URL = None
|
SES_STUB_URL = None
|
||||||
CRONITOR_ENABLED = True
|
CRONITOR_ENABLED = True
|
||||||
|
|
||||||
|
|||||||
@@ -207,6 +207,17 @@ def dao_fetch_service_by_inbound_number(number):
|
|||||||
Service.id == inbound_number.service_id
|
Service.id == inbound_number.service_id
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
|
def dao_fetch_service_by_reference(reference):
|
||||||
|
previous_message = Notification.query.filter(
|
||||||
|
Notification.reference == reference
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not previous_message:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return Service.query.filter(
|
||||||
|
Service.id == previous_message.service_id
|
||||||
|
).first()
|
||||||
|
|
||||||
def dao_fetch_service_by_id_with_api_keys(service_id, only_active=False):
|
def dao_fetch_service_by_id_with_api_keys(service_id, only_active=False):
|
||||||
query = Service.query.filter_by(
|
query = Service.query.filter_by(
|
||||||
|
|||||||
@@ -81,7 +81,11 @@ def send_sms_to_provider(notification):
|
|||||||
'international': notification.international,
|
'international': notification.international,
|
||||||
}
|
}
|
||||||
db.session.close() # no commit needed as no changes to objects have been made above
|
db.session.close() # no commit needed as no changes to objects have been made above
|
||||||
provider.send_sms(**send_sms_kwargs)
|
ref = provider.send_sms(**send_sms_kwargs)
|
||||||
|
notification.reference = ref
|
||||||
|
dao_update_notification(notification)
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
notification.billable_units = template.fragment_count
|
notification.billable_units = template.fragment_count
|
||||||
dao_update_notification(notification)
|
dao_update_notification(notification)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ DEFAULT_MAX_AGE = timedelta(days=10000)
|
|||||||
@ses_callback_blueprint.route('/notifications/email/ses', methods=['POST'])
|
@ses_callback_blueprint.route('/notifications/email/ses', methods=['POST'])
|
||||||
def email_ses_callback_handler():
|
def email_ses_callback_handler():
|
||||||
try:
|
try:
|
||||||
data = sns_notification_handler(request.data, request.headers)
|
data, _ = sns_notification_handler(request.data, request.headers)
|
||||||
except InvalidRequest as e:
|
except InvalidRequest as e:
|
||||||
return jsonify(
|
return jsonify(
|
||||||
result="error", message=str(e.message)
|
result="error", message=str(e.message)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from notifications_utils.recipients import try_validate_and_format_phone_number
|
|||||||
from app.celery import tasks
|
from app.celery import tasks
|
||||||
from app.config import QueueNames
|
from app.config import QueueNames
|
||||||
from app.dao.inbound_sms_dao import dao_create_inbound_sms
|
from app.dao.inbound_sms_dao import dao_create_inbound_sms
|
||||||
from app.dao.services_dao import dao_fetch_service_by_inbound_number
|
from app.dao.services_dao import dao_fetch_service_by_reference
|
||||||
from app.errors import InvalidRequest, register_errors
|
from app.errors import InvalidRequest, register_errors
|
||||||
from app.models import INBOUND_SMS_TYPE, SMS_TYPE, InboundSms
|
from app.models import INBOUND_SMS_TYPE, SMS_TYPE, InboundSms
|
||||||
from app.notifications.sns_handlers import sns_notification_handler
|
from app.notifications.sns_handlers import sns_notification_handler
|
||||||
@@ -39,17 +39,20 @@ def receive_sns_sms():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
post_data = sns_notification_handler(request.data, request.headers)
|
data, message_type = sns_notification_handler(request.data, request.headers)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise InvalidRequest(f"SMS-SNS callback failed with error: {e}", 400)
|
raise InvalidRequest(f"SMS-SNS callback failed with error: {e}", 400)
|
||||||
|
|
||||||
message = json.loads(post_data.get("Message"))
|
# TODO remove after smoke testing implemented on prod
|
||||||
|
current_app.logger.info(f'SNS message_type is: {message_type}, data is: {data}')
|
||||||
|
|
||||||
# TODO wrap this up
|
# TODO wrap this up
|
||||||
if "inboundMessageId" in message:
|
if message_type != 'SubscriptionConfirmation':
|
||||||
|
message = json.loads(data.get("Message"))
|
||||||
# TODO use standard formatting we use for all US numbers
|
# TODO use standard formatting we use for all US numbers
|
||||||
inbound_number = message['destinationNumber'].replace('+','')
|
inbound_number = message['destinationNumber'].replace('+','')
|
||||||
|
|
||||||
service = fetch_potential_service(inbound_number, 'sns')
|
service = fetch_potential_service(message['previousPublishedMessageId'], 'sns')
|
||||||
if not service:
|
if not service:
|
||||||
# since this is an issue with our service <-> number mapping, or no inbound_sms service permission
|
# since this is an issue with our service <-> number mapping, or no inbound_sms service permission
|
||||||
# we should still tell SNS that we received it successfully
|
# we should still tell SNS that we received it successfully
|
||||||
@@ -63,7 +66,7 @@ def receive_sns_sms():
|
|||||||
content = message.get("messageBody")
|
content = message.get("messageBody")
|
||||||
from_number = message.get('originationNumber')
|
from_number = message.get('originationNumber')
|
||||||
provider_ref = message.get('inboundMessageId')
|
provider_ref = message.get('inboundMessageId')
|
||||||
date_received = post_data.get('Timestamp')
|
date_received = data.get('Timestamp')
|
||||||
provider_name = "sns"
|
provider_name = "sns"
|
||||||
|
|
||||||
inbound = create_inbound_sms_object(service,
|
inbound = create_inbound_sms_object(service,
|
||||||
@@ -73,7 +76,7 @@ def receive_sns_sms():
|
|||||||
date_received=date_received,
|
date_received=date_received,
|
||||||
provider_name=provider_name)
|
provider_name=provider_name)
|
||||||
|
|
||||||
# TODO ensure inbound sms callback endpoints are accessible and functioning for notify api users, then uncomment the task below
|
# TODO ensure inbound sms callback endpoints are accessible and functioning for notify api users
|
||||||
tasks.send_inbound_sms_to_service.apply_async([str(inbound.id), str(service.id)], queue=QueueNames.NOTIFY)
|
tasks.send_inbound_sms_to_service.apply_async([str(inbound.id), str(service.id)], queue=QueueNames.NOTIFY)
|
||||||
|
|
||||||
current_app.logger.debug(
|
current_app.logger.debug(
|
||||||
@@ -215,12 +218,13 @@ def create_inbound_sms_object(service, content, from_number, provider_ref, date_
|
|||||||
return inbound
|
return inbound
|
||||||
|
|
||||||
|
|
||||||
def fetch_potential_service(inbound_number, provider_name):
|
def fetch_potential_service(reference, provider_name):
|
||||||
service = dao_fetch_service_by_inbound_number(inbound_number)
|
service = dao_fetch_service_by_reference(reference)
|
||||||
|
# service = dao_fetch_service_by_inbound_number(inbound_number)
|
||||||
|
|
||||||
if not service:
|
if not service:
|
||||||
current_app.logger.warning('Inbound number "{}" from {} not associated with a service'.format(
|
current_app.logger.warning('Inbound number "{}" from {} not associated with a service'.format(
|
||||||
inbound_number, provider_name
|
reference, provider_name
|
||||||
))
|
))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ def sns_notification_handler(data, headers):
|
|||||||
current_app.logger.warning(f"Attempt to raise_for_status()SubscriptionConfirmation Type message files for response: {response.text} with error {e}")
|
current_app.logger.warning(f"Attempt to raise_for_status()SubscriptionConfirmation Type message files for response: {response.text} with error {e}")
|
||||||
raise InvalidRequest("SES-SNS callback failed: attempt to raise_for_status()SubscriptionConfirmation Type message failed", 400)
|
raise InvalidRequest("SES-SNS callback failed: attempt to raise_for_status()SubscriptionConfirmation Type message failed", 400)
|
||||||
current_app.logger.info("SES-SNS auto-confirm subscription callback succeeded")
|
current_app.logger.info("SES-SNS auto-confirm subscription callback succeeded")
|
||||||
return message
|
return message, message.get('Type')
|
||||||
|
|
||||||
# TODO remove after smoke testing on prod is implemented
|
# TODO remove after smoke testing on prod is implemented
|
||||||
current_app.logger.info(f"SNS message: {message} is a valid message. Attempting to process it now.")
|
current_app.logger.info(f"SNS message: {message} is a valid message. Attempting to process it now.")
|
||||||
|
|
||||||
return message
|
return message, message.get('Type')
|
||||||
|
|||||||
23
manifest.yml
23
manifest.yml
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
applications:
|
applications:
|
||||||
- name: notifications-api-((env))
|
- name: notify-api-alt
|
||||||
buildpack: https://github.com/cloudfoundry/python-buildpack.git#v1.7.58
|
buildpack: https://github.com/cloudfoundry/python-buildpack.git#v1.7.58
|
||||||
instances: 1
|
instances: 1
|
||||||
memory: 1G
|
memory: 1G
|
||||||
@@ -8,29 +9,29 @@ applications:
|
|||||||
health-check-type: process
|
health-check-type: process
|
||||||
health-check-invocation-timeout: 1
|
health-check-invocation-timeout: 1
|
||||||
routes:
|
routes:
|
||||||
- route: notifications-api.app.cloud.gov
|
- route: notify-api-alt.app.cloud.gov
|
||||||
- route: notifications-api-((env)).apps.internal
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- notifications-api-rds-((env))
|
- api-alt-psql
|
||||||
- notifications-api-redis-((env))
|
- api-alt-redis
|
||||||
- notifications-api-csv-upload-bucket-((env))
|
|
||||||
- notifications-api-contact-list-bucket-((env))
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
BP_PIP_VERSION: latest
|
||||||
NOTIFY_APP_NAME: api
|
NOTIFY_APP_NAME: api
|
||||||
NOTIFY_LOG_PATH: /home/vcap/logs/app.log
|
NOTIFY_LOG_PATH: /home/vcap/logs/app.log
|
||||||
FLASK_APP: application.py
|
FLASK_APP: application.py
|
||||||
FLASK_ENV: production
|
FLASK_ENV: production
|
||||||
DEPLOY_ENV: ((env))
|
|
||||||
|
|
||||||
NOTIFY_ENVIRONMENT: live
|
NOTIFY_ENVIRONMENT: live
|
||||||
API_HOST_NAME: https://notifications-api.app.cloud.gov
|
API_HOST_NAME: https://notify-api-alt.app.cloud.gov
|
||||||
ADMIN_BASE_URL: https://notifications-admin.app.cloud.gov
|
ADMIN_BASE_URL: https://notify-admin-alt.app.cloud.gov
|
||||||
|
NOTIFICATION_QUEUE_PREFIX: notify_alt_
|
||||||
|
REDIS_ENABLED: true
|
||||||
STATSD_HOST: localhost
|
STATSD_HOST: localhost
|
||||||
|
|
||||||
# Credentials variables
|
|
||||||
INTERNAL_CLIENT_API_KEYS: '{"notify-admin":["((ADMIN_CLIENT_SECRET))"]}'
|
INTERNAL_CLIENT_API_KEYS: '{"notify-admin":["((ADMIN_CLIENT_SECRET))"]}'
|
||||||
|
|
||||||
|
# Credentials variables
|
||||||
ADMIN_CLIENT_SECRET: ((ADMIN_CLIENT_SECRET))
|
ADMIN_CLIENT_SECRET: ((ADMIN_CLIENT_SECRET))
|
||||||
DANGEROUS_SALT: ((DANGEROUS_SALT))
|
DANGEROUS_SALT: ((DANGEROUS_SALT))
|
||||||
SECRET_KEY: ((SECRET_KEY))
|
SECRET_KEY: ((SECRET_KEY))
|
||||||
|
|||||||
Reference in New Issue
Block a user