apache-airflow-providers-fab 1.0.1.dev0__py3-none-any.whl → 1.0.2b0__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.
@@ -15,6 +15,7 @@
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
17
  """Basic authentication backend."""
18
+
18
19
  from __future__ import annotations
19
20
 
20
21
  from functools import wraps
@@ -41,10 +41,9 @@ 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
44
- from airflow.www.security_manager import AirflowSecurityManagerV2
45
44
 
46
45
 
47
- def _check_action_and_resource(sm: AirflowSecurityManagerV2, perms: list[tuple[str, str]]) -> None:
46
+ def _check_action_and_resource(sm: FabAirflowSecurityManagerOverride, perms: list[tuple[str, str]]) -> None:
48
47
  """
49
48
  Check if the action or resource exists and otherwise raise 400.
50
49
 
@@ -57,7 +56,7 @@ def _check_action_and_resource(sm: AirflowSecurityManagerV2, perms: list[tuple[s
57
56
  raise BadRequest(detail=f"The specified resource: {resource!r} was not found")
58
57
 
59
58
 
60
- @requires_access_custom_view(permissions.ACTION_CAN_READ, permissions.RESOURCE_ROLE)
59
+ @requires_access_custom_view("GET", permissions.RESOURCE_ROLE)
61
60
  def get_role(*, role_name: str) -> APIResponse:
62
61
  """Get role."""
63
62
  security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
@@ -67,7 +66,7 @@ def get_role(*, role_name: str) -> APIResponse:
67
66
  return role_schema.dump(role)
68
67
 
69
68
 
70
- @requires_access_custom_view(permissions.ACTION_CAN_READ, permissions.RESOURCE_ROLE)
69
+ @requires_access_custom_view("GET", permissions.RESOURCE_ROLE)
71
70
  @format_parameters({"limit": check_limit})
72
71
  def get_roles(*, order_by: str = "name", limit: int, offset: int | None = None) -> APIResponse:
73
72
  """Get roles."""
@@ -95,7 +94,7 @@ def get_roles(*, order_by: str = "name", limit: int, offset: int | None = None)
95
94
  return role_collection_schema.dump(RoleCollection(roles=roles, total_entries=total_entries))
96
95
 
97
96
 
98
- @requires_access_custom_view(permissions.ACTION_CAN_READ, permissions.RESOURCE_ACTION)
97
+ @requires_access_custom_view("GET", permissions.RESOURCE_ACTION)
99
98
  @format_parameters({"limit": check_limit})
100
99
  def get_permissions(*, limit: int, offset: int | None = None) -> APIResponse:
101
100
  """Get permissions."""
@@ -107,7 +106,7 @@ def get_permissions(*, limit: int, offset: int | None = None) -> APIResponse:
107
106
  return action_collection_schema.dump(ActionCollection(actions=actions, total_entries=total_entries))
108
107
 
109
108
 
110
- @requires_access_custom_view(permissions.ACTION_CAN_DELETE, permissions.RESOURCE_ROLE)
109
+ @requires_access_custom_view("DELETE", permissions.RESOURCE_ROLE)
111
110
  def delete_role(*, role_name: str) -> APIResponse:
112
111
  """Delete a role."""
113
112
  security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
@@ -119,7 +118,7 @@ def delete_role(*, role_name: str) -> APIResponse:
119
118
  return NoContent, HTTPStatus.NO_CONTENT
120
119
 
121
120
 
122
- @requires_access_custom_view(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_ROLE)
121
+ @requires_access_custom_view("PUT", permissions.RESOURCE_ROLE)
123
122
  def patch_role(*, role_name: str, update_mask: UpdateMask = None) -> APIResponse:
124
123
  """Update a role."""
125
124
  security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
@@ -152,7 +151,7 @@ def patch_role(*, role_name: str, update_mask: UpdateMask = None) -> APIResponse
152
151
  return role_schema.dump(role)
153
152
 
154
153
 
155
- @requires_access_custom_view(permissions.ACTION_CAN_CREATE, permissions.RESOURCE_ROLE)
154
+ @requires_access_custom_view("POST", permissions.RESOURCE_ROLE)
156
155
  def post_role() -> APIResponse:
157
156
  """Create a new role."""
158
157
  security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
@@ -44,7 +44,7 @@ if TYPE_CHECKING:
44
44
  from airflow.providers.fab.auth_manager.models import Role
45
45
 
46
46
 
47
- @requires_access_custom_view(permissions.ACTION_CAN_READ, permissions.RESOURCE_USER)
47
+ @requires_access_custom_view("GET", permissions.RESOURCE_USER)
48
48
  def get_user(*, username: str) -> APIResponse:
49
49
  """Get a user."""
50
50
  security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
@@ -54,7 +54,7 @@ def get_user(*, username: str) -> APIResponse:
54
54
  return user_collection_item_schema.dump(user)
55
55
 
56
56
 
57
- @requires_access_custom_view(permissions.ACTION_CAN_READ, permissions.RESOURCE_USER)
57
+ @requires_access_custom_view("GET", permissions.RESOURCE_USER)
58
58
  @format_parameters({"limit": check_limit})
59
59
  def get_users(*, limit: int, order_by: str = "id", offset: str | None = None) -> APIResponse:
60
60
  """Get users."""
@@ -86,7 +86,7 @@ def get_users(*, limit: int, order_by: str = "id", offset: str | None = None) ->
86
86
  return user_collection_schema.dump(UserCollection(users=users, total_entries=total_entries))
87
87
 
88
88
 
89
- @requires_access_custom_view(permissions.ACTION_CAN_CREATE, permissions.RESOURCE_USER)
89
+ @requires_access_custom_view("POST", permissions.RESOURCE_USER)
90
90
  def post_user() -> APIResponse:
91
91
  """Create a new user."""
92
92
  try:
@@ -129,7 +129,7 @@ def post_user() -> APIResponse:
129
129
  return user_schema.dump(user)
130
130
 
131
131
 
132
- @requires_access_custom_view(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_USER)
132
+ @requires_access_custom_view("PUT", permissions.RESOURCE_USER)
133
133
  def patch_user(*, username: str, update_mask: UpdateMask = None) -> APIResponse:
134
134
  """Update a user."""
135
135
  try:
@@ -198,7 +198,7 @@ def patch_user(*, username: str, update_mask: UpdateMask = None) -> APIResponse:
198
198
  return user_schema.dump(user)
199
199
 
200
200
 
201
- @requires_access_custom_view(permissions.ACTION_CAN_DELETE, permissions.RESOURCE_USER)
201
+ @requires_access_custom_view("DELETE", permissions.RESOURCE_USER)
202
202
  def delete_user(*, username: str) -> APIResponse:
203
203
  """Delete a user."""
204
204
  security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
@@ -16,6 +16,7 @@
16
16
  # specific language governing permissions and limitations
17
17
  # under the License.
18
18
  """Roles sub-commands."""
19
+
19
20
  from __future__ import annotations
20
21
 
21
22
  import itertools
@@ -16,6 +16,7 @@
16
16
  # specific language governing permissions and limitations
17
17
  # under the License.
18
18
  """Sync permission command."""
19
+
19
20
  from __future__ import annotations
20
21
 
21
22
  from airflow.utils import cli as cli_utils
@@ -15,6 +15,7 @@
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
17
  """User sub-commands."""
18
+
18
19
  from __future__ import annotations
19
20
 
20
21
  import functools
@@ -93,11 +93,14 @@ def _has_access_fab(permissions: Sequence[tuple[str, str]] | None = None) -> Cal
93
93
 
94
94
  if len(unique_dag_ids) > 1:
95
95
  log.warning(
96
- f"There are different dag_ids passed in the request: {unique_dag_ids}. Returning 403."
96
+ "There are different dag_ids passed in the request: %s. Returning 403.", unique_dag_ids
97
97
  )
98
98
  log.warning(
99
- f"kwargs: {dag_id_kwargs}, args: {dag_id_args}, "
100
- f"form: {dag_id_form}, json: {dag_id_json}"
99
+ "kwargs: %s, args: %s, form: %s, json: %s",
100
+ dag_id_kwargs,
101
+ dag_id_args,
102
+ dag_id_form,
103
+ dag_id_json,
101
104
  )
102
105
  return (
103
106
  render_template(
@@ -54,8 +54,6 @@ from airflow.providers.fab.auth_manager.cli_commands.definition import (
54
54
  from airflow.providers.fab.auth_manager.models import Permission, Role, User
55
55
  from airflow.security import permissions
56
56
  from airflow.security.permissions import (
57
- ACTION_CAN_ACCESS_MENU,
58
- ACTION_CAN_READ,
59
57
  RESOURCE_AUDIT_LOG,
60
58
  RESOURCE_CLUSTER_ACTIVITY,
61
59
  RESOURCE_CONFIG,
@@ -84,6 +82,7 @@ from airflow.security.permissions import (
84
82
  )
85
83
  from airflow.utils.session import NEW_SESSION, provide_session
86
84
  from airflow.utils.yaml import safe_load
85
+ from airflow.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED
87
86
  from airflow.www.extensions.init_views import _CustomErrorRequestBodyValidator, _LazyResolver
88
87
 
89
88
  if TYPE_CHECKING:
@@ -156,9 +155,7 @@ class FabAuthManager(BaseAuthManager):
156
155
  specification=specification,
157
156
  resolver=_LazyResolver(),
158
157
  base_path="/auth/fab/v1",
159
- options={
160
- "swagger_ui": conf.getboolean("webserver", "enable_swagger_ui", fallback=True),
161
- },
158
+ options={"swagger_ui": SWAGGER_ENABLED, "swagger_path": SWAGGER_BUNDLE.__fspath__()},
162
159
  strict_validation=True,
163
160
  validate_responses=True,
164
161
  validator_map={"body": _CustomErrorRequestBodyValidator},
@@ -264,16 +261,19 @@ class FabAuthManager(BaseAuthManager):
264
261
  return self._is_authorized(method=method, resource_type=RESOURCE_VARIABLE, user=user)
265
262
 
266
263
  def is_authorized_view(self, *, access_view: AccessView, user: BaseUser | None = None) -> bool:
264
+ # "Docs" are only links in the menu, there is no page associated
265
+ method: ResourceMethod = "MENU" if access_view == AccessView.DOCS else "GET"
267
266
  return self._is_authorized(
268
- method="GET", resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], user=user
267
+ method=method, resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], user=user
269
268
  )
270
269
 
271
270
  def is_authorized_custom_view(
272
- self, *, fab_action_name: str, fab_resource_name: str, user: BaseUser | None = None
271
+ self, *, method: ResourceMethod, resource_name: str, user: BaseUser | None = None
273
272
  ):
274
273
  if not user:
275
274
  user = self.get_user()
276
- return (fab_action_name, fab_resource_name) in self._get_user_permissions(user)
275
+ fab_action_name = get_fab_action_from_method_map()[method]
276
+ return (fab_action_name, resource_name) in self._get_user_permissions(user)
277
277
 
278
278
  @provide_session
279
279
  def get_permitted_dag_ids(
@@ -351,7 +351,7 @@ class FabAuthManager(BaseAuthManager):
351
351
  if not self.security_manager.auth_view:
352
352
  raise AirflowException("`auth_view` not defined in the security manager.")
353
353
  if "next_url" in kwargs and kwargs["next_url"]:
354
- return url_for(f"{self.security_manager.auth_view.endpoint}.login", next_url=kwargs["next_url"])
354
+ return url_for(f"{self.security_manager.auth_view.endpoint}.login", next=kwargs["next_url"])
355
355
  else:
356
356
  return url_for(f"{self.security_manager.auth_view.endpoint}.login")
357
357
 
@@ -464,18 +464,11 @@ class FabAuthManager(BaseAuthManager):
464
464
  """
