apache-airflow-providers-fab 1.5.2rc1__py3-none-any.whl → 2.0.0rc1__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 (43) hide show
  1. airflow/providers/fab/__init__.py +3 -3
  2. airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +1 -1
  3. airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +1 -1
  4. airflow/providers/fab/auth_manager/api/auth/backend/session.py +1 -1
  5. airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +1 -1
  6. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +1 -1
  7. airflow/providers/fab/auth_manager/cli_commands/db_command.py +1 -1
  8. airflow/providers/fab/auth_manager/cli_commands/utils.py +4 -16
  9. airflow/providers/fab/auth_manager/decorators/auth.py +3 -2
  10. airflow/providers/fab/auth_manager/fab_auth_manager.py +26 -20
  11. airflow/providers/fab/auth_manager/security_manager/override.py +5 -116
  12. airflow/providers/fab/get_provider_info.py +3 -3
  13. airflow/providers/fab/www/__init__.py +17 -0
  14. airflow/providers/fab/www/app.py +87 -0
  15. airflow/providers/fab/www/extensions/__init__.py +16 -0
  16. airflow/providers/fab/www/extensions/init_appbuilder.py +557 -0
  17. airflow/providers/fab/www/extensions/init_jinja_globals.py +80 -0
  18. airflow/providers/fab/www/extensions/init_manifest_files.py +61 -0
  19. airflow/providers/fab/www/extensions/init_security.py +42 -0
  20. airflow/providers/fab/www/extensions/init_views.py +67 -0
  21. airflow/providers/fab/www/package-lock.json +21201 -0
  22. airflow/providers/fab/www/package.json +156 -0
  23. airflow/providers/fab/www/static/css/bootstrap-theme.css +6215 -0
  24. airflow/providers/fab/www/static/css/loading-dots.css +60 -0
  25. airflow/providers/fab/www/static/css/main.css +676 -0
  26. airflow/providers/fab/www/static/css/material-icons.css +84 -0
  27. airflow/providers/fab/www/static/js/datetime_utils.js +134 -0
  28. airflow/providers/fab/www/static/js/main.js +324 -0
  29. airflow/providers/fab/www/static/sort_asc.png +0 -0
  30. airflow/providers/fab/www/static/sort_both.png +0 -0
  31. airflow/providers/fab/www/static/sort_desc.png +0 -0
  32. airflow/providers/fab/www/templates/airflow/_messages.html +30 -0
  33. airflow/providers/fab/www/templates/airflow/error.html +35 -0
  34. airflow/providers/fab/www/templates/airflow/main.html +79 -0
  35. airflow/providers/fab/www/templates/airflow/traceback.html +57 -0
  36. airflow/providers/fab/www/templates/appbuilder/index.html +20 -0
  37. airflow/providers/fab/www/templates/appbuilder/navbar.html +53 -0
  38. airflow/providers/fab/www/templates/appbuilder/navbar_menu.html +60 -0
  39. airflow/providers/fab/www/webpack.config.js +248 -0
  40. {apache_airflow_providers_fab-1.5.2rc1.dist-info → apache_airflow_providers_fab-2.0.0rc1.dist-info}/METADATA +9 -11
  41. {apache_airflow_providers_fab-1.5.2rc1.dist-info → apache_airflow_providers_fab-2.0.0rc1.dist-info}/RECORD +43 -16
  42. {apache_airflow_providers_fab-1.5.2rc1.dist-info → apache_airflow_providers_fab-2.0.0rc1.dist-info}/WHEEL +0 -0
  43. {apache_airflow_providers_fab-1.5.2rc1.dist-info → apache_airflow_providers_fab-2.0.0rc1.dist-info}/entry_points.txt +0 -0
@@ -29,11 +29,11 @@ from airflow import __version__ as airflow_version
29
29
 
30
30
  __all__ = ["__version__"]
31
31
 
32
- __version__ = "1.5.2"
32
+ __version__ = "2.0.0"
33
33
 
34
34
  if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
35
- "2.9.0"
35
+ "3.0.0.dev0"
36
36
  ):
37
37
  raise RuntimeError(
38
- f"The package `apache-airflow-providers-fab:{__version__}` needs Apache Airflow 2.9.0+"
38
+ f"The package `apache-airflow-providers-fab:{__version__}` needs Apache Airflow 3.0.0.dev0+"
39
39
  )
