mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-21 07:51:13 -05:00
* Added service_email_reply_to_id to the POST /v2/notifications/email and a test to test the validator * Caught NoResultFound exception in check_service_email_reply_to_id as it was not being caught when there there was no valid service_id or reply_to_id. Fixed failing tests which were not passing due to the NoResultFound exception and added further tests to check for the good path through the code and an test to check for an invalid service_id * Added service_email_reply_to_id to the POST /v2/notifications/email and a test to test the validator * Caught NoResultFound exception in check_service_email_reply_to_id as it was not being caught when there there was no valid service_id or reply_to_id. Fixed failing tests which were not passing due to the NoResultFound exception and added further tests to check for the good path through the code and an test to check for an invalid service_id * Fixed code style in validators.py to confirm with rules Update the name of email_reply_to_id to conform better with other attributes in the schema and the resultant code in post_notifications.py Fixed code style in test_validators.py to confirm with rules Added tests to test_post_notifications.py to test the email_reply_to_id being present and being incorrect, it being optional is being tested by other tests. * Added service_email_reply_to_id to the POST /v2/notifications/email and a test to test the validator * Added service_email_reply_to_id to the POST /v2/notifications/email and a test to test the validator * Caught NoResultFound exception in check_service_email_reply_to_id as it was not being caught when there there was no valid service_id or reply_to_id. Fixed failing tests which were not passing due to the NoResultFound exception and added further tests to check for the good path through the code and an test to check for an invalid service_id * Caught NoResultFound exception in check_service_email_reply_to_id as it was not being caught when there there was no valid service_id or reply_to_id. Fixed failing tests which were not passing due to the NoResultFound exception and added further tests to check for the good path through the code and an test to check for an invalid service_id * Fixed code style in validators.py to confirm with rules Update the name of email_reply_to_id to conform better with other attributes in the schema and the resultant code in post_notifications.py Fixed code style in test_validators.py to confirm with rules Added tests to test_post_notifications.py to test the email_reply_to_id being present and being incorrect, it being optional is being tested by other tests. * Minor update after manual merge to fix check style rule break in test_validators.py where a single space was introduced. * Updates after code review. Moved the template from the exception message as it was not required and updated the error message to match the field name in the sschema for better debugging and error identification. * Fixed test after update of exception message
145 lines
6.2 KiB
Python
145 lines
6.2 KiB
Python
from sqlalchemy.orm.exc import NoResultFound
|
||
from flask import current_app
|
||
from notifications_utils.recipients import (
|
||
validate_and_format_phone_number,
|
||
validate_and_format_email_address,
|
||
get_international_phone_info
|
||
)
|
||
from notifications_utils.clients.redis import rate_limit_cache_key, daily_limit_cache_key
|
||
|
||
from app.dao import services_dao, templates_dao
|
||
from app.models import (
|
||
INTERNATIONAL_SMS_TYPE, SMS_TYPE, EMAIL_TYPE,
|
||
KEY_TYPE_TEST, KEY_TYPE_TEAM, SCHEDULE_NOTIFICATIONS
|
||
)
|
||
from app.service.utils import service_allowed_to_send_to
|
||
from app.v2.errors import TooManyRequestsError, BadRequestError, RateLimitError
|
||
from app import redis_store
|
||
from app.notifications.process_notifications import create_content_for_notification
|
||
from app.utils import get_public_notify_type_text
|
||
from app.dao.service_email_reply_to_dao import dao_get_reply_to_by_id
|
||
|
||
|
||
def check_service_over_api_rate_limit(service, api_key):
|
||
if current_app.config['API_RATE_LIMIT_ENABLED']:
|
||
cache_key = rate_limit_cache_key(service.id, api_key.key_type)
|
||
rate_limit = current_app.config['API_KEY_LIMITS'][api_key.key_type]['limit']
|
||
interval = current_app.config['API_KEY_LIMITS'][api_key.key_type]['interval']
|
||
if redis_store.exceeded_rate_limit(cache_key, rate_limit, interval):
|
||
current_app.logger.error("service {} has been rate limited for throughput".format(service.id))
|
||
raise RateLimitError(rate_limit, interval, api_key.key_type)
|
||
|
||
|
||
def check_service_over_daily_message_limit(key_type, service):
|
||
if key_type != KEY_TYPE_TEST:
|
||
cache_key = daily_limit_cache_key(service.id)
|
||
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)
|
||
if int(service_stats) >= service.message_limit:
|
||
current_app.logger.error(
|
||
"service {} has been rate limited for daily use sent {} limit {}".format(
|
||
service.id, int(service_stats), service.message_limit)
|
||
)
|
||
raise TooManyRequestsError(service.message_limit)
|
||
|
||
|
||
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)
|
||
|
||
|
||
def check_template_is_for_notification_type(notification_type, template_type):
|
||
if notification_type != template_type:
|
||
message = "{0} template is not suitable for {1} notification".format(template_type,
|
||
notification_type)
|
||
raise BadRequestError(fields=[{'template': message}], message=message)
|
||
|
||
|
||
def check_template_is_active(template):
|
||
if template.archived:
|
||
raise BadRequestError(fields=[{'template': 'Template has been deleted'}],
|
||
message="Template has been deleted")
|
||
|
||
|
||
def service_can_send_to_recipient(send_to, key_type, service):
|
||
if not service_allowed_to_send_to(send_to, service, key_type):
|
||
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'
|
||
)
|
||
raise BadRequestError(message=message)
|
||
|
||
|
||
def service_has_permission(notify_type, permissions):
|
||
return notify_type in [p.permission for p in permissions]
|
||
|
||
|
||
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)")
|
||
|
||
|
||
def validate_and_format_recipient(send_to, key_type, service, notification_type):
|
||
service_can_send_to_recipient(send_to, key_type, service)
|
||
|
||
if notification_type == SMS_TYPE:
|
||
international_phone_info = get_international_phone_info(send_to)
|
||
|
||
if international_phone_info.international and \
|
||
INTERNATIONAL_SMS_TYPE not in [p.permission for p in service.permissions]:
|
||
raise BadRequestError(message="Cannot send to international mobile numbers")
|
||
|
||
return validate_and_format_phone_number(
|
||
number=send_to,
|
||
international=international_phone_info.international
|
||
)
|
||
elif notification_type == EMAIL_TYPE:
|
||
return validate_and_format_email_address(email_address=send_to)
|
||
|
||
|
||
def check_sms_content_char_count(content_count):
|
||
char_count_limit = current_app.config.get('SMS_CHAR_COUNT_LIMIT')
|
||
if content_count > char_count_limit:
|
||
message = 'Content for template has a character count greater than the limit of {}'.format(char_count_limit)
|
||
raise BadRequestError(message=message)
|
||
|
||
|
||
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
|
||
|
||
|
||
def check_service_email_reply_to_id(service_id, reply_to_id):
|
||
if not (reply_to_id is None):
|
||
try:
|
||
reply_to = dao_get_reply_to_by_id(service_id, reply_to_id)
|
||
except NoResultFound:
|
||
message = 'email_reply_to_id does not exist in database'
|
||
raise BadRequestError(message=message)
|