465
465
  Return the user permissions.
466
466
 
467
- ACTION_CAN_READ and ACTION_CAN_ACCESS_MENU are merged into because they are very similar.
468
- We can assume that if a user has permissions to read variables, they also have permissions to access
469
- the menu "Variables".
470
-
471
467
  :param user: the user to get permissions for
472
468
 
473
469
  :meta private:
474
470
  """
475
- perms = getattr(user, "perms") or []
476
- return [
477
- (ACTION_CAN_READ if perm[0] == ACTION_CAN_ACCESS_MENU else perm[0], perm[1]) for perm in perms
478
- ]
471
+ return getattr(user, "perms") or []
479
472
 
480
473
  def _get_root_dag_id(self, dag_id: str) -> str:
481
474
  """
@@ -152,7 +152,7 @@ class User(Model, BaseUser):
152
152
  String(512).with_variant(String(512, collation="NOCASE"), "sqlite"), unique=True, nullable=False
153
153
  )
154
154
  password = Column(String(256))
155
- active = Column(Boolean)
155
+ active = Column(Boolean, default=True)
156
156
  email = Column(String(512), unique=True, nullable=False)
157
157
  last_login = Column(DateTime)
158
158
  login_count = Column(Integer)
@@ -203,7 +203,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
203
203
 
