apache-airflow-providers-fab 1.5.3rc1__py3-none-any.whl → 2.0.0b1__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 (101) 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 +3 -3
  4. airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +4 -4
  5. airflow/providers/fab/auth_manager/api/auth/backend/session.py +1 -1
  6. airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +14 -13
  7. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +12 -12
  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 +152 -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 +2 -4
  18. airflow/providers/fab/auth_manager/cli_commands/user_command.py +2 -2
  19. airflow/providers/fab/auth_manager/cli_commands/utils.py +10 -9
  20. airflow/providers/fab/auth_manager/fab_auth_manager.py +231 -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/schemas/user_schema.py +1 -1
  25. airflow/providers/fab/auth_manager/security_manager/override.py +71 -632
  26. airflow/providers/fab/auth_manager/views/permissions.py +1 -1
  27. airflow/providers/fab/auth_manager/views/roles_list.py +1 -1
  28. airflow/providers/fab/auth_manager/views/user.py +1 -1
  29. airflow/providers/fab/auth_manager/views/user_edit.py +1 -1
  30. airflow/providers/fab/auth_manager/views/user_stats.py +1 -1
  31. airflow/providers/fab/get_provider_info.py +22 -16
  32. airflow/providers/fab/www/airflow_flask_app.py +31 -0
  33. airflow/providers/fab/www/api_connexion/__init__.py +17 -0
  34. airflow/providers/fab/www/api_connexion/exceptions.py +197 -0
  35. airflow/providers/fab/www/api_connexion/parameters.py +131 -0
  36. airflow/providers/fab/www/api_connexion/security.py +84 -0
  37. airflow/providers/fab/www/api_connexion/types.py +30 -0
  38. airflow/providers/fab/www/app.py +112 -0
  39. airflow/providers/fab/www/auth.py +350 -0
  40. airflow/providers/fab/www/constants.py +28 -0
  41. airflow/providers/fab/www/extensions/__init__.py +16 -0
  42. airflow/providers/fab/www/extensions/init_appbuilder.py +602 -0
  43. airflow/providers/fab/www/extensions/init_jinja_globals.py +82 -0
  44. airflow/providers/fab/www/extensions/init_manifest_files.py +61 -0
  45. airflow/providers/fab/www/extensions/init_security.py +61 -0
  46. airflow/providers/fab/www/extensions/init_session.py +64 -0
  47. airflow/providers/fab/www/extensions/init_views.py +177 -0
  48. airflow/providers/fab/www/package-lock.json +10127 -0
  49. airflow/providers/fab/www/package.json +81 -0
  50. airflow/providers/fab/www/security/__init__.py +17 -0
  51. airflow/providers/fab/www/security/permissions.py +126 -0
  52. airflow/providers/fab/www/security_appless.py +44 -0
  53. airflow/providers/fab/www/security_manager.py +122 -0
  54. airflow/providers/fab/www/session.py +41 -0
  55. airflow/providers/fab/www/static/css/bootstrap-theme.css +6215 -0
  56. airflow/providers/fab/www/static/css/flash.css +57 -0
  57. airflow/providers/fab/www/static/css/loading-dots.css +60 -0
  58. airflow/providers/fab/www/static/css/main.css +676 -0
  59. airflow/providers/fab/www/static/css/material-icons.css +84 -0
  60. airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.css +33 -0
  61. airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.js +1 -0
  62. airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.css +18 -0
  63. airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.js +1 -0
  64. airflow/providers/fab/www/static/dist/jquery-ui.min.css +5 -0
  65. airflow/providers/fab/www/static/dist/jquery-ui.min.js +2 -0
  66. airflow/providers/fab/www/static/dist/jquery-ui.min.js.LICENSE.txt +4 -0
  67. airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.css +18 -0
  68. airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.js +1 -0
  69. airflow/providers/fab/www/static/dist/main.ec1d38d994d72bb083cd.css +18 -0
  70. airflow/providers/fab/www/static/dist/main.ec1d38d994d72bb083cd.js +2 -0
  71. airflow/providers/fab/www/static/dist/main.ec1d38d994d72bb083cd.js.LICENSE.txt +18 -0
  72. airflow/providers/fab/www/static/dist/manifest.json +17 -0
  73. airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.css +18 -0
  74. airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.js +1 -0
  75. airflow/providers/fab/www/static/dist/moment.4d28b37c229bdfc54575.js +2 -0
  76. airflow/providers/fab/www/static/dist/moment.4d28b37c229bdfc54575.js.LICENSE.txt +11 -0
  77. airflow/providers/fab/www/static/dist/oss-licenses.json +29 -0
  78. airflow/providers/fab/www/static/js/datetime_utils.js +134 -0
  79. airflow/providers/fab/www/static/js/main.js +324 -0
  80. airflow/providers/fab/www/static/sort_asc.png +0 -0
  81. airflow/providers/fab/www/static/sort_both.png +0 -0
  82. airflow/providers/fab/www/static/sort_desc.png +0 -0
  83. airflow/providers/fab/www/templates/airflow/_messages.html +30 -0
  84. airflow/providers/fab/www/templates/airflow/error.html +35 -0
  85. airflow/providers/fab/www/templates/airflow/main.html +78 -0
  86. airflow/providers/fab/www/templates/airflow/traceback.html +53 -0
  87. airflow/providers/fab/www/templates/appbuilder/flash.html +34 -0
  88. airflow/providers/fab/www/templates/appbuilder/index.html +20 -0
  89. airflow/providers/fab/www/templates/appbuilder/navbar.html +60 -0
  90. airflow/providers/fab/www/templates/appbuilder/navbar_menu.html +60 -0
  91. airflow/providers/fab/www/templates/appbuilder/navbar_right.html +64 -0
  92. airflow/providers/fab/www/utils.py +272 -0
  93. airflow/providers/fab/www/views.py +129 -0
  94. airflow/providers/fab/www/webpack.config.js +213 -0
  95. {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0b1.dist-info}/METADATA +18 -36
  96. apache_airflow_providers_fab-2.0.0b1.dist-info/RECORD +122 -0
  97. {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0b1.dist-info}/WHEEL +1 -1
  98. airflow/providers/fab/auth_manager/decorators/auth.py +0 -126
  99. apache_airflow_providers_fab-1.5.3rc1.dist-info/RECORD +0 -51
  100. /airflow/providers/fab/{auth_manager/decorators → www}/__init__.py +0 -0
  101. {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0b1.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.ASSET_EVENTS: RESOURCE_ASSET,
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")
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,68 @@ 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
+
415
524
  def _is_authorized(
416
525
  self,
417
526
  *,
418
527
  method: ResourceMethod,
419
528
  resource_type: str,
420
- user: BaseUser | None = None,
529
+ user: User,
421
530
  ) -> bool:
422
531
  """
423
532
  Return whether the user is authorized to perform a given action.
424
533
 
425
534
  :param method: the method to perform
426
535
  :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
536
+ :param user: the user to performing the action
428
537
 
429
538
  :meta private:
430
539
  """
431
- if not user:
432
- user = self.get_user()
433
-
434
540
  fab_action = self._get_fab_action(method)
435
541
  user_permissions = self._get_user_permissions(user)
436
542
 
@@ -439,15 +545,15 @@ class FabAuthManager(BaseAuthManager):
439
545
  def _is_authorized_dag(
440
546
  self,
441
547
  method: ResourceMethod,
442
- details: DagDetails | None = None,
443
- user: BaseUser | None = None,
548
+ details: DagDetails | None,
549
+ user: User,
444
550
  ) -> bool:
445
551
  """
446
552
  Return whether the user is authorized to perform a given action on a DAG.
447
553
 
448
554
  :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
555
+ :param details: details about the DAG
556
+ :param user: the user to performing the action
451
557
 
452
558
  :meta private:
453
559
  """
@@ -465,15 +571,15 @@ class FabAuthManager(BaseAuthManager):
465
571
  def _is_authorized_dag_run(
466
572
  self,
467
573
  method: ResourceMethod,
468
- details: DagDetails | None = None,
469
- user: BaseUser | None = None,
574
+ details: DagDetails | None,
575
+ user: User,
470
576
  ) -> bool:
471
577
  """
472
578
  Return whether the user is authorized to perform a given action on a DAG Run.
473
579
 
474
580
  :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
581
+ :param details: details about the DAG
582
+ :param user: the user to performing the action
477
583
 
478
584
  :meta private:
479
585
  """
@@ -529,7 +635,7 @@ class FabAuthManager(BaseAuthManager):
529
635
  return getattr(permissions, "resource_name_for_dag")(root_dag_id)
530
636
 
531
637
  @staticmethod
532
- def _get_user_permissions(user: BaseUser):
638
+ def _get_user_permissions(user: User):
533
639
  """
534
640
  Return the user permissions.
535
641
 
@@ -547,6 +653,9 @@ class FabAuthManager(BaseAuthManager):
547
653
 
548
654
  :meta private:
549
655
  """
656
+ if not self.appbuilder:
657
+ raise AirflowException("AppBuilder is not initialized.")
658
+
550
659
  if "." in dag_id and hasattr(DagModel, "root_dag_id"):
551
660
  return self.appbuilder.get_session.scalar(
552
661
  select(DagModel.dag_id, DagModel.root_dag_id).where(DagModel.dag_id == dag_id).limit(1)
@@ -563,11 +672,7 @@ class FabAuthManager(BaseAuthManager):
563
672
  # Otherwise, when the name of a view or menu is changed, the framework
564
673
  # will add the new Views and Menus names to the backend, but will not
565
674
  # 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):
675
+ if conf.getboolean("fab", "UPDATE_FAB_PERMS"):
571
676
  self.security_manager.sync_roles()
572
677
 
573
678
 
@@ -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):