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 afd7b230d1cad..d2bbd50276f99 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 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 460cfd47053f0..64668b566598e 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 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 @@ 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 0000000000000..ba64036b93d5b --- /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 b26433984f0e7..a42c3dd5baabc 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 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 @@ 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 8219b262d7264..64b051b632734 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 @@ -199,11 +199,11 @@ def get_cli_commands() -> list[CLICommand]: 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) @@ -217,10 +217,9 @@ def get_fastapi_app(self) -> FastAPI | None: ), ) - # 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 0000000000000..6655fbf11071b --- /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 == ""