204
204
  # [START security_viewer_perms]
205
205
  VIEWER_PERMISSIONS = [
206
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_AUDIT_LOG),
207
206
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG),
208
207
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_DEPENDENCIES),
209
208
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_CODE),
@@ -233,7 +232,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
233
232
  (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DOCS),
234
233
  (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DOCS_MENU),
235
234
  (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_JOB),
236
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_AUDIT_LOG),
237
235
  (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_PLUGIN),
238
236
  (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_SLA_MISS),
239
237
  (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_TASK_INSTANCE),
@@ -250,6 +248,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
250
248
  (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_DAG_RUN),
251
249
  (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG_RUN),
252
250
  (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_DAG_RUN),
251
+ (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_DATASET),
253
252
  ]
254
253
  # [END security_user_perms]
255
254
 
@@ -276,11 +275,15 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
276
275
  (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_VARIABLE),
277
276
  (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_VARIABLE),
278
277
  (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_XCOM),
278
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_DATASET),
279
+ (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_DATASET),
279
280
  ]
280
281
  # [END security_op_perms]
281
282
 
282
283
  # [START security_admin_perms]
283
284
  ADMIN_PERMISSIONS = [
285
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_AUDIT_LOG),
286
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_AUDIT_LOG),
284
287
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_RESCHEDULE),
285
288
  (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_TASK_RESCHEDULE),
