This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 71f976d4f0 Remove `BaseSecurityManager` (#35572)
71f976d4f0 is described below

commit 71f976d4f03b1aad13bd615740ee5667a4816d35
Author: Vincent <[email protected]>
AuthorDate: Fri Nov 10 14:14:15 2023 -0500

    Remove `BaseSecurityManager` (#35572)
---
 .../auth/managers/fab/security_manager/override.py | 149 +++++++-
 airflow/www/extensions/init_appbuilder.py          |   2 +
 airflow/www/fab_security/manager.py                | 425 ---------------------
 airflow/www/security_manager.py                    |  52 ++-
 4 files changed, 197 insertions(+), 431 deletions(-)

diff --git a/airflow/auth/managers/fab/security_manager/override.py 
b/airflow/auth/managers/fab/security_manager/override.py
index ba5ca1709f..2dc023f1da 100644
--- a/airflow/auth/managers/fab/security_manager/override.py
+++ b/airflow/auth/managers/fab/security_manager/override.py
@@ -27,6 +27,7 @@ import warnings
 from typing import TYPE_CHECKING, Any, Callable, Collection, Container, 
Iterable, Sequence
 
 import jwt
+import re2
 from flask import flash, g, session
 from flask_appbuilder import const
 from flask_appbuilder.const import (
@@ -44,6 +45,19 @@ from flask_appbuilder.const import (
 )
 from flask_appbuilder.models.sqla import Base
 from flask_appbuilder.models.sqla.interface import SQLAInterface
+from flask_appbuilder.security.registerviews import (
+    RegisterUserDBView,
+    RegisterUserOAuthView,
+    RegisterUserOIDView,
+)
+from flask_appbuilder.security.views import (
+    AuthDBView,
+    AuthLDAPView,
+    AuthOAuthView,
+    AuthOIDView,
+    AuthRemoteUserView,
+    RegisterUserModelView,
+)
 from flask_babel import lazy_gettext
 from flask_jwt_extended import JWTManager, current_user as current_user_jwt
 from flask_login import LoginManager
@@ -96,7 +110,6 @@ from airflow.www.session import 
AirflowDatabaseSessionInterface
 
 if TYPE_CHECKING:
     from airflow.auth.managers.base_auth_manager import ResourceMethod
-    from airflow.www.fab_security.manager import BaseSecurityManager
 
 log = logging.getLogger(__name__)
 
@@ -122,6 +135,8 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
 
     auth_view = None
     """ The obj instance for authentication view """
+    registeruser_view = None
+    """ The obj instance for registering user view """
     user_view = None
     """ The obj instance for user view """
 
@@ -131,10 +146,29 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
     action_model = Action
     resource_model = Resource
     permission_model = Permission
-    registeruser_model = RegisterUser
+
+    """ Views """
+    authdbview = AuthDBView
+    """ Override if you want your own Authentication DB view """
+    authldapview = AuthLDAPView
+    """ Override if you want your own Authentication LDAP view """
+    authoidview = AuthOIDView
+    """ Override if you want your own Authentication OID view """
+    authoauthview = AuthOAuthView
+    """ Override if you want your own Authentication OAuth view """
+    authremoteuserview = AuthRemoteUserView
+    """ Override if you want your own Authentication REMOTE_USER view """
+    registeruserdbview = RegisterUserDBView
+    """ Override if you want your own register user db view """
+    registeruseroidview = RegisterUserOIDView
+    """ Override if you want your own register user OpenID view """
+    registeruseroauthview = RegisterUserOAuthView
+    """ Override if you want your own register user OAuth view """
     actionmodelview = ActionModelView
     permissionmodelview = PermissionPairModelView
     rolemodelview = CustomRoleModelView
+    registeruser_model = RegisterUser
+    registerusermodelview = RegisterUserModelView
     resourcemodelview = ResourceModelView
     userdbmodelview = CustomUserDBModelView
     resetmypasswordview = CustomResetMyPasswordView
@@ -644,6 +678,16 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
         """The default user self registration role."""
         return self.appbuilder.get_app.config["AUTH_USER_REGISTRATION_ROLE"]
 
+    @property
+    def auth_roles_sync_at_login(self) -> bool:
+        """Should roles be synced at login."""
+        return self.appbuilder.get_app.config["AUTH_ROLES_SYNC_AT_LOGIN"]
+
+    @property
+    def auth_role_admin(self):
+        """Gets the admin role."""
+        return self.appbuilder.get_app.config["AUTH_ROLE_ADMIN"]
+
     @property
     def oauth_whitelists(self):
         warnings.warn(
@@ -657,6 +701,11 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
         """Returns FAB builtin roles."""
         return self.appbuilder.app.config.get("FAB_ROLES", {})
 
+    @property
+    def builtin_roles(self):
+        """Get the builtin roles."""
+        return self._builtin_roles
+
     def create_admin_standalone(self) -> tuple[str | None, str | None]:
         """Create an Admin user with a random password so that users can 
access airflow."""
         from airflow.configuration import AIRFLOW_HOME, 
make_group_other_inaccessible
@@ -1050,6 +1099,92 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
                 if dag_perm:
                     self.add_permission_to_role(role, dag_perm)
 
+    def add_permissions_view(self, base_action_names, resource_name):  # Keep 
name for compatibility with FAB.
+        """
+        Add an action on a resource to the backend.
+
+        :param base_action_names:
+            list of permissions from view (all exposed methods):
+             'can_add','can_edit' etc...
+        :param resource_name:
+            name of the resource to add
+        """
+        resource = self.create_resource(resource_name)
+        perms = self.get_resource_permissions(resource)
+
+        if not perms:
+            # No permissions yet on this view
+            for action_name in base_action_names:
+                action = self.create_permission(action_name, resource_name)
+                if self.auth_role_admin not in self.builtin_roles:
+                    admin_role = self.find_role(self.auth_role_admin)
+                    self.add_permission_to_role(admin_role, action)
+        else:
+            # Permissions on this view exist but....
+            admin_role = self.find_role(self.auth_role_admin)
+            for action_name in base_action_names:
+                # Check if base view permissions exist
+                if not self.perms_include_action(perms, action_name):
+                    action = self.create_permission(action_name, resource_name)
+                    if self.auth_role_admin not in self.builtin_roles:
+                        self.add_permission_to_role(admin_role, action)
+            for perm in perms:
+                if perm.action is None:
+                    # Skip this perm, it has a null permission
+                    continue
+                if perm.action.name not in base_action_names:
+                    # perm to delete
+                    roles = self.get_all_roles()
+                    # del permission from all roles
+                    for role in roles:
+                        # TODO: An action can't be removed from a role.
+                        # This is a bug in FAB. It has been reported.
+                        self.remove_permission_from_role(role, perm)
+                    self.delete_permission(perm.action.name, resource_name)
+                elif self.auth_role_admin not in self.builtin_roles and perm 
not in admin_role.permissions:
+                    # Role Admin must have all permissions
+                    self.add_permission_to_role(admin_role, perm)
+
+    def add_permissions_menu(self, resource_name):
+        """
+        Add menu_access to resource on permission_resource.
+
+        :param resource_name:
+            The resource name
+        """
+        self.create_resource(resource_name)
+        perm = self.get_permission("menu_access", resource_name)
+        if not perm:
+            perm = self.create_permission("menu_access", resource_name)
+        if self.auth_role_admin not in self.builtin_roles:
+            role_admin = self.find_role(self.auth_role_admin)
+            self.add_permission_to_role(role_admin, perm)
+
+    def security_cleanup(self, baseviews, menus):
+        """
+        Cleanup all unused permissions from the database.
+
+        :param baseviews: A list of BaseViews class
+        :param menus: Menu class
+        """
+        resources = self.get_all_resources()
+        roles = self.get_all_roles()
+        for resource in resources:
+            found = False
+            for baseview in baseviews:
+                if resource.name == baseview.class_permission_name:
+                    found = True
+                    break
+            if menus.find(resource.name):
+                found = True
+            if not found:
+                permissions = self.get_resource_permissions(resource)
+                for permission in permissions:
+                    for role in roles:
+                        self.remove_permission_from_role(role, permission)
+                    self.delete_permission(permission.action.name, 
resource.name)
+                self.delete_resource(resource.name)
+
     def sync_roles(self) -> None:
         """
         Initialize default and custom roles with related permissions.
@@ -1944,7 +2079,7 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
 
     def oauth_user_info_getter(
         self,
-        func: Callable[[BaseSecurityManager, str, dict[str, Any] | None], 
dict[str, Any]],
+        func: Callable[[AirflowSecurityManagerV2, str, dict[str, Any] | None], 
dict[str, Any]],
     ):
         """
         Decorator function to be the OAuth user info getter for all the 
providers.
@@ -2452,6 +2587,14 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
         result.update(role_resource_names)
         return result
 
+    def _has_access_builtin_roles(self, role, action_name: str, resource_name: 
str) -> bool:
+        """Checks permission on builtin role."""
+        perms = self.builtin_roles.get(role.name, [])
+        for _resource_name, _action_name in perms:
+            if re2.match(_resource_name, resource_name) and 
re2.match(_action_name, action_name):
+                return True
+        return False
+
     def _merge_perm(self, action_name: str, resource_name: str) -> None:
         """
         Add the new (action, resource) to assoc_permission_role if it doesn't 
exist.
diff --git a/airflow/www/extensions/init_appbuilder.py 
b/airflow/www/extensions/init_appbuilder.py
index 4503b81b43..ce87fdc76c 100644
--- a/airflow/www/extensions/init_appbuilder.py
+++ b/airflow/www/extensions/init_appbuilder.py
@@ -566,6 +566,8 @@ class AirflowAppBuilder:
             This deletes any permission that is no longer part of any 
registered
             view or menu. Only invoke AFTER YOU HAVE REGISTERED ALL VIEWS.
         """
+        if not hasattr(self.sm, "security_cleanup"):
+            raise NotImplementedError("The auth manager used does not support 
security_cleanup method.")
         self.sm.security_cleanup(self.baseviews, self.menu)
 
     def security_converge(self, dry=False) -> dict:
diff --git a/airflow/www/fab_security/manager.py 
b/airflow/www/fab_security/manager.py
index 1405425a41..94b90b654a 100644
--- a/airflow/www/fab_security/manager.py
+++ b/airflow/www/fab_security/manager.py
@@ -19,44 +19,6 @@
 from __future__ import annotations
 
 import logging
-from typing import TYPE_CHECKING
-
-import re2
-from flask import g, session, url_for
-from flask_appbuilder.security.registerviews import (
-    RegisterUserDBView,
-    RegisterUserOAuthView,
-    RegisterUserOIDView,
-)
-from flask_appbuilder.security.views import (
-    AuthDBView,
-    AuthLDAPView,
-    AuthOAuthView,
-    AuthOIDView,
-    AuthRemoteUserView,
-    PermissionModelView,
-    RegisterUserModelView,
-    ResetMyPasswordView,
-    ResetPasswordView,
-    RoleModelView,
-    UserInfoEditView,
-    UserLDAPModelView,
-    UserOAuthModelView,
-    UserOIDModelView,
-    UserRemoteUserModelView,
-    UserStatsChartView,
-)
-from flask_jwt_extended import current_user as current_user_jwt
-from flask_limiter import Limiter
-from flask_limiter.util import get_remote_address
-
-from airflow.www.extensions.init_auth_manager import get_auth_manager
-
-if TYPE_CHECKING:
-    from flask import Flask
-    from flask_appbuilder import AppBuilder
-
-    from airflow.auth.managers.fab.models import Action, Permission, 
RegisterUser, Resource, Role, User
 
 # This product contains a modified portion of 'Flask App Builder' developed by 
Daniel Vaz Gaspar.
 # (https://github.com/dpgaspar/Flask-AppBuilder).
@@ -82,390 +44,3 @@ def __getattr__(name: str):
     # Store for next time
     globals()[name] = val
     return val
-
-
-def _oauth_tokengetter(token=None):
-    """Return the current user oauth token from session cookie."""
-    token = session.get("oauth")
-    log.debug("Token Get: %s", token)
-    return token
-
-
-class BaseSecurityManager:
-    """Base class to define the Security Manager interface."""
-
-    appbuilder: AppBuilder
-    """The appbuilder instance for the current security manager."""
-    auth_view = None
-    """ The obj instance for authentication view """
-    user_view = None
-    """ The obj instance for user view """
-    registeruser_view = None
-    """ The obj instance for registering user view """
-    lm = None
-    """ Flask-Login LoginManager """
-
-    user_model: type[User]
-    """ Override to set your own User Model """
-    role_model: type[Role]
-    """ Override to set your own Role Model """
-    action_model: type[Action]
-    """ Override to set your own Action Model """
-    resource_model: type[Resource]
-    """ Override to set your own Resource Model """
-    permission_model: type[Permission]
-    """ Override to set your own Permission Model """
-    registeruser_model: type[RegisterUser]
-    """ Override to set your own RegisterUser Model """
-
-    userldapmodelview = UserLDAPModelView
-    """ Override if you want your own user ldap view """
-    useroidmodelview = UserOIDModelView
-    """ Override if you want your own user OID view """
-    useroauthmodelview = UserOAuthModelView
-    """ Override if you want your own user OAuth view """
-    userremoteusermodelview = UserRemoteUserModelView
-    """ Override if you want your own user REMOTE_USER view """
-    registerusermodelview = RegisterUserModelView
-
-    authdbview = AuthDBView
-    """ Override if you want your own Authentication DB view """
-    authldapview = AuthLDAPView
-    """ Override if you want your own Authentication LDAP view """
-    authoidview = AuthOIDView
-    """ Override if you want your own Authentication OID view """
-    authoauthview = AuthOAuthView
-    """ Override if you want your own Authentication OAuth view """
-    authremoteuserview = AuthRemoteUserView
-    """ Override if you want your own Authentication REMOTE_USER view """
-
-    registeruserdbview = RegisterUserDBView
-    """ Override if you want your own register user db view """
-    registeruseroidview = RegisterUserOIDView
-    """ Override if you want your own register user OpenID view """
-    registeruseroauthview = RegisterUserOAuthView
-    """ Override if you want your own register user OAuth view """
-
-    resetmypasswordview = ResetMyPasswordView
-    """ Override if you want your own reset my password view """
-    resetpasswordview = ResetPasswordView
-    """ Override if you want your own reset password view """
-    userinfoeditview = UserInfoEditView
-    """ Override if you want your own User information edit view """
-
-    rolemodelview = RoleModelView
-    actionmodelview = PermissionModelView
-    userstatschartview = UserStatsChartView
-    permissionmodelview = PermissionModelView
-
-    def __init__(self, appbuilder):
-        self.appbuilder = appbuilder
-        app = self.appbuilder.get_app
-
-        # Setup Flask-Limiter
-        self.limiter = self.create_limiter(app)
-
-    def create_limiter(self, app: Flask) -> Limiter:
-        limiter = Limiter(key_func=get_remote_address)
-        limiter.init_app(app)
-        return limiter
-
-    def add_role(self, name: str) -> Role:
-        raise NotImplementedError
-
-    @property
-    def get_url_for_registeruser(self):
-        """Gets the URL for Register User."""
-        return 
url_for(f"{self.registeruser_view.endpoint}.{self.registeruser_view.default_view}")
-
-    @property
-    def get_user_datamodel(self):
-        """Gets the User data model."""
-        return self.user_view.datamodel
-
-    @property
-    def get_register_user_datamodel(self):
-        """Gets the Register User data model."""
-        return self.registerusermodelview.datamodel
-
-    @property
-    def builtin_roles(self):
-        """Get the builtin roles."""
-        return self._builtin_roles
-
-    @property
-    def auth_role_admin(self):
-        """Gets the admin role."""
-        return self.appbuilder.get_app.config["AUTH_ROLE_ADMIN"]
-
-    @property
-    def auth_roles_sync_at_login(self) -> bool:
-        """Should roles be synced at login."""
-        return self.appbuilder.get_app.config["AUTH_ROLES_SYNC_AT_LOGIN"]
-
-    @property
-    def current_user(self):
-        """Current user object."""
-        if get_auth_manager().is_logged_in():
-            return g.user
-        elif current_user_jwt:
-            return current_user_jwt
-
-    def _has_access_builtin_roles(self, role, action_name: str, resource_name: 
str) -> bool:
-        """Checks permission on builtin role."""
-        perms = self.builtin_roles.get(role.name, [])
-        for _resource_name, _action_name in perms:
-            if re2.match(_resource_name, resource_name) and 
re2.match(_action_name, action_name):
-                return True
-        return False
-
-    def add_limit_view(self, baseview):
-        if not baseview.limits:
-            return
-
-        for limit in baseview.limits:
-            self.limiter.limit(
-                limit_value=limit.limit_value,
-                key_func=limit.key_func,
-                per_method=limit.per_method,
-                methods=limit.methods,
-                error_message=limit.error_message,
-                exempt_when=limit.exempt_when,
-                override_defaults=limit.override_defaults,
-                deduct_when=limit.deduct_when,
-                on_breach=limit.on_breach,
-                cost=limit.cost,
-            )(baseview.blueprint)
-
-    def add_permissions_view(self, base_action_names, resource_name):  # Keep 
name for compatibility with FAB.
-        """
-        Add an action on a resource to the backend.
-
-        :param base_action_names:
-            list of permissions from view (all exposed methods):
-             'can_add','can_edit' etc...
-        :param resource_name:
-            name of the resource to add
-        """
-        resource = self.create_resource(resource_name)
-        perms = self.get_resource_permissions(resource)
-
-        if not perms:
-            # No permissions yet on this view
-            for action_name in base_action_names:
-                action = self.create_permission(action_name, resource_name)
-                if self.auth_role_admin not in self.builtin_roles:
-                    admin_role = self.find_role(self.auth_role_admin)
-                    self.add_permission_to_role(admin_role, action)
-        else:
-            # Permissions on this view exist but....
-            admin_role = self.find_role(self.auth_role_admin)
-            for action_name in base_action_names:
-                # Check if base view permissions exist
-                if not self.perms_include_action(perms, action_name):
-                    action = self.create_permission(action_name, resource_name)
-                    if self.auth_role_admin not in self.builtin_roles:
-                        self.add_permission_to_role(admin_role, action)
-            for perm in perms:
-                if perm.action is None:
-                    # Skip this perm, it has a null permission
-                    continue
-                if perm.action.name not in base_action_names:
-                    # perm to delete
-                    roles = self.get_all_roles()
-                    # del permission from all roles
-                    for role in roles:
-                        # TODO: An action can't be removed from a role.
-                        # This is a bug in FAB. It has been reported.
-                        self.remove_permission_from_role(role, perm)
-                    self.delete_permission(perm.action.name, resource_name)
-                elif self.auth_role_admin not in self.builtin_roles and perm 
not in admin_role.permissions:
-                    # Role Admin must have all permissions
-                    self.add_permission_to_role(admin_role, perm)
-
-    def add_permissions_menu(self, resource_name):
-        """
-        Add menu_access to resource on permission_resource.
-
-        :param resource_name:
-            The resource name
-        """
-        self.create_resource(resource_name)
-        perm = self.get_permission("menu_access", resource_name)
-        if not perm:
-            perm = self.create_permission("menu_access", resource_name)
-        if self.auth_role_admin not in self.builtin_roles:
-            role_admin = self.find_role(self.auth_role_admin)
-            self.add_permission_to_role(role_admin, perm)
-
-    def get_resource(self, name: str) -> Resource:
-        raise NotImplementedError
-
-    def get_action(self, name: str) -> Action:
-        raise NotImplementedError
-
-    def security_cleanup(self, baseviews, menus):
-        """
-        Cleanup all unused permissions from the database.
-
-        :param baseviews: A list of BaseViews class
-        :param menus: Menu class
-        """
-        resources = self.get_all_resources()
-        roles = self.get_all_roles()
-        for resource in resources:
-            found = False
-            for baseview in baseviews:
-                if resource.name == baseview.class_permission_name:
-                    found = True
-                    break
-            if menus.find(resource.name):
-                found = True
-            if not found:
-                permissions = self.get_resource_permissions(resource)
-                for permission in permissions:
-                    for role in roles:
-                        self.remove_permission_from_role(role, permission)
-                    self.delete_permission(permission.action.name, 
resource.name)
-                self.delete_resource(resource.name)
-
-    def find_user(self, username=None, email=None):
-        """Find a user by its username or email."""
-        raise NotImplementedError
-
-    def get_role_permissions_from_db(self, role_id: int) -> list[Permission]:
-        """Get all DB permissions from a role id."""
-        raise NotImplementedError
-
-    def add_user(self, username, first_name, last_name, email, role, 
password=""):
-        """Create user."""
-        raise NotImplementedError
-
-    def update_user(self, user):
-        """
-        Update user.
-
-        :param user: User model to update to database
-        """
-        raise NotImplementedError
-
-    def find_role(self, name):
-        raise NotImplementedError
-
-    def get_all_roles(self):
-        raise NotImplementedError
-
-    def get_public_role(self):
-        """Return all permissions from public role."""
-        raise NotImplementedError
-
-    def permission_exists_in_one_or_more_roles(
-        self, resource_name: str, action_name: str, role_ids: list[int]
-    ) -> bool:
-        """Find and returns permission views for a group of roles."""
-        raise NotImplementedError
-
-    """
-    ----------------------
-     PRIMITIVES VIEW MENU
-    ----------------------
-    """
-
-    def get_all_resources(self) -> list[Resource]:
-        """
-        Get all existing resource records.
-
-        :return: List of all resources
-        """
-        raise NotImplementedError
-
-    def create_resource(self, name):
-        """
-        Create a resource with the given name.
-
-        :param name: The name of the resource to create created.
-        """
-        raise NotImplementedError
-
-    def delete_resource(self, name):
-        """
-        Delete a Resource from the backend.
-
-        :param name:
-            name of the Resource
-        """
-        raise NotImplementedError
-
-    """
-    ----------------------
-     PERMISSION VIEW MENU
-    ----------------------
-    """
-
-    def get_permission(self, action_name: str, resource_name: str) -> 
Permission | None:
-        """
-        Get a permission made with the given action->resource pair, if the 
permission already exists.
-
-        :param action_name: Name of action
-        :param resource_name: Name of resource
-        :return: The existing permission
-        """
-        raise NotImplementedError
-
-    def get_resource_permissions(self, resource) -> Permission:
-        """
-        Retrieve permission pairs associated with a specific resource object.
-
-        :param resource: Object representing a single resource.
-        :return: Action objects representing resource->action pair
-        """
-        raise NotImplementedError
-
-    def create_permission(self, action_name: str, resource_name: str) -> 
Permission | None:
-        """
-        Create a permission linking an action and resource.
-
-        :param action_name: Name of existing action
-        :param resource_name: Name of existing resource
-        :return: Resource created
-        """
-        raise NotImplementedError
-
-    def delete_permission(self, action_name: str, resource_name: str) -> None:
-        """
-        Delete the permission linking an action->resource pair.
-
-        Doesn't delete the underlying action or resource.
-
-        :param action_name: Name of existing action
-        :param resource_name: Name of existing resource
-        :return: None
-        """
-        raise NotImplementedError
-
-    def perms_include_action(self, perms, action_name):
-        raise NotImplementedError
-
-    def add_permission_to_role(self, role, permission) -> None:
-        """
-        Add an existing permission pair to a role.
-
-        :param role: The role about to get a new permission.
-        :param permission: The permission pair to add to a role.
-        :return: None
-        """
-        raise NotImplementedError
-
-    def remove_permission_from_role(self, role, permission) -> None:
-        """
-        Remove a permission pair from a role.
-
-        :param role: User role containing permissions.
-        :param permission: Object representing resource-> action pair
-        """
-        raise NotImplementedError
-
-    @staticmethod
-    def before_request():
-        """Run hook before request."""
-        g.user = get_auth_manager().get_user()
diff --git a/airflow/www/security_manager.py b/airflow/www/security_manager.py
index 552add8453..35e2a63e64 100644
--- a/airflow/www/security_manager.py
+++ b/airflow/www/security_manager.py
@@ -21,6 +21,8 @@ from functools import cached_property
 from typing import TYPE_CHECKING, Callable
 
 from flask import g
+from flask_limiter import Limiter
+from flask_limiter.util import get_remote_address
 from sqlalchemy import select
 
 from airflow.auth.managers.fab.security_manager.constants import 
EXISTING_ROLES as FAB_EXISTING_ROLES
@@ -67,7 +69,6 @@ from airflow.security.permissions import (
 from airflow.utils.log.logging_mixin import LoggingMixin
 from airflow.utils.session import NEW_SESSION, provide_session
 from airflow.www.extensions.init_auth_manager import get_auth_manager
-from airflow.www.fab_security.manager import BaseSecurityManager
 from airflow.www.utils import CustomSQLAInterface
 
 EXISTING_ROLES = FAB_EXISTING_ROLES
@@ -75,17 +76,22 @@ EXISTING_ROLES = FAB_EXISTING_ROLES
 if TYPE_CHECKING:
     from sqlalchemy.orm import Session
 
+    from airflow.auth.managers.fab.models import Action, Resource
     from airflow.auth.managers.models.base_user import BaseUser
 
 
-class AirflowSecurityManagerV2(BaseSecurityManager, LoggingMixin):
+class AirflowSecurityManagerV2(LoggingMixin):
     """Custom security manager, which introduces a permission model adapted to 
Airflow.
 
     It's named V2 to differentiate it from the obsolete 
airflow.www.security.AirflowSecurityManager.
     """
 
     def __init__(self, appbuilder) -> None:
-        super().__init__(appbuilder=appbuilder)
+        super().__init__()
+        self.appbuilder = appbuilder
+
+        # Setup Flask-Limiter
+        self.limiter = self.create_limiter()
 
         # Go and fix up the SQLAInterface used from the stock one to our 
subclass.
         # This is needed to support the "hack" where we had to edit
@@ -96,6 +102,16 @@ class AirflowSecurityManagerV2(BaseSecurityManager, 
LoggingMixin):
                 if view and getattr(view, "datamodel", None):
                     view.datamodel = CustomSQLAInterface(view.datamodel.obj)
 
+    @staticmethod
+    def before_request():
+        """Run hook before request."""
+        g.user = get_auth_manager().get_user()
+
+    def create_limiter(self) -> Limiter:
+        limiter = Limiter(key_func=get_remote_address)
+        limiter.init_app(self.appbuilder.get_app)
+        return limiter
+
     def has_access(
         self, action_name: str, resource_name: str, user=None, resource_pk: 
str | None = None
     ) -> bool:
@@ -134,6 +150,36 @@ class AirflowSecurityManagerV2(BaseSecurityManager, 
LoggingMixin):
         """
         return None, None
 
+    def add_limit_view(self, baseview):
+        if not baseview.limits:
+            return
+
+        for limit in baseview.limits:
+            self.limiter.limit(
+                limit_value=limit.limit_value,
+                key_func=limit.key_func,
+                per_method=limit.per_method,
+                methods=limit.methods,
+                error_message=limit.error_message,
+                exempt_when=limit.exempt_when,
+                override_defaults=limit.override_defaults,
+                deduct_when=limit.deduct_when,
+                on_breach=limit.on_breach,
+                cost=limit.cost,
+            )(baseview.blueprint)
+
+    def add_permissions_view(self, base_action_names, resource_name):
+        raise NotImplementedError("Sync FAB permissions is only available with 
the FAB auth manager")
+
+    def add_permissions_menu(self, resource_name):
+        raise NotImplementedError("Sync FAB permissions is only available with 
the FAB auth manager")
+
+    def get_action(self, name: str) -> Action:
+        raise NotImplementedError("Only available when FAB auth manager is 
used")
+
+    def get_resource(self, name: str) -> Resource:
+        raise NotImplementedError("Only available when FAB auth manager is 
used")
+
     @cached_property
     @provide_session
     def _auth_manager_is_authorized_map(

Reply via email to