@@ -25,8 +25,8 @@ from flask import Response, current_app, request
25
25
  from flask_appbuilder.const import AUTH_LDAP
26
26
  from flask_login import login_user
27
27
 
28
+ from airflow.api_fastapi.app import get_auth_manager
28
29
  from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
29
- from airflow.www.extensions.init_auth_manager import get_auth_manager
30
30
 
31
31
  if TYPE_CHECKING:
32
32
  from airflow.providers.fab.auth_manager.models import User
@@ -26,10 +26,10 @@ import kerberos
26
26
  from flask import Response, current_app, g, make_response, request
27
27
  from requests_kerberos import HTTPKerberosAuth
28
28
 
29
+ from airflow.api_fastapi.app import get_auth_manager
29
30
  from airflow.configuration import conf
30
31
  from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
31
32
  from airflow.utils.net import getfqdn
32
- from airflow.www.extensions.init_auth_manager import get_auth_manager
33
33
 
34
34
  if TYPE_CHECKING:
35
35
  from airflow.auth.managers.models.base_user import BaseUser
@@ -23,7 +23,7 @@ from typing import Any, Callable, TypeVar, cast
23
23
 
24
24
  from flask import Response
25
25
 
26
- from airflow.www.extensions.init_auth_manager import get_auth_manager
26
+ from airflow.api_fastapi.app import get_auth_manager
27
27
 
28
28
  CLIENT_AUTH: tuple[str, str] | Any | None = None
29
29
 
@@ -27,6 +27,7 @@ from sqlalchemy import asc, desc, func, select
27
27
  from airflow.api_connexion.exceptions import AlreadyExists, BadRequest, NotFound
28
28
  from airflow.api_connexion.parameters import check_limit, format_parameters
29
29
  from airflow.api_connexion.security import requires_access_custom_view
30
+ from airflow.api_fastapi.app import get_auth_manager
30
31
  from airflow.providers.fab.auth_manager.models import Action, Role
31
32
  from airflow.providers.fab.auth_manager.schemas.role_and_permission_schema import (
32
33
  ActionCollection,
@@ -37,7 +38,6 @@ from airflow.providers.fab.auth_manager.schemas.role_and_permission_schema impor
37
38
  )
38
39
  from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
39
40
  from airflow.security import permissions
40
- from airflow.www.extensions.init_auth_manager import get_auth_manager
41
41
 
42
42
  if TYPE_CHECKING:
43
43
  from airflow.api_connexion.types import APIResponse, UpdateMask
@@ -28,6 +28,7 @@ from werkzeug.security import generate_password_hash
28
28
  from airflow.api_connexion.exceptions import AlreadyExists, BadRequest, NotFound, Unknown
29
29
  from airflow.api_connexion.parameters import check_limit, format_parameters
30
30
  from airflow.api_connexion.security import requires_access_custom_view
31
+ from airflow.api_fastapi.app import get_auth_manager
31
32
  from airflow.providers.fab.auth_manager.models import User
