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(