From 532acdc9e4623b6d560226bffd3013aab52aeb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=BCnzel?= <simonk@fsmpi.rwth-aachen.de> Date: Tue, 29 Apr 2025 19:55:47 +0200 Subject: [PATCH] Add separate rate limiter for resources --- api/config/api_example_config.py | 15 +++++++++++++++ api/src/api/routes/resources.py | 19 ++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/api/config/api_example_config.py b/api/config/api_example_config.py index 3535503..cf43636 100644 --- a/api/config/api_example_config.py +++ b/api/config/api_example_config.py @@ -111,6 +111,21 @@ API_AUTH_RATE_LIMIT = [ } ] +# Works the same as global but used for resource requests (should be higher than default, since loading a course page can +# send dozens of requests at once; jumping in the video, etc.) +API_RESOURCES_RATE_LIMIT = [ + { + "id": "short", + "window_size_seconds": 60, + "max_request_count": 500 + }, + { + "id": "long", + "window_size_seconds": 60 * 60, + "max_request_count": 4000 + } +] + # Absolute limit. If there are already 32 chapters (visible or not visible), no more suggestions are accepted API_CHAPTER_SUGGESTIONS_LIMIT_PER_LECTURE = 32 # This is NOT host based but globally. It uses a sliding window. For example for a window size of 24 hours, no more than diff --git a/api/src/api/routes/resources.py b/api/src/api/routes/resources.py index 8e0ce29..68c7e75 100644 --- a/api/src/api/routes/resources.py +++ b/api/src/api/routes/resources.py @@ -18,6 +18,8 @@ _FILE_URI_PREFIX_PATH = url_parse.urlparse(_FILE_URI_PREFIX).path if _FILE_URI_PREFIX_PATH is None: raise ValueError("Cannot get path from FILE_URI_PREFIX") +_API_RESOURCE_RATE_LIMITERS = create_configured_host_rate_limiters("resources", api.config["API_RESOURCES_RATE_LIMIT"]) + def _check_access_medium_file(course_handle: str, medium_file_id: int) -> MediumFile: is_mod = is_moderator() @@ -54,17 +56,24 @@ def _decode_str_from_url(url_val: str) -> str: return base64.urlsafe_b64decode(url_val.encode(encoding="utf-8")).decode(encoding="utf-8") -@api_route("/course/<string:course_handle>/resources/medium_file/<int:medium_file_id>", "GET", allow_while_readonly=True, - no_documentation=True) +@api_add_route("/course/<string:course_handle>/resources/medium_file/<int:medium_file_id>", "GET") +@api_function( + rate_limiters=_API_RESOURCE_RATE_LIMITERS, + allow_while_readonly=True, + no_documentation=True, +) def api_route_access_target_medium(course_handle: str, medium_file_id: int): - download = request.args.get("download", "true").lower() == "true" + download = request.args.get("download", "false").lower() == "true" medium_file = _check_access_medium_file(course_handle, medium_file_id) # TODO download stats return redirect(f"{_FILE_URI_PREFIX}/{medium_file.file_path}?co_ha={_encode_str_for_url(course_handle)}&mf_id={medium_file.id}&download={"true" if download else "false"}") -@api_route("/resources/internal_auth_check", "GET", allow_while_readonly=True, - no_documentation=True) +@api_add_route("/resources/internal_auth_check", "GET") +@api_function( + rate_limiters=(), # Don't limit internal auth check. Always from same IP + allow_while_readonly=True, + no_documentation=True) def api_route_resource_internal_auth_check(): if "X-Original-URI" not in request.headers: raise ApiClientException(ERROR_REQUEST_MISSING_PARAMETER("Header 'X-Original-URI'")) -- GitLab