apache-airflow-providers-fab 1.5.3__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.3.dist-info → apache_airflow_providers_fab-2.0.0b1.dist-info}/METADATA +17 -35
  96. apache_airflow_providers_fab-2.0.0b1.dist-info/RECORD +122 -0
  97. {apache_airflow_providers_fab-1.5.3.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.3.dist-info/RECORD +0 -51
  100. /airflow/providers/fab/{auth_manager/decorators → www}/__init__.py +0 -0
  101. {apache_airflow_providers_fab-1.5.3.dist-info → apache_airflow_providers_fab-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -24,13 +24,10 @@ 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
28
+ from typing import TYPE_CHECKING, Any
29
29
 
30
30
  import jwt
31
- import packaging.version
32
- import re2
33
- from deprecated import deprecated
34
31
  from flask import flash, g, has_request_context, session
35
32
  from flask_appbuilder import const
36
33
  from flask_appbuilder.const import (
@@ -59,25 +56,21 @@ from flask_appbuilder.security.views import (
59
56
  AuthOAuthView,
60
57
  AuthOIDView,
61
58
  AuthRemoteUserView,
62
- AuthView,
63
59
  RegisterUserModelView,
64
60
  )
65
- from flask_appbuilder.views import expose
66
61
  from flask_babel import lazy_gettext
67
- from flask_jwt_extended import JWTManager, current_user as current_user_jwt
62
+ from flask_jwt_extended import JWTManager
68
63
  from flask_login import LoginManager
69
64
  from itsdangerous import want_bytes
70
65
  from markupsafe import Markup
71
- from sqlalchemy import and_, func, inspect, literal, or_, select
66
+ from sqlalchemy import func, inspect, or_, select
72
67
  from sqlalchemy.exc import MultipleResultsFound
73
- from sqlalchemy.orm import Session, joinedload
68
+ from sqlalchemy.orm import joinedload
74
69
  from werkzeug.security import check_password_hash, generate_password_hash
75
70
 
76
- from airflow import __version__ as airflow_version
77
- from airflow.auth.managers.utils.fab import get_method_from_fab_action_map
78
71
  from airflow.configuration import conf
79
- from airflow.exceptions import AirflowException, AirflowProviderDeprecationWarning, RemovedInAirflow3Warning
80
- from airflow.models import DagBag, DagModel
72
+ from airflow.exceptions import AirflowException
73
+ from airflow.models import DagBag
81
74
  from airflow.providers.fab.auth_manager.models import (
82
75
  Action,
83
76
  Permission,
@@ -85,7 +78,6 @@ from airflow.providers.fab.auth_manager.models import (
85
78
  Resource,
86
79
  Role,
87
80
  User,
88
- assoc_permission_role,
89
81
  )
90
82
  from airflow.providers.fab.auth_manager.models.anonymous_user import AnonymousUser
91
83
  from airflow.providers.fab.auth_manager.security_manager.constants import EXISTING_ROLES
@@ -108,17 +100,24 @@ from airflow.providers.fab.auth_manager.views.user_edit import (
108
100
  CustomUserInfoEditView,
109
101
  )
110
102
  from airflow.providers.fab.auth_manager.views.user_stats import CustomUserStatsChartView
111
- 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
- from airflow.www.security_manager import AirflowSecurityManagerV2
115
- from airflow.www.session import AirflowDatabaseSessionInterface
103
+ from airflow.providers.fab.www.security import permissions
104
+ from airflow.providers.fab.www.security_manager import AirflowSecurityManagerV2
105
+ from airflow.providers.fab.www.session import (
106
+ AirflowDatabaseSessionInterface,
107
+ AirflowDatabaseSessionInterface as FabAirflowDatabaseSessionInterface,
108
+ )
109
+ from airflow.security.permissions import RESOURCE_BACKFILL
116
110
 
117
111
  if TYPE_CHECKING:
118
- from airflow.auth.managers.base_auth_manager import ResourceMethod
119
- from airflow.security.permissions import RESOURCE_ASSET
112
+ from airflow.providers.fab.www.security.permissions import (
113
+ RESOURCE_ASSET,
114
+ RESOURCE_ASSET_ALIAS,
115
+ )
120
116
  else:
121
- from airflow.providers.common.compat.security.permissions import RESOURCE_ASSET
117
+ from airflow.providers.common.compat.security.permissions import (
118
+ RESOURCE_ASSET,
119
+ RESOURCE_ASSET_ALIAS,
120
+ )
122
121
 
123
122
  log = logging.getLogger(__name__)
124
123
 
@@ -131,29 +130,6 @@ log = logging.getLogger(__name__)
131
130
  MAX_NUM_DATABASE_USER_SESSIONS = 50000
132
131
 
133
132
 
134
- # The following logic patches the logout method within AuthView, so it supports POST method
135
- # to make CSRF protection effective. It is backward-compatible with Airflow versions <= 2.9.2 as it still
136
- # allows utilizing the GET method for them.
137
- # You could remove the patch and configure it when it is supported
138
- # natively by Flask-AppBuilder (https://github.com/dpgaspar/Flask-AppBuilder/issues/2248)
139
- if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
140
- "2.10.0"
141
- ):
142
- _methods = ["GET", "POST"]
143
- else:
144
- _methods = ["POST"]
145
-
146
-
147
- class _ModifiedAuthView(AuthView):
148
- @expose("/logout/", methods=_methods)
149
- def logout(self):
150
- return super().logout()
151
-
152
-
153
- for auth_view in [AuthDBView, AuthLDAPView, AuthOAuthView, AuthOIDView, AuthRemoteUserView]:
154
- auth_view.__bases__ = (_ModifiedAuthView,)
155
-
156
-
157
133
  class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
158
134
  """
159
135
  This security manager overrides the default AirflowSecurityManager security manager.
@@ -214,8 +190,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
214
190
 
215
191
  jwt_manager = None
216
192
  """ Flask-JWT-Extended """
217
- oid = None
218
- """ Flask-OpenID OpenID """
219
193
  oauth = None
220
194
  oauth_remotes: dict[str, Any]
221
195
  """ Initialized (remote_app) providers dict {'provider_name', OBJ } """
@@ -238,11 +212,14 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
238
212
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_DEPENDENCIES),
239
213
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_CODE),
240
214
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_RUN),
215
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_VERSION),
216
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_WARNING),
241
217
  (permissions.ACTION_CAN_READ, RESOURCE_ASSET),
218
+ (permissions.ACTION_CAN_READ, RESOURCE_ASSET_ALIAS),
219
+ (permissions.ACTION_CAN_READ, RESOURCE_BACKFILL),
242
220
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_CLUSTER_ACTIVITY),
243
221
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_POOL),
244
222
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_IMPORT_ERROR),
245
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_WARNING),
246
223
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_JOB),
247
224
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PASSWORD),
248
225
  (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_MY_PASSWORD),
@@ -306,8 +283,11 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
306
283
  (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_VARIABLE),
307
284
  (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_VARIABLE),
308
285
  (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_XCOM),
309
- (permissions.ACTION_CAN_DELETE, RESOURCE_ASSET),
310
286
  (permissions.ACTION_CAN_CREATE, RESOURCE_ASSET),
287
+ (permissions.ACTION_CAN_DELETE, RESOURCE_ASSET),
288
+ (permissions.ACTION_CAN_CREATE, RESOURCE_BACKFILL),
289
+ (permissions.ACTION_CAN_EDIT, RESOURCE_BACKFILL),
290
+ (permissions.ACTION_CAN_DELETE, RESOURCE_BACKFILL),
311
291
  ]
312
292
  # [END security_op_perms]
313
293
 
@@ -554,7 +534,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
554
534
  return self.update_user(user)
555
535
 
556
536
  def reset_user_sessions(self, user: User) -> None:
557
- if isinstance(self.appbuilder.get_app.session_interface, AirflowDatabaseSessionInterface):
537
+ if isinstance(
538
+ self.appbuilder.get_app.session_interface, AirflowDatabaseSessionInterface
539
+ ) or isinstance(
540
+ self.appbuilder.get_app.session_interface,
541
+ FabAirflowDatabaseSessionInterface,
542
+ ):
558
543
  interface = self.appbuilder.get_app.session_interface
559
544
  session = interface.db.session
560
545
  user_session_model = interface.sql_session_model
@@ -721,39 +706,11 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
721
706
  """The mapping of auth roles."""
722
707
  return self.appbuilder.get_app.config["AUTH_ROLES_MAPPING"]
723
708
 
724
- @property
725
- def auth_user_registration_role_jmespath(self) -> str:
726
- """The JMESPATH role to use for user registration."""
727
- return self.appbuilder.get_app.config["AUTH_USER_REGISTRATION_ROLE_JMESPATH"]
728
-
729
- @property
730
- def auth_remote_user_env_var(self) -> str:
731
- return self.appbuilder.get_app.config["AUTH_REMOTE_USER_ENV_VAR"]
732
-
733
- @property
734
- def api_login_allow_multiple_providers(self):
735
- return self.appbuilder.get_app.config["AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS"]
736
-
737
709
  @property
738
710
  def auth_username_ci(self):
739
711
  """Get the auth username for CI."""
740
712
  return self.appbuilder.get_app.config.get("AUTH_USERNAME_CI", True)
741
713
 
742
- @property
743
- def auth_ldap_bind_first(self):
744
- """LDAP bind first."""
745
- return self.appbuilder.get_app.config["AUTH_LDAP_BIND_FIRST"]
746
-
747
- @property
748
- def openid_providers(self):
749
- """Openid providers."""
750
- return self.appbuilder.get_app.config["OPENID_PROVIDERS"]
751
-
752
- @property
753
- def auth_type_provider_name(self):
754
- provider_to_auth_type = {AUTH_DB: "db", AUTH_LDAP: "ldap"}
755
- return provider_to_auth_type.get(self.auth_type)
756
-
757
714
  @property
758
715
  def auth_user_registration(self):
759
716
  """Will user self registration be allowed."""
@@ -774,14 +731,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
774
731
  """Get the admin role."""
775
732
  return self.appbuilder.get_app.config["AUTH_ROLE_ADMIN"]
776
733
 
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
734
  def create_builtin_roles(self):
786
735
  """Return FAB builtin roles."""
787
736
  return self.appbuilder.get_app.config.get("FAB_ROLES", {})
@@ -904,15 +853,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
904
853
  :meta private:
905
854
  """
906
855
  app = self.appbuilder.get_app
907
- if self.auth_type == AUTH_OID:
908
- from flask_openid import OpenID
909
-
910
- log.warning(
911
- "AUTH_OID is deprecated and will be removed in version 5. "
912
- "Migrate to other authentication methods."
913
- )
914
- self.oid = OpenID(app)
915
-
916
856
  if self.auth_type == AUTH_OAUTH:
917
857
  from authlib.integrations.flask_client import OAuth
918
858
 
@@ -985,91 +925,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
985
925
  log.exception(const.LOGMSG_ERR_SEC_CREATE_DB)
986
926
  exit(1)
987
927
 
988
- def get_readable_dags(self, user) -> Iterable[DagModel]:
989
- """Get the DAGs readable by authenticated user."""
990
- warnings.warn(
991
- "`get_readable_dags` has been deprecated. Please use `get_auth_manager().get_permitted_dag_ids` "
992
- "instead.",
993
- RemovedInAirflow3Warning,
994
- stacklevel=2,
995
- )
996
- with warnings.catch_warnings():
997
- warnings.simplefilter("ignore", RemovedInAirflow3Warning)
998
- return self.get_accessible_dags([permissions.ACTION_CAN_READ], user)
999
-
1000
- def get_editable_dags(self, user) -> Iterable[DagModel]:
1001
- """Get the DAGs editable by authenticated user."""
1002
- warnings.warn(
1003
- "`get_editable_dags` has been deprecated. Please use `get_auth_manager().get_permitted_dag_ids` "
1004
- "instead.",
1005
- RemovedInAirflow3Warning,
1006
- stacklevel=2,
1007
- )
1008
- with warnings.catch_warnings():
1009
- warnings.simplefilter("ignore", RemovedInAirflow3Warning)
1010
- return self.get_accessible_dags([permissions.ACTION_CAN_EDIT], user)
1011
-
1012
- @provide_session
1013
- def get_accessible_dags(
1014
- self,
1015
- user_actions: Container[str] | None,
1016
- user,
1017
- session: Session = NEW_SESSION,
1018
- ) -> Iterable[DagModel]:
1019
- warnings.warn(
1020
- "`get_accessible_dags` has been deprecated. Please use "
1021
- "`get_auth_manager().get_permitted_dag_ids` instead.",
1022
- RemovedInAirflow3Warning,
1023
- stacklevel=3,
1024
- )
1025
-
1026
- dag_ids = self.get_accessible_dag_ids(user, user_actions, session)
1027
- return session.scalars(select(DagModel).where(DagModel.dag_id.in_(dag_ids)))
1028
-
1029
- @provide_session
1030
- def get_accessible_dag_ids(
1031
- self,
1032
- user,
1033
- user_actions: Container[str] | None = None,
1034
- session: Session = NEW_SESSION,
1035
- ) -> set[str]:
1036
- warnings.warn(
1037
- "`get_accessible_dag_ids` has been deprecated. Please use "
1038
- "`get_auth_manager().get_permitted_dag_ids` instead.",
1039
- RemovedInAirflow3Warning,
1040
- stacklevel=3,
1041
- )
1042
- if not user_actions:
1043
- user_actions = [permissions.ACTION_CAN_EDIT, permissions.ACTION_CAN_READ]
1044
- method_from_fab_action_map = get_method_from_fab_action_map()
1045
- user_methods: Container[ResourceMethod] = [
1046
- method_from_fab_action_map[action]
1047
- for action in method_from_fab_action_map
1048
- if action in user_actions
1049
- ]
1050
- return get_auth_manager().get_permitted_dag_ids(user=user, methods=user_methods, session=session)
1051
-
1052
- @staticmethod
1053
- def get_readable_dag_ids(user=None) -> set[str]:
1054
- """Get the DAG IDs readable by authenticated user."""
1055
- return get_auth_manager().get_permitted_dag_ids(methods=["GET"], user=user)
1056
-
1057
- @staticmethod
1058
- def get_editable_dag_ids(user=None) -> set[str]:
1059
- """Get the DAG IDs editable by authenticated user."""
1060
- return get_auth_manager().get_permitted_dag_ids(methods=["PUT"], user=user)
1061
-
1062
- def can_access_some_dags(self, action: str, dag_id: str | None = None) -> bool:
1063
- """Check if user has read or write access to some dags."""
1064
- if dag_id and dag_id != "~":
1065
- root_dag_id = self._get_root_dag_id(dag_id)
1066
- return self.has_access(action, self._resource_name(root_dag_id, permissions.RESOURCE_DAG))
1067
-
1068
- user = g.user
1069
- if action == permissions.ACTION_CAN_READ:
1070
- return any(self.get_readable_dag_ids(user))
1071
- return any(self.get_editable_dag_ids(user))
1072
-
1073
928
  def get_all_permissions(self) -> set[tuple[str, str]]:
1074
929
  """Return all permissions as a set of tuples with the action and resource names."""
1075
930
  return set(
@@ -1096,8 +951,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1096
951
  dags = dagbag.dags.values()
1097
952
 
1098
953
  for dag in dags:
1099
- # TODO: Remove this when the minimum version of Airflow is bumped to 3.0
1100
- root_dag_id = (getattr(dag, "parent_dag", None) or dag).dag_id
954
+ root_dag_id = dag.dag_id
1101
955
  for resource_name, resource_values in self.RESOURCE_DETAILS_MAP.items():
1102
956
  dag_resource_name = self._resource_name(root_dag_id, resource_name)
1103
957
  for action_name in resource_values["actions"]:
@@ -1107,23 +961,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1107
961
  if dag.access_control is not None:
1108
962
  self.sync_perm_for_dag(root_dag_id, dag.access_control)
1109
963
 
1110
- def prefixed_dag_id(self, dag_id: str) -> str:
1111
- """Return the permission name for a DAG id."""
1112
- warnings.warn(
1113
- "`prefixed_dag_id` has been deprecated. "
1114
- "Please use `airflow.security.permissions.resource_name` instead.",
1115
- RemovedInAirflow3Warning,
1116
- stacklevel=2,
1117
- )
1118
- root_dag_id = self._get_root_dag_id(dag_id)
1119
- return self._resource_name(root_dag_id, permissions.RESOURCE_DAG)
1120
-
1121
- def is_dag_resource(self, resource_name: str) -> bool:
1122
- """Determine if a resource belongs to a DAG or all DAGs."""
1123
- if resource_name == permissions.RESOURCE_DAG:
1124
- return True
1125
- return resource_name.startswith(permissions.RESOURCE_DAG_PREFIX)
1126
-
1127
964
  def sync_perm_for_dag(
1128
965
  self,
1129
966
  dag_id: str,
@@ -1183,7 +1020,11 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1183
1020
  def _get_or_create_dag_permission(action_name: str, dag_resource_name: str) -> Permission | None:
1184
1021
  perm = self.get_permission(action_name, dag_resource_name)
1185
1022
  if not perm:
1186
- self.log.info("Creating new action '%s' on resource '%s'", action_name, dag_resource_name)
1023
+ self.log.info(
1024
+ "Creating new action '%s' on resource '%s'",
1025
+ action_name,
1026
+ dag_resource_name,
1027
+ )
1187
1028
  perm = self.create_permission(action_name, dag_resource_name)
1188
1029
  return perm
1189
1030
 
@@ -1268,6 +1109,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1268
1109
  action = self.create_permission(action_name, resource_name)
1269
1110
  if self.auth_role_admin not in self.builtin_roles:
1270
1111
  admin_role = self.find_role(self.auth_role_admin)
1112
+ if not admin_role:
1113
+ admin_role = self.add_role(self.auth_role_admin)
1271
1114
  self.add_permission_to_role(admin_role, action)
1272
1115
  else:
1273
1116
  # Permissions on this view exist but....
@@ -1310,31 +1153,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1310
1153
  role_admin = self.find_role(self.auth_role_admin)
1311
1154
  self.add_permission_to_role(role_admin, perm)
1312
1155
 
1313
- def security_cleanup(self, baseviews, menus):
1314
- """
1315
- Cleanup all unused permissions from the database.
1316
-
1317
- :param baseviews: A list of BaseViews class
1318
- :param menus: Menu class
1319
- """
1320
- resources = self.get_all_resources()
1321
- roles = self.get_all_roles()
1322
- for resource in resources:
1323
- found = False
1324
- for baseview in baseviews:
1325
- if resource.name == baseview.class_permission_name:
1326
- found = True
1327
- break
1328
- if menus.find(resource.name):
1329
- found = True
1330
- if not found:
1331
- permissions = self.get_resource_permissions(resource)
1332
- for permission in permissions:
1333
- for role in roles:
1334
- self.remove_permission_from_role(role, permission)
1335
- self.delete_permission(permission.action.name, resource.name)
1336
- self.delete_resource(resource.name)
1337
-
1338
1156
  def sync_roles(self) -> None:
1339
1157
  """
1340
1158
  Initialize default and custom roles with related permissions.
@@ -1414,57 +1232,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1414
1232
  if deleted_count:
1415
1233
  self.log.info("Deleted %s faulty permissions", deleted_count)
1416
1234
 
1417
- def permission_exists_in_one_or_more_roles(
1418
- self, resource_name: str, action_name: str, role_ids: list[int]
1419
- ) -> bool:
1420
- """
1421
- Efficiently check if a certain permission exists on a list of role ids; used by `has_access`.
1422
-
1423
- :param resource_name: The view's name to check if exists on one of the roles
1424
- :param action_name: The permission name to check if exists
1425
- :param role_ids: a list of Role ids
1426
- :return: Boolean
1427
- """
1428
- q = (
1429
- self.appbuilder.get_session.query(self.permission_model)
1430
- .join(
1431
- assoc_permission_role,
1432
- and_(self.permission_model.id == assoc_permission_role.c.permission_view_id),
1433
- )
1434
- .join(self.role_model)
1435
- .join(self.action_model)
1436
- .join(self.resource_model)
1437
- .filter(
1438
- self.resource_model.name == resource_name,
1439
- self.action_model.name == action_name,
1440
- self.role_model.id.in_(role_ids),
1441
- )
1442
- .exists()
1443
- )
1444
- # Special case for MSSQL/Oracle (works on PG and MySQL > 8)
1445
- # Note: We need to keep MSSQL compatibility as long as this provider package
1446
- # might still be updated by Airflow prior 2.9.0 users with MSSQL
1447
- if self.appbuilder.get_session.bind.dialect.name in ("mssql", "oracle"):
1448
- return self.appbuilder.get_session.query(literal(True)).filter(q).scalar()
1449
- return self.appbuilder.get_session.query(q).scalar()
1450
-
1451
1235
  def perms_include_action(self, perms, action_name):
1452
1236
  return any(perm.action and perm.action.name == action_name for perm in perms)
1453
1237
 
1454
- def init_role(self, role_name, perms) -> None:
1455
- """
1456
- Initialize the role with actions and related resources.
1457
-
1458
- :param role_name:
1459
- :param perms:
1460
- """
1461
- warnings.warn(
1462
- "`init_role` has been deprecated. Please use `bulk_sync_roles` instead.",
1463
- RemovedInAirflow3Warning,
1464
- stacklevel=2,
1465
- )
1466
- self.bulk_sync_roles([{"role": role_name, "perms": perms}])
1467
-
1468
1238
  def bulk_sync_roles(self, roles: Iterable[dict[str, Any]]) -> None:
1469
1239
  """Sync the provided roles and permissions."""
1470
1240
  existing_roles = self._get_all_roles_with_permissions()
@@ -1483,15 +1253,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1483
1253
  if perm not in role.permissions:
1484
1254
  self.add_permission_to_role(role, perm)
1485
1255
 
1486
- def sync_resource_permissions(self, perms: Iterable[tuple[str, str]] | None = None) -> None:
1487
- """Populate resource-based permissions."""
1488
- if not perms:
1489
- return
1490
-
1491
- for action_name, resource_name in perms:
1492
- self.create_resource(resource_name)
1493
- self.create_permission(action_name, resource_name)
1494
-
1495
1256
  """
1496
1257
  -----------
1497
1258
  Role entity
@@ -1575,7 +1336,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1575
1336
  if fab_role:
1576
1337
  _roles.add(fab_role)
1577
1338
  else:
1578
- log.warning("Can't find role specified in AUTH_ROLES_MAPPING: %s", fab_role_name)
1339
+ log.warning(
1340
+ "Can't find role specified in AUTH_ROLES_MAPPING: %s",
1341
+ fab_role_name,
1342
+ )
1579
1343
  return _roles
1580
1344
 
1581
1345
  def get_public_role(self):
@@ -1683,13 +1447,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1683
1447
  log.error("Multiple results found for user with email %s", email)
1684
1448
  return None
1685
1449
 
1686
- def find_register_user(self, registration_hash):
1687
- return self.get_session.scalar(
1688
- select(self.registeruser_mode)
1689
- .where(self.registeruser_model.registration_hash == registration_hash)
1690
- .limit(1)
1691
- )
1692
-
1693
1450
  def update_user(self, user: User) -> bool:
1694
1451
  try:
1695
1452
  self.get_session.merge(user)
@@ -1839,38 +1596,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1839
1596
  self.get_session.rollback()
1840
1597
  return resource
1841
1598
 
1842
- def get_all_resources(self) -> list[Resource]:
1843
- """Get all existing resource records."""
1844
- return self.get_session.query(self.resource_model).all()
1845
-
1846
- def delete_resource(self, name: str) -> bool:
1847
- """
1848
- Delete a Resource from the backend.
1849
-
1850
- :param name:
1851
- name of the resource
1852
- """
1853
- resource = self.get_resource(name)
1854
- if not resource:
1855
- log.warning(const.LOGMSG_WAR_SEC_DEL_VIEWMENU, name)
1856
- return False
1857
- try:
1858
- perms = (
1859
- self.get_session.query(self.permission_model)
1860
- .filter(self.permission_model.resource == resource)
1861
- .all()
1862
- )
1863
- if perms:
1864
- log.warning(const.LOGMSG_WAR_SEC_DEL_VIEWMENU_PVM, resource, perms)
1865
- return False
1866
- self.get_session.delete(resource)
1867
- self.get_session.commit()
1868
- return True
1869
- except Exception as e:
1870
- log.error(const.LOGMSG_ERR_SEC_DEL_PERMISSION, e)
1871
- self.get_session.rollback()
1872
- return False
1873
-
1874
1599
  """
1875
1600
  ---------------
1876
1601
  Permission entity
@@ -2000,13 +1725,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2000
1725
  log.error(const.LOGMSG_ERR_SEC_DEL_PERMROLE, e)
2001
1726
  self.get_session.rollback()
2002
1727
 
2003
- def get_oid_identity_url(self, provider_name: str) -> str | None:
2004
- """Return the OIDC identity provider URL."""
2005
- for provider in self.openid_providers:
2006
- if provider.get("name") == provider_name:
2007
- return provider.get("url")
2008
- return None
2009
-
2010
1728
  @staticmethod
2011
1729
  def get_user_roles(user=None):
2012
1730
  """
@@ -2209,6 +1927,20 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2209
1927
  log.error(e)
2210
1928
  return None
2211
1929
 
1930
+ def check_password(self, username, password) -> bool:
1931
+ """
1932
+ Check if the password is correct for the username.
1933
+
1934
+ :param username: the username
1935
+ :param password: the password
1936
+ """
1937
+ user = self.find_user(username=username)
1938
+ if user is None:
1939
+ user = self.find_user(email=username)
1940
+ if user is None:
1941
+ return False
1942
+ return check_password_hash(user.password, password)
1943
+
2212
1944
  def auth_user_db(self, username, password):
2213
1945
  """
2214
1946
  Authenticate user, auth db style.
@@ -2240,32 +1972,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2240
1972
  log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
2241
1973
  return None
2242
1974
 
2243
- def oauth_user_info_getter(
2244
- self,
2245
- func: Callable[[AirflowSecurityManagerV2, str, dict[str, Any] | None], dict[str, Any]],
2246
- ):
2247
- """
2248
- Get OAuth user info for all the providers.
2249
-
2250
- Receives provider and response return a dict with the information returned from the provider.
2251
- The returned user info dict should have its keys with the same name as the User Model.
2252
-
2253
- Use it like this an example for GitHub ::
2254
-
2255
- @appbuilder.sm.oauth_user_info_getter
2256
- def my_oauth_user_info(sm, provider, response=None):
2257
- if provider == "github":
2258
- me = sm.oauth_remotes[provider].get("user")
2259
- return {"username": me.data.get("login")}
2260
- return {}
2261
- """
2262
-
2263
- def wraps(provider: str, response: dict[str, Any] | None = None) -> dict[str, Any]:
2264
- return func(self, provider, response)
2265
-
2266
- self.oauth_user_info = wraps
2267
- return wraps
2268
-
2269
1975
  def get_oauth_user_info(self, provider: str, resp: dict[str, Any]) -> dict[str, Any]:
2270
1976
  """
2271
1977
  There are different OAuth APIs with different ways to retrieve user info.
@@ -2387,183 +2093,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2387
2093
  log.debug("Token Get: %s", token)
2388
2094
  return token
2389
2095
 
2390
- def check_authorization(
2391
- self,
2392
- perms: Sequence[tuple[str, str]] | None = None,
2393
- dag_id: str | None = None,
2394
- ) -> bool:
2395
- """Check the logged-in user has the specified permissions."""
2396
- if not perms:
2397
- return True
2398
-
2399
- for perm in perms:
2400
- if perm in (
2401
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG),
2402
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG),
2403
- (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_DAG),
2404
- ):
2405
- can_access_all_dags = self.has_access(*perm)
2406
- if not can_access_all_dags:
2407
- action = perm[0]
2408
- if not self.can_access_some_dags(action, dag_id):
2409
- return False
2410
- elif not self.has_access(*perm):
2411
- return False
2412
-
2413
- return True
2414
-
2415
- def set_oauth_session(self, provider, oauth_response):
2416
- """Set the current session with OAuth user secrets."""
2417
- # Get this provider key names for token_key and token_secret
2418
- token_key = self.get_oauth_token_key_name(provider)
2419
- token_secret = self.get_oauth_token_secret_name(provider)
2420
- # Save users token on encrypted session cookie
2421
- session["oauth"] = (
2422
- oauth_response[token_key],
2423
- oauth_response.get(token_secret, ""),
2424
- )
2425
- session["oauth_provider"] = provider
2426
-
2427
- def get_oauth_token_key_name(self, provider):
2428
- """
2429
- Return the token_key name for the oauth provider.
2430
-
2431
- If none is configured defaults to oauth_token
2432
- this is configured using OAUTH_PROVIDERS and token_key key.
2433
- """
2434
- for _provider in self.oauth_providers:
2435
- if _provider["name"] == provider:
2436
- return _provider.get("token_key", "oauth_token")
2437
-
2438
- def get_oauth_token_secret_name(self, provider):
2439
- """
2440
- Get the ``token_secret`` name for the oauth provider.
2441
-
2442
- If none is configured, defaults to ``oauth_secret``. This is configured
2443
- using ``OAUTH_PROVIDERS`` and ``token_secret``.
2444
- """
2445
- for _provider in self.oauth_providers:
2446
- if _provider["name"] == provider:
2447
- return _provider.get("token_secret", "oauth_token_secret")
2448
-
2449
- def auth_user_oauth(self, userinfo):
2450
- """
2451
- Authenticate user with OAuth.
2452
-
2453
- :userinfo: dict with user information
2454
- (keys are the same as User model columns)
2455
- """
2456
- # extract the username from `userinfo`
2457
- if "username" in userinfo:
2458
- username = userinfo["username"]
2459
- elif "email" in userinfo:
2460
- username = userinfo["email"]
2461
- else:
2462
- log.error("OAUTH userinfo does not have username or email %s", userinfo)
2463
- return None
2464
-
2465
- # If username is empty, go away
2466
- if (username is None) or username == "":
2467
- return None
2468
-
2469
- # Search the DB for this user
2470
- user = self.find_user(username=username)
2471
-
2472
- # If user is not active, go away
2473
- if user and (not user.is_active):
2474
- return None
2475
-
2476
- # If user is not registered, and not self-registration, go away
2477
- if (not user) and (not self.auth_user_registration):
2478
- return None
2479
-
2480
- # Sync the user's roles
2481
- if user and self.auth_roles_sync_at_login:
2482
- user.roles = self._oauth_calculate_user_roles(userinfo)
2483
- log.debug("Calculated new roles for user=%r as: %s", username, user.roles)
2484
-
2485
- # If the user is new, register them
2486
- if (not user) and self.auth_user_registration:
2487
- user = self.add_user(
2488
- username=username,
2489
- first_name=userinfo.get("first_name", ""),
2490
- last_name=userinfo.get("last_name", ""),
2491
- email=userinfo.get("email", "") or f"{username}@email.notfound",
2492
- role=self._oauth_calculate_user_roles(userinfo),
2493
- )
2494
- log.debug("New user registered: %s", user)
2495
-
2496
- # If user registration failed, go away
2497
- if not user:
2498
- log.error("Error creating a new OAuth user %s", username)
2499
- return None
2500
-
2501
- # LOGIN SUCCESS (only if user is now registered)
2502
- if user:
2503
- self._rotate_session_id()
2504
- self.update_user_auth_stat(user)
2505
- return user
2506
- else:
2507
- return None
2508
-
2509
- def auth_user_oid(self, email):
2510
- """
2511
- Openid user Authentication.
2512
-
2513
- :param email: user's email to authenticate
2514
- """
2515
- user = self.find_user(email=email)
2516
- if user is None or (not user.is_active):
2517
- log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, email)
2518
- return None
2519
- else:
2520
- self._rotate_session_id()
2521
- self.update_user_auth_stat(user)
2522
- return user
2523
-
2524
- def auth_user_remote_user(self, username):
2525
- """
2526
- REMOTE_USER user Authentication.
2527
-
2528
- :param username: user's username for remote auth
2529
- """
2530
- user = self.find_user(username=username)
2531
-
2532
- # User does not exist, create one if auto user registration.
2533
- if user is None and self.auth_user_registration:
2534
- user = self.add_user(
2535
- # All we have is REMOTE_USER, so we set
2536
- # the other fields to blank.
2537
- username=username,
2538
- first_name=username,
2539
- last_name="-",
2540
- email=username + "@email.notfound",
2541
- role=self.find_role(self.auth_user_registration_role),
2542
- )
2543
-
2544
- # If user does not exist on the DB and not auto user registration,
2545
- # or user is inactive, go away.
2546
- elif user is None or (not user.is_active):
2547
- log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
2548
- return None
2549
-
2550
- self._rotate_session_id()
2551
- self.update_user_auth_stat(user)
2552
- return user
2553
-
2554
- def get_user_menu_access(self, menu_names: list[str] | None = None) -> set[str]:
2555
- if get_auth_manager().is_logged_in():
2556
- return self._get_user_permission_resources(g.user, "menu_access", resource_names=menu_names)
2557
- elif current_user_jwt:
2558
- return self._get_user_permission_resources(
2559
- # the current_user_jwt is a lazy proxy, so we need to ignore type checking
2560
- current_user_jwt, # type: ignore[arg-type]
2561
- "menu_access",
2562
- resource_names=menu_names,
2563
- )
2564
- else:
2565
- return self._get_user_permission_resources(None, "menu_access", resource_names=menu_names)
2566
-
2567
2096
  @staticmethod
2568
2097
  def ldap_extract_list(ldap_dict: dict[str, list[bytes]], field_name: str) -> list[str]:
2569
2098
  raw_list = ldap_dict.get(field_name, [])
@@ -2658,7 +2187,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2658
2187
 
2659
2188
  # perform the LDAP search
2660
2189
  log.debug(
2661
- "LDAP search for %r with fields %s in scope %r", filter_str, request_fields, self.auth_ldap_search
2190
+ "LDAP search for %r with fields %s in scope %r",
2191
+ filter_str,
2192
+ request_fields,
2193
+ self.auth_ldap_search,
2662
2194
  )
2663
2195
  raw_search_result = con.search_s(
2664
2196
  self.auth_ldap_search, ldap.SCOPE_SUBTREE, filter_str, request_fields
@@ -2721,77 +2253,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2721
2253
 
2722
2254
  return list(user_role_objects)
2723
2255
 
2724
- def _oauth_calculate_user_roles(self, userinfo) -> list[str]:
2725
- user_role_objects = set()
2726
-
2727
- # apply AUTH_ROLES_MAPPING
2728
- if self.auth_roles_mapping:
2729
- user_role_keys = userinfo.get("role_keys", [])
2730
- user_role_objects.update(self.get_roles_from_keys(user_role_keys))
2731
-
2732
- # apply AUTH_USER_REGISTRATION_ROLE
2733
- if self.auth_user_registration:
2734
- registration_role_name = self.auth_user_registration_role
2735
-
2736
- # if AUTH_USER_REGISTRATION_ROLE_JMESPATH is set,
2737
- # use it for the registration role
2738
- if self.auth_user_registration_role_jmespath:
2739
- import jmespath
2740
-
2741
- registration_role_name = jmespath.search(self.auth_user_registration_role_jmespath, userinfo)
2742
-
2743
- # lookup registration role in flask db
2744
- fab_role = self.find_role(registration_role_name)
2745
- if fab_role:
2746
- user_role_objects.add(fab_role)
2747
- else:
2748
- log.warning("Can't find AUTH_USER_REGISTRATION role: %s", registration_role_name)
2749
-
2750
- return list(user_role_objects)
2751
-
2752
- def _get_user_permission_resources(
2753
- self, user: User | None, action_name: str, resource_names: list[str] | None = None
2754
- ) -> set[str]:
2755
- """
2756
- Get resource names with a certain action name that a user has access to.
2757
-
2758
- Mainly used to fetch all menu permissions on a single db call, will also
2759
- check public permissions and builtin roles
2760
- """
2761
- if not resource_names:
2762
- resource_names = []
2763
-
2764
- db_role_ids = []
2765
- if user is None:
2766
- # include public role
2767
- roles = [self.get_public_role()]
2768
- else:
2769
- roles = user.roles
2770
- # First check against builtin (statically configured) roles
2771
- # because no database query is needed
2772
- result = set()
2773
- for role in roles:
2774
- if role.name in self.builtin_roles:
2775
- for resource_name in resource_names:
2776
- if self._has_access_builtin_roles(role, action_name, resource_name):
2777
- result.add(resource_name)
2778
- else:
2779
- db_role_ids.append(role.id)
2780
- # Then check against database-stored roles
2781
- role_resource_names = [
2782
- perm.resource.name for perm in self.filter_roles_by_perm_with_action(action_name, db_role_ids)
2783
- ]
2784
- result.update(role_resource_names)
2785
- return result
2786
-
2787
- def _has_access_builtin_roles(self, role, action_name: str, resource_name: str) -> bool:
2788
- """Check permission on builtin role."""
2789
- perms = self.builtin_roles.get(role.name, [])
2790
- for _resource_name, _action_name in perms:
2791
- if re2.match(_resource_name, resource_name) and re2.match(_action_name, action_name):
2792
- return True
2793
- return False
2794
-
2795
2256
  def _merge_perm(self, action_name: str, resource_name: str) -> None:
2796
2257
  """
2797
2258
  Add the new (action, resource) to assoc_permission_role if it doesn't exist.
@@ -2831,7 +2292,11 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2831
2292
  (action_name, resource_name): viewmodel
2832
2293
  for action_name, resource_name, viewmodel in (
2833
2294
  self.appbuilder.get_session.execute(
2834
- select(self.action_model.name, self.resource_model.name, self.permission_model)
2295
+ select(
2296
+ self.action_model.name,
2297
+ self.resource_model.name,
2298
+ self.permission_model,
2299
+ )
2835
2300
  .join(self.permission_model.action)
2836
2301
  .join(self.permission_model.resource)
2837
2302
  .where(~self.resource_model.name.like(f"{permissions.RESOURCE_DAG_PREFIX}%"))
@@ -2839,32 +2304,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2839
2304
  )
2840
2305
  }
2841
2306
 
2842
- def filter_roles_by_perm_with_action(self, action_name: str, role_ids: list[int]):
2843
- """Find roles with permission."""
2844
- return (
2845
- self.appbuilder.get_session.query(self.permission_model)
2846
- .join(
2847
- assoc_permission_role,
2848
- and_(self.permission_model.id == assoc_permission_role.c.permission_view_id),
2849
- )
2850
- .join(self.role_model)
2851
- .join(self.action_model)
2852
- .join(self.resource_model)
2853
- .filter(
2854
- self.action_model.name == action_name,
2855
- self.role_model.id.in_(role_ids),
2856
- )
2857
- ).all()
2858
-
2859
- def _get_root_dag_id(self, dag_id: str) -> str:
2860
- # TODO: The "root_dag_id" check can be remove when the minimum version of Airflow is bumped to 3.0
2861
- if "." in dag_id and hasattr(DagModel, "root_dag_id"):
2862
- dm = self.appbuilder.get_session.execute(
2863
- select(DagModel.dag_id, DagModel.root_dag_id).where(DagModel.dag_id == dag_id)
2864
- ).one()
2865
- return dm.root_dag_id or dm.dag_id
2866
- return dag_id
2867
-
2868
2307
  @staticmethod
2869
2308
  def _cli_safe_flash(text: str, level: str) -> None:
2870
2309
  """Show a flash in a web context or prints a message if not."""