apache-airflow-providers-fab 2.4.4__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airflow/providers/fab/__init__.py +1 -1
- airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +2 -2
- airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +4 -4
- airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +7 -4
- airflow/providers/fab/auth_manager/cli_commands/user_command.py +1 -2
- airflow/providers/fab/auth_manager/cli_commands/utils.py +7 -3
- airflow/providers/fab/auth_manager/fab_auth_manager.py +15 -11
- airflow/providers/fab/auth_manager/models/__init__.py +179 -122
- airflow/providers/fab/auth_manager/models/db.py +11 -6
- airflow/providers/fab/auth_manager/security_manager/override.py +239 -213
- airflow/providers/fab/auth_manager/views/user.py +11 -5
- airflow/providers/fab/migrations/versions/0001_1_4_0_create_ab_tables_if_missing.py +1 -0
- airflow/providers/fab/www/app.py +3 -4
- airflow/providers/fab/www/extensions/init_appbuilder.py +26 -39
- airflow/providers/fab/www/extensions/init_session.py +2 -2
- airflow/providers/fab/www/security_appless.py +6 -1
- airflow/providers/fab/www/security_manager.py +4 -14
- airflow/providers/fab/www/session.py +26 -3
- airflow/providers/fab/www/utils.py +1 -208
- {apache_airflow_providers_fab-2.4.4.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/METADATA +18 -12
- {apache_airflow_providers_fab-2.4.4.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/RECORD +25 -25
- {apache_airflow_providers_fab-2.4.4.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_fab-2.4.4.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/entry_points.txt +0 -0
- {apache_airflow_providers_fab-2.4.4.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/licenses/3rd-party-licenses/LICENSES-ui.txt +0 -0
- {apache_airflow_providers_fab-2.4.4.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/licenses/NOTICE +0 -0
|
@@ -26,13 +26,12 @@ from collections.abc import Collection, Iterable, Mapping
|
|
|
26
26
|
from typing import TYPE_CHECKING, Any
|
|
27
27
|
|
|
28
28
|
import jwt
|
|
29
|
-
from flask import flash, g, has_request_context, session
|
|
30
|
-
from flask_appbuilder import const
|
|
29
|
+
from flask import current_app, flash, g, has_app_context, has_request_context, session
|
|
30
|
+
from flask_appbuilder import Model, const
|
|
31
31
|
from flask_appbuilder.const import (
|
|
32
32
|
AUTH_DB,
|
|
33
33
|
AUTH_LDAP,
|
|
34
34
|
AUTH_OAUTH,
|
|
35
|
-
AUTH_OID,
|
|
36
35
|
AUTH_REMOTE_USER,
|
|
37
36
|
LOGMSG_ERR_SEC_ADD_REGISTER_USER,
|
|
38
37
|
LOGMSG_ERR_SEC_AUTH_LDAP,
|
|
@@ -41,19 +40,16 @@ from flask_appbuilder.const import (
|
|
|
41
40
|
LOGMSG_WAR_SEC_NOLDAP_OBJ,
|
|
42
41
|
MICROSOFT_KEY_SET_URL,
|
|
43
42
|
)
|
|
44
|
-
from flask_appbuilder.models.sqla import Base
|
|
45
43
|
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
|
46
44
|
from flask_appbuilder.security.api import SecurityApi
|
|
47
45
|
from flask_appbuilder.security.registerviews import (
|
|
48
46
|
RegisterUserDBView,
|
|
49
47
|
RegisterUserOAuthView,
|
|
50
|
-
RegisterUserOIDView,
|
|
51
48
|
)
|
|
52
49
|
from flask_appbuilder.security.views import (
|
|
53
50
|
AuthDBView,
|
|
54
51
|
AuthLDAPView,
|
|
55
52
|
AuthOAuthView,
|
|
56
|
-
AuthOIDView,
|
|
57
53
|
AuthRemoteUserView,
|
|
58
54
|
RegisterUserModelView,
|
|
59
55
|
UserGroupModelView,
|
|
@@ -91,7 +87,6 @@ from airflow.providers.fab.auth_manager.views.user import (
|
|
|
91
87
|
CustomUserDBModelView,
|
|
92
88
|
CustomUserLDAPModelView,
|
|
93
89
|
CustomUserOAuthModelView,
|
|
94
|
-
CustomUserOIDModelView,
|
|
95
90
|
CustomUserRemoteUserModelView,
|
|
96
91
|
)
|
|
97
92
|
from airflow.providers.fab.auth_manager.views.user_edit import (
|
|
@@ -177,16 +172,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
177
172
|
""" Override if you want your own Authentication DB view """
|
|
178
173
|
authldapview = AuthLDAPView
|
|
179
174
|
""" Override if you want your own Authentication LDAP view """
|
|
180
|
-
authoidview = AuthOIDView
|
|
181
|
-
""" Override if you want your own Authentication OID view """
|
|
182
175
|
authoauthview = AuthOAuthView
|
|
183
176
|
""" Override if you want your own Authentication OAuth view """
|
|
184
177
|
authremoteuserview = AuthRemoteUserView
|
|
185
178
|
""" Override if you want your own Authentication REMOTE_USER view """
|
|
186
179
|
registeruserdbview = RegisterUserDBView
|
|
187
180
|
""" Override if you want your own register user db view """
|
|
188
|
-
registeruseroidview = RegisterUserOIDView
|
|
189
|
-
""" Override if you want your own register user OpenID view """
|
|
190
181
|
registeruseroauthview = RegisterUserOAuthView
|
|
191
182
|
""" Override if you want your own register user OAuth view """
|
|
192
183
|
actionmodelview = ActionModelView
|
|
@@ -203,7 +194,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
203
194
|
userldapmodelview = CustomUserLDAPModelView
|
|
204
195
|
useroauthmodelview = CustomUserOAuthModelView
|
|
205
196
|
userremoteusermodelview = CustomUserRemoteUserModelView
|
|
206
|
-
useroidmodelview = CustomUserOIDModelView
|
|
207
197
|
userstatschartview = CustomUserStatsChartView
|
|
208
198
|
|
|
209
199
|
# API
|
|
@@ -424,7 +414,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
424
414
|
|
|
425
415
|
def register_views(self):
|
|
426
416
|
"""Register FAB auth manager related views."""
|
|
427
|
-
if not
|
|
417
|
+
if not current_app.config.get("FAB_ADD_SECURITY_VIEWS", True):
|
|
428
418
|
return
|
|
429
419
|
|
|
430
420
|
# Security APIs
|
|
@@ -433,8 +423,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
433
423
|
if self.auth_user_registration:
|
|
434
424
|
if self.auth_type == AUTH_DB:
|
|
435
425
|
self.registeruser_view = self.registeruserdbview()
|
|
436
|
-
elif self.auth_type == AUTH_OID:
|
|
437
|
-
self.registeruser_view = self.registeruseroidview()
|
|
438
426
|
elif self.auth_type == AUTH_OAUTH:
|
|
439
427
|
self.registeruser_view = self.registeruseroauthview()
|
|
440
428
|
if self.registeruser_view:
|
|
@@ -456,9 +444,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
456
444
|
elif self.auth_type == AUTH_REMOTE_USER:
|
|
457
445
|
self.user_view = self.userremoteusermodelview
|
|
458
446
|
self.auth_view = self.authremoteuserview()
|
|
459
|
-
else:
|
|
460
|
-
self.user_view = self.useroidmodelview
|
|
461
|
-
self.auth_view = self.authoidview()
|
|
462
447
|
|
|
463
448
|
self.appbuilder.add_view_no_menu(self.auth_view)
|
|
464
449
|
|
|
@@ -504,7 +489,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
504
489
|
category="Security",
|
|
505
490
|
)
|
|
506
491
|
self.appbuilder.menu.add_separator("Security")
|
|
507
|
-
if
|
|
492
|
+
if current_app.config.get("FAB_ADD_SECURITY_PERMISSION_VIEW", True):
|
|
508
493
|
self.appbuilder.add_view(
|
|
509
494
|
self.actionmodelview,
|
|
510
495
|
"Actions",
|
|
@@ -512,7 +497,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
512
497
|
label=lazy_gettext("Actions"),
|
|
513
498
|
category="Security",
|
|
514
499
|
)
|
|
515
|
-
if
|
|
500
|
+
if current_app.config.get("FAB_ADD_SECURITY_VIEW_MENU_VIEW", True):
|
|
516
501
|
self.appbuilder.add_view(
|
|
517
502
|
self.resourcemodelview,
|
|
518
503
|
"Resources",
|
|
@@ -520,7 +505,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
520
505
|
label=lazy_gettext("Resources"),
|
|
521
506
|
category="Security",
|
|
522
507
|
)
|
|
523
|
-
if
|
|
508
|
+
if current_app.config.get("FAB_ADD_SECURITY_PERMISSION_VIEWS_VIEW", True):
|
|
524
509
|
self.appbuilder.add_view(
|
|
525
510
|
self.permissionmodelview,
|
|
526
511
|
"Permission Pairs",
|
|
@@ -530,12 +515,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
530
515
|
)
|
|
531
516
|
|
|
532
517
|
@property
|
|
533
|
-
def
|
|
534
|
-
return self.appbuilder.
|
|
518
|
+
def session(self):
|
|
519
|
+
return self.appbuilder.session
|
|
535
520
|
|
|
536
521
|
def create_login_manager(self) -> LoginManager:
|
|
537
522
|
"""Create the login manager."""
|
|
538
|
-
lm = LoginManager(
|
|
523
|
+
lm = LoginManager(current_app)
|
|
539
524
|
lm.anonymous_user = AnonymousUser
|
|
540
525
|
lm.login_view = "login"
|
|
541
526
|
lm.user_loader(self.load_user)
|
|
@@ -544,7 +529,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
544
529
|
def create_jwt_manager(self):
|
|
545
530
|
"""Create the JWT manager."""
|
|
546
531
|
jwt_manager = JWTManager()
|
|
547
|
-
jwt_manager.init_app(
|
|
532
|
+
jwt_manager.init_app(current_app)
|
|
548
533
|
jwt_manager.user_lookup_loader(self.load_user_jwt)
|
|
549
534
|
|
|
550
535
|
def reset_password(self, userid: int, password: str) -> bool:
|
|
@@ -562,9 +547,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
562
547
|
return self.update_user(user)
|
|
563
548
|
|
|
564
549
|
def reset_user_sessions(self, user: User) -> None:
|
|
565
|
-
if isinstance(
|
|
566
|
-
interface =
|
|
567
|
-
session = interface.
|
|
550
|
+
if isinstance(current_app.session_interface, AirflowDatabaseSessionInterface):
|
|
551
|
+
interface = current_app.session_interface
|
|
552
|
+
session = interface.client.session
|
|
568
553
|
user_session_model = interface.sql_session_model
|
|
569
554
|
num_sessions = session.scalars(select(func.count()).select_from(user_session_model)).one()
|
|
570
555
|
if num_sessions > MAX_NUM_DATABASE_USER_SESSIONS:
|
|
@@ -582,7 +567,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
582
567
|
)
|
|
583
568
|
else:
|
|
584
569
|
for s in session.scalars(select(user_session_model)).all():
|
|
585
|
-
session_details = interface.serializer.
|
|
570
|
+
session_details = interface.serializer.decode(want_bytes(s.data))
|
|
586
571
|
if session_details.get("_user_id") == user.id:
|
|
587
572
|
session.delete(s)
|
|
588
573
|
session.commit()
|
|
@@ -601,7 +586,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
601
586
|
def load_user_jwt(self, _jwt_header, jwt_data):
|
|
602
587
|
identity = jwt_data["sub"]
|
|
603
588
|
user = self.load_user(identity)
|
|
604
|
-
if user.is_active:
|
|
589
|
+
if user and user.is_active:
|
|
605
590
|
# Set flask g.user to JWT user, we can't do it on before request
|
|
606
591
|
g.user = user
|
|
607
592
|
return user
|
|
@@ -609,165 +594,169 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
609
594
|
@property
|
|
610
595
|
def auth_type(self):
|
|
611
596
|
"""Get the auth type."""
|
|
612
|
-
return
|
|
597
|
+
return current_app.config["AUTH_TYPE"]
|
|
613
598
|
|
|
614
599
|
@property
|
|
615
600
|
def is_auth_limited(self) -> bool:
|
|
616
601
|
"""Is the auth rate limited."""
|
|
617
|
-
return
|
|
602
|
+
return current_app.config["AUTH_RATE_LIMITED"]
|
|
618
603
|
|
|
619
604
|
@property
|
|
620
605
|
def auth_rate_limit(self) -> str:
|
|
621
606
|
"""Get the auth rate limit."""
|
|
622
|
-
return
|
|
607
|
+
return current_app.config["AUTH_RATE_LIMIT"]
|
|
623
608
|
|
|
624
609
|
@property
|
|
625
610
|
def auth_role_public(self):
|
|
626
611
|
"""Get the public role."""
|
|
627
|
-
return
|
|
612
|
+
return current_app.config.get("AUTH_ROLE_PUBLIC", None)
|
|
628
613
|
|
|
629
614
|
@property
|
|
630
615
|
def oauth_providers(self):
|
|
631
616
|
"""Oauth providers."""
|
|
632
|
-
return
|
|
617
|
+
return current_app.config["OAUTH_PROVIDERS"]
|
|
633
618
|
|
|
634
619
|
@property
|
|
635
620
|
def auth_ldap_tls_cacertdir(self):
|
|
636
621
|
"""LDAP TLS CA certificate directory."""
|
|
637
|
-
return
|
|
622
|
+
return current_app.config["AUTH_LDAP_TLS_CACERTDIR"]
|
|
638
623
|
|
|
639
624
|
@property
|
|
640
625
|
def auth_ldap_tls_cacertfile(self):
|
|
641
626
|
"""LDAP TLS CA certificate file."""
|
|
642
|
-
return
|
|
627
|
+
return current_app.config["AUTH_LDAP_TLS_CACERTFILE"]
|
|
643
628
|
|
|
644
629
|
@property
|
|
645
630
|
def auth_ldap_tls_certfile(self):
|
|
646
631
|
"""LDAP TLS certificate file."""
|
|
647
|
-
return
|
|
632
|
+
return current_app.config["AUTH_LDAP_TLS_CERTFILE"]
|
|
648
633
|
|
|
649
634
|
@property
|
|
650
635
|
def auth_ldap_tls_keyfile(self):
|
|
651
636
|
"""LDAP TLS key file."""
|
|
652
|
-
return
|
|
637
|
+
return current_app.config["AUTH_LDAP_TLS_KEYFILE"]
|
|
638
|
+
|
|
639
|
+
@property
|
|
640
|
+
def auth_ldap_use_nested_groups_for_roles(self):
|
|
641
|
+
return self.appbuilder.get_app.config["AUTH_LDAP_USE_NESTED_GROUPS_FOR_ROLES"]
|
|
653
642
|
|
|
654
643
|
@property
|
|
655
644
|
def auth_ldap_allow_self_signed(self):
|
|
656
645
|
"""LDAP allow self signed."""
|
|
657
|
-
return
|
|
646
|
+
return current_app.config["AUTH_LDAP_ALLOW_SELF_SIGNED"]
|
|
658
647
|
|
|
659
648
|
@property
|
|
660
649
|
def auth_ldap_tls_demand(self):
|
|
661
650
|
"""LDAP TLS demand."""
|
|
662
|
-
return
|
|
651
|
+
return current_app.config["AUTH_LDAP_TLS_DEMAND"]
|
|
663
652
|
|
|
664
653
|
@property
|
|
665
654
|
def auth_ldap_server(self):
|
|
666
655
|
"""Get the LDAP server object."""
|
|
667
|
-
return
|
|
656
|
+
return current_app.config["AUTH_LDAP_SERVER"]
|
|
668
657
|
|
|
669
658
|
@property
|
|
670
659
|
def auth_ldap_use_tls(self):
|
|
671
660
|
"""Should LDAP use TLS."""
|
|
672
|
-
return
|
|
661
|
+
return current_app.config["AUTH_LDAP_USE_TLS"]
|
|
673
662
|
|
|
674
663
|
@property
|
|
675
664
|
def auth_ldap_bind_user(self):
|
|
676
665
|
"""LDAP bind user."""
|
|
677
|
-
return
|
|
666
|
+
return current_app.config["AUTH_LDAP_BIND_USER"]
|
|
678
667
|
|
|
679
668
|
@property
|
|
680
669
|
def auth_ldap_bind_password(self):
|
|
681
670
|
"""LDAP bind password."""
|
|
682
|
-
return
|
|
671
|
+
return current_app.config["AUTH_LDAP_BIND_PASSWORD"]
|
|
683
672
|
|
|
684
673
|
@property
|
|
685
674
|
def auth_ldap_search(self):
|
|
686
675
|
"""LDAP search object."""
|
|
687
|
-
return
|
|
676
|
+
return current_app.config["AUTH_LDAP_SEARCH"]
|
|
688
677
|
|
|
689
678
|
@property
|
|
690
679
|
def auth_ldap_search_filter(self):
|
|
691
680
|
"""LDAP search filter."""
|
|
692
|
-
return
|
|
681
|
+
return current_app.config["AUTH_LDAP_SEARCH_FILTER"]
|
|
693
682
|
|
|
694
683
|
@property
|
|
695
684
|
def auth_ldap_uid_field(self):
|
|
696
685
|
"""LDAP UID field."""
|
|
697
|
-
return
|
|
686
|
+
return current_app.config["AUTH_LDAP_UID_FIELD"]
|
|
698
687
|
|
|
699
688
|
@property
|
|
700
689
|
def auth_ldap_firstname_field(self):
|
|
701
690
|
"""LDAP first name field."""
|
|
702
|
-
return
|
|
691
|
+
return current_app.config["AUTH_LDAP_FIRSTNAME_FIELD"]
|
|
703
692
|
|
|
704
693
|
@property
|
|
705
694
|
def auth_ldap_lastname_field(self):
|
|
706
695
|
"""LDAP last name field."""
|
|
707
|
-
return
|
|
696
|
+
return current_app.config["AUTH_LDAP_LASTNAME_FIELD"]
|
|
708
697
|
|
|
709
698
|
@property
|
|
710
699
|
def auth_ldap_email_field(self):
|
|
711
700
|
"""LDAP email field."""
|
|
712
|
-
return
|
|
701
|
+
return current_app.config["AUTH_LDAP_EMAIL_FIELD"]
|
|
713
702
|
|
|
714
703
|
@property
|
|
715
704
|
def auth_ldap_append_domain(self):
|
|
716
705
|
"""LDAP append domain."""
|
|
717
|
-
return
|
|
706
|
+
return current_app.config["AUTH_LDAP_APPEND_DOMAIN"]
|
|
718
707
|
|
|
719
708
|
@property
|
|
720
709
|
def auth_ldap_username_format(self):
|
|
721
710
|
"""LDAP username format."""
|
|
722
|
-
return
|
|
711
|
+
return current_app.config["AUTH_LDAP_USERNAME_FORMAT"]
|
|
723
712
|
|
|
724
713
|
@property
|
|
725
714
|
def auth_ldap_group_field(self) -> str:
|
|
726
715
|
"""LDAP group field."""
|
|
727
|
-
return
|
|
716
|
+
return current_app.config["AUTH_LDAP_GROUP_FIELD"]
|
|
728
717
|
|
|
729
718
|
@property
|
|
730
719
|
def auth_roles_mapping(self) -> dict[str, list[str]]:
|
|
731
720
|
"""The mapping of auth roles."""
|
|
732
|
-
return
|
|
721
|
+
return current_app.config["AUTH_ROLES_MAPPING"]
|
|
733
722
|
|
|
734
723
|
@property
|
|
735
724
|
def auth_user_registration_role_jmespath(self) -> str:
|
|
736
725
|
"""The JMESPATH role to use for user registration."""
|
|
737
|
-
return
|
|
726
|
+
return current_app.config["AUTH_USER_REGISTRATION_ROLE_JMESPATH"]
|
|
738
727
|
|
|
739
728
|
@property
|
|
740
729
|
def auth_username_ci(self):
|
|
741
730
|
"""Get the auth username for CI."""
|
|
742
|
-
return
|
|
731
|
+
return current_app.config.get("AUTH_USERNAME_CI", True)
|
|
743
732
|
|
|
744
733
|
@property
|
|
745
734
|
def auth_user_registration(self):
|
|
746
735
|
"""Will user self registration be allowed."""
|
|
747
|
-
return
|
|
736
|
+
return current_app.config["AUTH_USER_REGISTRATION"]
|
|
748
737
|
|
|
749
738
|
@property
|
|
750
739
|
def auth_user_registration_role(self):
|
|
751
740
|
"""The default user self registration role."""
|
|
752
|
-
return
|
|
741
|
+
return current_app.config["AUTH_USER_REGISTRATION_ROLE"]
|
|
753
742
|
|
|
754
743
|
@property
|
|
755
744
|
def auth_roles_sync_at_login(self) -> bool:
|
|
756
745
|
"""Should roles be synced at login."""
|
|
757
|
-
return
|
|
746
|
+
return current_app.config["AUTH_ROLES_SYNC_AT_LOGIN"]
|
|
758
747
|
|
|
759
748
|
@property
|
|
760
749
|
def auth_role_admin(self):
|
|
761
750
|
"""Get the admin role."""
|
|
762
|
-
return
|
|
751
|
+
return current_app.config["AUTH_ROLE_ADMIN"]
|
|
763
752
|
|
|
764
753
|
@property
|
|
765
754
|
def oauth_whitelists(self):
|
|
766
755
|
return self.oauth_allow_list
|
|
767
756
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
return
|
|
757
|
+
@staticmethod
|
|
758
|
+
def create_builtin_roles():
|
|
759
|
+
return current_app.config.get("FAB_ROLES", {})
|
|
771
760
|
|
|
772
761
|
@property
|
|
773
762
|
def builtin_roles(self):
|
|
@@ -776,7 +765,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
776
765
|
|
|
777
766
|
@property
|
|
778
767
|
def api_login_allow_multiple_providers(self):
|
|
779
|
-
return
|
|
768
|
+
return current_app.config["AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS"]
|
|
780
769
|
|
|
781
770
|
@property
|
|
782
771
|
def auth_type_provider_name(self):
|
|
@@ -789,33 +778,32 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
789
778
|
|
|
790
779
|
:meta private:
|
|
791
780
|
"""
|
|
792
|
-
app = self.appbuilder.get_app
|
|
793
781
|
# Base Security Config
|
|
794
|
-
|
|
795
|
-
|
|
782
|
+
current_app.config.setdefault("AUTH_ROLE_ADMIN", "Admin")
|
|
783
|
+
current_app.config.setdefault("AUTH_TYPE", AUTH_DB)
|
|
796
784
|
# Self Registration
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
785
|
+
current_app.config.setdefault("AUTH_USER_REGISTRATION", False)
|
|
786
|
+
current_app.config.setdefault("AUTH_USER_REGISTRATION_ROLE", self.auth_role_public)
|
|
787
|
+
current_app.config.setdefault("AUTH_USER_REGISTRATION_ROLE_JMESPATH", None)
|
|
800
788
|
# Role Mapping
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
789
|
+
current_app.config.setdefault("AUTH_ROLES_MAPPING", {})
|
|
790
|
+
current_app.config.setdefault("AUTH_ROLES_SYNC_AT_LOGIN", False)
|
|
791
|
+
current_app.config.setdefault("AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS", False)
|
|
804
792
|
|
|
805
793
|
from packaging.version import Version
|
|
806
794
|
from werkzeug import __version__ as werkzeug_version
|
|
807
795
|
|
|
808
796
|
parsed_werkzeug_version = Version(werkzeug_version)
|
|
809
797
|
if parsed_werkzeug_version < Version("3.0.0"):
|
|
810
|
-
|
|
811
|
-
|
|
798
|
+
current_app.config.setdefault("FAB_PASSWORD_HASH_METHOD", "pbkdf2:sha256")
|
|
799
|
+
current_app.config.setdefault(
|
|
812
800
|
"AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
|
|
813
801
|
"pbkdf2:sha256:150000$Z3t6fmj2$22da622d94a1f8118"
|
|
814
802
|
"c0976a03d2f18f680bfff877c9a965db9eedc51bc0be87c",
|
|
815
803
|
)
|
|
816
804
|
else:
|
|
817
|
-
|
|
818
|
-
|
|
805
|
+
current_app.config.setdefault("FAB_PASSWORD_HASH_METHOD", "scrypt")
|
|
806
|
+
current_app.config.setdefault(
|
|
819
807
|
"AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
|
|
820
808
|
"scrypt:32768:8:1$wiDa0ruWlIPhp9LM$6e409d093e62ad54df2af895d0e125b05ff6cf6414"
|
|
821
809
|
"8350189ffc4bcc71286edf1b8ad94a442c00f890224bf2b32153d0750c89ee9"
|
|
@@ -824,35 +812,38 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
824
812
|
|
|
825
813
|
# LDAP Config
|
|
826
814
|
if self.auth_type == AUTH_LDAP:
|
|
827
|
-
if "AUTH_LDAP_SERVER" not in
|
|
815
|
+
if "AUTH_LDAP_SERVER" not in current_app.config:
|
|
828
816
|
raise ValueError("No AUTH_LDAP_SERVER defined on config with AUTH_LDAP authentication type.")
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
817
|
+
current_app.config.setdefault("AUTH_LDAP_SEARCH", "")
|
|
818
|
+
current_app.config.setdefault("AUTH_LDAP_SEARCH_FILTER", "")
|
|
819
|
+
current_app.config.setdefault("AUTH_LDAP_APPEND_DOMAIN", "")
|
|
820
|
+
current_app.config.setdefault("AUTH_LDAP_USERNAME_FORMAT", "")
|
|
821
|
+
current_app.config.setdefault("AUTH_LDAP_BIND_USER", "")
|
|
822
|
+
current_app.config.setdefault("AUTH_LDAP_BIND_PASSWORD", "")
|
|
835
823
|
# TLS options
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
824
|
+
current_app.config.setdefault("AUTH_LDAP_USE_TLS", False)
|
|
825
|
+
current_app.config.setdefault("AUTH_LDAP_ALLOW_SELF_SIGNED", False)
|
|
826
|
+
current_app.config.setdefault("AUTH_LDAP_TLS_DEMAND", False)
|
|
827
|
+
current_app.config.setdefault("AUTH_LDAP_TLS_CACERTDIR", "")
|
|
828
|
+
current_app.config.setdefault("AUTH_LDAP_TLS_CACERTFILE", "")
|
|
829
|
+
current_app.config.setdefault("AUTH_LDAP_TLS_CERTFILE", "")
|
|
830
|
+
current_app.config.setdefault("AUTH_LDAP_TLS_KEYFILE", "")
|
|
843
831
|
# Mapping options
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
832
|
+
current_app.config.setdefault("AUTH_LDAP_UID_FIELD", "uid")
|
|
833
|
+
current_app.config.setdefault("AUTH_LDAP_GROUP_FIELD", "memberOf")
|
|
834
|
+
current_app.config.setdefault("AUTH_LDAP_FIRSTNAME_FIELD", "givenName")
|
|
835
|
+
current_app.config.setdefault("AUTH_LDAP_LASTNAME_FIELD", "sn")
|
|
836
|
+
current_app.config.setdefault("AUTH_LDAP_EMAIL_FIELD", "mail")
|
|
837
|
+
|
|
838
|
+
# Nested groups options
|
|
839
|
+
current_app.config.setdefault("AUTH_LDAP_USE_NESTED_GROUPS_FOR_ROLES", False)
|
|
849
840
|
|
|
850
841
|
if self.auth_type == AUTH_REMOTE_USER:
|
|
851
|
-
|
|
842
|
+
current_app.config.setdefault("AUTH_REMOTE_USER_ENV_VAR", "REMOTE_USER")
|
|
852
843
|
|
|
853
844
|
# Rate limiting
|
|
854
|
-
|
|
855
|
-
|
|
845
|
+
current_app.config.setdefault("AUTH_RATE_LIMITED", True)
|
|
846
|
+
current_app.config.setdefault("AUTH_RATE_LIMIT", "5 per 40 second")
|
|
856
847
|
|
|
857
848
|
def _init_auth(self):
|
|
858
849
|
"""
|
|
@@ -860,11 +851,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
860
851
|
|
|
861
852
|
:meta private:
|
|
862
853
|
"""
|
|
863
|
-
app = self.appbuilder.get_app
|
|
864
854
|
if self.auth_type == AUTH_OAUTH:
|
|
865
855
|
from authlib.integrations.flask_client import OAuth
|
|
866
856
|
|
|
867
|
-
self.oauth = OAuth(
|
|
857
|
+
self.oauth = OAuth(current_app)
|
|
868
858
|
self.oauth_remotes = {}
|
|
869
859
|
for provider in self.oauth_providers:
|
|
870
860
|
provider_name = provider["name"]
|
|
@@ -884,8 +874,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
884
874
|
self.userdbmodelview.datamodel = user_data_model
|
|
885
875
|
elif self.auth_type == const.AUTH_LDAP:
|
|
886
876
|
self.userldapmodelview.datamodel = user_data_model
|
|
887
|
-
elif self.auth_type == const.AUTH_OID:
|
|
888
|
-
self.useroidmodelview.datamodel = user_data_model
|
|
889
877
|
elif self.auth_type == const.AUTH_OAUTH:
|
|
890
878
|
self.useroauthmodelview.datamodel = user_data_model
|
|
891
879
|
elif self.auth_type == const.AUTH_REMOTE_USER:
|
|
@@ -908,37 +896,40 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
908
896
|
|
|
909
897
|
Creates admin and public roles if they don't exist.
|
|
910
898
|
"""
|
|
911
|
-
if not
|
|
912
|
-
log.debug("Skipping db since appbuilder disables update_perms")
|
|
899
|
+
if not current_app.config.get("FAB_CREATE_DB", True):
|
|
913
900
|
return
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
901
|
+
if not has_app_context():
|
|
902
|
+
# Create a new application context
|
|
903
|
+
with current_app.app_context():
|
|
904
|
+
self._create_db()
|
|
905
|
+
else:
|
|
906
|
+
self._create_db()
|
|
907
|
+
|
|
908
|
+
def _create_db(self) -> None:
|
|
909
|
+
engine = self.session.get_bind(mapper=None, clause=None)
|
|
910
|
+
inspector = inspect(engine)
|
|
911
|
+
existing_tables = inspector.get_table_names()
|
|
912
|
+
if "ab_user" not in existing_tables or "ab_group" not in existing_tables:
|
|
913
|
+
log.info(const.LOGMSG_INF_SEC_NO_DB)
|
|
914
|
+
Model.metadata.create_all(engine)
|
|
915
|
+
log.info(const.LOGMSG_INF_SEC_ADD_DB)
|
|
916
|
+
|
|
917
|
+
roles_mapping = current_app.config.get("FAB_ROLES_MAPPING", {})
|
|
918
|
+
for pk, name in roles_mapping.items():
|
|
919
|
+
self.update_role(pk, name)
|
|
920
|
+
for role_name in self._builtin_roles:
|
|
921
|
+
self.add_role(role_name)
|
|
922
|
+
if self.auth_role_admin not in self._builtin_roles:
|
|
923
|
+
self.add_role(self.auth_role_admin)
|
|
924
|
+
if self.auth_role_public:
|
|
925
|
+
self.add_role(self.auth_role_public)
|
|
926
|
+
if self.count_users() == 0 and self.auth_role_public != self.auth_role_admin:
|
|
927
|
+
log.warning(const.LOGMSG_WAR_SEC_NO_USER)
|
|
937
928
|
|
|
938
929
|
def get_all_permissions(self) -> set[tuple[str, str]]:
|
|
939
930
|
"""Return all permissions as a set of tuples with the action and resource names."""
|
|
940
931
|
return set(
|
|
941
|
-
self.
|
|
932
|
+
self.session.execute(
|
|
942
933
|
select(self.action_model.name, self.resource_model.name)
|
|
943
934
|
.join(self.permission_model.action)
|
|
944
935
|
.join(self.permission_model.resource)
|
|
@@ -1181,7 +1172,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1181
1172
|
for role in custom_roles:
|
|
1182
1173
|
self.add_permission_to_role(role, website_permission)
|
|
1183
1174
|
|
|
1184
|
-
self.
|
|
1175
|
+
self.session.commit()
|
|
1185
1176
|
|
|
1186
1177
|
def update_admin_permission(self) -> None:
|
|
1187
1178
|
"""
|
|
@@ -1191,26 +1182,24 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1191
1182
|
because Admin already has Dags permission.
|
|
1192
1183
|
Add the missing ones to the table for admin.
|
|
1193
1184
|
"""
|
|
1194
|
-
session = self.appbuilder.get_session
|
|
1195
1185
|
prefixes = getattr(permissions, "PREFIX_LIST", [permissions.RESOURCE_DAG_PREFIX])
|
|
1196
|
-
dag_resources = session.scalars(
|
|
1186
|
+
dag_resources = self.session.scalars(
|
|
1197
1187
|
select(Resource).where(or_(*[Resource.name.like(f"{prefix}%") for prefix in prefixes]))
|
|
1198
1188
|
)
|
|
1199
1189
|
resource_ids = [resource.id for resource in dag_resources]
|
|
1200
1190
|
|
|
1201
|
-
perms = session.scalars(select(Permission).where(~Permission.resource_id.in_(resource_ids)))
|
|
1191
|
+
perms = self.session.scalars(select(Permission).where(~Permission.resource_id.in_(resource_ids)))
|
|
1202
1192
|
perms = [p for p in perms if p.action and p.resource]
|
|
1203
1193
|
|
|
1204
1194
|
admin = self.find_role("Admin")
|
|
1205
1195
|
admin.permissions = list(set(admin.permissions) | set(perms))
|
|
1206
1196
|
|
|
1207
|
-
session.commit()
|
|
1197
|
+
self.session.commit()
|
|
1208
1198
|
|
|
1209
1199
|
def clean_perms(self) -> None:
|
|
1210
1200
|
"""FAB leaves faulty permissions that need to be cleaned up."""
|
|
1211
1201
|
self.log.debug("Cleaning faulty perms")
|
|
1212
|
-
|
|
1213
|
-
perms = sesh.scalars(
|
|
1202
|
+
perms = self.session.scalars(
|
|
1214
1203
|
select(Permission).where(
|
|
1215
1204
|
or_(
|
|
1216
1205
|
Permission.action == None, # noqa: E711
|
|
@@ -1224,9 +1213,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1224
1213
|
|
|
1225
1214
|
deleted_count = 0
|
|
1226
1215
|
for perm in perms:
|
|
1227
|
-
|
|
1216
|
+
self.session.delete(perm)
|
|
1228
1217
|
deleted_count += 1
|
|
1229
|
-
|
|
1218
|
+
self.session.commit()
|
|
1230
1219
|
if deleted_count:
|
|
1231
1220
|
self.log.info("Deleted %s faulty permissions", deleted_count)
|
|
1232
1221
|
|
|
@@ -1259,17 +1248,17 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1259
1248
|
|
|
1260
1249
|
def update_role(self, role_id, name: str) -> Role | None:
|
|
1261
1250
|
"""Update a role in the database."""
|
|
1262
|
-
role = self.
|
|
1251
|
+
role = self.session.get(self.role_model, role_id)
|
|
1263
1252
|
if not role:
|
|
1264
1253
|
return None
|
|
1265
1254
|
try:
|
|
1266
1255
|
role.name = name
|
|
1267
|
-
self.
|
|
1268
|
-
self.
|
|
1256
|
+
self.session.merge(role)
|
|
1257
|
+
self.session.commit()
|
|
1269
1258
|
log.info(const.LOGMSG_INF_SEC_UPD_ROLE, role)
|
|
1270
1259
|
except Exception as e:
|
|
1271
1260
|
log.error(const.LOGMSG_ERR_SEC_UPD_ROLE, e)
|
|
1272
|
-
self.
|
|
1261
|
+
self.session.rollback()
|
|
1273
1262
|
return None
|
|
1274
1263
|
return role
|
|
1275
1264
|
|
|
@@ -1280,13 +1269,13 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1280
1269
|
try:
|
|
1281
1270
|
role = self.role_model()
|
|
1282
1271
|
role.name = name
|
|
1283
|
-
self.
|
|
1284
|
-
self.
|
|
1272
|
+
self.session.add(role)
|
|
1273
|
+
self.session.commit()
|
|
1285
1274
|
log.info(const.LOGMSG_INF_SEC_ADD_ROLE, name)
|
|
1286
1275
|
return role
|
|
1287
1276
|
except Exception as e:
|
|
1288
1277
|
log.error(const.LOGMSG_ERR_SEC_ADD_ROLE, e)
|
|
1289
|
-
self.
|
|
1278
|
+
self.session.rollback()
|
|
1290
1279
|
return role
|
|
1291
1280
|
|
|
1292
1281
|
def find_role(self, name):
|
|
@@ -1295,10 +1284,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1295
1284
|
|
|
1296
1285
|
:param name: the role name
|
|
1297
1286
|
"""
|
|
1298
|
-
return self.
|
|
1287
|
+
return self.session.scalars(select(self.role_model).filter_by(name=name)).unique().one_or_none()
|
|
1299
1288
|
|
|
1300
1289
|
def get_all_roles(self):
|
|
1301
|
-
return self.
|
|
1290
|
+
return self.session.scalars(select(self.role_model)).unique().all()
|
|
1302
1291
|
|
|
1303
1292
|
def delete_role(self, role_name: str) -> None:
|
|
1304
1293
|
"""
|
|
@@ -1306,12 +1295,11 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1306
1295
|
|
|
1307
1296
|
:param role_name: the name of a role in the ab_role table
|
|
1308
1297
|
"""
|
|
1309
|
-
|
|
1310
|
-
role = session.scalars(select(Role).where(Role.name == role_name)).first()
|
|
1298
|
+
role = self.session.scalars(select(Role).where(Role.name == role_name)).first()
|
|
1311
1299
|
if role:
|
|
1312
1300
|
log.info("Deleting role '%s'", role_name)
|
|
1313
|
-
session.execute(delete(Role).where(Role.name == role_name))
|
|
1314
|
-
session.commit()
|
|
1301
|
+
self.session.execute(delete(Role).where(Role.name == role_name))
|
|
1302
|
+
self.session.commit()
|
|
1315
1303
|
else:
|
|
1316
1304
|
raise AirflowException(f"Role named '{role_name}' does not exist")
|
|
1317
1305
|
|
|
@@ -1342,7 +1330,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1342
1330
|
|
|
1343
1331
|
def get_public_role(self):
|
|
1344
1332
|
return (
|
|
1345
|
-
self.
|
|
1333
|
+
self.session.scalars(select(self.role_model).filter_by(name=self.auth_role_public))
|
|
1346
1334
|
.unique()
|
|
1347
1335
|
.one_or_none()
|
|
1348
1336
|
)
|
|
@@ -1376,33 +1364,34 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1376
1364
|
user.username = username
|
|
1377
1365
|
user.email = email
|
|
1378
1366
|
user.active = True
|
|
1379
|
-
self.
|
|
1367
|
+
self.session.add(user)
|
|
1380
1368
|
user.roles = roles
|
|
1381
1369
|
user.groups = groups or []
|
|
1382
1370
|
if hashed_password:
|
|
1383
1371
|
user.password = hashed_password
|
|
1384
1372
|
else:
|
|
1385
1373
|
user.password = generate_password_hash(password)
|
|
1386
|
-
self.
|
|
1374
|
+
self.session.commit()
|
|
1387
1375
|
log.info(const.LOGMSG_INF_SEC_ADD_USER, username)
|
|
1388
1376
|
|
|
1389
1377
|
return user
|
|
1390
1378
|
except Exception as e:
|
|
1391
1379
|
log.error(const.LOGMSG_ERR_SEC_ADD_USER, e)
|
|
1392
|
-
self.
|
|
1380
|
+
self.session.rollback()
|
|
1393
1381
|
return False
|
|
1394
1382
|
|
|
1395
|
-
def load_user(self,
|
|
1396
|
-
user = self.get_user_by_id(int(
|
|
1397
|
-
if user.is_active:
|
|
1383
|
+
def load_user(self, pk: int) -> Any | None:
|
|
1384
|
+
user = self.get_user_by_id(int(pk))
|
|
1385
|
+
if user and user.is_active:
|
|
1398
1386
|
return user
|
|
1387
|
+
return None
|
|
1399
1388
|
|
|
1400
1389
|
def get_user_by_id(self, pk):
|
|
1401
|
-
return self.
|
|
1390
|
+
return self.session.get(self.user_model, pk)
|
|
1402
1391
|
|
|
1403
1392
|
def count_users(self):
|
|
1404
1393
|
"""Return the number of users in the database."""
|
|
1405
|
-
return self.
|
|
1394
|
+
return self.session.scalar(select(func.count(self.user_model.id)))
|
|
1406
1395
|
|
|
1407
1396
|
def add_register_user(self, username, first_name, last_name, email, password="", hashed_password=""):
|
|
1408
1397
|
"""
|
|
@@ -1421,12 +1410,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1421
1410
|
register_user.password = generate_password_hash(password)
|
|
1422
1411
|
register_user.registration_hash = str(uuid.uuid1())
|
|
1423
1412
|
try:
|
|
1424
|
-
self.
|
|
1425
|
-
self.
|
|
1413
|
+
self.session.add(register_user)
|
|
1414
|
+
self.session.commit()
|
|
1426
1415
|
return register_user
|
|
1427
1416
|
except Exception as e:
|
|
1428
1417
|
log.error(const.LOGMSG_ERR_SEC_ADD_REGISTER_USER, e)
|
|
1429
|
-
self.
|
|
1418
|
+
self.session.rollback()
|
|
1430
1419
|
return None
|
|
1431
1420
|
|
|
1432
1421
|
def find_user(self, username=None, email=None):
|
|
@@ -1434,12 +1423,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1434
1423
|
if username:
|
|
1435
1424
|
try:
|
|
1436
1425
|
if self.auth_username_ci:
|
|
1437
|
-
return self.
|
|
1426
|
+
return self.session.scalars(
|
|
1438
1427
|
select(self.user_model).where(
|
|
1439
1428
|
func.lower(self.user_model.username) == func.lower(username)
|
|
1440
1429
|
)
|
|
1441
1430
|
).one_or_none()
|
|
1442
|
-
return self.
|
|
1431
|
+
return self.session.scalars(
|
|
1443
1432
|
select(self.user_model).where(
|
|
1444
1433
|
func.lower(self.user_model.username) == func.lower(username)
|
|
1445
1434
|
)
|
|
@@ -1449,19 +1438,19 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1449
1438
|
return None
|
|
1450
1439
|
elif email:
|
|
1451
1440
|
try:
|
|
1452
|
-
return self.
|
|
1441
|
+
return self.session.scalars(select(self.user_model).filter_by(email=email)).one_or_none()
|
|
1453
1442
|
except MultipleResultsFound:
|
|
1454
1443
|
log.error("Multiple results found for user with email %s", email)
|
|
1455
1444
|
return None
|
|
1456
1445
|
|
|
1457
1446
|
def update_user(self, user: User) -> bool:
|
|
1458
1447
|
try:
|
|
1459
|
-
self.
|
|
1460
|
-
self.
|
|
1448
|
+
self.session.merge(user)
|
|
1449
|
+
self.session.commit()
|
|
1461
1450
|
log.info(const.LOGMSG_INF_SEC_UPD_USER, user)
|
|
1462
1451
|
except Exception as e:
|
|
1463
1452
|
log.error(const.LOGMSG_ERR_SEC_UPD_USER, e)
|
|
1464
|
-
self.
|
|
1453
|
+
self.session.rollback()
|
|
1465
1454
|
return False
|
|
1466
1455
|
return True
|
|
1467
1456
|
|
|
@@ -1472,16 +1461,16 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1472
1461
|
:param register_user: RegisterUser object to delete
|
|
1473
1462
|
"""
|
|
1474
1463
|
try:
|
|
1475
|
-
self.
|
|
1476
|
-
self.
|
|
1464
|
+
self.session.delete(register_user)
|
|
1465
|
+
self.session.commit()
|
|
1477
1466
|
return True
|
|
1478
1467
|
except Exception as e:
|
|
1479
1468
|
log.error(const.LOGMSG_ERR_SEC_DEL_REGISTER_USER, e)
|
|
1480
|
-
self.
|
|
1469
|
+
self.session.rollback()
|
|
1481
1470
|
return False
|
|
1482
1471
|
|
|
1483
1472
|
def get_all_users(self):
|
|
1484
|
-
return self.
|
|
1473
|
+
return self.session.scalars(select(self.user_model)).all()
|
|
1485
1474
|
|
|
1486
1475
|
def update_user_auth_stat(self, user, success=True):
|
|
1487
1476
|
"""
|
|
@@ -1521,7 +1510,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1521
1510
|
|
|
1522
1511
|
:param name: name
|
|
1523
1512
|
"""
|
|
1524
|
-
return self.
|
|
1513
|
+
return self.session.scalars(select(self.action_model).filter_by(name=name)).one_or_none()
|
|
1525
1514
|
|
|
1526
1515
|
def create_action(self, name):
|
|
1527
1516
|
"""
|
|
@@ -1535,12 +1524,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1535
1524
|
try:
|
|
1536
1525
|
action = self.action_model()
|
|
1537
1526
|
action.name = name
|
|
1538
|
-
self.
|
|
1539
|
-
self.
|
|
1527
|
+
self.session.add(action)
|
|
1528
|
+
self.session.commit()
|
|
1540
1529
|
return action
|
|
1541
1530
|
except Exception as e:
|
|
1542
1531
|
log.error(const.LOGMSG_ERR_SEC_ADD_PERMISSION, e)
|
|
1543
|
-
self.
|
|
1532
|
+
self.session.rollback()
|
|
1544
1533
|
return action
|
|
1545
1534
|
|
|
1546
1535
|
def delete_action(self, name: str) -> bool:
|
|
@@ -1554,18 +1543,18 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1554
1543
|
log.warning(const.LOGMSG_WAR_SEC_DEL_PERMISSION, name)
|
|
1555
1544
|
return False
|
|
1556
1545
|
try:
|
|
1557
|
-
perms = self.
|
|
1558
|
-
select(self.permission_model).where(self.permission_model.
|
|
1546
|
+
perms = self.session.scalars(
|
|
1547
|
+
select(self.permission_model).where(self.permission_model.action_id == action.id)
|
|
1559
1548
|
).all()
|
|
1560
1549
|
if perms:
|
|
1561
1550
|
log.warning(const.LOGMSG_WAR_SEC_DEL_PERM_PVM, action, perms)
|
|
1562
1551
|
return False
|
|
1563
|
-
self.
|
|
1564
|
-
self.
|
|
1552
|
+
self.session.delete(action)
|
|
1553
|
+
self.session.commit()
|
|
1565
1554
|
return True
|
|
1566
1555
|
except Exception as e:
|
|
1567
1556
|
log.error(const.LOGMSG_ERR_SEC_DEL_PERMISSION, e)
|
|
1568
|
-
self.
|
|
1557
|
+
self.session.rollback()
|
|
1569
1558
|
return False
|
|
1570
1559
|
|
|
1571
1560
|
"""
|
|
@@ -1580,7 +1569,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1580
1569
|
|
|
1581
1570
|
:param name: Name of resource
|
|
1582
1571
|
"""
|
|
1583
|
-
return self.
|
|
1572
|
+
return self.session.scalars(select(self.resource_model).filter_by(name=name)).one_or_none()
|
|
1584
1573
|
|
|
1585
1574
|
def create_resource(self, name) -> Resource | None:
|
|
1586
1575
|
"""
|
|
@@ -1593,12 +1582,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1593
1582
|
try:
|
|
1594
1583
|
resource = self.resource_model()
|
|
1595
1584
|
resource.name = name
|
|
1596
|
-
self.
|
|
1597
|
-
self.
|
|
1585
|
+
self.session.add(resource)
|
|
1586
|
+
self.session.commit()
|
|
1598
1587
|
return resource
|
|
1599
1588
|
except Exception as e:
|
|
1600
1589
|
log.error(const.LOGMSG_ERR_SEC_ADD_VIEWMENU, e)
|
|
1601
|
-
self.
|
|
1590
|
+
self.session.rollback()
|
|
1602
1591
|
return resource
|
|
1603
1592
|
|
|
1604
1593
|
"""
|
|
@@ -1622,7 +1611,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1622
1611
|
resource = self.get_resource(resource_name)
|
|
1623
1612
|
if action and resource:
|
|
1624
1613
|
return (
|
|
1625
|
-
self.
|
|
1614
|
+
self.session.scalars(
|
|
1626
1615
|
select(self.permission_model).filter_by(action=action, resource=resource)
|
|
1627
1616
|
)
|
|
1628
1617
|
.unique()
|
|
@@ -1637,9 +1626,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1637
1626
|
|
|
1638
1627
|
:param resource: Object representing a single resource.
|
|
1639
1628
|
"""
|
|
1640
|
-
return self.
|
|
1641
|
-
select(self.permission_model).filter_by(resource_id=resource.id)
|
|
1642
|
-
).all()
|
|
1629
|
+
return self.session.scalars(select(self.permission_model).filter_by(resource_id=resource.id)).all()
|
|
1643
1630
|
|
|
1644
1631
|
def create_permission(self, action_name, resource_name) -> Permission | None:
|
|
1645
1632
|
"""
|
|
@@ -1663,13 +1650,13 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1663
1650
|
perm = self.permission_model()
|
|
1664
1651
|
perm.resource_id, perm.action_id = resource.id, action.id
|
|
1665
1652
|
try:
|
|
1666
|
-
self.
|
|
1667
|
-
self.
|
|
1653
|
+
self.session.add(perm)
|
|
1654
|
+
self.session.commit()
|
|
1668
1655
|
log.info(const.LOGMSG_INF_SEC_ADD_PERMVIEW, perm)
|
|
1669
1656
|
return perm
|
|
1670
1657
|
except Exception as e:
|
|
1671
1658
|
log.error(const.LOGMSG_ERR_SEC_ADD_PERMVIEW, e)
|
|
1672
|
-
self.
|
|
1659
|
+
self.session.rollback()
|
|
1673
1660
|
return None
|
|
1674
1661
|
|
|
1675
1662
|
def delete_permission(self, action_name: str, resource_name: str) -> None:
|
|
@@ -1686,7 +1673,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1686
1673
|
perm = self.get_permission(action_name, resource_name)
|
|
1687
1674
|
if not perm:
|
|
1688
1675
|
return
|
|
1689
|
-
roles = self.
|
|
1676
|
+
roles = self.session.scalars(
|
|
1690
1677
|
select(self.role_model).where(self.role_model.permissions.contains(perm))
|
|
1691
1678
|
).first()
|
|
1692
1679
|
if roles:
|
|
@@ -1694,17 +1681,15 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1694
1681
|
return
|
|
1695
1682
|
try:
|
|
1696
1683
|
# delete permission on resource
|
|
1697
|
-
self.
|
|
1698
|
-
self.
|
|
1684
|
+
self.session.delete(perm)
|
|
1685
|
+
self.session.commit()
|
|
1699
1686
|
# if no more permission on permission view, delete permission
|
|
1700
|
-
if not self.
|
|
1701
|
-
select(self.permission_model).filter_by(action=perm.action)
|
|
1702
|
-
).all():
|
|
1687
|
+
if not self.session.scalars(select(self.permission_model).filter_by(action=perm.action)).all():
|
|
1703
1688
|
self.delete_action(perm.action.name)
|
|
1704
1689
|
log.info(const.LOGMSG_INF_SEC_DEL_PERMVIEW, action_name, resource_name)
|
|
1705
1690
|
except Exception as e:
|
|
1706
1691
|
log.error(const.LOGMSG_ERR_SEC_DEL_PERMVIEW, e)
|
|
1707
|
-
self.
|
|
1692
|
+
self.session.rollback()
|
|
1708
1693
|
|
|
1709
1694
|
def add_permission_to_role(self, role: Role, permission: Permission | None) -> None:
|
|
1710
1695
|
"""
|
|
@@ -1716,12 +1701,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1716
1701
|
if permission and permission not in role.permissions:
|
|
1717
1702
|
try:
|
|
1718
1703
|
role.permissions.append(permission)
|
|
1719
|
-
self.
|
|
1720
|
-
self.
|
|
1704
|
+
self.session.merge(role)
|
|
1705
|
+
self.session.commit()
|
|
1721
1706
|
log.info(const.LOGMSG_INF_SEC_ADD_PERMROLE, permission, role.name)
|
|
1722
1707
|
except Exception as e:
|
|
1723
1708
|
log.error(const.LOGMSG_ERR_SEC_ADD_PERMROLE, e)
|
|
1724
|
-
self.
|
|
1709
|
+
self.session.rollback()
|
|
1725
1710
|
|
|
1726
1711
|
def remove_permission_from_role(self, role: Role, permission: Permission) -> None:
|
|
1727
1712
|
"""
|
|
@@ -1733,12 +1718,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1733
1718
|
if permission in role.permissions:
|
|
1734
1719
|
try:
|
|
1735
1720
|
role.permissions.remove(permission)
|
|
1736
|
-
self.
|
|
1737
|
-
self.
|
|
1721
|
+
self.session.merge(role)
|
|
1722
|
+
self.session.commit()
|
|
1738
1723
|
log.info(const.LOGMSG_INF_SEC_DEL_PERMROLE, permission, role.name)
|
|
1739
1724
|
except Exception as e:
|
|
1740
1725
|
log.error(const.LOGMSG_ERR_SEC_DEL_PERMROLE, e)
|
|
1741
|
-
self.
|
|
1726
|
+
self.session.rollback()
|
|
1742
1727
|
|
|
1743
1728
|
@staticmethod
|
|
1744
1729
|
def get_user_roles(user=None):
|
|
@@ -1974,7 +1959,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
1974
1959
|
if user is None or (not user.is_active):
|
|
1975
1960
|
# Balance failure and success
|
|
1976
1961
|
check_password_hash(
|
|
1977
|
-
|
|
1962
|
+
current_app.config["AUTH_DB_FAKE_PASSWORD_HASH_CHECK"],
|
|
1978
1963
|
"password",
|
|
1979
1964
|
)
|
|
1980
1965
|
log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
|
|
@@ -2331,11 +2316,52 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
2331
2316
|
user_dn = search_result[0][0]
|
|
2332
2317
|
# extract the other attributes
|
|
2333
2318
|
user_info = search_result[0][1]
|
|
2334
|
-
# return
|
|
2335
|
-
return user_dn, user_info
|
|
2336
2319
|
except (IndexError, NameError):
|
|
2337
2320
|
return None, None
|
|
2338
2321
|
|
|
2322
|
+
# get nested groups for user
|
|
2323
|
+
if self.auth_ldap_use_nested_groups_for_roles:
|
|
2324
|
+
nested_groups = self._ldap_get_nested_groups(ldap, con, user_dn)
|
|
2325
|
+
|
|
2326
|
+
if self.auth_ldap_group_field in user_info:
|
|
2327
|
+
user_info[self.auth_ldap_group_field].extend(nested_groups)
|
|
2328
|
+
else:
|
|
2329
|
+
user_info[self.auth_ldap_group_field] = nested_groups
|
|
2330
|
+
|
|
2331
|
+
# return
|
|
2332
|
+
return user_dn, user_info
|
|
2333
|
+
|
|
2334
|
+
def _ldap_get_nested_groups(self, ldap, con, user_dn) -> list[str]:
|
|
2335
|
+
"""
|
|
2336
|
+
Search nested groups for user.
|
|
2337
|
+
|
|
2338
|
+
Only for MS AD version.
|
|
2339
|
+
|
|
2340
|
+
:param ldap: The ldap module reference
|
|
2341
|
+
:param con: The ldap connection
|
|
2342
|
+
:param user_dn: user DN to match with CN
|
|
2343
|
+
:return: ldap groups array
|
|
2344
|
+
"""
|
|
2345
|
+
log.debug("Nested groups for LDAP enabled.")
|
|
2346
|
+
# filter for microsoft active directory only
|
|
2347
|
+
nested_groups_filter_str = f"(&(objectCategory=Group)(member:1.2.840.113556.1.4.1941:={user_dn}))"
|
|
2348
|
+
nested_groups_request_fields = ["cn"]
|
|
2349
|
+
|
|
2350
|
+
nested_groups_search_result = con.search_s(
|
|
2351
|
+
self.auth_ldap_search,
|
|
2352
|
+
ldap.SCOPE_SUBTREE,
|
|
2353
|
+
nested_groups_filter_str,
|
|
2354
|
+
nested_groups_request_fields,
|
|
2355
|
+
)
|
|
2356
|
+
log.debug(
|
|
2357
|
+
"LDAP search for nested groups returned: %s",
|
|
2358
|
+
nested_groups_search_result,
|
|
2359
|
+
)
|
|
2360
|
+
|
|
2361
|
+
nested_groups = [x[0].encode() for x in nested_groups_search_result if x[0] is not None]
|
|
2362
|
+
log.debug("LDAP nested groups for users: %s", nested_groups)
|
|
2363
|
+
return nested_groups
|
|
2364
|
+
|
|
2339
2365
|
@staticmethod
|
|
2340
2366
|
def _ldap_bind(ldap, con, dn: str, password: str) -> bool:
|
|
2341
2367
|
"""Validates/binds the provided dn/password with the LDAP sever."""
|
|
@@ -2381,7 +2407,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
2381
2407
|
resource = self.get_resource(resource_name)
|
|
2382
2408
|
perm = None
|
|
2383
2409
|
if action and resource:
|
|
2384
|
-
perm = self.
|
|
2410
|
+
perm = self.session.scalar(
|
|
2385
2411
|
select(self.permission_model).filter_by(action=action, resource=resource).limit(1)
|
|
2386
2412
|
)
|
|
2387
2413
|
if not perm and action_name and resource_name:
|
|
@@ -2391,7 +2417,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
2391
2417
|
"""Return a dict with a key of role name and value of role with early loaded permissions."""
|
|
2392
2418
|
return {
|
|
2393
2419
|
r.name: r
|
|
2394
|
-
for r in self.
|
|
2420
|
+
for r in self.session.scalars(
|
|
2395
2421
|
select(self.role_model).options(joinedload(self.role_model.permissions))
|
|
2396
2422
|
).unique()
|
|
2397
2423
|
}
|
|
@@ -2406,7 +2432,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
|
2406
2432
|
return {
|
|
2407
2433
|
(action_name, resource_name): viewmodel
|
|
2408
2434
|
for action_name, resource_name, viewmodel in (
|
|
2409
|
-
self.
|
|
2435
|
+
self.session.execute(
|
|
2410
2436
|
select(
|
|
2411
2437
|
self.action_model.name,
|
|
2412
2438
|
self.resource_model.name,
|