32
33
  from airflow.providers.fab.auth_manager.schemas.user_schema import (
33
34
  UserCollection,
@@ -37,7 +38,6 @@ from airflow.providers.fab.auth_manager.schemas.user_schema import (
37
38
  )
38
39
  from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
39
40
  from airflow.security import permissions
40
- from airflow.www.extensions.init_auth_manager import get_auth_manager
41
41
 
42
42
  if TYPE_CHECKING:
43
43
  from airflow.api_connexion.types import APIResponse, UpdateMask
@@ -17,7 +17,7 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  from airflow import settings
20
- from airflow.cli.commands.db_command import run_db_downgrade_command, run_db_migrate_command
20
+ from airflow.cli.commands.local_commands.db_command import run_db_downgrade_command, run_db_migrate_command
21
21
  from airflow.providers.fab.auth_manager.models.db import _REVISION_HEADS_MAP, FABDBManager
22
22
  from airflow.utils import cli as cli_utils
23
23
  from airflow.utils.providers_configuration_loader import providers_configuration_loaded
@@ -18,31 +18,27 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import os
21
+ from collections.abc import Generator
21
22
  from contextlib import contextmanager
22
- from functools import lru_cache
23
- from os.path import isabs
24
- from typing import TYPE_CHECKING, Generator
23
+ from functools import cache
24
+ from typing import TYPE_CHECKING
25
25
 
26
26
  from flask import Flask
27
27
 
28
28
  import airflow
29
29
  from airflow.configuration import conf
30
- from airflow.exceptions import AirflowConfigException
31
- from airflow.www.app import make_url
32
30
  from airflow.www.extensions.init_appbuilder import init_appbuilder
33
- from airflow.www.extensions.init_session import init_airflow_session_interface
34
31
  from airflow.www.extensions.init_views import init_plugins
35
32
 
36
33
  if TYPE_CHECKING:
37
34
  from airflow.www.extensions.init_appbuilder import AirflowAppBuilder
38
35
 
39
36
 
40
- @lru_cache(maxsize=None)
37
+ @cache
41
38
  def _return_appbuilder(app: Flask) -> AirflowAppBuilder:
42
39
  """Return an appbuilder instance for the given app."""
43
40
  init_appbuilder(app)
44
41
  init_plugins(app)
45
- init_airflow_session_interface(app)
46
42
  return app.appbuilder # type: ignore[attr-defined]
47
43
 
48
44
 
@@ -54,12 +50,4 @@ def get_application_builder() -> Generator[AirflowAppBuilder, None, None]:
54
50
  with flask_app.app_context():
55
51
  # Enable customizations in webserver_config.py to be applied via Flask.current_app.
56
52
  flask_app.config.from_pyfile(webserver_config, silent=True)
57
- flask_app.config["SQLALCHEMY_DATABASE_URI"] = conf.get("database", "SQL_ALCHEMY_CONN")
58
- url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
59
- if url.drivername == "sqlite" and url.database and not isabs(url.database):
60
- raise AirflowConfigException(
61
- f'Cannot use relative path: `{conf.get("database", "SQL_ALCHEMY_CONN")}` to connect to sqlite. '
62
- "Please use absolute path such as `sqlite:////tmp/airflow.db`."
63
- )
64
- flask_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
65
53
  yield _return_appbuilder(flask_app)
@@ -18,19 +18,20 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import logging
21
+ from collections.abc import Sequence
21
22
  from functools import wraps
22
- from typing import Callable, Sequence, TypeVar, cast
23
+ from typing import Callable, TypeVar, cast
23
24
 
24
25
  from flask import current_app, render_template, request
25
26
 
26
27
  from airflow.api_connexion.exceptions import PermissionDenied
27
28
  from airflow.api_connexion.security import check_authentication
29
+ from airflow.api_fastapi.app import get_auth_manager
28
30
  from airflow.configuration import conf
29
31
  from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
30
32
  from airflow.utils.airflow_flask_app import AirflowApp
31
33
  from airflow.utils.net import get_hostname
32
34
  from airflow.www.auth import _has_access
33
- from airflow.www.extensions.init_auth_manager import get_auth_manager
34
35
 
35
36
  T = TypeVar("T", bound=Callable)
36
37
 
@@ -18,10 +18,10 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import argparse
21
- import warnings
21
+ from collections.abc import Container
22
22
  from functools import cached_property
23
23
  from pathlib import Path
24
- from typing import TYPE_CHECKING, Container
24
+ from typing import TYPE_CHECKING, Any
25
25
 
26
26
  import packaging.version
27
27
  from connexion import FlaskApi
@@ -47,7 +47,7 @@ from airflow.cli.cli_config import (
47
47
  GroupCommand,
48
48
  )
49
49
  from airflow.configuration import conf
50
- from airflow.exceptions import AirflowConfigException, AirflowException, AirflowProviderDeprecationWarning
50
+ from airflow.exceptions import AirflowConfigException, AirflowException
51
51
  from airflow.models import DagModel
52
52
  from airflow.providers.fab.auth_manager.cli_commands.definition import (
53
53
  DB_COMMANDS,
@@ -82,7 +82,7 @@ from airflow.security.permissions import (
82
82
  RESOURCE_WEBSITE,
83
83
  RESOURCE_XCOM,
84
84
  )
85
- from airflow.utils.session import NEW_SESSION, provide_session
85
+ from airflow.utils.session import NEW_SESSION, create_session, provide_session
86
86
  from airflow.utils.yaml import safe_load
87
87
  from airflow.version import version
88
88
  from airflow.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED
@@ -131,13 +131,18 @@ _MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE = {
131
131
  }
132
132
 
133
133
 
134
- class FabAuthManager(BaseAuthManager):
134
+ class FabAuthManager(BaseAuthManager[User]):
135
135
  """
136
136
  Flask-AppBuilder auth manager.
137
137
 
138
138
  This auth manager is responsible for providing a backward compatible user management experience to users.
139
139
  """
140
140
 
141
+ def init(self) -> None:
142
+ """Run operations when Airflow is initializing."""
143
+ if self.appbuilder:
144
+ self._sync_appbuilder_roles()
145
+
141
146
  @staticmethod
142
147
  def get_cli_commands() -> list[CLICommand]:
143
148
  """Vends CLI commands to be included in Airflow CLI."""
@@ -199,9 +204,12 @@ class FabAuthManager(BaseAuthManager):
199
204
 
200
205
  return current_user
201
206
 
202
- def init(self) -> None:
203
- """Run operations when Airflow is initializing."""
204
- self._sync_appbuilder_roles()
207
+ def deserialize_user(self, token: dict[str, Any]) -> User:
208
+ with create_session() as session:
209
+ return session.get(User, token["id"])
210
+
211
+ def serialize_user(self, user: User) -> dict[str, Any]:
212
+ return {"id": user.id}
205
213
 
206
214
  def is_logged_in(self) -> bool:
207
215
  """Return whether the user is logged in."""
@@ -209,8 +217,10 @@ class FabAuthManager(BaseAuthManager):
209
217
  if Version(Version(version).base_version) < Version("3.0.0"):
210
218
  return not user.is_anonymous and user.is_active
211
219
  else:
212
- return self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None) or (
213
- not user.is_anonymous and user.is_active
220
+ return (
221
+ self.appbuilder
222
+ and self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None)
223
+ or (not user.is_anonymous and user.is_active)
214
224
  )
215
225
 
216
226
  def is_authorized_configuration(
@@ -283,16 +293,6 @@ class FabAuthManager(BaseAuthManager):
283
293
  ) -> bool:
284
294
  return self._is_authorized(method=method, resource_type=RESOURCE_ASSET, user=user)
285
295
 
286
- def is_authorized_dataset(
287
- self, *, method: ResourceMethod, details: AssetDetails | None = None, user: BaseUser | None = None
288
- ) -> 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)
295
-
296
296
  def is_authorized_pool(
297
297
  self, *, method: ResourceMethod, details: PoolDetails | None = None, user: BaseUser | None = None
298
298
  ) -> bool:
