apache-airflow-providers-fab 1.0.1.dev0__py3-none-any.whl → 1.0.2__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.
- airflow/providers/fab/__init__.py +1 -1
- airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +1 -0
- airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +7 -8
- airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +5 -5
- airflow/providers/fab/auth_manager/cli_commands/role_command.py +1 -0
- airflow/providers/fab/auth_manager/cli_commands/sync_perm_command.py +1 -0
- airflow/providers/fab/auth_manager/cli_commands/user_command.py +1 -0
- airflow/providers/fab/auth_manager/decorators/auth.py +6 -3
- airflow/providers/fab/auth_manager/fab_auth_manager.py +10 -17
- airflow/providers/fab/auth_manager/models/__init__.py +1 -1
- airflow/providers/fab/auth_manager/security_manager/override.py +84 -15
- airflow/providers/fab/get_provider_info.py +2 -2
- {apache_airflow_providers_fab-1.0.1.dev0.dist-info → apache_airflow_providers_fab-1.0.2.dist-info}/METADATA +11 -10
- {apache_airflow_providers_fab-1.0.1.dev0.dist-info → apache_airflow_providers_fab-1.0.2.dist-info}/RECORD +16 -16
- {apache_airflow_providers_fab-1.0.1.dev0.dist-info → apache_airflow_providers_fab-1.0.2.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_fab-1.0.1.dev0.dist-info → apache_airflow_providers_fab-1.0.2.dist-info}/entry_points.txt +0 -0
@@ -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:
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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)
|
@@ -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
|
-
|
96
|
+
"There are different dag_ids passed in the request: %s. Returning 403.", unique_dag_ids
|
97
97
|
)
|
98
98
|
log.warning(
|
99
|
-
|
100
|
-
|
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=
|
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, *,
|
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
|
-
|
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",
|
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
|
-
|
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
|
-
|
514
|
-
|
515
|
-
|
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
|
728
|
-
|
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
|
-
|
1472
|
-
|
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
|
-
|
2446
|
-
|
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
|
-
|
2466
|
-
|
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.
|
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.
|
3
|
+
Version: 1.0.2
|
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
|
24
|
-
Requires-Dist: flask-appbuilder==4.
|
24
|
+
Requires-Dist: apache-airflow>=2.9.0
|
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.
|
30
|
-
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.
|
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.
|
81
|
+
Release: ``1.0.2``
|
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.
|
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.
|
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.
|
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
|
-
airflow/providers/fab/__init__.py,sha256=
|
3
|
-
airflow/providers/fab/get_provider_info.py,sha256=
|
2
|
+
airflow/providers/fab/__init__.py,sha256=9upVEkKzvgO9ZNqrewLH33y3wqbNjRIDMxejcnR1HwQ,1578
|
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=
|
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=
|
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=
|
13
|
-
airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py,sha256=
|
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=
|
17
|
-
airflow/providers/fab/auth_manager/cli_commands/sync_perm_command.py,sha256=
|
18
|
-
airflow/providers/fab/auth_manager/cli_commands/user_command.py,sha256=
|
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=
|
22
|
-
airflow/providers/fab/auth_manager/models/__init__.py,sha256=
|
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=
|
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.
|
36
|
-
apache_airflow_providers_fab-1.0.
|
37
|
-
apache_airflow_providers_fab-1.0.
|
38
|
-
apache_airflow_providers_fab-1.0.
|
35
|
+
apache_airflow_providers_fab-1.0.2.dist-info/entry_points.txt,sha256=m05kASp7vFi0ZmQ--CFp7GeJpPL7UT2RQF8EEP5XRX8,99
|
36
|
+
apache_airflow_providers_fab-1.0.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
37
|
+
apache_airflow_providers_fab-1.0.2.dist-info/METADATA,sha256=xRrSacstossM4Z7S3xDwtLd9mPdo_zHY3jYSq8-F-dE,4947
|
38
|
+
apache_airflow_providers_fab-1.0.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|