This is an automated email from the ASF dual-hosted git repository.
vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 56a26f956ab fab: centralize FastAPI auth manager routing (#61647)
56a26f956ab is described below
commit 56a26f956ab45ef7c3f19e3cafe89fc1e7309513
Author: Henry Chen <[email protected]>
AuthorDate: Sat Feb 21 01:33:07 2026 +0800
fab: centralize FastAPI auth manager routing (#61647)
* fab: centralize FastAPI auth manager routing
* Add register_routes
---
.../fab/auth_manager/api_fastapi/routes/login.py | 10 +++---
.../fab/auth_manager/api_fastapi/routes/roles.py | 16 +++++-----
.../fab/auth_manager/api_fastapi/routes/router.py | 36 ++++++++++++++++++++++
.../fab/auth_manager/api_fastapi/routes/users.py | 14 ++++-----
.../providers/fab/auth_manager/fab_auth_manager.py | 15 +++++----
.../auth_manager/api_fastapi/routes/test_router.py | 36 ++++++++++++++++++++++
6 files changed, 96 insertions(+), 31 deletions(-)
diff --git
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
index afd7b230d1c..d2bbd50276f 100644
---
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
+++
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
@@ -23,17 +23,15 @@ from fastapi.responses import RedirectResponse
from airflow.api_fastapi.app import get_auth_manager
from airflow.api_fastapi.auth.managers.base_auth_manager import
COOKIE_NAME_JWT_TOKEN
-from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.core_api.openapi.exceptions import
create_openapi_http_exception_doc
from airflow.configuration import conf
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.login import
LoginResponse
+from airflow.providers.fab.auth_manager.api_fastapi.routes.router import
auth_router
from airflow.providers.fab.auth_manager.api_fastapi.services.login import
FABAuthManagerLogin
from airflow.providers.fab.auth_manager.cli_commands.utils import
get_application_builder
-login_router = AirflowRouter(tags=["FabAuthManager"])
-
-@login_router.post(
+@auth_router.post(
"/token",
response_model=LoginResponse,
status_code=status.HTTP_201_CREATED,
@@ -45,7 +43,7 @@ def create_token(request: Request, body: dict[str, Any] =
Body(...)) -> LoginRes
return FABAuthManagerLogin.create_token(headers=dict(request.headers),
body=body)
-@login_router.post(
+@auth_router.post(
"/token/cli",
response_model=LoginResponse,
status_code=status.HTTP_201_CREATED,
@@ -61,7 +59,7 @@ def create_token_cli(request: Request, body: dict[str, Any] =
Body(...)) -> Logi
)
-@login_router.get(
+@auth_router.get(
"/logout",
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
diff --git
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py
index 460cfd47053..64668b56659 100644
---
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py
+++
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py
@@ -18,7 +18,6 @@ from __future__ import annotations
from fastapi import Depends, Path, Query, status
-from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.core_api.openapi.exceptions import
create_openapi_http_exception_doc
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.roles import (
PermissionCollectionResponse,
@@ -27,15 +26,14 @@ from
airflow.providers.fab.auth_manager.api_fastapi.datamodels.roles import (
RoleResponse,
)
from airflow.providers.fab.auth_manager.api_fastapi.parameters import
get_effective_limit
+from airflow.providers.fab.auth_manager.api_fastapi.routes.router import
fab_router
from airflow.providers.fab.auth_manager.api_fastapi.security import
requires_fab_custom_view
from airflow.providers.fab.auth_manager.api_fastapi.services.roles import
FABAuthManagerRoles
from airflow.providers.fab.auth_manager.cli_commands.utils import
get_application_builder
from airflow.providers.fab.www.security import permissions
-roles_router = AirflowRouter(prefix="/fab/v1", tags=["FabAuthManager"])
-
-@roles_router.post(
+@fab_router.post(
"/roles",
responses=create_openapi_http_exception_doc(
[
@@ -54,7 +52,7 @@ def create_role(body: RoleBody) -> RoleResponse:
return FABAuthManagerRoles.create_role(body=body)
-@roles_router.get(
+@fab_router.get(
"/roles",
response_model=RoleCollectionResponse,
responses=create_openapi_http_exception_doc(
@@ -77,7 +75,7 @@ def get_roles(
return FABAuthManagerRoles.get_roles(order_by=order_by, limit=limit,
offset=offset)
-@roles_router.delete(
+@fab_router.delete(
"/roles/{name}",
responses=create_openapi_http_exception_doc(
[
@@ -94,7 +92,7 @@ def delete_role(name: str = Path(..., min_length=1)) -> None:
return FABAuthManagerRoles.delete_role(name=name)
-@roles_router.get(
+@fab_router.get(
"/roles/{name}",
responses=create_openapi_http_exception_doc(
[status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN,
status.HTTP_404_NOT_FOUND]
@@ -107,7 +105,7 @@ def get_role(name: str = Path(..., min_length=1)) ->
RoleResponse:
return FABAuthManagerRoles.get_role(name=name)
-@roles_router.patch(
+@fab_router.patch(
"/roles/{name}",
responses=create_openapi_http_exception_doc(
[
@@ -129,7 +127,7 @@ def patch_role(
return FABAuthManagerRoles.patch_role(name=name, body=body,
update_mask=update_mask)
-@roles_router.get(
+@fab_router.get(
"/permissions",
response_model=PermissionCollectionResponse,
responses=create_openapi_http_exception_doc(
diff --git
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/router.py
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/router.py
new file mode 100644
index 00000000000..ba64036b93d
--- /dev/null
+++
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/router.py
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from enum import Enum
+
+from airflow.api_fastapi.common.router import AirflowRouter
+
+FAB_AUTH_TAGS: list[str | Enum] = ["FabAuthManager"]
+FAB_AUTH_PREFIX = "/fab/v1"
+
+auth_router = AirflowRouter(tags=FAB_AUTH_TAGS)
+fab_router = AirflowRouter(prefix=FAB_AUTH_PREFIX, tags=FAB_AUTH_TAGS)
+
+
+def register_routes() -> None:
+ """Register FastAPI routes by importing modules for side effects."""
+ import importlib
+
+
importlib.import_module("airflow.providers.fab.auth_manager.api_fastapi.routes.login")
+
importlib.import_module("airflow.providers.fab.auth_manager.api_fastapi.routes.roles")
+
importlib.import_module("airflow.providers.fab.auth_manager.api_fastapi.routes.users")
diff --git
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/users.py
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/users.py
index b26433984f0..a42c3dd5baa 100644
---
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/users.py
+++
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/users.py
@@ -18,7 +18,6 @@ from __future__ import annotations
from fastapi import Depends, Path, Query, status
-from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.core_api.openapi.exceptions import
create_openapi_http_exception_doc
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.users import (
UserBody,
@@ -27,15 +26,14 @@ from
airflow.providers.fab.auth_manager.api_fastapi.datamodels.users import (
UserResponse,
)
from airflow.providers.fab.auth_manager.api_fastapi.parameters import
get_effective_limit
+from airflow.providers.fab.auth_manager.api_fastapi.routes.router import
fab_router
from airflow.providers.fab.auth_manager.api_fastapi.security import
requires_fab_custom_view
from airflow.providers.fab.auth_manager.api_fastapi.services.users import
FABAuthManagerUsers
from airflow.providers.fab.auth_manager.cli_commands.utils import
get_application_builder
from airflow.providers.fab.www.security import permissions
-users_router = AirflowRouter(prefix="/fab/v1", tags=["FabAuthManager"])
-
-@users_router.post(
+@fab_router.post(
"/users",
responses=create_openapi_http_exception_doc(
[
@@ -53,7 +51,7 @@ def create_user(body: UserBody) -> UserResponse:
return FABAuthManagerUsers.create_user(body=body)
-@users_router.get(
+@fab_router.get(
"/users",
response_model=UserCollectionResponse,
responses=create_openapi_http_exception_doc(
@@ -75,7 +73,7 @@ def get_users(
return FABAuthManagerUsers.get_users(order_by=order_by, limit=limit,
offset=offset)
-@users_router.get(
+@fab_router.get(
"/users/{username}",
responses=create_openapi_http_exception_doc(
[
@@ -92,7 +90,7 @@ def get_user(username: str = Path(..., min_length=1)) ->
UserResponse:
return FABAuthManagerUsers.get_user(username=username)
-@users_router.patch(
+@fab_router.patch(
"/users/{username}",
responses=create_openapi_http_exception_doc(
[
@@ -115,7 +113,7 @@ def update_user(
return FABAuthManagerUsers.update_user(username=username, body=body,
update_mask=update_mask)
-@users_router.delete(
+@fab_router.delete(
"/users/{username}",
status_code=status.HTTP_204_NO_CONTENT,
responses=create_openapi_http_exception_doc(
diff --git
a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
index 57ae2c4289d..e7f4278fad3 100644
--- a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
+++ b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
@@ -200,11 +200,11 @@ class FabAuthManager(BaseAuthManager[User]):
def get_fastapi_app(self) -> FastAPI | None:
"""Get the FastAPI app."""
- from airflow.providers.fab.auth_manager.api_fastapi.routes.login
import (
- login_router,
+ from airflow.providers.fab.auth_manager.api_fastapi.routes.router
import (
+ auth_router,
+ fab_router,
+ register_routes,
)
- from airflow.providers.fab.auth_manager.api_fastapi.routes.roles
import roles_router
- from airflow.providers.fab.auth_manager.api_fastapi.routes.users
import users_router
flask_app = create_app(enable_plugins=False)
@@ -218,10 +218,9 @@ class FabAuthManager(BaseAuthManager[User]):
),
)
- # Add the login router to the FastAPI app
- app.include_router(login_router)
- app.include_router(roles_router)
- app.include_router(users_router)
+ register_routes()
+ app.include_router(auth_router)
+ app.include_router(fab_router)
# Session cleanup middleware to prevent PendingRollbackError.
# FAB's Flask views (e.g., /users/list/, /roles/list/) are mounted
below via
diff --git
a/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_router.py
b/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_router.py
new file mode 100644
index 00000000000..6655fbf1107
--- /dev/null
+++
b/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_router.py
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from airflow.providers.fab.auth_manager.api_fastapi.routes.router import (
+ FAB_AUTH_PREFIX,
+ auth_router,
+ fab_router,
+)
+
+
+def test_root_routers_share_tags() -> None:
+ assert auth_router.tags == fab_router.tags
+
+
+def test_fab_router_prefix() -> None:
+ assert fab_router.prefix == FAB_AUTH_PREFIX
+
+
+def test_auth_router_prefix() -> None:
+ assert auth_router.prefix == ""