286
289
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_TRIGGER),
@@ -337,6 +340,43 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
337
340
  # Setup Flask-Jwt-Extended
338
341
  self.create_jwt_manager()
339
342
 
343
+ def _get_authentik_jwks(self, jwks_url) -> dict:
344
+ import requests
345
+
346
+ resp = requests.get(jwks_url)
347
+ if resp.status_code == 200:
348
+ return resp.json()
349
+ return {}
350
+
351
+ def _validate_jwt(self, id_token, jwks):
352
+ from authlib.jose import JsonWebKey, jwt as authlib_jwt
353
+
354
+ keyset = JsonWebKey.import_key_set(jwks)
355
+ claims = authlib_jwt.decode(id_token, keyset)
356
+ claims.validate()
357
+ log.info("JWT token is validated")
358
+ return claims
359
+
360
+ def _get_authentik_token_info(self, id_token):
361
+ me = jwt.decode(id_token, options={"verify_signature": False})
362
+
363
+ verify_signature = self.oauth_remotes["authentik"].client_kwargs.get("verify_signature", True)
364
+ if verify_signature:
365
+ # Validate the token using authentik certificate
366
+ jwks_uri = self.oauth_remotes["authentik"].server_metadata.get("jwks_uri")
367
+ if jwks_uri:
368
+ jwks = self._get_authentik_jwks(jwks_uri)
369
+ if jwks:
370
+ return self._validate_jwt(id_token, jwks)
371
+ else:
372
+ log.error("jwks_uri not specified in OAuth Providers, could not verify token signature")
373
+ else:
374
+ # Return the token info without validating
375
+ log.warning("JWT token is not validated!")
376
+ return me
377
+
378
+ raise AirflowException("OAuth signature verify failed")
379
+
340
380
  def register_views(self):
341
381
  """Register FAB auth manager related views."""
342
382
  if not self.appbuilder.get_app.config.get("FAB_ADD_SECURITY_VIEWS", True):
@@ -510,9 +550,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
510
550
  def load_user_jwt(self, _jwt_header, jwt_data):
511
551
  identity = jwt_data["sub"]
512
552
  user = self.load_user(identity)
513
- # Set flask g.user to JWT user, we can't do it on before request
514
- g.user = user
515
- return user
553
+ if user.is_active:
554
+ # Set flask g.user to JWT user, we can't do it on before request
555
+ g.user = user
556
+ return user
516
557
 
517
558
  @property
518
559
  def auth_type(self):
@@ -644,6 +685,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
644
685
  """The JMESPATH role to use for user registration."""
645
686
  return self.appbuilder.get_app.config["AUTH_USER_REGISTRATION_ROLE_JMESPATH"]
646
687
 
688
+ @property
689
+ def auth_remote_user_env_var(self) -> str:
690
+ return self.appbuilder.get_app.config["AUTH_REMOTE_USER_ENV_VAR"]
691
+
647
692
  @property