@@ -376,6 +376,9 @@ class FabAuthManager(BaseAuthManager):
376
376
  FabAirflowSecurityManagerOverride,
377
377
  )
378
378
 
379
+ if not self.appbuilder:
380
+ raise AirflowException("AppBuilder is not initialized.")
381
+
379
382
  sm_from_config = self.appbuilder.get_app.config.get("SECURITY_MANAGER_CLASS")
380
383
  if sm_from_config:
381
384
  if not issubclass(sm_from_config, FabAirflowSecurityManagerOverride):
@@ -547,6 +550,9 @@ class FabAuthManager(BaseAuthManager):
547
550
 
548
551
  :meta private:
549
552
  """
553
+ if not self.appbuilder:
554
+ raise AirflowException("AppBuilder is not initialized.")
555
+
550
556
  if "." in dag_id and hasattr(DagModel, "root_dag_id"):
551
557
  return self.appbuilder.get_session.scalar(
552
558
  select(DagModel.dag_id, DagModel.root_dag_id).where(DagModel.dag_id == dag_id).limit(1)
@@ -24,13 +24,12 @@ import logging
24
24
  import os
25
25
  import random
26
26
  import uuid
27
- import warnings
28
- from typing import TYPE_CHECKING, Any, Callable, Collection, Container, Iterable, Mapping, Sequence
27
+ from collections.abc import Collection, Iterable, Mapping, Sequence
28
+ from typing import TYPE_CHECKING, Any, Callable
29
29
 
30
30
  import jwt
31
31
  import packaging.version
32
32
  import re2
33
- from deprecated import deprecated
34
33
  from flask import flash, g, has_request_context, session
35
34
  from flask_appbuilder import const
36
35
  from flask_appbuilder.const import (
@@ -70,13 +69,13 @@ from itsdangerous import want_bytes
70
69
  from markupsafe import Markup
71
70
  from sqlalchemy import and_, func, inspect, literal, or_, select
72
71
  from sqlalchemy.exc import MultipleResultsFound
73
- from sqlalchemy.orm import Session, joinedload
72
+ from sqlalchemy.orm import joinedload
74
73
  from werkzeug.security import check_password_hash, generate_password_hash
75
74
 
76
75
  from airflow import __version__ as airflow_version
77
- from airflow.auth.managers.utils.fab import get_method_from_fab_action_map
76
+ from airflow.api_fastapi.app import get_auth_manager
78
77
  from airflow.configuration import conf
79
- from airflow.exceptions import AirflowException, AirflowProviderDeprecationWarning, RemovedInAirflow3Warning
78
+ from airflow.exceptions import AirflowException
80
79
  from airflow.models import DagBag, DagModel
81
80
  from airflow.providers.fab.auth_manager.models import (
82
81
  Action,
@@ -109,13 +108,10 @@ from airflow.providers.fab.auth_manager.views.user_edit import (
109
108
  )
110
109
  from airflow.providers.fab.auth_manager.views.user_stats import CustomUserStatsChartView
111
110
  from airflow.security import permissions
112
- from airflow.utils.session import NEW_SESSION, provide_session
113
- from airflow.www.extensions.init_auth_manager import get_auth_manager
114
111
  from airflow.www.security_manager import AirflowSecurityManagerV2
115
112
  from airflow.www.session import AirflowDatabaseSessionInterface
116
113
 
117
114
  if TYPE_CHECKING:
118
- from airflow.auth.managers.base_auth_manager import ResourceMethod
119
115
  from airflow.security.permissions import RESOURCE_ASSET
120
116
  else:
121
117
  from airflow.providers.common.compat.security.permissions import RESOURCE_ASSET
@@ -576,7 +572,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
576
572
  session_details = interface.serializer.loads(want_bytes(s.data))
577
573
  if session_details.get("_user_id") == user.id:
578
574
  session.delete(s)
579
- session.commit()
580
575
  else:
581
576
  self._cli_safe_flash(
582
577
  "Since you are using `securecookie` session backend mechanism, we cannot prevent "
@@ -774,14 +769,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
774
769
  """Get the admin role."""
775
770
  return self.appbuilder.get_app.config["AUTH_ROLE_ADMIN"]
776
771
 
777
- @property
778
- @deprecated(
779
- reason="The 'oauth_whitelists' property is deprecated. Please use 'oauth_allow_list' instead.",
780
- category=AirflowProviderDeprecationWarning,
781
- )
782
- def oauth_whitelists(self):
783
- return self.oauth_allow_list
784
-
785
772
  def create_builtin_roles(self):
786
773
  """Return FAB builtin roles."""
787
774
  return self.appbuilder.get_app.config.get("FAB_ROLES", {})
@@ -886,15 +873,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
886
873
  :meta private:
887
874
  """
