Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion airflow/api_fastapi/core_api/openapi/v1-generated.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7216,7 +7216,7 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/VersionInfo'
/public/login:
/public/auth/login:
get:
tags:
- Login
Expand Down Expand Up @@ -7250,6 +7250,40 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/public/auth/logout:
get:
tags:
- Login
summary: Logout
description: Logout the user.
operationId: logout
parameters:
- name: next
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Next
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
'307':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
description: Temporary Redirect
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
AppBuilderMenuItemResponse:
Expand Down
4 changes: 2 additions & 2 deletions airflow/api_fastapi/core_api/routes/public/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.routes.public.assets import assets_router
from airflow.api_fastapi.core_api.routes.public.auth import auth_router
from airflow.api_fastapi.core_api.routes.public.backfills import backfills_router
from airflow.api_fastapi.core_api.routes.public.config import config_router
from airflow.api_fastapi.core_api.routes.public.connections import connections_router
Expand All @@ -39,7 +40,6 @@
from airflow.api_fastapi.core_api.routes.public.import_error import import_error_router
from airflow.api_fastapi.core_api.routes.public.job import job_router
from airflow.api_fastapi.core_api.routes.public.log import task_instances_log_router
from airflow.api_fastapi.core_api.routes.public.login import login_router
from airflow.api_fastapi.core_api.routes.public.monitor import monitor_router
from airflow.api_fastapi.core_api.routes.public.plugins import plugins_router
from airflow.api_fastapi.core_api.routes.public.pools import pools_router
Expand Down Expand Up @@ -90,4 +90,4 @@
# Following routers are not included in common router, for now we don't expect it to have authentication
public_router.include_router(monitor_router)
public_router.include_router(version_router)
public_router.include_router(login_router)
public_router.include_router(auth_router)
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import is_safe_url

login_router = AirflowRouter(tags=["Login"], prefix="/login")
auth_router = AirflowRouter(tags=["Login"], prefix="/auth")


@login_router.get(
"",
@auth_router.get(
"/login",
responses=create_openapi_http_exception_doc([status.HTTP_307_TEMPORARY_REDIRECT]),
)
def login(request: Request, next: None | str = None) -> RedirectResponse:
Expand All @@ -40,3 +40,17 @@ def login(request: Request, next: None | str = None) -> RedirectResponse:
if next:
login_url += f"?next={next}"
return RedirectResponse(login_url)


@auth_router.get(
"/logout",
responses=create_openapi_http_exception_doc([status.HTTP_307_TEMPORARY_REDIRECT]),
)
def logout(request: Request, next: None | str = None) -> RedirectResponse:
"""Logout the user."""
logout_url = request.app.state.auth_manager.get_url_logout()

if not logout_url:
logout_url = request.app.state.auth_manager.get_url_login()

return RedirectResponse(logout_url)
14 changes: 14 additions & 0 deletions airflow/ui/openapi-gen/queries/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,20 @@ export const UseLoginServiceLoginKeyFn = (
} = {},
queryKey?: Array<unknown>,
) => [useLoginServiceLoginKey, ...(queryKey ?? [{ next }])];
export type LoginServiceLogoutDefaultResponse = Awaited<ReturnType<typeof LoginService.logout>>;
export type LoginServiceLogoutQueryResult<
TData = LoginServiceLogoutDefaultResponse,
TError = unknown,
> = UseQueryResult<TData, TError>;
export const useLoginServiceLogoutKey = "LoginServiceLogout";
export const UseLoginServiceLogoutKeyFn = (
{
next,
}: {
next?: string;
} = {},
queryKey?: Array<unknown>,
) => [useLoginServiceLogoutKey, ...(queryKey ?? [{ next }])];
export type AssetServiceCreateAssetEventMutationResult = Awaited<
ReturnType<typeof AssetService.createAssetEvent>
>;
Expand Down
20 changes: 20 additions & 0 deletions airflow/ui/openapi-gen/queries/prefetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2486,3 +2486,23 @@ export const prefetchUseLoginServiceLogin = (
queryKey: Common.UseLoginServiceLoginKeyFn({ next }),
queryFn: () => LoginService.login({ next }),
});
/**
* Logout
* Logout the user.
* @param data The data for the request.
* @param data.next
* @returns unknown Successful Response
* @throws ApiError
*/
export const prefetchUseLoginServiceLogout = (
queryClient: QueryClient,
{
next,
}: {
next?: string;
} = {},
) =>
queryClient.prefetchQuery({
queryKey: Common.UseLoginServiceLogoutKeyFn({ next }),
queryFn: () => LoginService.logout({ next }),
});
26 changes: 26 additions & 0 deletions airflow/ui/openapi-gen/queries/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2953,6 +2953,32 @@ export const useLoginServiceLogin = <
queryFn: () => LoginService.login({ next }) as TData,
...options,
});
/**
* Logout
* Logout the user.
* @param data The data for the request.
* @param data.next
* @returns unknown Successful Response
* @throws ApiError
*/
export const useLoginServiceLogout = <
TData = Common.LoginServiceLogoutDefaultResponse,
TError = unknown,
TQueryKey extends Array<unknown> = unknown[],
>(
{
next,
}: {
next?: string;
} = {},
queryKey?: TQueryKey,
options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
) =>
useQuery<TData, TError>({
queryKey: Common.UseLoginServiceLogoutKeyFn({ next }, queryKey),
queryFn: () => LoginService.logout({ next }) as TData,
...options,
});
/**
* Create Asset Event
* Create asset events.
Expand Down
26 changes: 26 additions & 0 deletions airflow/ui/openapi-gen/queries/suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2930,3 +2930,29 @@ export const useLoginServiceLoginSuspense = <
queryFn: () => LoginService.login({ next }) as TData,
...options,
});
/**
* Logout
* Logout the user.
* @param data The data for the request.
* @param data.next
* @returns unknown Successful Response
* @throws ApiError
*/
export const useLoginServiceLogoutSuspense = <
TData = Common.LoginServiceLogoutDefaultResponse,
TError = unknown,
TQueryKey extends Array<unknown> = unknown[],
>(
{
next,
}: {
next?: string;
} = {},
queryKey?: TQueryKey,
options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
) =>
useSuspenseQuery<TData, TError>({
queryKey: Common.UseLoginServiceLogoutKeyFn({ next }, queryKey),
queryFn: () => LoginService.logout({ next }) as TData,
...options,
});
26 changes: 25 additions & 1 deletion airflow/ui/openapi-gen/requests/services.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ import type {
GetVersionResponse,
LoginData,
LoginResponse,
LogoutData,
LogoutResponse,
} from "./types.gen";

