Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validation to check that notifications dont exceed sqs msg limit #2085

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions app/notifications/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
increment_todays_requested_sms_count,
)
from app.utils import (
flatten_dct,
get_document_url,
get_limit_reset_time_et,
get_public_notify_type_text,
Expand Down Expand Up @@ -594,3 +595,19 @@ def decode_personalisation_files(json_personalisation):
}
)
return json_personalisation, errors

def validate_notification_does_not_exceed_sqs_limit(notification):
# SQS max payload size is 256KB
if len(str(notification)) >= 262144:
whabanks marked this conversation as resolved.
Show resolved Hide resolved
# find the largest value in the payload for logging and return message
max_key, max_length = max(
((key, len(str(value))) for key, value in flatten_dct(dict(notification)).items()),
key=lambda x: x[1]
)
current_app.logger.debug(
f"Unable to send notification {notification['id']}. Payload size exceeds SQS limit of 262144 bytes\nLikely culprit: {max_key}\nLargest value: {max_length} bytes"
)

raise BadRequestError(
message=f"Notification size cannot exceed 256Kb. Consider reducing the size of: {max_key}.", status_code=413
)
25 changes: 24 additions & 1 deletion app/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from collections.abc import MutableMapping
from datetime import datetime, timedelta
from typing import Any
from typing import Any, List, Tuple

import pytz
from flask import current_app, url_for
Expand Down Expand Up @@ -142,6 +143,28 @@ def email_address_is_nhs(email_address):
)


def flatten_dct(dictionary: MutableMapping[Any, Any], parent_key: str = "", separator: str = ".") -> dict:
"""Recursively flattens a nested dict structure into a single level dict structure.
Composite keys are generated from the nested structure keys using the specified separator

Args:
dictionary: The dict to flatten
parent_key: Root level identifier for compositing keys, defaults to the top level key of the dict
separator: Separator used to when compositing keys.

Returns:
_type_: _description_
"""
items: List[Tuple[str, Any]] = []
for key, value in dictionary.items():
new_key = parent_key + separator + key if parent_key else key
if isinstance(value, MutableMapping):
items.extend(flatten_dct(value, new_key, separator=separator).items())
else:
items.append((new_key, value))
return dict(items)


def update_dct_to_str(update_dct, lang):
if lang not in ["EN", "FR"]:
raise NotImplementedError
Expand Down
6 changes: 5 additions & 1 deletion app/v2/notifications/post_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import csv
import functools
import uuid
from collections.abc import MutableMapping
Fixed Show fixed Hide fixed
from datetime import datetime
from io import StringIO

Expand Down Expand Up @@ -83,14 +84,15 @@
increment_email_daily_count_send_warnings_if_needed,
increment_sms_daily_count_send_warnings_if_needed,
validate_and_format_recipient,
validate_notification_does_not_exceed_sqs_limit,
validate_template,
validate_template_exists,
)
from app.schema_validation import validate
from app.schemas import job_schema
from app.service.utils import safelisted_members
from app.sms_fragment_utils import fetch_todays_requested_sms_count
from app.utils import get_delivery_queue_for_template
from app.utils import flatten_dct, get_delivery_queue_for_template
Fixed Show fixed Hide fixed
from app.v2.errors import BadRequestError
from app.v2.notifications import v2_notification_blueprint
from app.v2.notifications.create_response import (
Expand Down Expand Up @@ -415,6 +417,8 @@
"reply_to_text": reply_to_text,
}

validate_notification_does_not_exceed_sqs_limit(_notification)

signed_notification_data = signer_notification.sign(_notification)
notification = {**_notification}
scheduled_for = form.get("scheduled_for", None)
Expand Down
Loading