888
875
  app = self.appbuilder.get_app
889
- if self.auth_type == AUTH_OID:
890
- from flask_openid import OpenID
891
-
892
- log.warning(
893
- "AUTH_OID is deprecated and will be removed in version 5. "
894
- "Migrate to other authentication methods."
895
- )
896
- self.oid = OpenID(app)
897
-
898
876
  if self.auth_type == AUTH_OAUTH:
899
877
  from authlib.integrations.flask_client import OAuth
900
878
 
@@ -967,70 +945,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
967
945
  log.exception(const.LOGMSG_ERR_SEC_CREATE_DB)
968
946
  exit(1)
969
947
 
970
- def get_readable_dags(self, user) -> Iterable[DagModel]:
971
- """Get the DAGs readable by authenticated user."""
972
- warnings.warn(
973
- "`get_readable_dags` has been deprecated. Please use `get_auth_manager().get_permitted_dag_ids` "
974
- "instead.",
975
- RemovedInAirflow3Warning,
976
- stacklevel=2,
977
- )
978
- with warnings.catch_warnings():
979
- warnings.simplefilter("ignore", RemovedInAirflow3Warning)
980
- return self.get_accessible_dags([permissions.ACTION_CAN_READ], user)
981
-
982
- def get_editable_dags(self, user) -> Iterable[DagModel]:
983
- """Get the DAGs editable by authenticated user."""
984
- warnings.warn(
985
- "`get_editable_dags` has been deprecated. Please use `get_auth_manager().get_permitted_dag_ids` "
986
- "instead.",
987
- RemovedInAirflow3Warning,
988
- stacklevel=2,
989
- )
990
- with warnings.catch_warnings():
991
- warnings.simplefilter("ignore", RemovedInAirflow3Warning)
992
- return self.get_accessible_dags([permissions.ACTION_CAN_EDIT], user)
993
-
994
- @provide_session
995
- def get_accessible_dags(
996
- self,
997
- user_actions: Container[str] | None,
998
- user,
999
- session: Session = NEW_SESSION,
1000
- ) -> Iterable[DagModel]:
1001
- warnings.warn(
1002
- "`get_accessible_dags` has been deprecated. Please use "
1003
- "`get_auth_manager().get_permitted_dag_ids` instead.",
1004
- RemovedInAirflow3Warning,
1005
- stacklevel=3,
1006
- )
1007
-
1008
- dag_ids = self.get_accessible_dag_ids(user, user_actions, session)
1009
- return session.scalars(select(DagModel).where(DagModel.dag_id.in_(dag_ids)))
1010
-
1011
- @provide_session
1012
- def get_accessible_dag_ids(
1013
- self,
1014
- user,
1015
- user_actions: Container[str] | None = None,
1016
- session: Session = NEW_SESSION,
1017
- ) -> set[str]:
1018
- warnings.warn(
1019
- "`get_accessible_dag_ids` has been deprecated. Please use "
1020
- "`get_auth_manager().get_permitted_dag_ids` instead.",
1021
- RemovedInAirflow3Warning,
1022
- stacklevel=3,
1023
- )
1024
- if not user_actions:
1025
- user_actions = [permissions.ACTION_CAN_EDIT, permissions.ACTION_CAN_READ]
1026
- method_from_fab_action_map = get_method_from_fab_action_map()
1027
- user_methods: Container[ResourceMethod] = [
1028
- method_from_fab_action_map[action]
1029
- for action in method_from_fab_action_map
1030
- if action in user_actions
1031
- ]
1032
- return get_auth_manager().get_permitted_dag_ids(user=user, methods=user_methods, session=session)
1033
-
1034
948
  @staticmethod
