2016-10-27 11:46:37 +01:00
|
|
|
|
from flask import current_app
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from sqlalchemy.orm.exc import NoResultFound
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from app import redis_store
|
2023-09-21 13:26:27 -06:00
|
|
|
|
from app.dao.notifications_dao import dao_get_notification_count_for_service
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from app.dao.service_email_reply_to_dao import dao_get_reply_to_by_id
|
2017-10-30 13:36:49 +00:00
|
|
|
|
from app.dao.service_sms_sender_dao import dao_get_service_sms_senders_by_id
|
2024-01-16 07:37:21 -05:00
|
|
|
|
from app.enums import KeyType, NotificationType, ServicePermissionType, TemplateType
|
2025-01-23 13:41:13 -08:00
|
|
|
|
from app.errors import BadRequestError, TotalRequestsError
|
2024-01-18 10:28:50 -05:00
|
|
|
|
from app.models import ServicePermission
|
2023-08-25 08:10:33 -07:00
|
|
|
|
from app.notifications.process_notifications import create_content_for_notification
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from app.serialised_models import SerialisedTemplate
|
2016-10-27 11:46:37 +01:00
|
|
|
|
from app.service.utils import service_allowed_to_send_to
|
2025-01-23 13:28:26 -08:00
|
|
|
|
from app.utils import get_public_notify_type_text, hilite
|
2024-05-16 10:17:45 -04:00
|
|
|
|
from notifications_utils import SMS_CHAR_COUNT_LIMIT
|
2025-01-23 13:41:13 -08:00
|
|
|
|
from notifications_utils.clients.redis import total_limit_cache_key
|
2024-05-16 10:17:45 -04:00
|
|
|
|
from notifications_utils.recipients import (
|
|
|
|
|
|
get_international_phone_info,
|
|
|
|
|
|
validate_and_format_email_address,
|
|
|
|
|
|
validate_and_format_phone_number,
|
|
|
|
|
|
)
|
2020-05-13 11:06:27 +01:00
|
|
|
|
|
2016-10-25 18:04:03 +01:00
|
|
|
|
|
2023-04-28 12:37:06 -07:00
|
|
|
|
def check_service_over_total_message_limit(key_type, service):
|
2025-01-23 13:28:26 -08:00
|
|
|
|
print(hilite("ENTER check_service_over_total_message_limit"))
|
2024-01-18 10:28:50 -05:00
|
|
|
|
if key_type == KeyType.TEST or not current_app.config["REDIS_ENABLED"]:
|
2023-04-28 12:37:06 -07:00
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
cache_key = total_limit_cache_key(service.id)
|
2025-01-23 13:28:26 -08:00
|
|
|
|
print(hilite(f"CACHE_KEY = {cache_key}"))
|
2023-04-28 12:37:06 -07:00
|
|
|
|
service_stats = redis_store.get(cache_key)
|
2025-01-23 10:12:17 -08:00
|
|
|
|
|
2025-01-23 10:32:12 -08:00
|
|
|
|
# Originally this was a daily limit check. It is now a free-tier limit check.
|
|
|
|
|
|
# TODO is this annual or forever for each service?
|
|
|
|
|
|
# TODO do we need a way to clear this out? How do we determine if it is
|
|
|
|
|
|
# free-tier or paid? What are the limits for paid? Etc.
|
|
|
|
|
|
# TODO
|
|
|
|
|
|
# setting expiration to one year for now on the assume that the free tier
|
|
|
|
|
|
# limit resets annually.
|
2025-01-23 13:28:26 -08:00
|
|
|
|
|
|
|
|
|
|
# add column for actual charges to notifications and notifification_history table
|
|
|
|
|
|
# add service api to return total_message_limit and actual number of messages for service
|
2023-04-28 12:37:06 -07:00
|
|
|
|
if service_stats is None:
|
|
|
|
|
|
service_stats = 0
|
2025-01-23 13:41:13 -08:00
|
|
|
|
redis_store.set(cache_key, service_stats, ex=365 * 24 * 60 * 60)
|
2023-04-28 12:37:06 -07:00
|
|
|
|
return service_stats
|
2025-01-23 13:28:26 -08:00
|
|
|
|
if int(service_stats) >= 5:
|
2025-01-23 13:41:13 -08:00
|
|
|
|
# if int(service_stats) >= service.total_message_limit:
|
2023-05-01 13:26:19 -07:00
|
|
|
|
current_app.logger.warning(
|
2023-04-28 12:37:06 -07:00
|
|
|
|
"service {} has been rate limited for total use sent {} limit {}".format(
|
2023-08-31 10:57:54 -04:00
|
|
|
|
service.id, int(service_stats), service.total_message_limit
|
|
|
|
|
|
)
|
2023-04-28 12:37:06 -07:00
|
|
|
|
)
|
2023-08-31 10:57:54 -04:00
|
|
|
|
raise TotalRequestsError(service.total_message_limit)
|
2025-01-23 13:28:26 -08:00
|
|
|
|
else:
|
2025-01-23 13:41:13 -08:00
|
|
|
|
print(
|
|
|
|
|
|
hilite(
|
|
|
|
|
|
f"TOTAL MESSAGE LIMIT {service.total_message_limit} CURRENT {service_stats}"
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2023-04-28 12:37:06 -07:00
|
|
|
|
return int(service_stats)
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-07-13 07:25:19 -07:00
|
|
|
|
def check_application_over_retention_limit(key_type, service):
|
2024-01-18 10:28:50 -05:00
|
|
|
|
if key_type == KeyType.TEST or not current_app.config["REDIS_ENABLED"]:
|
2023-03-14 16:28:38 -04:00
|
|
|
|
return 0
|
2023-10-27 11:17:22 -06:00
|
|
|
|
total_stats = dao_get_notification_count_for_service(service_id=service.id)
|
2023-03-14 16:28:38 -04:00
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
daily_message_limit = current_app.config["DAILY_MESSAGE_LIMIT"]
|
2023-09-21 13:26:27 -06:00
|
|
|
|
|
2023-03-14 16:28:38 -04:00
|
|
|
|
if int(total_stats) >= daily_message_limit:
|
|
|
|
|
|
current_app.logger.info(
|
|
|
|
|
|
"while sending for service {}, daily message limit of {} reached".format(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
service.id, daily_message_limit
|
|
|
|
|
|
)
|
2023-03-14 16:28:38 -04:00
|
|
|
|
)
|
|
|
|
|
|
raise TotalRequestsError(daily_message_limit)
|
|
|
|
|
|
return int(total_stats)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-10-25 18:04:03 +01:00
|
|
|
|
def check_template_is_for_notification_type(notification_type, template_type):
|
|
|
|
|
|
if notification_type != template_type:
|
2023-08-23 10:35:43 -07: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:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
raise BadRequestError(
|
|
|
|
|
|
fields=[{"template": "Template has been deleted"}],
|
|
|
|
|
|
message="Template has been deleted",
|
|
|
|
|
|
)
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
def service_can_send_to_recipient(
|
|
|
|
|
|
send_to, key_type, service, allow_guest_list_recipients=True
|
|
|
|
|
|
):
|
|
|
|
|
|
if not service_allowed_to_send_to(
|
|
|
|
|
|
send_to, service, key_type, allow_guest_list_recipients
|
|
|
|
|
|
):
|
2024-01-18 10:28:50 -05:00
|
|
|
|
if key_type == KeyType.TEAM:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
message = "Can’t send to this recipient using a team-only API key"
|
2016-10-27 11:46:37 +01:00
|
|
|
|
else:
|
|
|
|
|
|
message = (
|
2023-08-23 10:35:43 -07:00
|
|
|
|
"Can’t send to this recipient when service is in trial mode "
|
|
|
|
|
|
"– see https://www.notifications.service.gov.uk/trial-mode"
|
2016-10-27 11:46:37 +01:00
|
|
|
|
)
|
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):
|
2020-06-26 14:10:12 +01:00
|
|
|
|
return notify_type 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):
|
2023-08-23 10:35:43 -07:00
|
|
|
|
raise BadRequestError(
|
|
|
|
|
|
message="Service is not allowed to send {}".format(
|
|
|
|
|
|
get_public_notify_type_text(notify_type, plural=True)
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2020-02-25 16:11:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-02-26 16:04:15 +00:00
|
|
|
|
def check_if_service_can_send_files_by_email(service_contact_link, service_id):
|
2020-02-25 16:11:53 +00:00
|
|
|
|
if not service_contact_link:
|
2020-02-25 17:10:22 +00:00
|
|
|
|
raise BadRequestError(
|
2020-02-26 16:04:15 +00:00
|
|
|
|
message=f"Send files by email has not been set up - add contact details for your service at "
|
2020-11-04 14:25:22 +00:00
|
|
|
|
f"{current_app.config['ADMIN_BASE_URL']}/services/{service_id}/service-settings/send-files-by-email"
|
2020-02-25 17:10:22 +00:00
|
|
|
|
)
|
2017-07-03 13:25:02 +01:00
|
|
|
|
|
|
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
def validate_and_format_recipient(
|
|
|
|
|
|
send_to, key_type, service, notification_type, allow_guest_list_recipients=True
|
|
|
|
|
|
):
|
2018-01-11 14:25:40 +00:00
|
|
|
|
if send_to is None:
|
|
|
|
|
|
raise BadRequestError(message="Recipient can't be empty")
|
|
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
service_can_send_to_recipient(
|
|
|
|
|
|
send_to, key_type, service, allow_guest_list_recipients
|
|
|
|
|
|
)
|
2017-04-26 15:56:45 +01:00
|
|
|
|
|
2024-02-28 12:40:52 -05:00
|
|
|
|
if notification_type == NotificationType.SMS:
|
2020-06-16 17:55:02 +01:00
|
|
|
|
international_phone_info = check_if_service_can_send_to_number(service, send_to)
|
2017-04-26 15:56:45 +01:00
|
|
|
|
|
|
|
|
|
|
return validate_and_format_phone_number(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
number=send_to, international=international_phone_info.international
|
2017-04-26 15:56:45 +01:00
|
|
|
|
)
|
2024-02-28 12:40:52 -05:00
|
|
|
|
elif notification_type == NotificationType.EMAIL:
|
2017-01-17 12:08:24 +00:00
|
|
|
|
return validate_and_format_email_address(email_address=send_to)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-06-16 17:55:02 +01:00
|
|
|
|
def check_if_service_can_send_to_number(service, number):
|
|
|
|
|
|
international_phone_info = get_international_phone_info(number)
|
|
|
|
|
|
|
2020-06-29 14:43:33 +01:00
|
|
|
|
if service.permissions and isinstance(service.permissions[0], ServicePermission):
|
|
|
|
|
|
permissions = [p.permission for p in service.permissions]
|
|
|
|
|
|
else:
|
|
|
|
|
|
permissions = service.permissions
|
|
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
if (
|
|
|
|
|
|
international_phone_info.international
|
2024-02-28 12:40:52 -05:00
|
|
|
|
and ServicePermissionType.INTERNATIONAL_SMS not in permissions
|
2023-08-23 10:35:43 -07:00
|
|
|
|
):
|
2020-06-16 17:55:02 +01:00
|
|
|
|
raise BadRequestError(message="Cannot send to international mobile numbers")
|
|
|
|
|
|
else:
|
|
|
|
|
|
return international_phone_info
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-11-09 15:19:00 +00:00
|
|
|
|
def check_is_message_too_long(template_with_content):
|
2020-03-04 17:04:11 +00:00
|
|
|
|
if template_with_content.is_message_too_long():
|
2020-11-09 15:19:00 +00:00
|
|
|
|
message = "Your message is too long. "
|
2024-02-28 12:40:52 -05:00
|
|
|
|
if template_with_content.template_type == TemplateType.SMS:
|
2020-11-09 15:19:00 +00:00
|
|
|
|
message += (
|
|
|
|
|
|
f"Text messages cannot be longer than {SMS_CHAR_COUNT_LIMIT} characters. "
|
|
|
|
|
|
f"Your message is {template_with_content.content_count_without_prefix} characters long."
|
|
|
|
|
|
)
|
2024-02-28 12:40:52 -05:00
|
|
|
|
elif template_with_content.template_type == TemplateType.EMAIL:
|
2020-11-09 15:19:00 +00:00
|
|
|
|
message += (
|
2020-12-16 15:29:06 +00:00
|
|
|
|
f"Emails cannot be longer than 2000000 bytes. "
|
2020-11-09 15:19:00 +00:00
|
|
|
|
f"Your message is {template_with_content.content_size_in_bytes} bytes."
|
|
|
|
|
|
)
|
2016-10-28 17:10:00 +01:00
|
|
|
|
raise BadRequestError(message=message)
|
2017-05-24 16:27:15 +01:00
|
|
|
|
|
|
|
|
|
|
|
2019-11-19 17:05:50 +00:00
|
|
|
|
def check_notification_content_is_not_empty(template_with_content):
|
|
|
|
|
|
if template_with_content.is_message_empty():
|
2023-08-23 10:35:43 -07:00
|
|
|
|
message = "Your message is empty."
|
2019-11-08 13:44:27 +00:00
|
|
|
|
raise BadRequestError(message=message)
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
def validate_template(
|
|
|
|
|
|
template_id, personalisation, service, notification_type, check_char_count=True
|
|
|
|
|
|
):
|
2017-06-13 17:33:04 +01:00
|
|
|
|
try:
|
2020-06-22 10:20:53 +01:00
|
|
|
|
template = SerialisedTemplate.from_id_and_service_id(template_id, service.id)
|
2017-06-13 17:33:04 +01:00
|
|
|
|
except NoResultFound:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
message = "Template not found"
|
|
|
|
|
|
raise BadRequestError(message=message, fields=[{"template": message}])
|
2017-06-13 17:33:04 +01:00
|
|
|
|
|
|
|
|
|
|
check_template_is_for_notification_type(notification_type, template.template_type)
|
|
|
|
|
|
check_template_is_active(template)
|
2019-11-19 17:05:50 +00:00
|
|
|
|
|
2017-06-13 17:33:04 +01:00
|
|
|
|
template_with_content = create_content_for_notification(template, personalisation)
|
2020-03-04 17:04:11 +00:00
|
|
|
|
|
2019-11-19 17:05:50 +00:00
|
|
|
|
check_notification_content_is_not_empty(template_with_content)
|
2020-03-04 17:04:11 +00:00
|
|
|
|
|
2020-11-04 14:25:22 +00:00
|
|
|
|
# validating the template in post_notifications happens before the file is uploaded for doc download,
|
|
|
|
|
|
# which means the length of the message can be exceeded because it's including the file.
|
|
|
|
|
|
# The document download feature is only available through the api.
|
|
|
|
|
|
if check_char_count:
|
2020-11-09 15:19:00 +00:00
|
|
|
|
check_is_message_too_long(template_with_content)
|
2019-11-19 17:05:50 +00:00
|
|
|
|
|
2017-06-13 17:33:04 +01:00
|
|
|
|
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_):
|
2024-02-28 12:40:52 -05:00
|
|
|
|
if type_ == NotificationType.EMAIL:
|
2017-12-15 16:51:40 +00:00
|
|
|
|
return check_service_email_reply_to_id(service_id, reply_to_id, type_)
|
2024-02-28 12:40:52 -05:00
|
|
|
|
elif type_ == NotificationType.SMS:
|
2017-12-15 16:51:40 +00:00
|
|
|
|
return check_service_sms_sender_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:
|
2023-08-23 10:35:43 -07: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:
|
2023-08-23 10:35:43 -07: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:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
message = (
|
|
|
|
|
|
"sms_sender_id {} does not exist in database for service id {}".format(
|
|
|
|
|
|
sms_sender_id, service_id
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2017-10-30 13:36:49 +00:00
|
|
|
|
raise BadRequestError(message=message)
|