Coverage for /home/airflow/.local/lib/python3.12/site-packages/airflow/api_fastapi/core_api/routes/public/auth.py: 57%
36 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 14:22 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 14:22 +0000
1# Licensed to the Apache Software Foundation (ASF) under one
2# or more contributor license agreements. See the NOTICE file
3# distributed with this work for additional information
4# regarding copyright ownership. The ASF licenses this file
5# to you under the Apache License, Version 2.0 (the
6# "License"); you may not use this file except in compliance
7# with the License. You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing,
12# software distributed under the License is distributed on an
13# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14# KIND, either express or implied. See the License for the
15# specific language governing permissions and limitations
16# under the License.
17from __future__ import annotations
19from urllib.parse import urlencode
21import structlog
22from fastapi import Depends, HTTPException, Request, status
23from fastapi.responses import RedirectResponse
24from fastapi.security import HTTPAuthorizationCredentials
26from airflow.api_fastapi.app import get_cookie_path
27from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN
28from airflow.api_fastapi.common.router import AirflowRouter
29from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
30from airflow.api_fastapi.core_api.security import (
31 AuthManagerDep,
32 bearer_scheme,
33 collect_request_tokens,
34 is_safe_url,
35 oauth2_scheme,
36)
37from airflow.configuration import conf
39log = structlog.get_logger(logger_name=__name__)
41auth_router = AirflowRouter(tags=["Login"], prefix="/auth")
44@auth_router.get(
45 "/login",
46 responses=create_openapi_http_exception_doc(
47 [status.HTTP_307_TEMPORARY_REDIRECT, status.HTTP_400_BAD_REQUEST]
48 ),
49)
50def login(request: Request, auth_manager: AuthManagerDep, next: None | str = None) -> RedirectResponse:
51 """Redirect to the login URL depending on the AuthManager configured."""
52 login_url = auth_manager.get_url_login()
54 if next and not is_safe_url(next, request=request): 54 ↛ 55line 54 didn't jump to line 55 because the condition on line 54 was never true
55 raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Invalid or unsafe next URL")
57 if next:
58 login_url += f"?{urlencode({'next': next})}"
60 return RedirectResponse(login_url)
63@auth_router.get(
64 "/logout",
65 responses=create_openapi_http_exception_doc([status.HTTP_307_TEMPORARY_REDIRECT]),
66)
67def logout(
68 request: Request,
69 auth_manager: AuthManagerDep,
70 # Kept for the OpenAPI security spec so ``/docs`` still renders the OAuth2 password
71 # login form. It resolves to the same ``Authorization: Bearer`` header
72 # ``bearer_scheme`` reads, so the value is unused at runtime.
73 _oauth_token: str | None = Depends(oauth2_scheme),
74 bearer_credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
75) -> RedirectResponse:
76 """Logout the user."""
77 # Invalidate both tokens from the Authorization header and the _token cookie, if present.
78 for token_str in collect_request_tokens(request, bearer_credentials):
79 auth_manager.revoke_token(token_str)
81 logout_url = auth_manager.get_url_logout()
82 if logout_url:
83 return RedirectResponse(logout_url)
85 secure = request.base_url.scheme == "https" or bool(conf.get("api", "ssl_cert", fallback=""))
86 cookie_path = get_cookie_path()
87 response = RedirectResponse(auth_manager.get_url_login())
88 response.delete_cookie(
89 key=COOKIE_NAME_JWT_TOKEN,
90 path=cookie_path,
91 secure=secure,
92 httponly=True,
93 )
94 # Clear any stale _token cookie at root path "/" left by
95 # older Airflow instances to prevent redirect loops.
96 if cookie_path != "/":
97 response.delete_cookie(
98 key=COOKIE_NAME_JWT_TOKEN,
99 path="/",
100 secure=secure,
101 httponly=True,
102 )
104 return response