648
693
  def api_login_allow_multiple_providers(self):
649
694
  return self.appbuilder.get_app.config["AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS"]
@@ -724,8 +769,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
724
769
  # If the user does not exist, make a random password and make it
725
770
  if not user_exists:
726
771
  print(f"FlaskAppBuilder Authentication Manager: Creating {user_name} user")
727
- role = self.find_role("Admin")
728
- assert role is not None
772
+ if (role := self.find_role("Admin")) is None:
773
+ raise AirflowException("Unable to find role 'Admin'")
729
774
  # password does not contain visually similar characters: ijlIJL1oO0
730
775
  password = "".join(random.choices("abcdefghkmnpqrstuvwxyzABCDEFGHKMNPQRSTUVWXYZ23456789", k=16))
731
776
  with open(password_path, "w") as file:
@@ -787,6 +832,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
787
832
  app.config.setdefault("AUTH_LDAP_LASTNAME_FIELD", "sn")
788
833
  app.config.setdefault("AUTH_LDAP_EMAIL_FIELD", "mail")
789
834
 
835
+ if self.auth_type == AUTH_REMOTE_USER:
836
+ app.config.setdefault("AUTH_REMOTE_USER_ENV_VAR", "REMOTE_USER")
837
+
790
838
  # Rate limiting
791
839
  app.config.setdefault("AUTH_RATE_LIMITED", True)
792
840
  app.config.setdefault("AUTH_RATE_LIMIT", "5 per 40 second")
@@ -801,7 +849,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
801
849
  if self.auth_type == AUTH_OID:
802
850
  from flask_openid import OpenID
803
851
 
852
+ log.warning(
853
+ "AUTH_OID is deprecated and will be removed in version 5. "
854
+ "Migrate to other authentication methods."
855
+ )
804
856
  self.oid = OpenID(app)
857
+
805
858
  if self.auth_type == AUTH_OAUTH:
806
859
  from authlib.integrations.flask_client import OAuth
807
860
 
@@ -1246,8 +1299,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1246
1299
  sesh = self.appbuilder.get_session
1247
1300
  perms = sesh.query(Permission).filter(
1248
1301
  or_(
1249
- Permission.action == None, # noqa
1250
- Permission.resource == None, # noqa
1302
+ Permission.action == None, # noqa: E711
1303
+ Permission.resource == None, # noqa: E711
1251
1304
  )
1252
1305
  )
1253
1306
  # Since FAB doesn't define ON DELETE CASCADE on these tables, we need
@@ -1468,8 +1521,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1468
1521
  return False
1469
1522
 
1470
1523
  def load_user(self, user_id):
1471
- """Load user by ID."""
1472
- return self.get_user_by_id(int(user_id))
1524
+ user = self.get_user_by_id(int(user_id))
1525
+ if user.is_active:
1526
+ return user
1473
1527
 
1474
1528
  def get_user_by_id(self, pk):
1475
1529
  return self.get_session.get(self.user_model, pk)
@@ -2205,6 +2259,19 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2205
2259
  "last_name": data.get("family_name", ""),
2206
2260
  "email": data.get("email", ""),
2207
2261
  }
2262
+
2263
+ # for Authentik
2264
+ if provider == "authentik":
2265
+ id_token = resp["id_token"]
2266
+ me = self._get_authentik_token_info(id_token)
2267
+ log.debug("User info from authentik: %s", me)
2268
+ return {
2269
+ "email": me["preferred_username"],
2270
+ "first_name": me.get("given_name", ""),
2271
+ "username": me["nickname"],
2272
+ "role_keys": me.get("groups", []),
2273
+ }
2274
+
2208
2275
  else:
2209
2276
  return {}
2210
2277
 
@@ -2442,8 +2509,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2442
2509
  :param ldap: The ldap module reference
2443
2510
  :param con: The ldap connection
2444
2511
  """
2445
- # always check AUTH_LDAP_BIND_USER is set before calling this method
2446
- assert self.auth_ldap_bind_user, "AUTH_LDAP_BIND_USER must be set"
2512
+ if not self.auth_ldap_bind_user:
2513
+ # always check AUTH_LDAP_BIND_USER is set before calling this method
2514
+ raise ValueError("AUTH_LDAP_BIND_USER must be set")
2447
2515
 
2448
2516
  try:
2449
2517
  log.debug("LDAP bind indirect TRY with username: %r", self.auth_ldap_bind_user)
@@ -2462,8 +2530,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2462
2530
  :param username: username to match with AUTH_LDAP_UID_FIELD
2463
2531
  :return: ldap object array
2464
2532
  """
