This is an automated email from the ASF dual-hosted git repository. kaxilnaik pushed a commit to branch v3-1-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 5ab7ae61dc52575f0ee0c7a938d058a3457a87cc Author: anasatzemoso <[email protected]> AuthorDate: Wed Sep 24 19:01:48 2025 +0530 fix: Gracefully handle fastAPI plugins with empty url_prefix (#55262) * fix: Gracefully handle fastAPI plugins with empty url_prefix * fix: log empty url_prefix and return all plugins in get_plugin_info * Validate plugin url_prefix against reserved routes * fix: Gracefully handle fastAPI plugins with empty url_prefix * fix: log empty url_prefix and return all plugins in get_plugin_info * Validate plugin url_prefix against reserved routes * Testcases for checking empty and reserved url_prefix * fix: Validate url_prefix using startswith * Simplified the for loop Co-authored-by: LIU ZHE YOU <[email protected]> * Converted set to list data structure for RESERVED_URL_PREFIXES Co-authored-by: LIU ZHE YOU <[email protected]> * Refactoring the test cases * fix: CI issue --------- Co-authored-by: LIU ZHE YOU <[email protected]> (cherry picked from commit 757d9ae3fb7e8d82ff544e434743abdaece51daa) --- airflow-core/src/airflow/api_fastapi/app.py | 9 ++++++++ airflow-core/tests/unit/api_fastapi/test_app.py | 28 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/airflow-core/src/airflow/api_fastapi/app.py b/airflow-core/src/airflow/api_fastapi/app.py index bae201c464e..3e76c4f8f2d 100644 --- a/airflow-core/src/airflow/api_fastapi/app.py +++ b/airflow-core/src/airflow/api_fastapi/app.py @@ -48,6 +48,9 @@ API_ROOT_PATH = urlsplit(API_BASE_URL).path # Define the full path on which the potential auth manager fastapi is mounted AUTH_MANAGER_FASTAPI_APP_PREFIX = f"{API_ROOT_PATH}auth" +# Fast API apps mounted under these prefixes are not allowed +RESERVED_URL_PREFIXES = ["/api/v2", "/ui", "/execution"] + log = logging.getLogger(__name__) app: FastAPI | None = None @@ -183,6 +186,12 @@ def init_plugins(app: FastAPI) -> None: if url_prefix is None: log.error("'url_prefix' key is missing for the fastapi app: %s", name) continue + if url_prefix == "": + log.error("'url_prefix' key is empty string for the fastapi app: %s", name) + continue + if any(url_prefix.startswith(prefix) for prefix in RESERVED_URL_PREFIXES): + log.error("Plugin %s attempted to use reserved url_prefix '%s'", name, url_prefix) + continue log.debug("Adding subapplication %s under prefix %s", name, url_prefix) app.mount(url_prefix, subapp) diff --git a/airflow-core/tests/unit/api_fastapi/test_app.py b/airflow-core/tests/unit/api_fastapi/test_app.py index d708aae2edf..448d527ab6b 100644 --- a/airflow-core/tests/unit/api_fastapi/test_app.py +++ b/airflow-core/tests/unit/api_fastapi/test_app.py @@ -19,6 +19,10 @@ from __future__ import annotations from unittest import mock import pytest +from fastapi import FastAPI + +import airflow.api_fastapi.app as app_module +import airflow.plugins_manager as plugins_manager pytestmark = pytest.mark.db_test @@ -90,3 +94,27 @@ def test_catch_all_route_last(client): """ test_app = client(apps="all").app assert test_app.routes[-1].path == "/{rest_of_path:path}" + + [email protected]( + "fastapi_apps, expected_message, invalid_path", + [ + ( + [{"name": "test", "app": FastAPI(), "url_prefix": ""}], + "'url_prefix' key is empty string for the fastapi app: test", + "", + ), + ( + [{"name": "test", "app": FastAPI(), "url_prefix": next(iter(app_module.RESERVED_URL_PREFIXES))}], + "attempted to use reserved url_prefix", + next(iter(app_module.RESERVED_URL_PREFIXES)), + ), + ], +) +def test_plugin_with_invalid_url_prefix(caplog, fastapi_apps, expected_message, invalid_path): + app = FastAPI() + with mock.patch.object(plugins_manager, "fastapi_apps", fastapi_apps): + app_module.init_plugins(app) + + assert any(expected_message in rec.message for rec in caplog.records) + assert not any(r.path == invalid_path for r in app.routes)
