This is an automated email from the ASF dual-hosted git repository.
jasonliu 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 95c1e3c6f2d feat: added /ui/auth/me to private endpoints (#58292)
95c1e3c6f2d is described below
commit 95c1e3c6f2d8c1a38233cca2761df5e7034e90e2
Author: Aaron Wolmutt <[email protected]>
AuthorDate: Wed Nov 26 20:11:46 2025 -0600
feat: added /ui/auth/me to private endpoints (#58292)
* feat: added /ui/auth/me to private endpoints
* add username and role to BaseUser class
* fixed base user and response type for ci
* fix: change base user interface for consistency and fallbacks between
auth providers
* feat: added /ui/auth/me endpoint for fab auth
* fix mypy errors without changing existing providers
* remove change from Keycloack user
* fix KeyCloak tests
* remove provider import, add method to check auth manager type
* remove extra pydantic fields
* modified required pydantic fields for BaseUser based on child auth
manager users. ran prek --all-files
* add comment. set username to optional and add get_groups method for aws
auth manager pluggability
* worked on BaseUser api contracts. added empty string fallback to
/ui/auth/me endpoint
* fixed failing unit test
* decoupled /ui/auth/me endpoint from existing providers and BaseUser
* remove pydantic attriubtes
* removed change made by vscode github co pilot agent
* remove docstring added by agent
* fix ci
* changed field name to ui_attributes
* removed ui_attributes field
* deleted init file
---
.../api_fastapi/core_api/datamodels/ui/auth.py | 7 +++++
.../api_fastapi/core_api/openapi/_private_ui.yaml | 31 ++++++++++++++++++++++
.../airflow/api_fastapi/core_api/routes/ui/auth.py | 12 +++++++++
.../src/airflow/ui/openapi-gen/queries/common.ts | 4 +++
.../ui/openapi-gen/queries/ensureQueryData.ts | 7 +++++
.../src/airflow/ui/openapi-gen/queries/prefetch.ts | 7 +++++
.../src/airflow/ui/openapi-gen/queries/queries.ts | 7 +++++
.../src/airflow/ui/openapi-gen/queries/suspense.ts | 7 +++++
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 17 ++++++++++++
.../ui/openapi-gen/requests/services.gen.ts | 15 ++++++++++-
.../airflow/ui/openapi-gen/requests/types.gen.ts | 20 ++++++++++++++
.../api_fastapi/core_api/routes/ui/test_auth.py | 18 +++++++++++++
12 files changed, 151 insertions(+), 1 deletion(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/auth.py
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/auth.py
index 736f4ffdc06..8cc6c4648a2 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/auth.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/auth.py
@@ -26,3 +26,10 @@ class MenuItemCollectionResponse(BaseModel):
authorized_menu_items: list[MenuItem]
extra_menu_items: list[ExtraMenuItem]
+
+
+class AuthenticatedMeResponse(BaseModel):
+ """Authenticated user information serializer for responses."""
+
+ id: str
+ username: str
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
index 4061b761003..b4e93a4c9f2 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
@@ -23,6 +23,23 @@ paths:
security:
- OAuth2PasswordBearer: []
- HTTPBearer: []
+ /ui/auth/me:
+ get:
+ tags:
+ - Auth Links
+ summary: Get Current User Info
+ description: Convienently get the current authenticated user information.
+ operationId: get_current_user_info
+ responses:
+ '200':
+ description: Successful Response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AuthenticatedMeResponse'
+ security:
+ - OAuth2PasswordBearer: []
+ - HTTPBearer: []
/ui/next_run_assets/{dag_id}:
get:
tags:
@@ -1079,6 +1096,20 @@ paths:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
+ AuthenticatedMeResponse:
+ properties:
+ id:
+ type: string
+ title: Id
+ username:
+ type: string
+ title: Username
+ type: object
+ required:
+ - id
+ - username
+ title: AuthenticatedMeResponse
+ description: Authenticated user information serializer for responses.
BackfillCollectionResponse:
properties:
backfills:
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/auth.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/auth.py
index f3c20a9ccef..7927f810d49 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/auth.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/auth.py
@@ -20,6 +20,7 @@ from __future__ import annotations
from airflow.api_fastapi.app import get_auth_manager
from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.core_api.datamodels.ui.auth import (
+ AuthenticatedMeResponse,
MenuItemCollectionResponse,
)
from airflow.api_fastapi.core_api.security import GetUserDep
@@ -38,3 +39,14 @@ def get_auth_menus(
authorized_menu_items=authorized_menu_items,
extra_menu_items=extra_menu_items,
)
+
+
+@auth_router.get("/auth/me")
+def get_current_user_info(
+ user: GetUserDep,
+) -> AuthenticatedMeResponse:
+ """Convienently get the current authenticated user information."""
+ return AuthenticatedMeResponse(
+ id=user.get_id(),
+ username=user.get_name(),
+ )
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 9453643554c..3a3ea3dd54e 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -772,6 +772,10 @@ export type AuthLinksServiceGetAuthMenusDefaultResponse =
Awaited<ReturnType<typ
export type AuthLinksServiceGetAuthMenusQueryResult<TData =
AuthLinksServiceGetAuthMenusDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
export const useAuthLinksServiceGetAuthMenusKey =
"AuthLinksServiceGetAuthMenus";
export const UseAuthLinksServiceGetAuthMenusKeyFn = (queryKey?:
Array<unknown>) => [useAuthLinksServiceGetAuthMenusKey, ...(queryKey ?? [])];
+export type AuthLinksServiceGetCurrentUserInfoDefaultResponse =
Awaited<ReturnType<typeof AuthLinksService.getCurrentUserInfo>>;
+export type AuthLinksServiceGetCurrentUserInfoQueryResult<TData =
AuthLinksServiceGetCurrentUserInfoDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
+export const useAuthLinksServiceGetCurrentUserInfoKey =
"AuthLinksServiceGetCurrentUserInfo";
+export const UseAuthLinksServiceGetCurrentUserInfoKeyFn = (queryKey?:
Array<unknown>) => [useAuthLinksServiceGetCurrentUserInfoKey, ...(queryKey ??
[])];
export type DependenciesServiceGetDependenciesDefaultResponse =
Awaited<ReturnType<typeof DependenciesService.getDependencies>>;
export type DependenciesServiceGetDependenciesQueryResult<TData =
DependenciesServiceGetDependenciesDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
export const useDependenciesServiceGetDependenciesKey =
"DependenciesServiceGetDependencies";
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index 939dbfa7987..6ff5dc02fb5 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1467,6 +1467,13 @@ export const ensureUseLoginServiceLogoutData =
(queryClient: QueryClient) => que
*/
export const ensureUseAuthLinksServiceGetAuthMenusData = (queryClient:
QueryClient) => queryClient.ensureQueryData({ queryKey:
Common.UseAuthLinksServiceGetAuthMenusKeyFn(), queryFn: () =>
AuthLinksService.getAuthMenus() });
/**
+* Get Current User Info
+* Convienently get the current authenticated user information.
+* @returns AuthenticatedMeResponse Successful Response
+* @throws ApiError
+*/
+export const ensureUseAuthLinksServiceGetCurrentUserInfoData = (queryClient:
QueryClient) => queryClient.ensureQueryData({ queryKey:
Common.UseAuthLinksServiceGetCurrentUserInfoKeyFn(), queryFn: () =>
AuthLinksService.getCurrentUserInfo() });
+/**
* Get Dependencies
* Dependencies graph.
* @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index 12d4fd68ea1..b8eaa08e038 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1467,6 +1467,13 @@ export const prefetchUseLoginServiceLogout =
(queryClient: QueryClient) => query
*/
export const prefetchUseAuthLinksServiceGetAuthMenus = (queryClient:
QueryClient) => queryClient.prefetchQuery({ queryKey:
Common.UseAuthLinksServiceGetAuthMenusKeyFn(), queryFn: () =>
AuthLinksService.getAuthMenus() });
/**
+* Get Current User Info
+* Convienently get the current authenticated user information.
+* @returns AuthenticatedMeResponse Successful Response
+* @throws ApiError
+*/
+export const prefetchUseAuthLinksServiceGetCurrentUserInfo = (queryClient:
QueryClient) => queryClient.prefetchQuery({ queryKey:
Common.UseAuthLinksServiceGetCurrentUserInfoKeyFn(), queryFn: () =>
AuthLinksService.getCurrentUserInfo() });
+/**
* Get Dependencies
* Dependencies graph.
* @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index 3d20b53573f..57cb60e7332 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1467,6 +1467,13 @@ export const useLoginServiceLogout = <TData =
Common.LoginServiceLogoutDefaultRe
*/
export const useAuthLinksServiceGetAuthMenus = <TData =
Common.AuthLinksServiceGetAuthMenusDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?:
Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) =>
useQuery<TData, TError>({ queryKey:
Common.UseAuthLinksServiceGetAuthMenusKeyFn(queryKey), queryFn: () =>
AuthLinksService.getAuthMenus() as TData, ...options });
/**
+* Get Current User Info
+* Convienently get the current authenticated user information.
+* @returns AuthenticatedMeResponse Successful Response
+* @throws ApiError
+*/
+export const useAuthLinksServiceGetCurrentUserInfo = <TData =
Common.AuthLinksServiceGetCurrentUserInfoDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?:
Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) =>
useQuery<TData, TError>({ queryKey:
Common.UseAuthLinksServiceGetCurrentUserInfoKeyFn(queryKey), queryFn: () =>
AuthLinksService.getCurrentUserInfo() as TData, ...options });
+/**
* Get Dependencies
* Dependencies graph.
* @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index 2745bee178c..47cd4bc8289 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1467,6 +1467,13 @@ export const useLoginServiceLogoutSuspense = <TData =
Common.LoginServiceLogoutD
*/
export const useAuthLinksServiceGetAuthMenusSuspense = <TData =
Common.AuthLinksServiceGetAuthMenusDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?:
Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) =>
useSuspenseQuery<TData, TError>({ queryKey:
Common.UseAuthLinksServiceGetAuthMenusKeyFn(queryKey), queryFn: () =>
AuthLinksService.getAuthMenus() as TData, ...options });
/**
+* Get Current User Info
+* Convienently get the current authenticated user information.
+* @returns AuthenticatedMeResponse Successful Response
+* @throws ApiError
+*/
+export const useAuthLinksServiceGetCurrentUserInfoSuspense = <TData =
Common.AuthLinksServiceGetCurrentUserInfoDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?:
Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) =>
useSuspenseQuery<TData, TError>({ queryKey:
Common.UseAuthLinksServiceGetCurrentUserInfoKeyFn(queryKey), queryFn: () =>
AuthLinksService.getCurrentUserInfo() as TData, ...options });
+/**
* Get Dependencies
* Dependencies graph.
* @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index 5142b744a77..a9b70b7dbbb 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -6904,6 +6904,23 @@ export const $XComUpdateBody = {
description: 'Payload serializer for updating an XCom entry.'
} as const;
+export const $AuthenticatedMeResponse = {
+ properties: {
+ id: {
+ type: 'string',
+ title: 'Id'
+ },
+ username: {
+ type: 'string',
+ title: 'Username'
+ }
+ },
+ type: 'object',
+ required: ['id', 'username'],
+ title: 'AuthenticatedMeResponse',
+ description: 'Authenticated user information serializer for responses.'
+} as const;
+
export const $BaseEdgeResponse = {
properties: {
source_id: {
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index dea17f7b8f6..f01a1f9be69 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -3,7 +3,7 @@
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
-import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData,
GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse,
GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData,
CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse,
GetAssetQueuedEventsData, GetAssetQueuedEventsResponse,
DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData,
GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse,
Dele [...]
+import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData,
GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse,
GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData,
CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse,
GetAssetQueuedEventsData, GetAssetQueuedEventsResponse,
DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData,
GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse,
Dele [...]
export class AssetService {
/**
@@ -3760,6 +3760,19 @@ export class AuthLinksService {
});
}
+ /**
+ * Get Current User Info
+ * Convienently get the current authenticated user information.
+ * @returns AuthenticatedMeResponse Successful Response
+ * @throws ApiError
+ */
+ public static getCurrentUserInfo():
CancelablePromise<GetCurrentUserInfoResponse> {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/ui/auth/me'
+ });
+ }
+
}
export class DependenciesService {
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index 9704369f913..b65cd4d2d45 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -1696,6 +1696,14 @@ export type XComUpdateBody = {
map_index?: number;
};
+/**
+ * Authenticated user information serializer for responses.
+ */
+export type AuthenticatedMeResponse = {
+ id: string;
+ username: string;
+};
+
/**
* Base Edge serializer for responses.
*/
@@ -3334,6 +3342,8 @@ export type LogoutResponse = unknown;
export type GetAuthMenusResponse = MenuItemCollectionResponse;
+export type GetCurrentUserInfoResponse = AuthenticatedMeResponse;
+
export type GetDependenciesData = {
nodeId?: string | null;
};
@@ -6429,6 +6439,16 @@ export type $OpenApiTs = {
};
};
};
+ '/ui/auth/me': {
+ get: {
+ res: {
+ /**
+ * Successful Response
+ */
+ 200: AuthenticatedMeResponse;
+ };
+ };
+ };
'/ui/dependencies': {
get: {
req: GetDependenciesData;
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_auth.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_auth.py
index 1b37bc4ba86..98a46ea08a6 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_auth.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_auth.py
@@ -58,3 +58,21 @@ class TestGetAuthLinks:
response = unauthorized_test_client.get("/auth/menus")
assert response.status_code == 200
assert response.json() == {"authorized_menu_items": [],
"extra_menu_items": []}
+
+
+class TestGetMeResponse:
+ def test_should_response_200_with_authenticated_user(self, test_client):
+ """Test /auth/me endpoint with SimpleAuthManager authenticated user."""
+ response = test_client.get("/auth/me")
+
+ assert response.status_code == 200
+ assert response.json() == {
+ "username": "test",
+ "id": "test",
+ }
+
+ def test_with_unauthenticated_user(self, unauthenticated_test_client):
+ """Test /auth/me endpoint with no authentication."""
+ response = unauthenticated_test_client.get("/auth/me")
+ assert response.status_code == 401
+ assert response.json() == {"detail": "Not authenticated"}