apache-airflow-providers-fab 1.5.3__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. airflow/providers/fab/LICENSE +0 -52
  2. airflow/providers/fab/__init__.py +3 -3
  3. airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +4 -5
  4. airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +5 -5
  5. airflow/providers/fab/auth_manager/api/auth/backend/session.py +2 -2
  6. airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +15 -15
  7. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +13 -14
  8. airflow/providers/fab/auth_manager/api_fastapi/__init__.py +16 -0
  9. airflow/providers/fab/auth_manager/api_fastapi/datamodels/__init__.py +16 -0
  10. airflow/providers/fab/auth_manager/api_fastapi/datamodels/login.py +32 -0
  11. airflow/providers/fab/auth_manager/api_fastapi/openapi/__init__.py +16 -0
  12. airflow/providers/fab/auth_manager/api_fastapi/openapi/v1-generated.yaml +153 -0
  13. airflow/providers/fab/auth_manager/api_fastapi/routes/__init__.py +16 -0
  14. airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +51 -0
  15. airflow/providers/fab/auth_manager/api_fastapi/services/__init__.py +16 -0
  16. airflow/providers/fab/auth_manager/api_fastapi/services/login.py +58 -0
  17. airflow/providers/fab/auth_manager/cli_commands/db_command.py +1 -3
  18. airflow/providers/fab/auth_manager/cli_commands/user_command.py +2 -2
  19. airflow/providers/fab/auth_manager/cli_commands/utils.py +12 -11
  20. airflow/providers/fab/auth_manager/fab_auth_manager.py +238 -126
  21. airflow/providers/fab/auth_manager/models/__init__.py +1 -1
  22. airflow/providers/fab/auth_manager/models/anonymous_user.py +1 -1
  23. airflow/providers/fab/auth_manager/models/db.py +22 -5
  24. airflow/providers/fab/auth_manager/openapi/v1.yaml +9 -0
  25. airflow/providers/fab/auth_manager/schemas/user_schema.py +1 -1
  26. airflow/providers/fab/auth_manager/security_manager/override.py +186 -655
  27. airflow/providers/fab/auth_manager/views/permissions.py +1 -1
  28. airflow/providers/fab/auth_manager/views/roles_list.py +1 -1
  29. airflow/providers/fab/auth_manager/views/user.py +1 -1
  30. airflow/providers/fab/auth_manager/views/user_edit.py +1 -1
  31. airflow/providers/fab/auth_manager/views/user_stats.py +1 -1
  32. airflow/providers/fab/get_provider_info.py +29 -34
  33. airflow/providers/fab/www/airflow_flask_app.py +31 -0
  34. airflow/providers/fab/www/api_connexion/__init__.py +17 -0
  35. airflow/providers/fab/www/api_connexion/exceptions.py +197 -0
  36. airflow/providers/fab/www/api_connexion/parameters.py +131 -0
  37. airflow/providers/fab/www/api_connexion/security.py +84 -0
  38. airflow/providers/fab/www/api_connexion/types.py +30 -0
  39. airflow/providers/fab/www/app.py +120 -0
  40. airflow/providers/fab/www/auth.py +350 -0
  41. airflow/providers/fab/www/constants.py +28 -0
  42. airflow/providers/fab/www/extensions/__init__.py +16 -0
  43. airflow/providers/fab/www/extensions/init_appbuilder.py +606 -0
  44. airflow/providers/fab/www/extensions/init_jinja_globals.py +82 -0
  45. airflow/providers/fab/www/extensions/init_manifest_files.py +61 -0
  46. airflow/providers/fab/www/extensions/init_security.py +61 -0
  47. airflow/providers/fab/www/extensions/init_session.py +64 -0
  48. airflow/providers/fab/www/extensions/init_views.py +177 -0
  49. airflow/providers/fab/www/package-lock.json +8939 -0
  50. airflow/providers/fab/www/package.json +77 -0
  51. airflow/providers/fab/www/security/__init__.py +17 -0
  52. airflow/providers/fab/www/security/permissions.py +126 -0
  53. airflow/providers/fab/www/security_appless.py +44 -0
  54. airflow/providers/fab/www/security_manager.py +122 -0
  55. airflow/providers/fab/www/session.py +41 -0
  56. airflow/providers/fab/www/static/css/bootstrap-theme.css +6215 -0
  57. airflow/providers/fab/www/static/css/flash.css +57 -0
  58. airflow/providers/fab/www/static/css/loading-dots.css +60 -0
  59. airflow/providers/fab/www/static/css/main.css +676 -0
  60. airflow/providers/fab/www/static/css/material-icons.css +84 -0
  61. airflow/providers/fab/www/static/dist/48f0ea180c40270a5b05.png +1 -0
  62. airflow/providers/fab/www/static/dist/649c0b07771e68fafdeb.png +1 -0
  63. airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.css +33 -0
  64. airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.js +1 -0
  65. airflow/providers/fab/www/static/dist/f7490d556a6c42e49ba4.png +1 -0
  66. airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.css +18 -0
  67. airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.js +1 -0
  68. airflow/providers/fab/www/static/dist/jquery-ui.min.css +5 -0
  69. airflow/providers/fab/www/static/dist/jquery-ui.min.js +2 -0
  70. airflow/providers/fab/www/static/dist/jquery-ui.min.js.LICENSE.txt +4 -0
  71. airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.css +18 -0
  72. airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.js +1 -0
  73. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.css +18 -0
  74. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.js +2 -0
  75. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.js.LICENSE.txt +18 -0
  76. airflow/providers/fab/www/static/dist/manifest.json +20 -0
  77. airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.css +18 -0
  78. airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.js +1 -0
  79. airflow/providers/fab/www/static/dist/moment.624b1f00ba723d39ce06.js +2 -0
  80. airflow/providers/fab/www/static/dist/moment.624b1f00ba723d39ce06.js.LICENSE.txt +11 -0
  81. airflow/providers/fab/www/static/dist/oss-licenses.json +20 -0
  82. airflow/providers/fab/www/static/js/datetime_utils.js +134 -0
  83. airflow/providers/fab/www/static/js/main.js +324 -0
  84. airflow/providers/fab/www/static/sort_asc.png +0 -0
  85. airflow/providers/fab/www/static/sort_both.png +0 -0
  86. airflow/providers/fab/www/static/sort_desc.png +0 -0
  87. airflow/providers/fab/www/templates/airflow/_messages.html +30 -0
  88. airflow/providers/fab/www/templates/airflow/error.html +35 -0
  89. airflow/providers/fab/www/templates/airflow/main.html +78 -0
  90. airflow/providers/fab/www/templates/airflow/traceback.html +53 -0
  91. airflow/providers/fab/www/templates/appbuilder/flash.html +34 -0
  92. airflow/providers/fab/www/templates/appbuilder/index.html +20 -0
  93. airflow/providers/fab/www/templates/appbuilder/navbar.html +60 -0
  94. airflow/providers/fab/www/templates/appbuilder/navbar_menu.html +60 -0
  95. airflow/providers/fab/www/templates/appbuilder/navbar_right.html +64 -0
  96. airflow/providers/fab/www/utils.py +288 -0
  97. airflow/providers/fab/www/views.py +129 -0
  98. airflow/providers/fab/www/webpack.config.js +213 -0
  99. {apache_airflow_providers_fab-1.5.3.dist-info → apache_airflow_providers_fab-2.0.0.dist-info}/METADATA +29 -37
  100. apache_airflow_providers_fab-2.0.0.dist-info/RECORD +125 -0
  101. {apache_airflow_providers_fab-1.5.3.dist-info → apache_airflow_providers_fab-2.0.0.dist-info}/WHEEL +1 -1
  102. airflow/providers/fab/auth_manager/decorators/auth.py +0 -126
  103. apache_airflow_providers_fab-1.5.3.dist-info/RECORD +0 -51
  104. /airflow/providers/fab/{auth_manager/decorators → www}/__init__.py +0 -0
  105. {apache_airflow_providers_fab-1.5.3.dist-info → apache_airflow_providers_fab-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -18,22 +18,25 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import argparse
21
- import warnings
22
21
  from functools import cached_property
23
22
  from pathlib import Path
24
- from typing import TYPE_CHECKING, Container
23
+ from typing import TYPE_CHECKING, Any
24
+ from urllib.parse import urljoin
25
25
 
26
26
  import packaging.version
27
27
  from connexion import FlaskApi
28
- from flask import Blueprint, g, url_for
29
- from packaging.version import Version
28
+ from fastapi import FastAPI
29
+ from flask import Blueprint, g
30
30
  from sqlalchemy import select
31
31
  from sqlalchemy.orm import Session, joinedload
32
+ from starlette.middleware.wsgi import WSGIMiddleware
32
33
 
33
34
  from airflow import __version__ as airflow_version
34
- from airflow.auth.managers.base_auth_manager import BaseAuthManager, ResourceMethod
35
- from airflow.auth.managers.models.resource_details import (
35
+ from airflow.api_fastapi.app import AUTH_MANAGER_FASTAPI_APP_PREFIX
36
+ from airflow.api_fastapi.auth.managers.base_auth_manager import BaseAuthManager
37
+ from airflow.api_fastapi.auth.managers.models.resource_details import (
36
38
  AccessView,
39
+ BackfillDetails,
37
40
  ConfigurationDetails,
38
41
  ConnectionDetails,
39
42
  DagAccessEntity,
@@ -41,13 +44,13 @@ from airflow.auth.managers.models.resource_details import (
41
44
  PoolDetails,
42
45
  VariableDetails,
43
46
  )
44
- from airflow.auth.managers.utils.fab import get_fab_action_from_method_map, get_method_from_fab_action_map
47
+ from airflow.api_fastapi.common.types import ExtraMenuItem, MenuItem
45
48
  from airflow.cli.cli_config import (
46
49
  DefaultHelpParser,
47
50
  GroupCommand,
48
51
  )
49
52
  from airflow.configuration import conf
50
- from airflow.exceptions import AirflowConfigException, AirflowException, AirflowProviderDeprecationWarning
53
+ from airflow.exceptions import AirflowConfigException, AirflowException
51
54
  from airflow.models import DagModel
52
55
  from airflow.providers.fab.auth_manager.cli_commands.definition import (
53
56
  DB_COMMANDS,
@@ -56,8 +59,15 @@ from airflow.providers.fab.auth_manager.cli_commands.definition import (
56
59
  USERS_COMMANDS,
57
60
  )
58
61
  from airflow.providers.fab.auth_manager.models import Permission, Role, User
59
- from airflow.security import permissions
60
- from airflow.security.permissions import (
62
+ from airflow.providers.fab.auth_manager.models.anonymous_user import AnonymousUser
63
+ from airflow.providers.fab.www.app import create_app
64
+ from airflow.providers.fab.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED
65
+ from airflow.providers.fab.www.extensions.init_views import (
66
+ _CustomErrorRequestBodyValidator,
67
+ _LazyResolver,
68
+ )
69
+ from airflow.providers.fab.www.security import permissions
70
+ from airflow.providers.fab.www.security.permissions import (
61
71
  RESOURCE_AUDIT_LOG,
62
72
  RESOURCE_CLUSTER_ACTIVITY,
63
73
  RESOURCE_CONFIG,
@@ -66,6 +76,7 @@ from airflow.security.permissions import (
66
76
  RESOURCE_DAG_CODE,
67
77
  RESOURCE_DAG_DEPENDENCIES,
68
78
  RESOURCE_DAG_RUN,
79
+ RESOURCE_DAG_VERSION,
69
80
  RESOURCE_DAG_WARNING,
70
81
  RESOURCE_DOCS,
71
82
  RESOURCE_IMPORT_ERROR,
@@ -82,22 +93,33 @@ from airflow.security.permissions import (
82
93
  RESOURCE_WEBSITE,
83
94
  RESOURCE_XCOM,
84
95
  )
85
- from airflow.utils.session import NEW_SESSION, provide_session
96
+ from airflow.providers.fab.www.utils import (
97
+ get_fab_action_from_method_map,
98
+ get_method_from_fab_action_map,
99
+ )
100
+ from airflow.security.permissions import RESOURCE_BACKFILL
101
+ from airflow.utils.session import NEW_SESSION, create_session, provide_session
86
102
  from airflow.utils.yaml import safe_load
87
- from airflow.version import version
88
- from airflow.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED
89
- from airflow.www.extensions.init_views import _CustomErrorRequestBodyValidator, _LazyResolver
90
103
 
91
104
  if TYPE_CHECKING:
92
- from airflow.auth.managers.models.base_user import BaseUser
105
+ from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod
93
106
  from airflow.cli.cli_config import (
94
107
  CLICommand,
95
108
  )
96
- from airflow.providers.common.compat.assets import AssetDetails
97
- from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
98
- from airflow.security.permissions import RESOURCE_ASSET
109
+ from airflow.providers.common.compat.assets import AssetAliasDetails, AssetDetails
110
+ from airflow.providers.fab.auth_manager.security_manager.override import (
111
+ FabAirflowSecurityManagerOverride,
112
+ )
113
+ from airflow.providers.fab.www.extensions.init_appbuilder import AirflowAppBuilder
114
+ from airflow.providers.fab.www.security.permissions import (
115
+ RESOURCE_ASSET,
116
+ RESOURCE_ASSET_ALIAS,
117
+ )
99
118
  else:
100
- from airflow.providers.common.compat.security.permissions import RESOURCE_ASSET
119
+ from airflow.providers.common.compat.security.permissions import (
120
+ RESOURCE_ASSET,
121
+ RESOURCE_ASSET_ALIAS,
122
+ )
101
123
 
102
124
 
103
125
  _MAP_DAG_ACCESS_ENTITY_TO_FAB_RESOURCE_TYPE: dict[DagAccessEntity, tuple[str, ...]] = {
@@ -115,6 +137,7 @@ _MAP_DAG_ACCESS_ENTITY_TO_FAB_RESOURCE_TYPE: dict[DagAccessEntity, tuple[str, ..
115
137
  DagAccessEntity.TASK_INSTANCE: (RESOURCE_DAG_RUN, RESOURCE_TASK_INSTANCE),
116
138
  DagAccessEntity.TASK_LOGS: (RESOURCE_TASK_LOG,),
117
139
  DagAccessEntity.TASK_RESCHEDULE: (RESOURCE_TASK_RESCHEDULE,),
140
+ DagAccessEntity.VERSION: (RESOURCE_DAG_VERSION,),
118
141
  DagAccessEntity.WARNING: (RESOURCE_DAG_WARNING,),
119
142
  DagAccessEntity.XCOM: (RESOURCE_XCOM,),
120
143
  }
@@ -130,14 +153,36 @@ _MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE = {
130
153
  AccessView.WEBSITE: RESOURCE_WEBSITE,
131
154
  }
132
155
 
156
+ _MAP_MENU_ITEM_TO_FAB_RESOURCE_TYPE = {
157
+ MenuItem.ASSETS: RESOURCE_ASSET,
158
+ MenuItem.AUDIT_LOG: RESOURCE_AUDIT_LOG,
159
+ MenuItem.CONNECTIONS: RESOURCE_CONNECTION,
160
+ MenuItem.DAGS: RESOURCE_DAG,
161
+ MenuItem.DOCS: RESOURCE_DOCS,
162
+ MenuItem.PLUGINS: RESOURCE_PLUGIN,
163
+ MenuItem.POOLS: RESOURCE_POOL,
164
+ MenuItem.PROVIDERS: RESOURCE_PROVIDER,
165
+ MenuItem.VARIABLES: RESOURCE_VARIABLE,
166
+ MenuItem.XCOMS: RESOURCE_XCOM,
167
+ }
168
+
133
169
 
134
- class FabAuthManager(BaseAuthManager):
170
+ class FabAuthManager(BaseAuthManager[User]):
135
171
  """
136
172
  Flask-AppBuilder auth manager.
137
173
 
138
174
  This auth manager is responsible for providing a backward compatible user management experience to users.
139
175
  """
140
176
 
177
+ appbuilder: AirflowAppBuilder | None = None
178
+
179
+ def init_flask_resources(self) -> None:
180
+ self._sync_appbuilder_roles()
181
+
182
+ @cached_property
183
+ def apiserver_endpoint(self) -> str:
184
+ return conf.get("api", "base_url", fallback="/")
185
+
141
186
  @staticmethod
142
187
  def get_cli_commands() -> list[CLICommand]:
143
188
  """Vends CLI commands to be included in Airflow CLI."""
@@ -161,6 +206,31 @@ class FabAuthManager(BaseAuthManager):
161
206
  commands.append(GroupCommand(name="fab-db", help="Manage FAB", subcommands=DB_COMMANDS))
162
207
  return commands
163
208
 
209
+ def get_fastapi_app(self) -> FastAPI | None:
210
+ """Get the FastAPI app."""
211
+ from airflow.providers.fab.auth_manager.api_fastapi.routes.login import (
212
+ login_router,
213
+ )
214
+
215
+ flask_app = create_app(enable_plugins=False)
216
+
217
+ app = FastAPI(
218
+ title="FAB auth manager API",
219
+ description=(
220
+ "This is FAB auth manager API. This API is only available if the auth manager used in "
221
+ "the Airflow environment is FAB auth manager. "
222
+ "This API provides endpoints to manage users and permissions managed by the FAB auth "
223
+ "manager."
224
+ ),
225
+ )
226
+
227
+ # Add the login router to the FastAPI app
228
+ app.include_router(login_router)
229
+
230
+ app.mount("/", WSGIMiddleware(flask_app))
231
+
232
+ return app
233
+
164
234
  def get_api_endpoints(self) -> None | Blueprint:
165
235
  folder = Path(__file__).parents[0].resolve() # this is airflow/auth/managers/fab/
166
236
  with folder.joinpath("openapi", "v1.yaml").open() as f:
@@ -168,20 +238,16 @@ class FabAuthManager(BaseAuthManager):
168
238
  return FlaskApi(
169
239
  specification=specification,
170
240
  resolver=_LazyResolver(),
171
- base_path="/auth/fab/v1",
172
- options={"swagger_ui": SWAGGER_ENABLED, "swagger_path": SWAGGER_BUNDLE.__fspath__()},
241
+ base_path="/fab/v1",
242
+ options={
243
+ "swagger_ui": SWAGGER_ENABLED,
244
+ "swagger_path": SWAGGER_BUNDLE.__fspath__(),
245
+ },
173
246
  strict_validation=True,
174
247
  validate_responses=True,
175
248
  validator_map={"body": _CustomErrorRequestBodyValidator},
176
249
  ).blueprint
177
250
 
178
- def get_user_display_name(self) -> str:
179
- """Return the user's display name associated to the user in session."""
180
- user = self.get_user()
181
- first_name = user.first_name.strip() if isinstance(user.first_name, str) else ""
182
- last_name = user.last_name.strip() if isinstance(user.last_name, str) else ""
183
- return f"{first_name} {last_name}".strip()
184
-
185
251
  def get_user(self) -> User:
186
252
  """
187
253
  Return the user associated to the user in session.
@@ -199,26 +265,28 @@ class FabAuthManager(BaseAuthManager):
199
265
 
200
266
  return current_user
201
267
 
202
- def init(self) -> None:
203
- """Run operations when Airflow is initializing."""
204
- self._sync_appbuilder_roles()
268
+ def deserialize_user(self, token: dict[str, Any]) -> User:
269
+ with create_session() as session:
270
+ return session.get(User, int(token["sub"]))
271
+
272
+ def serialize_user(self, user: User) -> dict[str, Any]:
273
+ return {"sub": str(user.id)}
205
274
 
206
275
  def is_logged_in(self) -> bool:
207
276
  """Return whether the user is logged in."""
208
277
  user = self.get_user()
209
- if Version(Version(version).base_version) < Version("3.0.0"):
210
- return not user.is_anonymous and user.is_active
211
- else:
212
- return self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None) or (
213
- not user.is_anonymous and user.is_active
214
- )
278
+ return (
279
+ self.appbuilder
280
+ and self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None)
281
+ or (not user.is_anonymous and user.is_active)
282
+ )
215
283
 
216
284
  def is_authorized_configuration(
217
285
  self,
218
286
  *,
219
287
  method: ResourceMethod,
288
+ user: User,
220
289
  details: ConfigurationDetails | None = None,
221
- user: BaseUser | None = None,
222
290
  ) -> bool:
223
291
  return self._is_authorized(method=method, resource_type=RESOURCE_CONFIG, user=user)
224
292
 
@@ -226,8 +294,8 @@ class FabAuthManager(BaseAuthManager):
226
294
  self,
227
295
  *,
228
296
  method: ResourceMethod,
297
+ user: User,
229
298
  details: ConnectionDetails | None = None,
230
- user: BaseUser | None = None,
231
299
  ) -> bool:
232
300
  return self._is_authorized(method=method, resource_type=RESOURCE_CONNECTION, user=user)
233
301
 
@@ -235,9 +303,9 @@ class FabAuthManager(BaseAuthManager):
235
303
  self,
236
304
  *,
237
305
  method: ResourceMethod,
306
+ user: User,
238
307
  access_entity: DagAccessEntity | None = None,
239
308
  details: DagDetails | None = None,
240
- user: BaseUser | None = None,
241
309
  ) -> bool:
242
310
  """
243
311
  Return whether the user is authorized to access the dag.
@@ -254,9 +322,9 @@ class FabAuthManager(BaseAuthManager):
254
322
  if no specific DAG is targeted, just check the sub entity.
255
323
 
256
324
  :param method: The method to authorize.
325
+ :param user: The user performing the action.
257
326
  :param access_entity: The dag access entity.
258
327
  :param details: The dag details.
259
- :param user: The user.
260
328
  """
261
329
  if not access_entity:
262
330
  # Scenario 1
@@ -272,84 +340,100 @@ class FabAuthManager(BaseAuthManager):
272
340
  return False
273
341
 
274
342
  return all(
275
- self._is_authorized(method=method, resource_type=resource_type, user=user)
276
- if resource_type != RESOURCE_DAG_RUN or not hasattr(permissions, "resource_name")
277
- else self._is_authorized_dag_run(method=method, details=details, user=user)
343
+ (
344
+ self._is_authorized(method=method, resource_type=resource_type, user=user)
345
+ if resource_type != RESOURCE_DAG_RUN or not hasattr(permissions, "resource_name")
346
+ else self._is_authorized_dag_run(method=method, details=details, user=user)
347
+ )
278
348
  for resource_type in resource_types
279
349
  )
280
350
 
351
+ def is_authorized_backfill(
352
+ self,
353
+ *,
354
+ method: ResourceMethod,
355
+ user: User,
356
+ details: BackfillDetails | None = None,
357
+ ) -> bool:
358
+ return self._is_authorized(method=method, resource_type=RESOURCE_BACKFILL, user=user)
359
+
281
360
  def is_authorized_asset(
282
- self, *, method: ResourceMethod, details: AssetDetails | None = None, user: BaseUser | None = None
361
+ self, *, method: ResourceMethod, user: User, details: AssetDetails | None = None
283
362
  ) -> bool:
284
363
  return self._is_authorized(method=method, resource_type=RESOURCE_ASSET, user=user)
285
364
 
286
- def is_authorized_dataset(
287
- self, *, method: ResourceMethod, details: AssetDetails | None = None, user: BaseUser | None = None
365
+ def is_authorized_asset_alias(
366
+ self,
367
+ *,
368
+ method: ResourceMethod,
369
+ user: User,
370
+ details: AssetAliasDetails | None = None,
288
371
  ) -> bool:
289
- warnings.warn(
290
- "is_authorized_dataset will be renamed as is_authorized_asset in Airflow 3 and will be removed when the minimum Airflow version is set to 3.0 for the fab provider",
291
- AirflowProviderDeprecationWarning,
292
- stacklevel=2,
293
- )
294
- return self.is_authorized_asset(method=method, user=user)
372
+ return self._is_authorized(method=method, resource_type=RESOURCE_ASSET_ALIAS, user=user)
295
373
 
296
374
  def is_authorized_pool(
297
- self, *, method: ResourceMethod, details: PoolDetails | None = None, user: BaseUser | None = None
375
+ self, *, method: ResourceMethod, user: User, details: PoolDetails | None = None
298
376
  ) -> bool:
299
377
  return self._is_authorized(method=method, resource_type=RESOURCE_POOL, user=user)
300
378
 
301
379
  def is_authorized_variable(
302
- self, *, method: ResourceMethod, details: VariableDetails | None = None, user: BaseUser | None = None
380
+ self,
381
+ *,
382
+ method: ResourceMethod,
383
+ user: User,
384
+ details: VariableDetails | None = None,
303
385
  ) -> bool:
304
386
  return self._is_authorized(method=method, resource_type=RESOURCE_VARIABLE, user=user)
305
387
 
306
- def is_authorized_view(self, *, access_view: AccessView, user: BaseUser | None = None) -> bool:
388
+ def is_authorized_view(self, *, access_view: AccessView, user: User) -> bool:
307
389
  # "Docs" are only links in the menu, there is no page associated
308
390
  method: ResourceMethod = "MENU" if access_view == AccessView.DOCS else "GET"
309
391
  return self._is_authorized(
310
- method=method, resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], user=user
392
+ method=method,
393
+ resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view],
394
+ user=user,
311
395
  )
312
396
 
313
397
  def is_authorized_custom_view(
314
- self, *, method: ResourceMethod | str, resource_name: str, user: BaseUser | None = None
315
- ):
316
- if not user:
317
- user = self.get_user()
398
+ self, *, method: ResourceMethod | str, resource_name: str, user: User
399
+ ) -> bool:
318
400
  fab_action_name = get_fab_action_from_method_map().get(method, method)
319
401
  return (fab_action_name, resource_name) in self._get_user_permissions(user)
320
402
 
403
+ def filter_authorized_menu_items(self, menu_items: list[MenuItem], user: User) -> list[MenuItem]:
404
+ return [
405
+ menu_item
406
+ for menu_item in menu_items
407
+ if self._is_authorized(
408
+ method="MENU",
409
+ resource_type=_MAP_MENU_ITEM_TO_FAB_RESOURCE_TYPE.get(menu_item, menu_item.value),
410
+ user=user,
411
+ )
412
+ ]
413
+
321
414
  @provide_session
322
- def get_permitted_dag_ids(
415
+ def get_authorized_dag_ids(
323
416
  self,
324
417
  *,
325
- methods: Container[ResourceMethod] | None = None,
326
- user=None,
418
+ user: User,
419
+ method: ResourceMethod = "GET",
327
420
  session: Session = NEW_SESSION,
328
421
  ) -> set[str]:
329
- if not methods:
330
- methods = ["PUT", "GET"]
331
-
332
- if not user:
333
- user = self.get_user()
334
-
335
- if not self.is_logged_in():
336
- roles = user.roles
337
- else:
338
- if ("GET" in methods and self.is_authorized_dag(method="GET", user=user)) or (
339
- "PUT" in methods and self.is_authorized_dag(method="PUT", user=user)
340
- ):
341
- # If user is authorized to read/edit all DAGs, return all DAGs
342
- return {dag.dag_id for dag in session.execute(select(DagModel.dag_id))}
343
- user_query = session.scalar(
344
- select(User)
345
- .options(
346
- joinedload(User.roles)
347
- .subqueryload(Role.permissions)
348
- .options(joinedload(Permission.action), joinedload(Permission.resource))
349
- )
350
- .where(User.id == user.id)
422
+ if self._is_authorized(method=method, resource_type=RESOURCE_DAG, user=user):
423
+ # If user is authorized to access all DAGs, return all DAGs
424
+ return {dag.dag_id for dag in session.execute(select(DagModel.dag_id))}
425
+ if isinstance(user, AnonymousUser):
426
+ return set()
427
+ user_query = session.scalar(
428
+ select(User)
429
+ .options(
430
+ joinedload(User.roles)
431
+ .subqueryload(Role.permissions)
432
+ .options(joinedload(Permission.action), joinedload(Permission.resource))
351
433
  )
352
- roles = user_query.roles
434
+ .where(User.id == user.id)
435
+ )
436
+ roles = user_query.roles
353
437
 
354
438
  map_fab_action_name_to_method_name = get_method_from_fab_action_map()
355
439
  resources = set()
@@ -358,7 +442,7 @@ class FabAuthManager(BaseAuthManager):
358
442
  action = permission.action.name
359
443
  if (
360
444
  action in map_fab_action_name_to_method_name
361
- and map_fab_action_name_to_method_name[action] in methods
445
+ and map_fab_action_name_to_method_name[action] == method
362
446
  ):
363
447
  resource = permission.resource.name
364
448
  if resource == permissions.RESOURCE_DAG:
@@ -376,6 +460,9 @@ class FabAuthManager(BaseAuthManager):
376
460
  FabAirflowSecurityManagerOverride,
377
461
  )
378
462
 
463
+ if not self.appbuilder:
464
+ raise AirflowException("AppBuilder is not initialized.")
465
+
379
466
  sm_from_config = self.appbuilder.get_app.config.get("SECURITY_MANAGER_CLASS")
380
467
  if sm_from_config:
381
468
  if not issubclass(sm_from_config, FabAirflowSecurityManagerOverride):
@@ -388,49 +475,72 @@ class FabAuthManager(BaseAuthManager):
388
475
 
389
476
  def get_url_login(self, **kwargs) -> str:
390
477
  """Return the login page url."""
391
- if not self.security_manager.auth_view:
392
- raise AirflowException("`auth_view` not defined in the security manager.")
393
- if next_url := kwargs.get("next_url"):
394
- return url_for(f"{self.security_manager.auth_view.endpoint}.login", next=next_url)
395
- else:
396
- return url_for(f"{self.security_manager.auth_view.endpoint}.login")
478
+ return urljoin(self.apiserver_endpoint, f"{AUTH_MANAGER_FASTAPI_APP_PREFIX}/login/")
397
479
 
398
- def get_url_logout(self):
480
+ def get_url_logout(self) -> str | None:
399
481
  """Return the logout page url."""
400
- if not self.security_manager.auth_view:
401
- raise AirflowException("`auth_view` not defined in the security manager.")
402
- return url_for(f"{self.security_manager.auth_view.endpoint}.logout")
403
-
404
- def get_url_user_profile(self) -> str | None:
405
- """Return the url to a page displaying info about the current user."""
406
- if not self.security_manager.user_view or self.appbuilder.get_app.config.get(
407
- "AUTH_ROLE_PUBLIC", None
408
- ):
409
- return None
410
- return url_for(f"{self.security_manager.user_view.endpoint}.userinfo")
482
+ return urljoin(self.apiserver_endpoint, f"{AUTH_MANAGER_FASTAPI_APP_PREFIX}/logout/")
411
483
 
412
484
  def register_views(self) -> None:
413
485
  self.security_manager.register_views()
414
486
 
487
+ def get_extra_menu_items(self, *, user: User) -> list[ExtraMenuItem]:
488
+ # Contains the list of menu items. ``resource_type`` is the name of the resource in FAB
489
+ # permission model to check whether the user is allowed to see this menu item
490
+ items = [
491
+ {
492
+ "resource_type": "List Users",
493
+ "text": "Users",
494
+ "href": f"{AUTH_MANAGER_FASTAPI_APP_PREFIX}/users/list/",
495
+ },
496
+ {
497
+ "resource_type": "List Roles",
498
+ "text": "Roles",
499
+ "href": f"{AUTH_MANAGER_FASTAPI_APP_PREFIX}/roles/list/",
500
+ },
501
+ {
502
+ "resource_type": "Actions",
503
+ "text": "Actions",
504
+ "href": f"{AUTH_MANAGER_FASTAPI_APP_PREFIX}/actions/list/",
505
+ },
506
+ {
507
+ "resource_type": "Resources",
508
+ "text": "Resources",
509
+ "href": f"{AUTH_MANAGER_FASTAPI_APP_PREFIX}/resources/list/",
510
+ },
511
+ {
512
+ "resource_type": "Permission Pairs",
513
+ "text": "Permissions",
514
+ "href": f"{AUTH_MANAGER_FASTAPI_APP_PREFIX}/permissions/list/",
515
+ },
516
+ ]
517
+
518
+ return [
519
+ ExtraMenuItem(text=item["text"], href=item["href"])
520
+ for item in items
521
+ if self._is_authorized(method="MENU", resource_type=item["resource_type"], user=user)
522
+ ]
523
+
524
+ @staticmethod
525
+ def get_db_manager() -> str | None:
526
+ return "airflow.providers.fab.auth_manager.models.db.FABDBManager"
527
+
415
528
  def _is_authorized(
416
529
  self,
417
530
  *,
418
531
  method: ResourceMethod,
419
532
  resource_type: str,
420
- user: BaseUser | None = None,
533
+ user: User,
421
534
  ) -> bool:
422
535
  """
423
536
  Return whether the user is authorized to perform a given action.
424
537
 
425
538
  :param method: the method to perform
426
539
  :param resource_type: the type of resource the user attempts to perform the action on
427
- :param user: the user to perform the action on. If not provided (or None), it uses the current user
540
+ :param user: the user to performing the action
428
541
 
429
542
  :meta private:
430
543
  """
431
- if not user:
432
- user = self.get_user()
433
-
434
544
  fab_action = self._get_fab_action(method)
435
545
  user_permissions = self._get_user_permissions(user)
436
546
 
@@ -439,15 +549,15 @@ class FabAuthManager(BaseAuthManager):
439
549
  def _is_authorized_dag(
440
550
  self,
441
551
  method: ResourceMethod,
442
- details: DagDetails | None = None,
443
- user: BaseUser | None = None,
552
+ details: DagDetails | None,
553
+ user: User,
444
554
  ) -> bool:
445
555
  """
446
556
  Return whether the user is authorized to perform a given action on a DAG.
447
557
 
448
558
  :param method: the method to perform
449
- :param details: optional details about the DAG
450
- :param user: the user to perform the action on. If not provided (or None), it uses the current user
559
+ :param details: details about the DAG
560
+ :param user: the user to performing the action
451
561
 
452
562
  :meta private:
453
563
  """
@@ -465,15 +575,15 @@ class FabAuthManager(BaseAuthManager):
465
575
  def _is_authorized_dag_run(
466
576
  self,
467
577
  method: ResourceMethod,
468
- details: DagDetails | None = None,
469
- user: BaseUser | None = None,
578
+ details: DagDetails | None,
579
+ user: User,
470
580
  ) -> bool:
471
581
  """
472
582
  Return whether the user is authorized to perform a given action on a DAG Run.
473
583
 
474
584
  :param method: the method to perform
475
- :param details: optional, details about the DAG
476
- :param user: optional, the user to perform the action on. If not provided, it uses the current user
585
+ :param details: details about the DAG
586
+ :param user: the user to performing the action
477
587
 
478
588
  :meta private:
479
589
  """
@@ -529,7 +639,7 @@ class FabAuthManager(BaseAuthManager):
529
639
  return getattr(permissions, "resource_name_for_dag")(root_dag_id)
530
640
 
531
641
  @staticmethod
532
- def _get_user_permissions(user: BaseUser):
642
+ def _get_user_permissions(user: User):
533
643
  """
534
644
  Return the user permissions.
535
645
 
@@ -537,6 +647,9 @@ class FabAuthManager(BaseAuthManager):
537
647
 
538
648
  :meta private:
539
649
  """
650
+ # If the user gets deleted while being logged in
651
+ if not user:
652
+ return []
540
653
  return getattr(user, "perms") or []
541
654
 
542
655
  def _get_root_dag_id(self, dag_id: str) -> str:
@@ -547,6 +660,9 @@ class FabAuthManager(BaseAuthManager):
547
660
 
548
661
  :meta private:
549
662
  """
663
+ if not self.appbuilder:
664
+ raise AirflowException("AppBuilder is not initialized.")
665
+
550
666
  if "." in dag_id and hasattr(DagModel, "root_dag_id"):
551
667
  return self.appbuilder.get_session.scalar(
552
668
  select(DagModel.dag_id, DagModel.root_dag_id).where(DagModel.dag_id == dag_id).limit(1)
@@ -563,11 +679,7 @@ class FabAuthManager(BaseAuthManager):
563
679
  # Otherwise, when the name of a view or menu is changed, the framework
564
680
  # will add the new Views and Menus names to the backend, but will not
565
681
  # delete the old ones.
566
- if Version(Version(version).base_version) >= Version("3.0.0"):
567
- fallback = None
568
- else:
569
- fallback = conf.getboolean("webserver", "UPDATE_FAB_PERMS")
570
- if conf.getboolean("fab", "UPDATE_FAB_PERMS", fallback=fallback):
682
+ if conf.getboolean("fab", "UPDATE_FAB_PERMS"):
571
683
  self.security_manager.sync_roles()
572
684
 
573
685
 
@@ -44,7 +44,7 @@ from sqlalchemy import (
44
44
  from sqlalchemy.orm import backref, declared_attr, registry, relationship
45
45
 
46
46
  from airflow import __version__ as airflow_version
47
- from airflow.auth.managers.models.base_user import BaseUser
47
+ from airflow.api_fastapi.auth.managers.models.base_user import BaseUser
48
48
  from airflow.models.base import _get_schema, naming_convention
49
49
 
50
50
  if TYPE_CHECKING:
@@ -20,7 +20,7 @@ from __future__ import annotations
20
20
  from flask import current_app
21
21
  from flask_login import AnonymousUserMixin
22
22
 
23
- from airflow.auth.managers.models.base_user import BaseUser
23
+ from airflow.api_fastapi.auth.managers.models.base_user import BaseUser
24
24
 
25
25
 
26
26
  class AnonymousUser(AnonymousUserMixin, BaseUser):