export class AuthLinksService {
Expand Down Expand Up @@ -3546,7 +3548,29 @@ export class LoginService {
public static login(data: LoginData = {}): CancelablePromise<LoginResponse> {
return __request(OpenAPI, {
method: "GET",
url: "/public/login",
url: "/public/auth/login",
query: {
next: data.next,
},
errors: {
307: "Temporary Redirect",
422: "Validation Error",
},
});
}

/**
* Logout
* Logout the user.
* @param data The data for the request.
* @param data.next
* @returns unknown Successful Response
* @throws ApiError
*/
public static logout(data: LogoutData = {}): CancelablePromise<LogoutResponse> {
return __request(OpenAPI, {
method: "GET",
url: "/public/auth/logout",
query: {
next: data.next,
},
Expand Down
27 changes: 26 additions & 1 deletion airflow/ui/openapi-gen/requests/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2593,6 +2593,12 @@ export type LoginData = {

export type LoginResponse = unknown;

export type LogoutData = {
next?: string | null;
};

export type LogoutResponse = unknown;

export type $OpenApiTs = {
"/ui/auth/links": {
get: {
Expand Down Expand Up @@ -5381,7 +5387,7 @@ export type $OpenApiTs = {
};
};
};
"/public/login": {
"/public/auth/login": {
get: {
req: LoginData;
res: {
Expand All @@ -5400,4 +5406,23 @@ export type $OpenApiTs = {
};
};
};
"/public/auth/logout": {
get: {
req: LogoutData;
res: {
/**
* Successful Response
*/
200: unknown;
/**
* Temporary Redirect
*/
307: HTTPExceptionResponse;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
};
2 changes: 1 addition & 1 deletion airflow/ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ axios.interceptors.response.use(
const params = new URLSearchParams();

params.set("next", globalThis.location.href);
globalThis.location.replace(`/public/login?${params.toString()}`);
globalThis.location.replace(`/public/auth/login?${params.toString()}`);
}

return Promise.reject(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
pytestmark = pytest.mark.db_test


class TestLoginEndpoint:
class TestAuthEndpoint:
@pytest.fixture(autouse=True)
def setup(self, test_client) -> None:
auth_manager_mock = MagicMock()
auth_manager_mock.get_url_login.return_value = AUTH_MANAGER_LOGIN_URL
test_client.app.state.auth_manager = auth_manager_mock


class TestGetLogin(TestLoginEndpoint):
class TestGetLogin(TestAuthEndpoint):
@pytest.mark.parametrize(
"params",
[
Expand All @@ -46,7 +46,7 @@ class TestGetLogin(TestLoginEndpoint):
],
)
def test_should_respond_307(self, test_client, params):
response = test_client.get("/public/login", follow_redirects=False, params=params)
response = test_client.get("/public/auth/login", follow_redirects=False, params=params)

assert response.status_code == 307
assert (
Expand All @@ -64,6 +64,29 @@ def test_should_respond_307(self, test_client, params):
)
@conf_vars({("api", "base_url"): "http://localhost:8080/prefix"})
def test_should_respond_400(self, test_client, params):
response = test_client.get("/public/login", follow_redirects=False, params=params)
response = test_client.get("/public/auth/login", follow_redirects=False, params=params)

assert response.status_code == 400


class TestLogout(TestAuthEndpoint):
@pytest.mark.parametrize(
"mock_logout_url, expected_redirection",
[
# logout_url is None, should redirect to the login page directly.
(None, AUTH_MANAGER_LOGIN_URL),
# logout_url is defined, should redirect to the logout_url.
("http://localhost/auth/some_logout_url", "http://localhost/auth/some_logout_url"),
],
)
def test_should_respond_307(
self,
test_client,
mock_logout_url,
expected_redirection,
):
test_client.app.state.auth_manager.get_url_logout.return_value = mock_logout_url
response = test_client.get("/public/auth/logout", follow_redirects=False)

assert response.status_code == 307
assert response.headers["location"] == expected_redirection
3 changes: 2 additions & 1 deletion tests/api_fastapi/core_api/routes/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@

# Set of paths that are allowed to be accessible without authentication
NO_AUTH_PATHS = {
"/public/login",
"/public/auth/login",
"/public/auth/logout",
"/public/version",
"/public/monitor/health",
}
Expand Down
Loading