1035
949
  def get_readable_dag_ids(user=None) -> set[str]:
1036
950
  """Get the DAG IDs readable by authenticated user."""
@@ -1089,17 +1003,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1089
1003
  if dag.access_control is not None:
1090
1004
  self.sync_perm_for_dag(root_dag_id, dag.access_control)
1091
1005
 
1092
- def prefixed_dag_id(self, dag_id: str) -> str:
1093
- """Return the permission name for a DAG id."""
1094
- warnings.warn(
1095
- "`prefixed_dag_id` has been deprecated. "
1096
- "Please use `airflow.security.permissions.resource_name` instead.",
1097
- RemovedInAirflow3Warning,
1098
- stacklevel=2,
1099
- )
1100
- root_dag_id = self._get_root_dag_id(dag_id)
1101
- return self._resource_name(root_dag_id, permissions.RESOURCE_DAG)
1102
-
1103
1006
  def is_dag_resource(self, resource_name: str) -> bool:
1104
1007
  """Determine if a resource belongs to a DAG or all DAGs."""
1105
1008
  if resource_name == permissions.RESOURCE_DAG:
@@ -1433,20 +1336,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1433
1336
  def perms_include_action(self, perms, action_name):
1434
1337
  return any(perm.action and perm.action.name == action_name for perm in perms)
1435
1338
 
1436
- def init_role(self, role_name, perms) -> None:
1437
- """
1438
- Initialize the role with actions and related resources.
1439
-
1440
- :param role_name:
1441
- :param perms:
1442
- """
1443
- warnings.warn(
1444
- "`init_role` has been deprecated. Please use `bulk_sync_roles` instead.",
1445
- RemovedInAirflow3Warning,
1446
- stacklevel=2,
1447
- )
1448
- self.bulk_sync_roles([{"role": role_name, "perms": perms}])
1449
-
1450
1339
  def bulk_sync_roles(self, roles: Iterable[dict[str, Any]]) -> None:
