apache-airflow-providers-fab 2.4.4rc1__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.
Files changed (25) hide show
  1. airflow/providers/fab/__init__.py +1 -1
  2. airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +2 -2
  3. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +4 -4
  4. airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +7 -4
  5. airflow/providers/fab/auth_manager/cli_commands/user_command.py +1 -2
  6. airflow/providers/fab/auth_manager/cli_commands/utils.py +7 -3
  7. airflow/providers/fab/auth_manager/fab_auth_manager.py +15 -11
  8. airflow/providers/fab/auth_manager/models/__init__.py +179 -122
  9. airflow/providers/fab/auth_manager/models/db.py +11 -6
  10. airflow/providers/fab/auth_manager/security_manager/override.py +239 -213
  11. airflow/providers/fab/auth_manager/views/user.py +11 -5
  12. airflow/providers/fab/migrations/versions/0001_1_4_0_create_ab_tables_if_missing.py +1 -0
  13. airflow/providers/fab/www/app.py +3 -4
  14. airflow/providers/fab/www/extensions/init_appbuilder.py +26 -39
  15. airflow/providers/fab/www/extensions/init_session.py +2 -2
  16. airflow/providers/fab/www/security_appless.py +6 -1
  17. airflow/providers/fab/www/security_manager.py +4 -14
  18. airflow/providers/fab/www/session.py +26 -3
  19. airflow/providers/fab/www/utils.py +1 -208
  20. {apache_airflow_providers_fab-2.4.4rc1.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/METADATA +16 -10
  21. {apache_airflow_providers_fab-2.4.4rc1.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/RECORD +25 -25
  22. {apache_airflow_providers_fab-2.4.4rc1.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/WHEEL +0 -0
  23. {apache_airflow_providers_fab-2.4.4rc1.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/entry_points.txt +0 -0
  24. {apache_airflow_providers_fab-2.4.4rc1.dist-info → apache_airflow_providers_fab-3.0.0rc1.dist-info}/licenses/3rd-party-licenses/LICENSES-ui.txt +0 -0
  25. {apache_airflow_providers_fab-2.4.4rc1.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 self.appbuilder.get_app.config.get("FAB_ADD_SECURITY_VIEWS", True):
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 self.appbuilder.get_app.config.get("FAB_ADD_SECURITY_PERMISSION_VIEW", True):
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 self.appbuilder.get_app.config.get("FAB_ADD_SECURITY_VIEW_MENU_VIEW", True):
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 self.appbuilder.get_app.config.get("FAB_ADD_SECURITY_PERMISSION_VIEWS_VIEW", True):
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 get_session(self):
534
- return self.appbuilder.get_session
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(self.appbuilder.app)
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(self.appbuilder.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(self.appbuilder.get_app.session_interface, AirflowDatabaseSessionInterface):
566
- interface = self.appbuilder.get_app.session_interface
567
- session = interface.db.session
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.loads(want_bytes(s.data))
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 self.appbuilder.get_app.config["AUTH_TYPE"]
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 self.appbuilder.get_app.config["AUTH_RATE_LIMITED"]
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 self.appbuilder.get_app.config["AUTH_RATE_LIMIT"]
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 self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None)
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 self.appbuilder.get_app.config["OAUTH_PROVIDERS"]
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 self.appbuilder.get_app.config["AUTH_LDAP_TLS_CACERTDIR"]
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 self.appbuilder.get_app.config["AUTH_LDAP_TLS_CACERTFILE"]
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 self.appbuilder.get_app.config["AUTH_LDAP_TLS_CERTFILE"]
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 self.appbuilder.get_app.config["AUTH_LDAP_TLS_KEYFILE"]
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 self.appbuilder.get_app.config["AUTH_LDAP_ALLOW_SELF_SIGNED"]
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 self.appbuilder.get_app.config["AUTH_LDAP_TLS_DEMAND"]
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 self.appbuilder.get_app.config["AUTH_LDAP_SERVER"]
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 self.appbuilder.get_app.config["AUTH_LDAP_USE_TLS"]
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 self.appbuilder.get_app.config["AUTH_LDAP_BIND_USER"]
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 self.appbuilder.get_app.config["AUTH_LDAP_BIND_PASSWORD"]
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 self.appbuilder.get_app.config["AUTH_LDAP_SEARCH"]
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 self.appbuilder.get_app.config["AUTH_LDAP_SEARCH_FILTER"]
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 self.appbuilder.get_app.config["AUTH_LDAP_UID_FIELD"]
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 self.appbuilder.get_app.config["AUTH_LDAP_FIRSTNAME_FIELD"]
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 self.appbuilder.get_app.config["AUTH_LDAP_LASTNAME_FIELD"]
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 self.appbuilder.get_app.config["AUTH_LDAP_EMAIL_FIELD"]
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 self.appbuilder.get_app.config["AUTH_LDAP_APPEND_DOMAIN"]
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 self.appbuilder.get_app.config["AUTH_LDAP_USERNAME_FORMAT"]
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 self.appbuilder.get_app.config["AUTH_LDAP_GROUP_FIELD"]
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 self.appbuilder.get_app.config["AUTH_ROLES_MAPPING"]
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 self.appbuilder.get_app.config["AUTH_USER_REGISTRATION_ROLE_JMESPATH"]
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 self.appbuilder.get_app.config.get("AUTH_USERNAME_CI", True)
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 self.appbuilder.get_app.config["AUTH_USER_REGISTRATION"]
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 self.appbuilder.get_app.config["AUTH_USER_REGISTRATION_ROLE"]
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 self.appbuilder.get_app.config["AUTH_ROLES_SYNC_AT_LOGIN"]
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 self.appbuilder.get_app.config["AUTH_ROLE_ADMIN"]
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
- def create_builtin_roles(self):
769
- """Return FAB builtin roles."""
770
- return self.appbuilder.get_app.config.get("FAB_ROLES", {})
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 self.appbuilder.get_app.config["AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS"]
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
- app.config.setdefault("AUTH_ROLE_ADMIN", "Admin")
795
- app.config.setdefault("AUTH_TYPE", AUTH_DB)
782
+ current_app.config.setdefault("AUTH_ROLE_ADMIN", "Admin")
783
+ current_app.config.setdefault("AUTH_TYPE", AUTH_DB)
796
784
  # Self Registration
797
- app.config.setdefault("AUTH_USER_REGISTRATION", False)
798
- app.config.setdefault("AUTH_USER_REGISTRATION_ROLE", self.auth_role_public)
799
- app.config.setdefault("AUTH_USER_REGISTRATION_ROLE_JMESPATH", None)
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
- app.config.setdefault("AUTH_ROLES_MAPPING", {})
802
- app.config.setdefault("AUTH_ROLES_SYNC_AT_LOGIN", False)
803
- app.config.setdefault("AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS", False)
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
- app.config.setdefault("FAB_PASSWORD_HASH_METHOD", "pbkdf2:sha256")
811
- app.config.setdefault(
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
- app.config.setdefault("FAB_PASSWORD_HASH_METHOD", "scrypt")
818
- app.config.setdefault(
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 app.config:
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
- app.config.setdefault("AUTH_LDAP_SEARCH", "")
830
- app.config.setdefault("AUTH_LDAP_SEARCH_FILTER", "")
831
- app.config.setdefault("AUTH_LDAP_APPEND_DOMAIN", "")
832
- app.config.setdefault("AUTH_LDAP_USERNAME_FORMAT", "")
833
- app.config.setdefault("AUTH_LDAP_BIND_USER", "")
834
- app.config.setdefault("AUTH_LDAP_BIND_PASSWORD", "")
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
- app.config.setdefault("AUTH_LDAP_USE_TLS", False)
837
- app.config.setdefault("AUTH_LDAP_ALLOW_SELF_SIGNED", False)
838
- app.config.setdefault("AUTH_LDAP_TLS_DEMAND", False)
839
- app.config.setdefault("AUTH_LDAP_TLS_CACERTDIR", "")
840
- app.config.setdefault("AUTH_LDAP_TLS_CACERTFILE", "")
841
- app.config.setdefault("AUTH_LDAP_TLS_CERTFILE", "")
842
- app.config.setdefault("AUTH_LDAP_TLS_KEYFILE", "")
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
- app.config.setdefault("AUTH_LDAP_UID_FIELD", "uid")
845
- app.config.setdefault("AUTH_LDAP_GROUP_FIELD", "memberOf")
846
- app.config.setdefault("AUTH_LDAP_FIRSTNAME_FIELD", "givenName")
847
- app.config.setdefault("AUTH_LDAP_LASTNAME_FIELD", "sn")
848
- app.config.setdefault("AUTH_LDAP_EMAIL_FIELD", "mail")
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
- app.config.setdefault("AUTH_REMOTE_USER_ENV_VAR", "REMOTE_USER")
842
+ current_app.config.setdefault("AUTH_REMOTE_USER_ENV_VAR", "REMOTE_USER")
852
843
 
853
844
  # Rate limiting
854
- app.config.setdefault("AUTH_RATE_LIMITED", True)
855
- app.config.setdefault("AUTH_RATE_LIMIT", "5 per 40 second")
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(app)
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 self.appbuilder.update_perms:
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
- try:
915
- engine = self.get_session.get_bind(mapper=None, clause=None)
916
- inspector = inspect(engine)
917
- existing_tables = inspector.get_table_names()
918
- if "ab_user" not in existing_tables or "ab_group" not in existing_tables:
919
- log.info(const.LOGMSG_INF_SEC_NO_DB)
920
- Base.metadata.create_all(engine)
921
- log.info(const.LOGMSG_INF_SEC_ADD_DB)
922
-
923
- roles_mapping = self.appbuilder.get_app.config.get("FAB_ROLES_MAPPING", {})
924
- for pk, name in roles_mapping.items():
925
- self.update_role(pk, name)
926
- for role_name in self._builtin_roles:
927
- self.add_role(role_name)
928
- if self.auth_role_admin not in self._builtin_roles:
929
- self.add_role(self.auth_role_admin)
930
- if self.auth_role_public:
931
- self.add_role(self.auth_role_public)
932
- if self.count_users() == 0 and self.auth_role_public != self.auth_role_admin:
933
- log.warning(const.LOGMSG_WAR_SEC_NO_USER)
934
- except Exception:
935
- log.exception(const.LOGMSG_ERR_SEC_CREATE_DB)
936
- exit(1)
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.appbuilder.get_session.execute(
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.appbuilder.get_session.commit()
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
- sesh = self.appbuilder.get_session
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
- sesh.delete(perm)
1216
+ self.session.delete(perm)
1228
1217
  deleted_count += 1
1229
- sesh.commit()
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.get_session.get(self.role_model, role_id)
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.get_session.merge(role)
1268
- self.get_session.commit()
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.get_session.rollback()
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.get_session.add(role)
1284
- self.get_session.commit()
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.get_session.rollback()
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.get_session.scalars(select(self.role_model).filter_by(name=name)).unique().one_or_none()
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.get_session.scalars(select(self.role_model)).unique().all()
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
- session = self.get_session
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.get_session.scalars(select(self.role_model).filter_by(name=self.auth_role_public))
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.get_session.add(user)
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.get_session.commit()
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.get_session.rollback()
1380
+ self.session.rollback()
1393
1381
  return False
1394
1382
 
1395
- def load_user(self, user_id):
1396
- user = self.get_user_by_id(int(user_id))
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.get_session.get(self.user_model, pk)
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.get_session.scalar(select(func.count(self.user_model.id)))
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.get_session.add(register_user)
1425
- self.get_session.commit()
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.get_session.rollback()
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.get_session.scalars(
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.get_session.scalars(
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.get_session.scalars(select(self.user_model).filter_by(email=email)).one_or_none()
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.get_session.merge(user)
1460
- self.get_session.commit()
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.get_session.rollback()
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.get_session.delete(register_user)
1476
- self.get_session.commit()
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.get_session.rollback()
1469
+ self.session.rollback()
1481
1470
  return False
1482
1471
 
1483
1472
  def get_all_users(self):
1484
- return self.get_session.scalars(select(self.user_model)).all()
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.get_session.scalars(select(self.action_model).filter_by(name=name)).one_or_none()
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.get_session.add(action)
1539
- self.get_session.commit()
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.get_session.rollback()
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.get_session.scalars(
1558
- select(self.permission_model).where(self.permission_model.action == action)
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.get_session.delete(action)
1564
- self.get_session.commit()
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.get_session.rollback()
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.get_session.scalars(select(self.resource_model).filter_by(name=name)).one_or_none()
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.get_session.add(resource)
1597
- self.get_session.commit()
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.get_session.rollback()
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.get_session.scalars(
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.get_session.scalars(
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.get_session.add(perm)
1667
- self.get_session.commit()
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.get_session.rollback()
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.get_session.scalars(
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.get_session.delete(perm)
1698
- self.get_session.commit()
1684
+ self.session.delete(perm)
1685
+ self.session.commit()
1699
1686
  # if no more permission on permission view, delete permission
1700
- if not self.get_session.scalars(
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.get_session.rollback()
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.get_session.merge(role)
1720
- self.get_session.commit()
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.get_session.rollback()
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.get_session.merge(role)
1737
- self.get_session.commit()
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.get_session.rollback()
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
- self.appbuilder.get_app.config["AUTH_DB_FAKE_PASSWORD_HASH_CHECK"],
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.appbuilder.get_session.scalar(
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.appbuilder.get_session.scalars(
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.appbuilder.get_session.execute(
2435
+ self.session.execute(
2410
2436
  select(
2411
2437
  self.action_model.name,
2412
2438
  self.resource_model.name,