mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-16 10:12:32 -05:00
Add MMG_INBOUND_SMS_AUTH config variable and auth check
Checks authentication header value on inbound SMS requests from MMG against a list of allowed API keys set in the application config. At the moment, we're only logging the attempts without aborting the requests. Once this is rolled out to production and we've checked the logs we'll switch on the aborts and add the tests for 401 and 403 responses. This work has already been done for Firetext in a previous PR: https://github.com/alphagov/notifications-api/pull/1409
This commit is contained in:
@@ -48,6 +48,7 @@ def extract_notify_config(notify_config):
|
||||
os.environ['DANGEROUS_SALT'] = notify_config['credentials']['dangerous_salt']
|
||||
os.environ['SMS_INBOUND_WHITELIST'] = json.dumps(notify_config['credentials']['allow_ip_inbound_sms'])
|
||||
os.environ['FIRETEXT_INBOUND_SMS_AUTH'] = json.dumps(notify_config['credentials']['firetext_inbound_sms_auth'])
|
||||
os.environ['MMG_INBOUND_SMS_AUTH'] = json.dumps(notify_config['credentials']['mmg_inbound_sms_auth'])
|
||||
os.environ['ROUTE_SECRET_KEY_1'] = notify_config['credentials']['route_secret_key_1']
|
||||
os.environ['ROUTE_SECRET_KEY_2'] = notify_config['credentials']['route_secret_key_2']
|
||||
|
||||
|
||||
@@ -300,6 +300,7 @@ class Config(object):
|
||||
|
||||
SMS_INBOUND_WHITELIST = json.loads(os.environ.get('SMS_INBOUND_WHITELIST', '[]'))
|
||||
FIRETEXT_INBOUND_SMS_AUTH = json.loads(os.environ.get('FIRETEXT_INBOUND_SMS_AUTH', '[]'))
|
||||
MMG_INBOUND_SMS_AUTH = json.loads(os.environ.get('MMG_INBOUND_SMS_AUTH', '[]'))
|
||||
|
||||
ROUTE_SECRET_KEY_1 = os.environ.get('ROUTE_SECRET_KEY_1', '')
|
||||
ROUTE_SECRET_KEY_2 = os.environ.get('ROUTE_SECRET_KEY_2', '')
|
||||
@@ -376,6 +377,7 @@ class Test(Config):
|
||||
|
||||
SMS_INBOUND_WHITELIST = ['203.0.113.195']
|
||||
FIRETEXT_INBOUND_SMS_AUTH = ['testkey']
|
||||
MMG_INBOUND_SMS_AUTH = ['testkey']
|
||||
TEMPLATE_PREVIEW_API_HOST = 'http://localhost:9999'
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,15 @@ def receive_mmg_sms():
|
||||
"""
|
||||
post_data = request.get_json()
|
||||
|
||||
auth = request.authorization
|
||||
|
||||
if not auth:
|
||||
current_app.logger.warning("Inbound sms (MMG) no auth header")
|
||||
# abort(401)
|
||||
elif auth.username != 'mmg_co_2017' or auth.password not in current_app.config['MMG_INBOUND_SMS_AUTH']:
|
||||
current_app.logger.warning("Inbound sms (MMG) incorrect username ({}) or password".format(auth.username))
|
||||
# abort(403)
|
||||
|
||||
inbound_number = strip_leading_forty_four(post_data['Number'])
|
||||
|
||||
service = fetch_potential_service(inbound_number, 'mmg')
|
||||
@@ -49,20 +58,23 @@ def receive_mmg_sms():
|
||||
|
||||
tasks.send_inbound_sms_to_service.apply_async([str(inbound.id), str(service.id)], queue=QueueNames.NOTIFY)
|
||||
|
||||
return 'RECEIVED', 200
|
||||
current_app.logger.info(
|
||||
'{} received inbound SMS with reference {} from MMG'.format(service.id, inbound.provider_reference))
|
||||
return jsonify({
|
||||
"status": "ok"
|
||||
}), 200
|
||||
|
||||
|
||||
@receive_notifications_blueprint.route('/notifications/sms/receive/firetext', methods=['POST'])
|
||||
def receive_firetext_sms():
|
||||
post_data = request.form
|
||||
|
||||
# This is pre-implementation test code to validate the provider is basic auth headers.
|
||||
auth = request.authorization
|
||||
if not auth:
|
||||
current_app.logger.warning("Inbound sms no auth header")
|
||||
current_app.logger.warning("Inbound sms (Firetext) no auth header")
|
||||
abort(401)
|
||||
elif auth.username != 'notify' or auth.password not in current_app.config['FIRETEXT_INBOUND_SMS_AUTH']:
|
||||
current_app.logger.warning("Inbound sms incorrect username ({}) or password".format(auth.username))
|
||||
current_app.logger.warning("Inbound sms (Firetext) incorrect username ({}) or password".format(auth.username))
|
||||
abort(403)
|
||||
|
||||
inbound_number = strip_leading_forty_four(post_data['destination'])
|
||||
|
||||
@@ -37,14 +37,21 @@ def firetext_post(client, data, auth=True, password='testkey'):
|
||||
)
|
||||
|
||||
|
||||
def mmg_post(client, data):
|
||||
def mmg_post(client, data, auth=True, password='testkey'):
|
||||
headers = [
|
||||
('Content-Type', 'application/json'),
|
||||
('X-Forwarded-For', '203.0.113.195, 70.41.3.18, 150.172.238.178')
|
||||
]
|
||||
|
||||
if auth:
|
||||
auth_value = base64.b64encode("notify:{}".format(password).encode('utf-8')).decode('utf-8')
|
||||
headers.append(('Authorization', 'Basic ' + auth_value))
|
||||
|
||||
return client.post(
|
||||
path='/notifications/sms/receive/mmg',
|
||||
data=json.dumps(data),
|
||||
headers=[
|
||||
('Content-Type', 'application/json'),
|
||||
('X-Forwarded-For', '203.0.113.195, 70.41.3.18, 150.172.238.178')
|
||||
])
|
||||
headers=headers
|
||||
)
|
||||
|
||||
|
||||
def test_receive_notification_returns_received_to_mmg(client, mocker, sample_service_full_permissions):
|
||||
@@ -61,7 +68,9 @@ def test_receive_notification_returns_received_to_mmg(client, mocker, sample_ser
|
||||
response = mmg_post(client, data)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.get_data(as_text=True) == 'RECEIVED'
|
||||
result = json.loads(response.get_data(as_text=True))
|
||||
assert result['status'] == 'ok'
|
||||
|
||||
inbound_sms_id = InboundSms.query.all()[0].id
|
||||
mocked.assert_called_once_with(
|
||||
[str(inbound_sms_id), str(sample_service_full_permissions.id)], queue="notify-internal-tasks")
|
||||
@@ -411,6 +420,31 @@ def test_firetext_inbound_sms_auth(notify_db_session, notify_api, client, mocker
|
||||
assert response.status_code == status_code
|
||||
|
||||
|
||||
@pytest.mark.parametrize("auth, keys, status_code", [
|
||||
["testkey", ["testkey"], 200],
|
||||
["", ["testkey"], 401],
|
||||
["wrong", ["testkey"], 403],
|
||||
["testkey1", ["testkey1", "testkey2"], 200],
|
||||
["testkey2", ["testkey1", "testkey2"], 200],
|
||||
["wrong", ["testkey1", "testkey2"], 403],
|
||||
["", [], 401],
|
||||
["testkey", [], 403],
|
||||
])
|
||||
@pytest.mark.skip(reason="aborts are disabled at the moment")
|
||||
def test_mmg_inbound_sms_auth(notify_db_session, notify_api, client, mocker, auth, keys, status_code):
|
||||
mocker.patch("app.notifications.receive_notifications.tasks.send_inbound_sms_to_service.apply_async")
|
||||
|
||||
create_service_with_inbound_number(
|
||||
service_name='b', inbound_number='07111111111', service_permissions=[EMAIL_TYPE, SMS_TYPE, INBOUND_SMS_TYPE]
|
||||
)
|
||||
|
||||
data = "source=07999999999&destination=07111111111&message=this is a message&time=2017-01-01 12:00:00"
|
||||
|
||||
with set_config(notify_api, 'MMG_INBOUND_SMS_AUTH', keys):
|
||||
response = mmg_post(client, data, auth=bool(auth), password=auth)
|
||||
assert response.status_code == status_code
|
||||
|
||||
|
||||
def test_create_inbound_sms_object_works_with_alphanumeric_sender(sample_service_full_permissions):
|
||||
data = {
|
||||
'Message': 'hello',
|
||||
|
||||
@@ -18,6 +18,7 @@ def notify_config():
|
||||
'dangerous_salt': 'dangerous salt',
|
||||
'allow_ip_inbound_sms': ['111.111.111.111', '100.100.100.100'],
|
||||
'firetext_inbound_sms_auth': ['testkey'],
|
||||
'mmg_inbound_sms_auth': ['testkey'],
|
||||
'route_secret_key_1': "key_1",
|
||||
'route_secret_key_2': ""
|
||||
}
|
||||
@@ -232,6 +233,13 @@ def test_firetext_inbound_sms_auth_config():
|
||||
assert os.environ['FIRETEXT_INBOUND_SMS_AUTH'] == json.dumps(['testkey'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('os_environ', 'cloudfoundry_environ')
|
||||
def test_mmg_inbound_sms_auth_config():
|
||||
extract_cloudfoundry_config()
|
||||
|
||||
assert os.environ['MMG_INBOUND_SMS_AUTH'] == json.dumps(['testkey'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('os_environ', 'cloudfoundry_environ')
|
||||
def test_performance_platform_config():
|
||||
extract_cloudfoundry_config()
|
||||
|
||||
Reference in New Issue
Block a user