2465
- # always check AUTH_LDAP_SEARCH is set before calling this method
2466
- assert self.auth_ldap_search, "AUTH_LDAP_SEARCH must be set"
2533
+ if not self.auth_ldap_search:
2534
+ # always check AUTH_LDAP_SEARCH is set before calling this method
2535
+ raise ValueError("AUTH_LDAP_SEARCH must be set")
2467
2536
 
2468
2537
  # build the filter string for the LDAP search
2469
2538
  if self.auth_ldap_search_filter:
@@ -29,11 +29,11 @@ def get_provider_info():
29
29
  "description": "`Flask App Builder <https://flask-appbuilder.readthedocs.io/>`__\n",
30
30
  "state": "ready",
31
31
  "source-date-epoch": 1703288133,
32
- "versions": ["1.0.1", "1.0.0"],
32
+ "versions": ["1.0.2", "1.0.1", "1.0.0"],
33
33
  "dependencies": [
34
34
  "apache-airflow>=2.9.0",
35
35
  "flask>=2.2,<2.3",
36
- "flask-appbuilder==4.3.11",
36
+ "flask-appbuilder==4.4.1",
37
37
  "flask-login>=0.6.2",
38
38
  "google-re2>=1.0",
39
39
  ],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: apache-airflow-providers-fab
3
- Version: 1.0.1.dev0
3
+ Version: 1.0.2b0
4
4
  Summary: Provider package apache-airflow-providers-fab for Apache Airflow
5
5
  Keywords: airflow-provider,fab,airflow,integration
6
6
  Author-email: Apache Software Foundation <dev@airflow.apache.org>
@@ -19,15 +19,16 @@ Classifier: Programming Language :: Python :: 3.8
19
19
  Classifier: Programming Language :: Python :: 3.9
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
22
23
  Classifier: Topic :: System :: Monitoring
23
- Requires-Dist: apache-airflow>=2.9.0.dev0
24
- Requires-Dist: flask-appbuilder==4.3.11
24
+ Requires-Dist: apache-airflow>=2.9.0b0
25
+ Requires-Dist: flask-appbuilder==4.4.1
25
26
  Requires-Dist: flask-login>=0.6.2
26
27
  Requires-Dist: flask>=2.2,<2.3
27
28
  Requires-Dist: google-re2>=1.0
28
29
  Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
29
- Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.1/changelog.html
30
- Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.1
30
+ Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/changelog.html
31
+ Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2
31
32
  Project-URL: Slack Chat, https://s.apache.org/airflow-slack
32
33
  Project-URL: Source Code, https://github.com/apache/airflow
33
34
  Project-URL: Twitter, https://twitter.com/ApacheAirflow
@@ -77,7 +78,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
77
78
 
78
79
  Package ``apache-airflow-providers-fab``
79
80
 
80
- Release: ``1.0.1.dev0``
81
+ Release: ``1.0.2.b0``
81
82
 
82
83
 
83
84
  `Flask App Builder <https://flask-appbuilder.readthedocs.io/>`__
@@ -90,7 +91,7 @@ This is a provider package for ``fab`` provider. All classes for this provider p
90
91
  are in ``airflow.providers.fab`` python package.
91
92
 
92
93
  You can find package information and changelog for the provider
93
- in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.1/>`_.
94
+ in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/>`_.
94
95
 
95
96
  Installation
96
97
  ------------
