From 7fac5ab248166b97342e58aa53860548776dd10f Mon Sep 17 00:00:00 2001 From: vatsrahul1001 Date: Fri, 7 Mar 2025 11:06:32 +0530 Subject: [PATCH 1/4] add auth to backfills endpoints --- .../core_api/routes/public/backfills.py | 10 +- airflow/ui/openapi-gen/queries/queries.ts | 99 +++++++++++++++++-- .../ui/openapi-gen/requests/services.gen.ts | 16 +++ airflow/ui/openapi-gen/requests/types.gen.ts | 4 + 4 files changed, 121 insertions(+), 8 deletions(-) diff --git a/airflow/api_fastapi/core_api/routes/public/backfills.py b/airflow/api_fastapi/core_api/routes/public/backfills.py index 31638e55503e3..ea882f9dc8b6c 100644 --- a/airflow/api_fastapi/core_api/routes/public/backfills.py +++ b/airflow/api_fastapi/core_api/routes/public/backfills.py @@ -116,7 +116,10 @@ def get_backfill( status.HTTP_409_CONFLICT, ] ), - dependencies=[Depends(requires_access_backfill(method="PUT"))], + dependencies=[ + Depends(requires_access_backfill(method="PUT")), + Depends(requires_access_dag(method="PUT", access_entity=DagAccessEntity.RUN)), + ], ) def pause_backfill(backfill_id, session: SessionDep) -> BackfillResponse: b = session.get(Backfill, backfill_id) @@ -138,7 +141,10 @@ def pause_backfill(backfill_id, session: SessionDep) -> BackfillResponse: status.HTTP_409_CONFLICT, ] ), - dependencies=[Depends(requires_access_backfill(method="PUT"))], + dependencies=[ + Depends(requires_access_backfill(method="PUT")), + Depends(requires_access_dag(method="PUT", access_entity=DagAccessEntity.RUN)), + ], ) def unpause_backfill(backfill_id, session: SessionDep) -> BackfillResponse: b = session.get(Backfill, backfill_id) diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index 8aac17090b316..58cb0030968c4 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -3008,6 +3008,84 @@ export const useAssetServiceMaterializeAsset = < ...options, }); /** +<<<<<<< HEAD +======= + * Create Backfill + * @param data The data for the request. + * @param data.requestBody + * @param data.dagId + * @returns BackfillResponse Successful Response + * @throws ApiError + */ +export const useBackfillServiceCreateBackfill = < + TData = Common.BackfillServiceCreateBackfillMutationResult, + TError = unknown, + TContext = unknown, +>( + options?: Omit< + UseMutationOptions< + TData, + TError, + { + dagId?: string; + requestBody: BackfillPostBody; + }, + TContext + >, + "mutationFn" + >, +) => + useMutation< + TData, + TError, + { + dagId?: string; + requestBody: BackfillPostBody; + }, + TContext + >({ + mutationFn: ({ dagId, requestBody }) => + BackfillService.createBackfill({ dagId, requestBody }) as unknown as Promise, + ...options, + }); +/** + * Create Backfill Dry Run + * @param data The data for the request. + * @param data.requestBody + * @returns DryRunBackfillCollectionResponse Successful Response + * @throws ApiError + */ +export const useBackfillServiceCreateBackfillDryRun = < + TData = Common.BackfillServiceCreateBackfillDryRunMutationResult, + TError = unknown, + TContext = unknown, +>( + options?: Omit< + UseMutationOptions< + TData, + TError, + { + requestBody: BackfillPostBody; + }, + TContext + >, + "mutationFn" + >, +) => + useMutation< + TData, + TError, + { + requestBody: BackfillPostBody; + }, + TContext + >({ + mutationFn: ({ requestBody }) => + BackfillService.createBackfillDryRun({ requestBody }) as unknown as Promise, + ...options, + }); +/** +>>>>>>> 337339e8b6 (add auth to backfills endpoints) * Post Connection * Create connection entry. * @param data The data for the request. @@ -3506,6 +3584,7 @@ export const useVariableServicePostVariable = < * Pause Backfill * @param data The data for the request. * @param data.backfillId + * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -3520,6 +3599,7 @@ export const useBackfillServicePauseBackfill = < TError, { backfillId: unknown; + dagId?: string; }, TContext >, @@ -3531,17 +3611,19 @@ export const useBackfillServicePauseBackfill = < TError, { backfillId: unknown; + dagId?: string; }, TContext >({ - mutationFn: ({ backfillId }) => - BackfillService.pauseBackfill({ backfillId }) as unknown as Promise, + mutationFn: ({ backfillId, dagId }) => + BackfillService.pauseBackfill({ backfillId, dagId }) as unknown as Promise, ...options, }); /** * Unpause Backfill * @param data The data for the request. * @param data.backfillId + * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -3556,6 +3638,7 @@ export const useBackfillServiceUnpauseBackfill = < TError, { backfillId: unknown; + dagId?: string; }, TContext >, @@ -3567,17 +3650,19 @@ export const useBackfillServiceUnpauseBackfill = < TError, { backfillId: unknown; + dagId?: string; }, TContext >({ - mutationFn: ({ backfillId }) => - BackfillService.unpauseBackfill({ backfillId }) as unknown as Promise, + mutationFn: ({ backfillId, dagId }) => + BackfillService.unpauseBackfill({ backfillId, dagId }) as unknown as Promise, ...options, }); /** * Cancel Backfill * @param data The data for the request. * @param data.backfillId + * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -3592,6 +3677,7 @@ export const useBackfillServiceCancelBackfill = < TError, { backfillId: unknown; + dagId?: string; }, TContext >, @@ -3603,11 +3689,12 @@ export const useBackfillServiceCancelBackfill = < TError, { backfillId: unknown; + dagId?: string; }, TContext >({ - mutationFn: ({ backfillId }) => - BackfillService.cancelBackfill({ backfillId }) as unknown as Promise, + mutationFn: ({ backfillId, dagId }) => + BackfillService.cancelBackfill({ backfillId, dagId }) as unknown as Promise, ...options, }); /** diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow/ui/openapi-gen/requests/services.gen.ts index ef8a2d833af22..ff2ac58032746 100644 --- a/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow/ui/openapi-gen/requests/services.gen.ts @@ -1103,6 +1103,7 @@ export class BackfillService { * Create Backfill * @param data The data for the request. * @param data.requestBody + * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -1110,6 +1111,9 @@ export class BackfillService { return __request(OpenAPI, { method: "POST", url: "/public/backfills", + query: { + dag_id: data.dagId, + }, body: data.requestBody, mediaType: "application/json", errors: { @@ -1149,6 +1153,7 @@ export class BackfillService { * Pause Backfill * @param data The data for the request. * @param data.backfillId + * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -1159,6 +1164,9 @@ export class BackfillService { path: { backfill_id: data.backfillId, }, + query: { + dag_id: data.dagId, + }, errors: { 401: "Unauthorized", 403: "Forbidden", @@ -1173,6 +1181,7 @@ export class BackfillService { * Unpause Backfill * @param data The data for the request. * @param data.backfillId + * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -1183,6 +1192,9 @@ export class BackfillService { path: { backfill_id: data.backfillId, }, + query: { + dag_id: data.dagId, + }, errors: { 401: "Unauthorized", 403: "Forbidden", @@ -1197,6 +1209,7 @@ export class BackfillService { * Cancel Backfill * @param data The data for the request. * @param data.backfillId + * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -1207,6 +1220,9 @@ export class BackfillService { path: { backfill_id: data.backfillId, }, + query: { + dag_id: data.dagId, + }, errors: { 401: "Unauthorized", 403: "Forbidden", diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow/ui/openapi-gen/requests/types.gen.ts index ce1b31a13eabf..1231c7452ce38 100644 --- a/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow/ui/openapi-gen/requests/types.gen.ts @@ -1893,6 +1893,7 @@ export type ListBackfills1Data = { export type ListBackfills1Response = BackfillCollectionResponse; export type CreateBackfillData = { + dagId?: string | null; requestBody: BackfillPostBody; }; @@ -1906,18 +1907,21 @@ export type GetBackfillResponse = BackfillResponse; export type PauseBackfillData = { backfillId: unknown; + dagId?: string | null; }; export type PauseBackfillResponse = BackfillResponse; export type UnpauseBackfillData = { backfillId: unknown; + dagId?: string | null; }; export type UnpauseBackfillResponse = BackfillResponse; export type CancelBackfillData = { backfillId: unknown; + dagId?: string | null; }; export type CancelBackfillResponse = BackfillResponse; From 1b7905acbb6ad2e05c5c7b2e095c5de9759cabe6 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Tue, 11 Mar 2025 22:11:40 +0800 Subject: [PATCH 2/4] feat(security): add is_authorized_backfill --- .../fab/auth_manager/fab_auth_manager.py | 45 +++++++++++++++---- .../auth_manager/security_manager/override.py | 1 + 2 files changed, 37 insertions(+), 9 deletions(-) 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 a793a80d0e637..45f73b6382207 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 @@ -63,7 +63,10 @@ from airflow.providers.fab.auth_manager.models.anonymous_user import AnonymousUser from airflow.providers.fab.www.app import create_app from airflow.providers.fab.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED -from airflow.providers.fab.www.extensions.init_views import _CustomErrorRequestBodyValidator, _LazyResolver +from airflow.providers.fab.www.extensions.init_views import ( + _CustomErrorRequestBodyValidator, + _LazyResolver, +) from airflow.providers.fab.www.security import permissions from airflow.providers.fab.www.security.permissions import ( RESOURCE_AUDIT_LOG, @@ -90,7 +93,10 @@ RESOURCE_WEBSITE, RESOURCE_XCOM, ) -from airflow.providers.fab.www.utils import get_fab_action_from_method_map, get_method_from_fab_action_map +from airflow.providers.fab.www.utils import ( + get_fab_action_from_method_map, + get_method_from_fab_action_map, +) from airflow.security.permissions import RESOURCE_BACKFILL from airflow.utils.session import NEW_SESSION, create_session, provide_session from airflow.utils.yaml import safe_load @@ -101,7 +107,9 @@ CLICommand, ) from airflow.providers.common.compat.assets import AssetAliasDetails, AssetDetails - from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride + from airflow.providers.fab.auth_manager.security_manager.override import ( + FabAirflowSecurityManagerOverride, + ) from airflow.providers.fab.www.extensions.init_appbuilder import AirflowAppBuilder from airflow.providers.fab.www.security.permissions import ( RESOURCE_ASSET, @@ -188,7 +196,9 @@ 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.login import ( + login_router, + ) flask_app = create_app(enable_plugins=False) @@ -217,7 +227,10 @@ def get_api_endpoints(self) -> None | Blueprint: specification=specification, resolver=_LazyResolver(), base_path="/fab/v1", - options={"swagger_ui": SWAGGER_ENABLED, "swagger_path": SWAGGER_BUNDLE.__fspath__()}, + options={ + "swagger_ui": SWAGGER_ENABLED, + "swagger_path": SWAGGER_BUNDLE.__fspath__(), + }, strict_validation=True, validate_responses=True, validator_map={"body": _CustomErrorRequestBodyValidator}, @@ -324,7 +337,11 @@ def is_authorized_dag( ) def is_authorized_backfill( - self, *, method: ResourceMethod, user: User, details: BackfillDetails | None = None + self, + *, + method: ResourceMethod, + user: User, + details: BackfillDetails | None = None, ) -> bool: return self._is_authorized(method=method, resource_type=RESOURCE_BACKFILL, user=user) @@ -334,7 +351,11 @@ def is_authorized_asset( return self._is_authorized(method=method, resource_type=RESOURCE_ASSET, user=user) def is_authorized_asset_alias( - self, *, method: ResourceMethod, user: User, details: AssetAliasDetails | None = None + self, + *, + method: ResourceMethod, + user: User, + details: AssetAliasDetails | None = None, ) -> bool: return self._is_authorized(method=method, resource_type=RESOURCE_ASSET_ALIAS, user=user) @@ -344,7 +365,11 @@ def is_authorized_pool( return self._is_authorized(method=method, resource_type=RESOURCE_POOL, user=user) def is_authorized_variable( - self, *, method: ResourceMethod, user: User, details: VariableDetails | None = None + self, + *, + method: ResourceMethod, + user: User, + details: VariableDetails | None = None, ) -> bool: return self._is_authorized(method=method, resource_type=RESOURCE_VARIABLE, user=user) @@ -352,7 +377,9 @@ def is_authorized_view(self, *, access_view: AccessView, user: User) -> bool: # "Docs" are only links in the menu, there is no page associated method: ResourceMethod = "MENU" if access_view == AccessView.DOCS else "GET" return self._is_authorized( - method=method, resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], user=user + method=method, + resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], + user=user, ) def is_authorized_custom_view( diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py index 97eca2a858077..cafc91e9a6c12 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py @@ -110,6 +110,7 @@ AirflowDatabaseSessionInterface, AirflowDatabaseSessionInterface as FabAirflowDatabaseSessionInterface, ) +from airflow.security.permissions import RESOURCE_BACKFILL if TYPE_CHECKING: from airflow.providers.fab.www.security.permissions import ( From 502435da04b986e88afe13e5af37e5c4c7cb5adf Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Tue, 11 Mar 2025 22:33:19 +0800 Subject: [PATCH 3/4] feat(api_fastapi): add required permission for backfills --- airflow/ui/openapi-gen/queries/queries.ts | 99 ++----------------- .../ui/openapi-gen/requests/services.gen.ts | 16 --- airflow/ui/openapi-gen/requests/types.gen.ts | 4 - 3 files changed, 6 insertions(+), 113 deletions(-) diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index 58cb0030968c4..8aac17090b316 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -3008,84 +3008,6 @@ export const useAssetServiceMaterializeAsset = < ...options, }); /** -<<<<<<< HEAD -======= - * Create Backfill - * @param data The data for the request. - * @param data.requestBody - * @param data.dagId - * @returns BackfillResponse Successful Response - * @throws ApiError - */ -export const useBackfillServiceCreateBackfill = < - TData = Common.BackfillServiceCreateBackfillMutationResult, - TError = unknown, - TContext = unknown, ->( - options?: Omit< - UseMutationOptions< - TData, - TError, - { - dagId?: string; - requestBody: BackfillPostBody; - }, - TContext - >, - "mutationFn" - >, -) => - useMutation< - TData, - TError, - { - dagId?: string; - requestBody: BackfillPostBody; - }, - TContext - >({ - mutationFn: ({ dagId, requestBody }) => - BackfillService.createBackfill({ dagId, requestBody }) as unknown as Promise, - ...options, - }); -/** - * Create Backfill Dry Run - * @param data The data for the request. - * @param data.requestBody - * @returns DryRunBackfillCollectionResponse Successful Response - * @throws ApiError - */ -export const useBackfillServiceCreateBackfillDryRun = < - TData = Common.BackfillServiceCreateBackfillDryRunMutationResult, - TError = unknown, - TContext = unknown, ->( - options?: Omit< - UseMutationOptions< - TData, - TError, - { - requestBody: BackfillPostBody; - }, - TContext - >, - "mutationFn" - >, -) => - useMutation< - TData, - TError, - { - requestBody: BackfillPostBody; - }, - TContext - >({ - mutationFn: ({ requestBody }) => - BackfillService.createBackfillDryRun({ requestBody }) as unknown as Promise, - ...options, - }); -/** ->>>>>>> 337339e8b6 (add auth to backfills endpoints) * Post Connection * Create connection entry. * @param data The data for the request. @@ -3584,7 +3506,6 @@ export const useVariableServicePostVariable = < * Pause Backfill * @param data The data for the request. * @param data.backfillId - * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -3599,7 +3520,6 @@ export const useBackfillServicePauseBackfill = < TError, { backfillId: unknown; - dagId?: string; }, TContext >, @@ -3611,19 +3531,17 @@ export const useBackfillServicePauseBackfill = < TError, { backfillId: unknown; - dagId?: string; }, TContext >({ - mutationFn: ({ backfillId, dagId }) => - BackfillService.pauseBackfill({ backfillId, dagId }) as unknown as Promise, + mutationFn: ({ backfillId }) => + BackfillService.pauseBackfill({ backfillId }) as unknown as Promise, ...options, }); /** * Unpause Backfill * @param data The data for the request. * @param data.backfillId - * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -3638,7 +3556,6 @@ export const useBackfillServiceUnpauseBackfill = < TError, { backfillId: unknown; - dagId?: string; }, TContext >, @@ -3650,19 +3567,17 @@ export const useBackfillServiceUnpauseBackfill = < TError, { backfillId: unknown; - dagId?: string; }, TContext >({ - mutationFn: ({ backfillId, dagId }) => - BackfillService.unpauseBackfill({ backfillId, dagId }) as unknown as Promise, + mutationFn: ({ backfillId }) => + BackfillService.unpauseBackfill({ backfillId }) as unknown as Promise, ...options, }); /** * Cancel Backfill * @param data The data for the request. * @param data.backfillId - * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -3677,7 +3592,6 @@ export const useBackfillServiceCancelBackfill = < TError, { backfillId: unknown; - dagId?: string; }, TContext >, @@ -3689,12 +3603,11 @@ export const useBackfillServiceCancelBackfill = < TError, { backfillId: unknown; - dagId?: string; }, TContext >({ - mutationFn: ({ backfillId, dagId }) => - BackfillService.cancelBackfill({ backfillId, dagId }) as unknown as Promise, + mutationFn: ({ backfillId }) => + BackfillService.cancelBackfill({ backfillId }) as unknown as Promise, ...options, }); /** diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow/ui/openapi-gen/requests/services.gen.ts index ff2ac58032746..ef8a2d833af22 100644 --- a/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow/ui/openapi-gen/requests/services.gen.ts @@ -1103,7 +1103,6 @@ export class BackfillService { * Create Backfill * @param data The data for the request. * @param data.requestBody - * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -1111,9 +1110,6 @@ export class BackfillService { return __request(OpenAPI, { method: "POST", url: "/public/backfills", - query: { - dag_id: data.dagId, - }, body: data.requestBody, mediaType: "application/json", errors: { @@ -1153,7 +1149,6 @@ export class BackfillService { * Pause Backfill * @param data The data for the request. * @param data.backfillId - * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -1164,9 +1159,6 @@ export class BackfillService { path: { backfill_id: data.backfillId, }, - query: { - dag_id: data.dagId, - }, errors: { 401: "Unauthorized", 403: "Forbidden", @@ -1181,7 +1173,6 @@ export class BackfillService { * Unpause Backfill * @param data The data for the request. * @param data.backfillId - * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -1192,9 +1183,6 @@ export class BackfillService { path: { backfill_id: data.backfillId, }, - query: { - dag_id: data.dagId, - }, errors: { 401: "Unauthorized", 403: "Forbidden", @@ -1209,7 +1197,6 @@ export class BackfillService { * Cancel Backfill * @param data The data for the request. * @param data.backfillId - * @param data.dagId * @returns BackfillResponse Successful Response * @throws ApiError */ @@ -1220,9 +1207,6 @@ export class BackfillService { path: { backfill_id: data.backfillId, }, - query: { - dag_id: data.dagId, - }, errors: { 401: "Unauthorized", 403: "Forbidden", diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow/ui/openapi-gen/requests/types.gen.ts index 1231c7452ce38..ce1b31a13eabf 100644 --- a/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow/ui/openapi-gen/requests/types.gen.ts @@ -1893,7 +1893,6 @@ export type ListBackfills1Data = { export type ListBackfills1Response = BackfillCollectionResponse; export type CreateBackfillData = { - dagId?: string | null; requestBody: BackfillPostBody; }; @@ -1907,21 +1906,18 @@ export type GetBackfillResponse = BackfillResponse; export type PauseBackfillData = { backfillId: unknown; - dagId?: string | null; }; export type PauseBackfillResponse = BackfillResponse; export type UnpauseBackfillData = { backfillId: unknown; - dagId?: string | null; }; export type UnpauseBackfillResponse = BackfillResponse; export type CancelBackfillData = { backfillId: unknown; - dagId?: string | null; }; export type CancelBackfillResponse = BackfillResponse; From 37ce4cdbf2436021aed3198cf871e28b42af6972 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Wed, 12 Mar 2025 15:21:39 +0800 Subject: [PATCH 4/4] feat(AIP-84): add auth to /ui/backfills --- airflow/api_fastapi/core_api/openapi/v1-generated.yaml | 2 ++ airflow/api_fastapi/core_api/routes/ui/backfills.py | 5 +++++ airflow/assets/manager.py | 2 +- .../fab/auth_manager/security_manager/override.py | 2 -- tests/api_fastapi/core_api/routes/ui/test_backfills.py | 10 +++++++++- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml index fb2e3c79d45fe..cbc9d7c204c4f 100644 --- a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml +++ b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml @@ -358,6 +358,8 @@ paths: - Backfill summary: List Backfills operationId: list_backfills + security: + - OAuth2PasswordBearer: [] parameters: - name: limit in: query diff --git a/airflow/api_fastapi/core_api/routes/ui/backfills.py b/airflow/api_fastapi/core_api/routes/ui/backfills.py index a749cdd6cfcd3..add5c536e76e3 100644 --- a/airflow/api_fastapi/core_api/routes/ui/backfills.py +++ b/airflow/api_fastapi/core_api/routes/ui/backfills.py @@ -35,6 +35,7 @@ from airflow.api_fastapi.core_api.openapi.exceptions import ( create_openapi_http_exception_doc, ) +from airflow.api_fastapi.core_api.security import requires_access_backfill, requires_access_dag from airflow.models.backfill import Backfill backfills_router = AirflowRouter(tags=["Backfill"], prefix="/backfills") @@ -43,6 +44,10 @@ @backfills_router.get( path="", responses=create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]), + dependencies=[ + Depends(requires_access_backfill(method="GET")), + Depends(requires_access_dag(method="GET")), + ], ) def list_backfills( limit: QueryLimit, diff --git a/airflow/assets/manager.py b/airflow/assets/manager.py index ca6cb345dd4e9..4e5dd37a66b8d 100644 --- a/airflow/assets/manager.py +++ b/airflow/assets/manager.py @@ -17,7 +17,7 @@ # under the License. from __future__ import annotations -from collections.abc import Collection, Iterable +from collections.abc import Collection from typing import TYPE_CHECKING from sqlalchemy import exc, or_, select diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py index cafc91e9a6c12..fb573ed9adb97 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py @@ -116,13 +116,11 @@ from airflow.providers.fab.www.security.permissions import ( RESOURCE_ASSET, RESOURCE_ASSET_ALIAS, - RESOURCE_BACKFILL, ) else: from airflow.providers.common.compat.security.permissions import ( RESOURCE_ASSET, RESOURCE_ASSET_ALIAS, - RESOURCE_BACKFILL, ) log = logging.getLogger(__name__) diff --git a/tests/api_fastapi/core_api/routes/ui/test_backfills.py b/tests/api_fastapi/core_api/routes/ui/test_backfills.py index bf405b087a21c..0c5e2a7cda7a9 100644 --- a/tests/api_fastapi/core_api/routes/ui/test_backfills.py +++ b/tests/api_fastapi/core_api/routes/ui/test_backfills.py @@ -87,7 +87,7 @@ class TestListBackfills(TestBackfillEndpoint): ({"dag_id": "TEST_DAG_1"}, ["backfill1"], 1), ], ) - def test_list_backfill(self, test_params, response_params, total_entries, test_client, session): + def test_should_response_200(self, test_params, response_params, total_entries, test_client, session): dags = self._create_dag_models() from_date = timezone.utcnow() to_date = timezone.utcnow() @@ -150,3 +150,11 @@ def test_list_backfill(self, test_params, response_params, total_entries, test_c "backfills": expected_response, "total_entries": total_entries, } + + def test_should_response_401(self, unauthenticated_test_client): + response = unauthenticated_test_client.get("/ui/backfills", params={}) + assert response.status_code == 401 + + def test_should_response_403(self, unauthorized_test_client): + response = unauthorized_test_client.get("/ui/backfills", params={}) + assert response.status_code == 403