2017-06-13 17:33:04 +01:00
|
|
|
|
from sqlalchemy.orm.exc import NoResultFound
|
2016-10-27 11:46:37 +01:00
|
|
|
|
from flask import current_app
|
2018-08-16 16:25:58 +01:00
|
|
|
|
from notifications_utils import SMS_CHAR_COUNT_LIMIT
|
2017-04-26 15:56:45 +01:00
|
|
|
|
from notifications_utils.recipients import (
|
|
|
|
|
|
validate_and_format_phone_number,
|
|
|
|
|
|
validate_and_format_email_address,
|
|
|
|
|
|
get_international_phone_info
|
|
|
|
|
|
)
|
2017-06-13 17:33:04 +01:00
|
|
|
|
from notifications_utils.clients.redis import rate_limit_cache_key, daily_limit_cache_key
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
2017-06-13 17:33:04 +01:00
|
|
|
|
from app.dao import services_dao, templates_dao
|
2017-10-30 13:36:49 +00:00
|
|
|
|
from app.dao.service_sms_sender_dao import dao_get_service_sms_senders_by_id
|
2017-06-28 12:14:36 +01:00
|
|
|
|
from app.models import (
|
2017-12-15 16:51:40 +00:00
|
|
|
|
INTERNATIONAL_SMS_TYPE, SMS_TYPE, EMAIL_TYPE, LETTER_TYPE,
|
2017-06-28 12:14:36 +01:00
|
|
|
|
KEY_TYPE_TEST, KEY_TYPE_TEAM, SCHEDULE_NOTIFICATIONS
|
|
|
|
|
|
)
|
2016-10-27 11:46:37 +01:00
|
|
|
|
from app.service.utils import service_allowed_to_send_to
|
2017-04-24 14:15:08 +01:00
|
|
|
|
from app.v2.errors import TooManyRequestsError, BadRequestError, RateLimitError
|
2016-11-11 16:47:52 +00:00
|
|
|
|
from app import redis_store
|
2017-06-13 17:33:04 +01:00
|
|
|
|
from app.notifications.process_notifications import create_content_for_notification
|
2017-07-03 13:25:02 +01:00
|
|
|
|
from app.utils import get_public_notify_type_text
|
2017-10-04 14:34:45 +01:00
|
|
|
|
from app.dao.service_email_reply_to_dao import dao_get_reply_to_by_id
|
2017-12-15 16:51:40 +00:00
|
|
|
|
from app.dao.service_letter_contact_dao import dao_get_letter_contact_by_id
|
2016-10-25 18:04:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-04-24 14:15:08 +01:00
|
|
|
|
def check_service_over_api_rate_limit(service, api_key):
|
2017-12-04 11:12:26 +00:00
|
|
|
|
if current_app.config['API_RATE_LIMIT_ENABLED'] and current_app.config['REDIS_ENABLED']:
|
2017-05-12 16:10:00 +01:00
|
|
|
|
cache_key = rate_limit_cache_key(service.id, api_key.key_type)
|
2018-01-09 13:24:54 +00:00
|
|
|
|
rate_limit = service.rate_limit
|
|
|
|
|
|
interval = 60
|
2017-04-26 10:24:39 +01:00
|
|
|
|
if redis_store.exceeded_rate_limit(cache_key, rate_limit, interval):
|
2017-05-02 11:14:45 +01:00
|
|
|
|
current_app.logger.error("service {} has been rate limited for throughput".format(service.id))
|
2017-04-26 21:08:43 +01:00
|
|
|
|
raise RateLimitError(rate_limit, interval, api_key.key_type)
|
2017-04-24 14:15:08 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_service_over_daily_message_limit(key_type, service):
|
2017-12-04 11:12:26 +00:00
|
|
|
|
if key_type != KEY_TYPE_TEST and current_app.config['REDIS_ENABLED']:
|
2017-05-12 16:10:00 +01:00
|
|
|
|
cache_key = daily_limit_cache_key(service.id)
|
2016-11-11 16:47:52 +00:00
|
|
|
|
service_stats = redis_store.get(cache_key)
|
|
|
|
|
|
if not service_stats:
|
|
|
|
|
|
service_stats = services_dao.fetch_todays_total_message_count(service.id)
|
|
|
|
|
|
redis_store.set(cache_key, service_stats, ex=3600)
|
2016-11-12 15:37:57 +00:00
|
|
|
|
if int(service_stats) >= service.message_limit:
|
2017-05-02 11:14:45 +01:00
|
|
|
|
current_app.logger.error(
|
|
|
|
|
|
"service {} has been rate limited for daily use sent {} limit {}".format(
|
|
|
|
|
|
service.id, int(service_stats), service.message_limit)
|
|
|
|
|
|
)
|
2016-10-27 11:46:37 +01:00
|
|
|
|
raise TooManyRequestsError(service.message_limit)
|
2016-10-25 18:04:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-04-24 14:15:08 +01:00
|
|
|
|
def check_rate_limiting(service, api_key):
|
|
|
|
|
|
check_service_over_api_rate_limit(service, api_key)
|
|
|
|
|
|
check_service_over_daily_message_limit(api_key.key_type, service)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-10-25 18:04:03 +01:00
|
|
|
|
def check_template_is_for_notification_type(notification_type, template_type):
|
|
|
|
|
|
if notification_type != template_type:
|
2016-10-31 12:22:26 +00:00
|
|
|
|
message = "{0} template is not suitable for {1} notification".format(template_type,
|
|
|
|
|
|
notification_type)
|
|
|
|
|
|
raise BadRequestError(fields=[{'template': message}], message=message)
|
2016-10-25 18:04:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_template_is_active(template):
|
|
|
|
|
|
if template.archived:
|
2016-10-31 12:22:26 +00:00
|
|
|
|
raise BadRequestError(fields=[{'template': 'Template has been deleted'}],
|
|
|
|
|
|
message="Template has been deleted")
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
2018-01-17 15:20:04 +00:00
|
|
|
|
def service_can_send_to_recipient(send_to, key_type, service, allow_whitelisted_recipients=True):
|
|
|
|
|
|
if not service_allowed_to_send_to(send_to, service, key_type, allow_whitelisted_recipients):
|
2016-10-27 11:46:37 +01:00
|
|
|
|
if key_type == KEY_TYPE_TEAM:
|
|
|
|
|
|
message = 'Can’t send to this recipient using a team-only API key'
|
|
|
|
|
|
else:
|
|
|
|
|
|
message = (
|
|
|
|
|
|
'Can’t send to this recipient when service is in trial mode '
|
|
|
|
|
|
'– see https://www.notifications.service.gov.uk/trial-mode'
|
|
|
|
|
|
)
|
2016-10-28 17:10:00 +01:00
|
|
|
|
raise BadRequestError(message=message)
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-06-29 18:02:21 +01:00
|
|
|
|
def service_has_permission(notify_type, permissions):
|
|
|
|
|
|
return notify_type in [p.permission for p in permissions]
|
2017-06-29 11:13:32 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-07-03 13:25:02 +01:00
|
|
|
|
def check_service_has_permission(notify_type, permissions):
|
|
|
|
|
|
if not service_has_permission(notify_type, permissions):
|
|
|
|
|
|
raise BadRequestError(message="Cannot send {}".format(
|
|
|
|
|
|
get_public_notify_type_text(notify_type, plural=True)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_service_can_schedule_notification(permissions, scheduled_for):
|
|
|
|
|
|
if scheduled_for:
|
|
|
|
|
|
if not service_has_permission(SCHEDULE_NOTIFICATIONS, permissions):
|
|
|
|
|
|
raise BadRequestError(message="Cannot schedule notifications (this feature is invite-only)")
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-01-17 15:20:04 +00:00
|
|
|
|
def validate_and_format_recipient(send_to, key_type, service, notification_type, allow_whitelisted_recipients=True):
|
2018-01-11 14:25:40 +00:00
|
|
|
|
if send_to is None:
|
|
|
|
|
|
raise BadRequestError(message="Recipient can't be empty")
|
|
|
|
|
|
|
2018-01-17 15:20:04 +00:00
|
|
|
|
service_can_send_to_recipient(send_to, key_type, service, allow_whitelisted_recipients)
|
2017-04-26 15:56:45 +01:00
|
|
|
|
|
2017-01-17 12:08:24 +00:00
|
|
|
|
if notification_type == SMS_TYPE:
|
2017-04-26 15:56:45 +01:00
|
|
|
|
international_phone_info = get_international_phone_info(send_to)
|
|
|
|
|
|
|
2017-06-26 14:18:04 +01:00
|
|
|
|
if international_phone_info.international and \
|
|
|
|
|
|
INTERNATIONAL_SMS_TYPE not in [p.permission for p in service.permissions]:
|
2017-04-26 15:56:45 +01:00
|
|
|
|
raise BadRequestError(message="Cannot send to international mobile numbers")
|
|
|
|
|
|
|
|
|
|
|
|
return validate_and_format_phone_number(
|
|
|
|
|
|
number=send_to,
|
|
|
|
|
|
international=international_phone_info.international
|
|
|
|
|
|
)
|
2017-07-07 17:10:25 +01:00
|
|
|
|
elif notification_type == EMAIL_TYPE:
|
2017-01-17 12:08:24 +00:00
|
|
|
|
return validate_and_format_email_address(email_address=send_to)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-10-27 11:46:37 +01:00
|
|
|
|
def check_sms_content_char_count(content_count):
|
2018-08-16 16:25:58 +01:00
|
|
|
|
if content_count > SMS_CHAR_COUNT_LIMIT:
|
|
|
|
|
|
message = 'Content for template has a character count greater than the limit of {}'.format(SMS_CHAR_COUNT_LIMIT)
|
2016-10-28 17:10:00 +01:00
|
|
|
|
raise BadRequestError(message=message)
|
2017-05-24 16:27:15 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-06-13 17:33:04 +01:00
|
|
|
|
def validate_template(template_id, personalisation, service, notification_type):
|
|
|
|
|
|
try:
|
|
|
|
|
|
template = templates_dao.dao_get_template_by_id_and_service_id(
|
|
|
|
|
|
template_id=template_id,
|
|
|
|
|
|
service_id=service.id
|
|
|
|
|
|
)
|
|
|
|
|
|
except NoResultFound:
|
|
|
|
|
|
message = 'Template not found'
|
|
|
|
|
|
raise BadRequestError(message=message,
|
|
|
|
|
|
fields=[{'template': message}])
|
|
|
|
|
|
|
|
|
|
|
|
check_template_is_for_notification_type(notification_type, template.template_type)
|
|
|
|
|
|
check_template_is_active(template)
|
|
|
|
|
|
template_with_content = create_content_for_notification(template, personalisation)
|
|
|
|
|
|
if template.template_type == SMS_TYPE:
|
|
|
|
|
|
check_sms_content_char_count(template_with_content.content_count)
|
|
|
|
|
|
return template, template_with_content
|
2017-10-04 14:34:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-15 16:51:40 +00:00
|
|
|
|
def check_reply_to(service_id, reply_to_id, type_):
|
|
|
|
|
|
if type_ == EMAIL_TYPE:
|
|
|
|
|
|
return check_service_email_reply_to_id(service_id, reply_to_id, type_)
|
|
|
|
|
|
elif type_ == SMS_TYPE:
|
|
|
|
|
|
return check_service_sms_sender_id(service_id, reply_to_id, type_)
|
|
|
|
|
|
elif type_ == LETTER_TYPE:
|
|
|
|
|
|
return check_service_letter_contact_id(service_id, reply_to_id, type_)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-10-30 13:36:49 +00:00
|
|
|
|
def check_service_email_reply_to_id(service_id, reply_to_id, notification_type):
|
2017-11-01 11:01:20 +00:00
|
|
|
|
if reply_to_id:
|
2017-10-04 14:34:45 +01:00
|
|
|
|
try:
|
2017-11-23 14:55:49 +00:00
|
|
|
|
return dao_get_reply_to_by_id(service_id, reply_to_id).email_address
|
2017-10-04 14:34:45 +01:00
|
|
|
|
except NoResultFound:
|
2017-10-05 13:22:00 +01:00
|
|
|
|
message = 'email_reply_to_id {} does not exist in database for service id {}'\
|
|
|
|
|
|
.format(reply_to_id, service_id)
|
2017-10-04 14:34:45 +01:00
|
|
|
|
raise BadRequestError(message=message)
|
2017-10-30 13:36:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_service_sms_sender_id(service_id, sms_sender_id, notification_type):
|
2017-11-01 11:01:20 +00:00
|
|
|
|
if sms_sender_id:
|
2017-10-30 13:36:49 +00:00
|
|
|
|
try:
|
2017-11-10 14:17:29 +00:00
|
|
|
|
return dao_get_service_sms_senders_by_id(service_id, sms_sender_id).sms_sender
|
2017-10-30 13:36:49 +00:00
|
|
|
|
except NoResultFound:
|
|
|
|
|
|
message = 'sms_sender_id {} does not exist in database for service id {}'\
|
|
|
|
|
|
.format(sms_sender_id, service_id)
|
|
|
|
|
|
raise BadRequestError(message=message)
|
2017-12-15 16:51:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_service_letter_contact_id(service_id, letter_contact_id, notification_type):
|
|
|
|
|
|
if letter_contact_id:
|
|
|
|
|
|
try:
|
|
|
|
|
|
return dao_get_letter_contact_by_id(service_id, letter_contact_id).contact_block
|
|
|
|
|
|
except NoResultFound:
|
|
|
|
|
|
message = 'letter_contact_id {} does not exist in database for service id {}'\
|
|
|
|
|
|
.format(letter_contact_id, service_id)
|
|
|
|
|
|
raise BadRequestError(message=message)
|