@@ -99,7 +100,7 @@ You can install this package on top of an existing Airflow 2 installation (see `
99
100
  for the minimum Airflow version supported) via
100
101
  ``pip install apache-airflow-providers-fab``
101
102
 
102
- The package supports the following python versions: 3.8,3.9,3.10,3.11
103
+ The package supports the following python versions: 3.8,3.9,3.10,3.11,3.12
103
104
 
104
105
  Requirements
105
106
  ------------
@@ -109,10 +110,10 @@ PIP package Version required
109
110
  ==================== ==================
110
111
  ``apache-airflow`` ``>=2.9.0``
111
112
  ``flask`` ``>=2.2,<2.3``
112
- ``flask-appbuilder`` ``==4.3.11``
113
+ ``flask-appbuilder`` ``==4.4.1``
113
114
  ``flask-login`` ``>=0.6.2``
114
115
  ``google-re2`` ``>=1.0``
115
116
  ==================== ==================
116
117
 
117
118
  The changelog for the provider package can be found in the
118
- `changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.1/changelog.html>`_.
119
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/changelog.html>`_.
@@ -1,38 +1,38 @@
1
1
  airflow/providers/fab/LICENSE,sha256=ywUBpKZc7Jb96rVt5I3IDbg7dIJAbUSHkuoDcF3jbH4,13569
2
2
  airflow/providers/fab/__init__.py,sha256=i7O17EJB9DcN_BQKB75NJcb0CEYr7Y3gzfZNm2Fpx9s,1578
3
- airflow/providers/fab/get_provider_info.py,sha256=HvijAf9lkS2MpA7zZz52shiKjsuU4s6n0s9qDYeS_YQ,2936
3
+ airflow/providers/fab/get_provider_info.py,sha256=cojfnZKpmibjoYUrcf_Ab4fMZsm16dscFwS5tqU8fNw,2944
4
4
  airflow/providers/fab/auth_manager/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
5
- airflow/providers/fab/auth_manager/fab_auth_manager.py,sha256=_yiL1QdnFArYZ44eAv6_IAJlWBzV-Vmz1HgXy-DTyzY,19720
5
+ airflow/providers/fab/auth_manager/fab_auth_manager.py,sha256=det_jvKb0hozwC7XgLgJXjYbdnW58zrtA1k0RhOUWbA,19545
6
6
  airflow/providers/fab/auth_manager/api/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
7
7
  airflow/providers/fab/auth_manager/api/auth/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
8
8
  airflow/providers/fab/auth_manager/api/auth/backend/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
9
- airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py,sha256=G-aYU3WxdcOoaw0LU3wtvy1mBecWOpN51sc1_X6GwL8,2501
9
+ airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py,sha256=UBGP5UHV6kNsChvL9rkYOQWHszEJtE4AkSFagcRsqrs,2502
10
10
  airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py,sha256=SvuJ9jo06ZfB_40FUKaiHb8cw2d3Jpxm-YYSOWEzA7I,1712
11
11
  airflow/providers/fab/auth_manager/api_endpoints/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
12
- airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py,sha256=Qw63lCekkDGovdIXJGXUnrvrf4ZQGBnIVnO01PhEZaU,7749
13
- airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py,sha256=Oe7LTF3cqQVvISZeYw66L0DMEP4UOmzIQI_aW-6oVms,8598
12
+ airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py,sha256=9GRizOLGF5Rsic_42H_gom8LkjVTpvJT0oRxO98lwlw,7556
13
+ airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py,sha256=8GfpmpkABVO834Os8rNoH_rqLh4eFz1sfayL1Fwz8FQ,8488
14
14
  airflow/providers/fab/auth_manager/cli_commands/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
15
15
  airflow/providers/fab/auth_manager/cli_commands/definition.py,sha256=hjcb3IL-G1s120UcdOZcV5ssgC7uuTfTmHECGlmr7bk,9075
16
- airflow/providers/fab/auth_manager/cli_commands/role_command.py,sha256=ChVDM1KBWZ83lknBj-6L9jtsczZjlOiUOHx1dXW5hzc,9097
17
- airflow/providers/fab/auth_manager/cli_commands/sync_perm_command.py,sha256=yd1AU86mfj35PiynQdrx1eIxjurRNAk0w6nO-JgfSmw,1662
18
- airflow/providers/fab/auth_manager/cli_commands/user_command.py,sha256=XjXyPGpULYvmUlzz1xArgrgyyzza1sQ4DfcqkgMK4aU,10206
16
+ airflow/providers/fab/auth_manager/cli_commands/role_command.py,sha256=4w1tHTR5gBbsymeqGIJ4Rs8CmGdw0l49y58pfI0DB_s,9098
17
+ airflow/providers/fab/auth_manager/cli_commands/sync_perm_command.py,sha256=VpW-rWhgHAL_ReU66D_BrsxlXQN4okfxzj6dyE5IfwA,1663
18
+ airflow/providers/fab/auth_manager/cli_commands/user_command.py,sha256=qaYuBbIvF_P0C_SIdQuEFS-hcjRkyn74Gl9Jt0hh2Gs,10207
19
19
  airflow/providers/fab/auth_manager/cli_commands/utils.py,sha256=V--SdqJgAtNkxmqhOq4Jvpiqk5Dec7OCbIAHih6RPYM,1768
20
20
  airflow/providers/fab/auth_manager/decorators/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
21
- airflow/providers/fab/auth_manager/decorators/auth.py,sha256=6IuE4NyiVbRzCpdvriZybymPM6HdpzaSsbHL5ygoLEM,4883
22
- airflow/providers/fab/auth_manager/models/__init__.py,sha256=9-iWWgOfFYLNxw05pHSWMxw80TOAiogxTLQvIpF5J60,8813
21
+ airflow/providers/fab/auth_manager/decorators/auth.py,sha256=Z4_xiS1N1E0MqDP9OxHx_YJvKe0sH15afP0tn5LP3Ss,4948
22
+ airflow/providers/fab/auth_manager/models/__init__.py,sha256=IEGXoz1HaCHxoH8DzI5DIoYHBKlrN1wjru7Ey_YhtkA,8827
23
23
  airflow/providers/fab/auth_manager/models/anonymous_user.py,sha256=ki1jM-SdxSEJzXhrsiulmziWHlKkU2LguQkXMaRmykE,1775
24
24
  airflow/providers/fab/auth_manager/openapi/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
25
25
  airflow/providers/fab/auth_manager/openapi/v1.yaml,sha256=xFlQMccLoGarx8SnQvGJ1DRfHo4jQ3p9DzoPqQINcCw,19380
26
26
  airflow/providers/fab/auth_manager/security_manager/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
27
27
  airflow/providers/fab/auth_manager/security_manager/constants.py,sha256=x1Sjl_Mu3wmaSy3NFZlHxK2z-juzWmMs1SrzJ0aiBBQ,907
28
- airflow/providers/fab/auth_manager/security_manager/override.py,sha256=n1m9lt2d_zfVXGiK8SPkm65hlNe0HlxHMgLkfXcqpeU,108712
28
+ airflow/providers/fab/auth_manager/security_manager/override.py,sha256=OMJca7teRJ4ria11J-uAIvipKri7gm-YyG1d9i_MyME,111419
29
29
  airflow/providers/fab/auth_manager/views/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
30
30
  airflow/providers/fab/auth_manager/views/permissions.py,sha256=k5APUTqqKZqMxCWlKrUEgqdDVrGa9719HJ3UmFpiSLc,2886
31
31
  airflow/providers/fab/auth_manager/views/roles_list.py,sha256=o_VqnLbQa4sisKxLwyx6VCl3HmvjKcjkrJG23R81FMQ,1494
32
32
  airflow/providers/fab/auth_manager/views/user.py,sha256=XsUXXx4qEbZJYMTmWz3R-lpavsUZXvpwKAASHjxceGc,5917
33
33
  airflow/providers/fab/auth_manager/views/user_edit.py,sha256=d8wQ_8Rk8Ce95sCElZIXN7ATwbXrT2Tauqn5uTwuo2E,2140
34
34
  airflow/providers/fab/auth_manager/views/user_stats.py,sha256=zP2eX6e40rpEohJcvnvri4Khu3Q4ULLxjz1zOWvOr8A,1300
35
- apache_airflow_providers_fab-1.0.1.dev0.dist-info/entry_points.txt,sha256=m05kASp7vFi0ZmQ--CFp7GeJpPL7UT2RQF8EEP5XRX8,99
36
- apache_airflow_providers_fab-1.0.1.dev0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
37
- apache_airflow_providers_fab-1.0.1.dev0.dist-info/METADATA,sha256=xPi6PDGpNbJrjVPwAd7D866ThP8FAMiaz1wMIHR2_sE,4908
38
- apache_airflow_providers_fab-1.0.1.dev0.dist-info/RECORD,,
35
+ apache_airflow_providers_fab-1.0.2b0.dist-info/entry_points.txt,sha256=m05kASp7vFi0ZmQ--CFp7GeJpPL7UT2RQF8EEP5XRX8,99
36
+ apache_airflow_providers_fab-1.0.2b0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
37
+ apache_airflow_providers_fab-1.0.2b0.dist-info/METADATA,sha256=zwrSBwQXSYWtzDpSd0EnLOGErjD1vg0x1Uob3tgdQF8,4954
38
+ apache_airflow_providers_fab-1.0.2b0.dist-info/RECORD,,