1451
1340
  """Sync the provided roles and permissions."""
1452
1341
  existing_roles = self._get_all_roles_with_permissions()
@@ -28,9 +28,9 @@ def get_provider_info():
28
28
  "name": "Fab",
29
29
  "description": "`Flask App Builder <https://flask-appbuilder.readthedocs.io/>`__\n",
30
30
  "state": "ready",
31
- "source-date-epoch": 1735219867,
31
+ "source-date-epoch": 1731570160,
32
32
  "versions": [
33
- "1.5.2",
33
+ "2.0.0",
34
34
  "1.5.1",
35
35
  "1.5.0",
36
36
  "1.4.1",
@@ -48,7 +48,7 @@ def get_provider_info():
48
48
  "1.0.0",
49
49
  ],
50
50
  "dependencies": [
51
- "apache-airflow>=2.9.0",
51
+ "apache-airflow>=3.0.0.dev0",
52
52
  "apache-airflow-providers-common-compat>=1.2.1",
53
53
  "flask>=2.2,<2.3",
54
54
  "flask-appbuilder==4.5.2",
@@ -0,0 +1,17 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
@@ -0,0 +1,87 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+ from __future__ import annotations
19
+
20
+ from os.path import isabs
21
+
22
+ from flask import Flask
23
+ from flask_appbuilder import SQLA
24
+ from flask_wtf.csrf import CSRFProtect
25
+ from sqlalchemy.engine.url import make_url
26
+
27
+ from airflow import settings
28
+ from airflow.configuration import conf
29
+ from airflow.exceptions import AirflowConfigException
30
+ from airflow.logging_config import configure_logging
31
+ from airflow.providers.fab.www.extensions.init_appbuilder import init_appbuilder
32
+ from airflow.providers.fab.www.extensions.init_jinja_globals import init_jinja_globals
33
+ from airflow.providers.fab.www.extensions.init_manifest_files import configure_manifest_files
34
+ from airflow.providers.fab.www.extensions.init_security import init_xframe_protection
35
+ from airflow.providers.fab.www.extensions.init_views import init_error_handlers, init_plugins
36
+
37
+ app: Flask | None = None
38
+
39
+ # Initializes at the module level, so plugins can access it.
40
+ # See: /docs/plugins.rst
41
+ csrf = CSRFProtect()
42
+
43
+
44
+ def create_app(config=None, testing=False):
45
+ """Create a new instance of Airflow WWW app."""
46
+ flask_app = Flask(__name__)
47
+ flask_app.secret_key = conf.get("webserver", "SECRET_KEY")
48
+ flask_app.config["SQLALCHEMY_DATABASE_URI"] = conf.get("database", "SQL_ALCHEMY_CONN")
49
+
50
+ url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
51
+ if url.drivername == "sqlite" and url.database and not isabs(url.database):
52
+ raise AirflowConfigException(
53
+ f'Cannot use relative path: `{conf.get("database", "SQL_ALCHEMY_CONN")}` to connect to sqlite. '
54
+ "Please use absolute path such as `sqlite:////tmp/airflow.db`."
55
+ )
56
+
57
+ if "SQLALCHEMY_ENGINE_OPTIONS" not in flask_app.config:
58
+ flask_app.config["SQLALCHEMY_ENGINE_OPTIONS"] = settings.prepare_engine_args()
59
+
60
+ db = SQLA()
61
+ db.session = settings.Session
62
+ db.init_app(flask_app)
63
+
64
+ configure_logging()
65
+ configure_manifest_files(flask_app)
66
+
67
+ with flask_app.app_context():
68
+ init_appbuilder(flask_app)
69
+ init_plugins(flask_app)
70
+ init_error_handlers(flask_app)
71
+ init_jinja_globals(flask_app)
72
+ init_xframe_protection(flask_app)
73
+ return flask_app
74
+
75
+
76
+ def cached_app(config=None, testing=False):
77
+ """Return cached instance of Airflow WWW app."""
78
+ global app
79
+ if not app:
80
+ app = create_app(config=config, testing=testing)
81
+ return app
82
+
83
+
84
+ def purge_cached_app():
85
+ """Remove the cached version of the app in global state."""
86
+ global app
87
+ app = None
@@ -0,0 +1,16 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.