more input checking

This commit is contained in:
Kenneth Kehl
2025-06-26 10:35:46 -07:00
parent 3e93d6c9c6
commit 58a8b51f59
16 changed files with 214 additions and 86 deletions

View File

@@ -116,7 +116,7 @@ from app.service.service_senders_schema import (
)
from app.service.utils import get_guest_list_objects
from app.user.users_schema import post_set_permissions_schema
from app.utils import get_prev_next_pagination_links, utc_now
from app.utils import check_suspicious_id, get_prev_next_pagination_links, utc_now
service_blueprint = Blueprint("service", __name__)
@@ -202,6 +202,7 @@ def get_live_services_data():
@service_blueprint.route("/<uuid:service_id>", methods=["GET"])
def get_service_by_id(service_id):
check_suspicious_id(service_id)
if request.args.get("detailed") == "True":
data = get_detailed_service(
service_id, today_only=request.args.get("today_only") == "True"
@@ -217,6 +218,7 @@ def get_service_by_id(service_id):
@service_blueprint.route("/<uuid:service_id>/statistics")
def get_service_notification_statistics(service_id):
check_suspicious_id(service_id)
return jsonify(
data=get_service_statistics(
service_id,
@@ -228,12 +230,14 @@ def get_service_notification_statistics(service_id):
@service_blueprint.route("/<uuid:service_id>/statistics/<string:start>/<int:days>")
def get_service_notification_statistics_by_day(service_id, start, days):
check_suspicious_id(service_id)
return jsonify(
data=get_service_statistics_for_specific_days(service_id, start, int(days))
)
def get_service_statistics_for_specific_days(service_id, start, days=1):
check_suspicious_id(service_id)
# Calculate start and end date range
end_date = datetime.strptime(start, "%Y-%m-%d")
start_date = end_date - timedelta(days=days - 1)
@@ -264,6 +268,7 @@ def get_service_statistics_for_specific_days(service_id, start, days=1):
def get_service_notification_statistics_by_day_by_user(
service_id, user_id, start, days
):
check_suspicious_id(service_id, user_id)
return jsonify(
data=get_service_statistics_for_specific_days_by_user(
service_id, user_id, start, int(days)
@@ -323,6 +328,7 @@ def create_service():
@service_blueprint.route("/<uuid:service_id>", methods=["POST"])
def update_service(service_id):
check_suspicious_id(service_id)
req_json = request.get_json()
fetched_service = dao_fetch_service_by_id(service_id)
service_going_live = fetched_service.restricted and not req_json.get(
@@ -364,6 +370,8 @@ def update_service(service_id):
@service_blueprint.route("/<uuid:service_id>/api-key", methods=["POST"])
def create_api_key(service_id=None):
if service_id:
check_suspicious_id(service_id)
fetched_service = dao_fetch_service_by_id(service_id=service_id)
valid_api_key = api_key_schema.load(request.get_json(), session=db.session)
valid_api_key.service = fetched_service
@@ -376,6 +384,7 @@ def create_api_key(service_id=None):
"/<uuid:service_id>/api-key/revoke/<uuid:api_key_id>", methods=["POST"]
)
def revoke_api_key(service_id, api_key_id):
check_suspicious_id(service_id, api_key_id)
expire_api_key(service_id=service_id, api_key_id=api_key_id)
return jsonify(), 202
@@ -383,6 +392,10 @@ def revoke_api_key(service_id, api_key_id):
@service_blueprint.route("/<uuid:service_id>/api-keys", methods=["GET"])
@service_blueprint.route("/<uuid:service_id>/api-keys/<uuid:key_id>", methods=["GET"])
def get_api_keys(service_id, key_id=None):
if key_id:
check_suspicious_id(service_id, key_id)
else:
check_suspicious_id(service_id)
dao_fetch_service_by_id(service_id=service_id)
try:
@@ -399,12 +412,14 @@ def get_api_keys(service_id, key_id=None):
@service_blueprint.route("/<uuid:service_id>/users", methods=["GET"])
def get_users_for_service(service_id):
check_suspicious_id(service_id)
fetched = dao_fetch_service_by_id(service_id)
return jsonify(data=[x.serialize() for x in fetched.users])
@service_blueprint.route("/<uuid:service_id>/users/<user_id>", methods=["POST"])
def add_user_to_service(service_id, user_id):
check_suspicious_id(service_id, user_id)
service = dao_fetch_service_by_id(service_id)
user = get_user_by_id(user_id=user_id)
if user in service.users:
@@ -428,6 +443,7 @@ def add_user_to_service(service_id, user_id):
@service_blueprint.route("/<uuid:service_id>/users/<user_id>", methods=["DELETE"])
def remove_user_from_service(service_id, user_id):
check_suspicious_id(service_id, user_id)
service = dao_fetch_service_by_id(service_id)
user = get_user_by_id(user_id=user_id)
if user not in service.users:
@@ -447,6 +463,7 @@ def remove_user_from_service(service_id, user_id):
# tables. This is so product owner can pass stories as done
@service_blueprint.route("/<uuid:service_id>/history", methods=["GET"])
def get_service_history(service_id):
check_suspicious_id(service_id)
from app.models import ApiKey, Service, TemplateHistory
from app.schemas import (
api_key_history_schema,
@@ -496,6 +513,7 @@ def get_service_history(service_id):
@service_blueprint.route("/<uuid:service_id>/notifications", methods=["GET", "POST"])
def get_all_notifications_for_service(service_id):
check_suspicious_id(service_id)
current_app.logger.debug("enter get_all_notifications_for_service")
if request.method == "GET":
data = notifications_filter_schema.load(request.args)
@@ -625,6 +643,7 @@ def get_all_notifications_for_service(service_id):
"/<uuid:service_id>/notifications/<uuid:notification_id>", methods=["GET"]
)
def get_notification_for_service(service_id, notification_id):
check_suspicious_id(service_id, notification_id)
notification = notifications_dao.get_notification_with_personalisation(
service_id,
notification_id,
@@ -640,6 +659,7 @@ def get_notification_for_service(service_id, notification_id):
@service_blueprint.route("/<uuid:service_id>/notifications/monthly", methods=["GET"])
def get_monthly_notification_stats(service_id):
check_suspicious_id(service_id)
# check service_id validity
dao_fetch_service_by_id(service_id)
@@ -672,6 +692,7 @@ def get_monthly_notification_stats(service_id):
"/<uuid:service_id>/notifications/<uuid:user_id>/monthly", methods=["GET"]
)
def get_monthly_notification_stats_by_user(service_id, user_id):
check_suspicious_id(service_id, user_id)
# check service_id validity
dao_fetch_service_by_id(service_id)
# user = get_user_by_id(user_id=user_id)
@@ -705,6 +726,7 @@ def get_monthly_notification_stats_by_user(service_id, user_id):
"/<uuid:service_id>/notifications/<uuid:user_id>/month", methods=["GET"]
)
def get_single_month_notification_stats_by_user(service_id, user_id):
check_suspicious_id(service_id, user_id)
# check service_id validity
dao_fetch_service_by_id(service_id)
@@ -734,6 +756,7 @@ def get_single_month_notification_stats_by_user(service_id, user_id):
@service_blueprint.route("/<uuid:service_id>/notifications/month", methods=["GET"])
def get_single_month_notification_stats_for_service(service_id):
check_suspicious_id(service_id)
# check service_id validity
dao_fetch_service_by_id(service_id)
@@ -757,6 +780,7 @@ def get_single_month_notification_stats_for_service(service_id):
def get_detailed_service(service_id, today_only=False):
check_suspicious_id(service_id)
service = dao_fetch_service_by_id(service_id)
service.statistics = get_service_statistics(service_id, today_only)
@@ -764,6 +788,7 @@ def get_detailed_service(service_id, today_only=False):
def get_service_statistics(service_id, today_only, limit_days=7):
check_suspicious_id(service_id)
# today_only flag is used by the send page to work out if the service will exceed their daily usage by sending a job
if today_only:
stats = dao_fetch_todays_stats_for_service(service_id)
@@ -810,6 +835,7 @@ def get_detailed_services(
@service_blueprint.route("/<uuid:service_id>/guest-list", methods=["GET"])
def get_guest_list(service_id):
check_suspicious_id(service_id)
from app.enums import RecipientType
service = dao_fetch_service_by_id(service_id)
@@ -834,6 +860,7 @@ def get_guest_list(service_id):
@service_blueprint.route("/<uuid:service_id>/guest-list", methods=["PUT"])
def update_guest_list(service_id):
check_suspicious_id(service_id)
# doesn't commit so if there are any errors, we preserve old values in db
dao_remove_service_guest_list(service_id)
try:
@@ -850,6 +877,7 @@ def update_guest_list(service_id):
@service_blueprint.route("/<uuid:service_id>/archive", methods=["POST"])
def archive_service(service_id):
check_suspicious_id(service_id)
"""
When a service is archived the service is made inactive, templates are archived and api keys are revoked.
There is no coming back from this operation.
@@ -866,6 +894,7 @@ def archive_service(service_id):
@service_blueprint.route("/<uuid:service_id>/suspend", methods=["POST"])
def suspend_service(service_id):
check_suspicious_id(service_id)
"""
Suspending a service will mark the service as inactive and revoke API keys.
:param service_id:
@@ -881,6 +910,7 @@ def suspend_service(service_id):
@service_blueprint.route("/<uuid:service_id>/resume", methods=["POST"])
def resume_service(service_id):
check_suspicious_id(service_id)
"""
Resuming a service that has been suspended will mark the service as active.
The service will need to re-create API keys
@@ -899,6 +929,7 @@ def resume_service(service_id):
"/<uuid:service_id>/notifications/templates_usage/monthly", methods=["GET"]
)
def get_monthly_template_usage(service_id):
check_suspicious_id(service_id)
try:
start_date, end_date = get_calendar_year(int(request.args.get("year", "NaN")))
data = fetch_monthly_template_usage_for_service(
@@ -924,12 +955,14 @@ def get_monthly_template_usage(service_id):
@service_blueprint.route("/<uuid:service_id>/send-notification", methods=["POST"])
def create_one_off_notification(service_id):
check_suspicious_id(service_id)
resp = send_one_off_notification(service_id, request.get_json())
return jsonify(resp), 201
@service_blueprint.route("/<uuid:service_id>/email-reply-to", methods=["GET"])
def get_email_reply_to_addresses(service_id):
check_suspicious_id(service_id)
result = dao_get_reply_to_by_service_id(service_id)
return jsonify([i.serialize() for i in result]), 200
@@ -938,12 +971,14 @@ def get_email_reply_to_addresses(service_id):
"/<uuid:service_id>/email-reply-to/<uuid:reply_to_id>", methods=["GET"]
)
def get_email_reply_to_address(service_id, reply_to_id):
check_suspicious_id(service_id, reply_to_id)
result = dao_get_reply_to_by_id(service_id=service_id, reply_to_id=reply_to_id)
return jsonify(result.serialize()), 200
@service_blueprint.route("/<uuid:service_id>/email-reply-to/verify", methods=["POST"])
def verify_reply_to_email_address(service_id):
check_suspicious_id(service_id)
email_address = email_data_request_schema.load(request.get_json())
check_if_reply_to_address_already_in_use(service_id, email_address["email"])
@@ -970,6 +1005,7 @@ def verify_reply_to_email_address(service_id):
@service_blueprint.route("/<uuid:service_id>/email-reply-to", methods=["POST"])
def add_service_reply_to_email_address(service_id):
check_suspicious_id(service_id)
# validate the service exists, throws ResultNotFound exception.
dao_fetch_service_by_id(service_id)
form = validate(request.get_json(), add_service_email_reply_to_request)
@@ -986,6 +1022,7 @@ def add_service_reply_to_email_address(service_id):
"/<uuid:service_id>/email-reply-to/<uuid:reply_to_email_id>", methods=["POST"]
)
def update_service_reply_to_email_address(service_id, reply_to_email_id):
check_suspicious_id(service_id, reply_to_email_id)
# validate the service exists, throws ResultNotFound exception.
dao_fetch_service_by_id(service_id)
form = validate(request.get_json(), add_service_email_reply_to_request)
@@ -1003,6 +1040,7 @@ def update_service_reply_to_email_address(service_id, reply_to_email_id):
methods=["POST"],
)
def delete_service_reply_to_email_address(service_id, reply_to_email_id):
check_suspicious_id(service_id, reply_to_email_id)
archived_reply_to = archive_reply_to_email_address(service_id, reply_to_email_id)
return jsonify(data=archived_reply_to.serialize()), 200
@@ -1010,6 +1048,7 @@ def delete_service_reply_to_email_address(service_id, reply_to_email_id):
@service_blueprint.route("/<uuid:service_id>/sms-sender", methods=["POST"])
def add_service_sms_sender(service_id):
check_suspicious_id(service_id)
dao_fetch_service_by_id(service_id)
form = validate(request.get_json(), add_service_sms_sender_request)
inbound_number_id = form.get("inbound_number_id", None)
@@ -1046,6 +1085,7 @@ def add_service_sms_sender(service_id):
"/<uuid:service_id>/sms-sender/<uuid:sms_sender_id>", methods=["POST"]
)
def update_service_sms_sender(service_id, sms_sender_id):
check_suspicious_id(service_id, sms_sender_id)
form = validate(request.get_json(), add_service_sms_sender_request)
sms_sender_to_update = dao_get_service_sms_senders_by_id(
@@ -1073,6 +1113,7 @@ def update_service_sms_sender(service_id, sms_sender_id):
"/<uuid:service_id>/sms-sender/<uuid:sms_sender_id>/archive", methods=["POST"]
)
def delete_service_sms_sender(service_id, sms_sender_id):
check_suspicious_id(service_id, sms_sender_id)
sms_sender = archive_sms_sender(service_id, sms_sender_id)
return jsonify(data=sms_sender.serialize()), 200
@@ -1082,6 +1123,8 @@ def delete_service_sms_sender(service_id, sms_sender_id):
"/<uuid:service_id>/sms-sender/<uuid:sms_sender_id>", methods=["GET"]
)
def get_service_sms_sender_by_id(service_id, sms_sender_id):
check_suspicious_id(service_id, sms_sender_id)
sms_sender = dao_get_service_sms_senders_by_id(
service_id=service_id, service_sms_sender_id=sms_sender_id
)
@@ -1090,18 +1133,22 @@ def get_service_sms_sender_by_id(service_id, sms_sender_id):
@service_blueprint.route("/<uuid:service_id>/sms-sender", methods=["GET"])
def get_service_sms_senders_for_service(service_id):
check_suspicious_id(service_id)
sms_senders = dao_get_sms_senders_by_service_id(service_id=service_id)
return jsonify([sms_sender.serialize() for sms_sender in sms_senders]), 200
@service_blueprint.route("/<uuid:service_id>/organization", methods=["GET"])
def get_organization_for_service(service_id):
check_suspicious_id(service_id)
organization = dao_get_organization_by_service_id(service_id=service_id)
return jsonify(organization.serialize() if organization else {}), 200
@service_blueprint.route("/<uuid:service_id>/data-retention", methods=["GET"])
def get_data_retention_for_service(service_id):
check_suspicious_id(service_id)
data_retention_list = fetch_service_data_retention(service_id)
return (
jsonify([data_retention.serialize() for data_retention in data_retention_list]),
@@ -1114,6 +1161,7 @@ def get_data_retention_for_service(service_id):
methods=["GET"],
)
def get_data_retention_for_service_notification_type(service_id, notification_type):
check_suspicious_id(service_id)
data_retention = fetch_service_data_retention_by_notification_type(
service_id, notification_type
)
@@ -1124,12 +1172,14 @@ def get_data_retention_for_service_notification_type(service_id, notification_ty
"/<uuid:service_id>/data-retention/<uuid:data_retention_id>", methods=["GET"]
)
def get_data_retention_for_service_by_id(service_id, data_retention_id):
check_suspicious_id(service_id, data_retention_id)
data_retention = fetch_service_data_retention_by_id(service_id, data_retention_id)
return jsonify(data_retention.serialize() if data_retention else {}), 200
@service_blueprint.route("/<uuid:service_id>/data-retention", methods=["POST"])
def create_service_data_retention(service_id):
check_suspicious_id(service_id)
form = validate(request.get_json(), add_service_data_retention_request)
try:
new_data_retention = insert_service_data_retention(
@@ -1152,6 +1202,7 @@ def create_service_data_retention(service_id):
"/<uuid:service_id>/data-retention/<uuid:data_retention_id>", methods=["POST"]
)
def modify_service_data_retention(service_id, data_retention_id):
check_suspicious_id(service_id, data_retention_id)
form = validate(request.get_json(), update_service_data_retention_request)
update_count = update_service_data_retention(
@@ -1252,5 +1303,6 @@ def check_if_reply_to_address_already_in_use(service_id, email_address):
@service_blueprint.route("/<uuid:service_id>/notification-count", methods=["GET"])
def get_notification_count_for_service_id(service_id):
check_suspicious_id(service_id)
count = dao_get_notification_count_for_service(service_id=service_id)
return jsonify(count=count), 200