You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2023/11/09 00:23:58 UTC
(airflow) branch main updated: Remove FAB related auth methods from security manager to auth manager (#35515)
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 ad717ef50d Remove FAB related auth methods from security manager to auth manager (#35515)
ad717ef50d is described below
commit ad717ef50d58bfc442f62a7da03e2a77acd636b9
Author: Vincent <97...@users.noreply.github.com>
AuthorDate: Wed Nov 8 19:23:51 2023 -0500
Remove FAB related auth methods from security manager to auth manager (#35515)
---
.../auth/managers/fab/security_manager/override.py | 308 +++++++++++++++++++--
airflow/www/security_manager.py | 293 +-------------------
docs/apache-airflow/security/access-control.rst | 6 +-
3 files changed, 288 insertions(+), 319 deletions(-)
diff --git a/airflow/auth/managers/fab/security_manager/override.py b/airflow/auth/managers/fab/security_manager/override.py
index 257c284920..fe4c407277 100644
--- a/airflow/auth/managers/fab/security_manager/override.py
+++ b/airflow/auth/managers/fab/security_manager/override.py
@@ -24,8 +24,7 @@ import os
import random
import uuid
import warnings
-from functools import cached_property
-from typing import TYPE_CHECKING, Any, Callable, Container, Iterable, Sequence
+from typing import TYPE_CHECKING, Any, Callable, Collection, Container, Iterable, Sequence
import jwt
from flask import flash, g, session
@@ -65,6 +64,25 @@ from airflow.auth.managers.fab.models import (
)
from airflow.auth.managers.fab.models.anonymous_user import AnonymousUser
from airflow.auth.managers.fab.security_manager.constants import EXISTING_ROLES
+from airflow.auth.managers.fab.views.permissions import (
+ ActionModelView,
+ PermissionPairModelView,
+ ResourceModelView,
+)
+from airflow.auth.managers.fab.views.roles_list import CustomRoleModelView
+from airflow.auth.managers.fab.views.user import (
+ CustomUserDBModelView,
+ CustomUserLDAPModelView,
+ CustomUserOAuthModelView,
+ CustomUserOIDModelView,
+ CustomUserRemoteUserModelView,
+)
+from airflow.auth.managers.fab.views.user_edit import (
+ CustomResetMyPasswordView,
+ CustomResetPasswordView,
+ CustomUserInfoEditView,
+)
+from airflow.auth.managers.fab.views.user_stats import CustomUserStatsChartView
from airflow.auth.managers.utils.fab import get_method_from_fab_action_map
from airflow.configuration import conf
from airflow.exceptions import AirflowException, RemovedInAirflow3Warning
@@ -113,6 +131,19 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
resource_model = Resource
permission_model = Permission
registeruser_model = RegisterUser
+ actionmodelview = ActionModelView
+ permissionmodelview = PermissionPairModelView
+ rolemodelview = CustomRoleModelView
+ resourcemodelview = ResourceModelView
+ userdbmodelview = CustomUserDBModelView
+ resetmypasswordview = CustomResetMyPasswordView
+ resetpasswordview = CustomResetPasswordView
+ userinfoeditview = CustomUserInfoEditView
+ userldapmodelview = CustomUserLDAPModelView
+ useroauthmodelview = CustomUserOAuthModelView
+ userremoteusermodelview = CustomUserRemoteUserModelView
+ useroidmodelview = CustomUserOIDModelView
+ userstatschartview = CustomUserStatsChartView
jwt_manager = None
""" Flask-JWT-Extended """
@@ -130,6 +161,123 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
# global resource for dag-level access
DAG_RESOURCES = {permissions.RESOURCE_DAG}
+ ###########################################################################
+ # PERMISSIONS
+ ###########################################################################
+
+ # [START security_viewer_perms]
+ VIEWER_PERMISSIONS = [
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_AUDIT_LOG),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_DEPENDENCIES),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_CODE),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_RUN),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_DATASET),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_CLUSTER_ACTIVITY),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_IMPORT_ERROR),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_WARNING),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_JOB),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PASSWORD),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_MY_PASSWORD),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PROFILE),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_MY_PROFILE),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_PLUGIN),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_SLA_MISS),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_INSTANCE),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_LOG),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_XCOM),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_BROWSE_MENU),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DAG),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DAG_DEPENDENCIES),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DAG_RUN),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DATASET),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_CLUSTER_ACTIVITY),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DOCS),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DOCS_MENU),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_JOB),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_AUDIT_LOG),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_PLUGIN),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_SLA_MISS),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_TASK_INSTANCE),
+ ]
+ # [END security_viewer_perms]
+
+ # [START security_user_perms]
+ USER_PERMISSIONS = [
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG),
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_DAG),
+ (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_TASK_INSTANCE),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_TASK_INSTANCE),
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_TASK_INSTANCE),
+ (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_DAG_RUN),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG_RUN),
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_DAG_RUN),
+ ]
+ # [END security_user_perms]
+
+ # [START security_op_perms]
+ OP_PERMISSIONS = [
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_CONFIG),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_ADMIN_MENU),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_CONFIG),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_CONNECTION),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_POOL),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_VARIABLE),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_PROVIDER),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_XCOM),
+ (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_CONNECTION),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_CONNECTION),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_CONNECTION),
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_CONNECTION),
+ (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_POOL),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_POOL),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_POOL),
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_POOL),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_PROVIDER),
+ (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_VARIABLE),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_VARIABLE),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_VARIABLE),
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_VARIABLE),
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_XCOM),
+ ]
+ # [END security_op_perms]
+
+ ADMIN_PERMISSIONS = [
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_RESCHEDULE),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_TASK_RESCHEDULE),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_TRIGGER),
+ (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_TRIGGER),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_PASSWORD),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_PASSWORD),
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_ROLE),
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_ROLE),
+ ]
+
+ ###########################################################################
+ # DEFAULT ROLE CONFIGURATIONS
+ ###########################################################################
+
+ ROLE_CONFIGS: list[dict[str, Any]] = [
+ {"role": "Public", "perms": []},
+ {"role": "Viewer", "perms": VIEWER_PERMISSIONS},
+ {
+ "role": "User",
+ "perms": VIEWER_PERMISSIONS + USER_PERMISSIONS,
+ },
+ {
+ "role": "Op",
+ "perms": VIEWER_PERMISSIONS + USER_PERMISSIONS + OP_PERMISSIONS,
+ },
+ {
+ "role": "Admin",
+ "perms": VIEWER_PERMISSIONS + USER_PERMISSIONS + OP_PERMISSIONS + ADMIN_PERMISSIONS,
+ },
+ ]
+
+ # global resource for dag-level access
+ DAG_ACTIONS = permissions.DAG_ACTIONS
+
def __init__(self, appbuilder):
# done in super, but we need it before we can call super.
self.appbuilder = appbuilder
@@ -342,13 +490,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
"""Get the auth rate limit."""
return self.appbuilder.app.config["AUTH_RATE_LIMIT"]
- @cached_property
- def resourcemodelview(self):
- """Return the resource model view."""
- from airflow.auth.managers.fab.views.permissions import ResourceModelView
-
- return ResourceModelView
-
@property
def auth_role_public(self):
"""Gets the public role."""
@@ -799,6 +940,111 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
if dag.access_control is not None:
self.sync_perm_for_dag(dag_resource_name, dag.access_control)
+ def prefixed_dag_id(self, dag_id: str) -> str:
+ """Return the permission name for a DAG id."""
+ warnings.warn(
+ "`prefixed_dag_id` has been deprecated. "
+ "Please use `airflow.security.permissions.resource_name_for_dag` instead.",
+ RemovedInAirflow3Warning,
+ stacklevel=2,
+ )
+ root_dag_id = self._get_root_dag_id(dag_id)
+ return permissions.resource_name_for_dag(root_dag_id)
+
+ def is_dag_resource(self, resource_name: str) -> bool:
+ """Determine if a resource belongs to a DAG or all DAGs."""
+ if resource_name == permissions.RESOURCE_DAG:
+ return True
+ return resource_name.startswith(permissions.RESOURCE_DAG_PREFIX)
+
+ def sync_perm_for_dag(
+ self,
+ dag_id: str,
+ access_control: dict[str, Collection[str]] | None = None,
+ ) -> None:
+ """
+ Sync permissions for given dag id.
+
+ The dag id surely exists in our dag bag as only / refresh button or DagBag will call this function.
+
+ :param dag_id: the ID of the DAG whose permissions should be updated
+ :param access_control: a dict where each key is a rolename and
+ each value is a set() of action names (e.g.,
+ {'can_read'}
+ :return:
+ """
+ dag_resource_name = permissions.resource_name_for_dag(dag_id)
+ for dag_action_name in self.DAG_ACTIONS:
+ self.create_permission(dag_action_name, dag_resource_name)
+
+ if access_control is not None:
+ self.log.debug("Syncing DAG-level permissions for DAG '%s'", dag_resource_name)
+ self._sync_dag_view_permissions(dag_resource_name, access_control)
+ else:
+ self.log.debug(
+ "Not syncing DAG-level permissions for DAG '%s' as access control is unset.",
+ dag_resource_name,
+ )
+
+ def _sync_dag_view_permissions(self, dag_id: str, access_control: dict[str, Collection[str]]) -> None:
+ """
+ Set the access policy on the given DAG's ViewModel.
+
+ :param dag_id: the ID of the DAG whose permissions should be updated
+ :param access_control: a dict where each key is a rolename and
+ each value is a set() of action names (e.g. {'can_read'})
+ """
+ dag_resource_name = permissions.resource_name_for_dag(dag_id)
+
+ def _get_or_create_dag_permission(action_name: str) -> Permission | None:
+ perm = self.get_permission(action_name, dag_resource_name)
+ if not perm:
+ self.log.info("Creating new action '%s' on resource '%s'", action_name, dag_resource_name)
+ perm = self.create_permission(action_name, dag_resource_name)
+
+ return perm
+
+ def _revoke_stale_permissions(resource: Resource):
+ existing_dag_perms = self.get_resource_permissions(resource)
+ for perm in existing_dag_perms:
+ non_admin_roles = [role for role in perm.role if role.name != "Admin"]
+ for role in non_admin_roles:
+ target_perms_for_role = access_control.get(role.name, ())
+ if perm.action.name not in target_perms_for_role:
+ self.log.info(
+ "Revoking '%s' on DAG '%s' for role '%s'",
+ perm.action,
+ dag_resource_name,
+ role.name,
+ )
+ self.remove_permission_from_role(role, perm)
+
+ resource = self.get_resource(dag_resource_name)
+ if resource:
+ _revoke_stale_permissions(resource)
+
+ for rolename, action_names in access_control.items():
+ role = self.find_role(rolename)
+ if not role:
+ raise AirflowException(
+ f"The access_control mapping for DAG '{dag_id}' includes a role named "
+ f"'{rolename}', but that role does not exist"
+ )
+
+ action_names = set(action_names)
+ invalid_action_names = action_names - self.DAG_ACTIONS
+ if invalid_action_names:
+ raise AirflowException(
+ f"The access_control map for DAG '{dag_resource_name}' includes "
+ f"the following invalid permissions: {invalid_action_names}; "
+ f"The set of valid permissions is: {self.DAG_ACTIONS}"
+ )
+
+ for action_name in action_names:
+ dag_perm = _get_or_create_dag_permission(action_name)
+ if dag_perm:
+ self.add_permission_to_role(role, dag_perm)
+
def sync_roles(self) -> None:
"""
Initialize default and custom roles with related permissions.
@@ -1422,6 +1668,18 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
log.error(const.LOGMSG_ERR_SEC_DEL_PERMROLE, e)
self.get_session.rollback()
+ @staticmethod
+ def get_user_roles(user=None):
+ """
+ Get all the roles associated with the user.
+
+ :param user: the ab_user in FAB model.
+ :return: a list of roles associated with the user.
+ """
+ if user is None:
+ user = g.user
+ return user.roles
+
"""
--------------------
Auth related methods
@@ -1925,21 +2183,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
self.update_user_auth_stat(user)
return user
- def _rotate_session_id(self):
- """Rotate the session ID.
-
- We need to do this upon successful authentication when using the
- database session backend.
- """
- if conf.get("webserver", "SESSION_BACKEND") == "database":
- session.sid = str(uuid.uuid4())
-
- """
- ---------------
- Private methods
- ---------------
- """
-
def get_user_menu_access(self, menu_names: list[str] | None = None) -> set[str]:
if get_auth_manager().is_logged_in():
return self._get_user_permission_resources(g.user, "menu_access", resource_names=menu_names)
@@ -1971,6 +2214,15 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
---------------
"""
+ def _rotate_session_id(self):
+ """Rotate the session ID.
+
+ We need to do this upon successful authentication when using the
+ database session backend.
+ """
+ if conf.get("webserver", "SESSION_BACKEND") == "database":
+ session.sid = str(uuid.uuid4())
+
def _get_microsoft_jwks(self) -> list[dict[str, Any]]:
import requests
@@ -2223,3 +2475,11 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
self.role_model.id.in_(role_ids),
)
).all()
+
+ def _get_root_dag_id(self, dag_id: str) -> str:
+ if "." in dag_id:
+ dm = self.appbuilder.get_session.execute(
+ select(DagModel.dag_id, DagModel.root_dag_id).where(DagModel.dag_id == dag_id)
+ ).one()
+ return dm.root_dag_id or dm.dag_id
+ return dag_id
diff --git a/airflow/www/security_manager.py b/airflow/www/security_manager.py
index de183b1d7f..8fcd4f5d78 100644
--- a/airflow/www/security_manager.py
+++ b/airflow/www/security_manager.py
@@ -16,40 +16,16 @@
# under the License.
from __future__ import annotations
-import warnings
from functools import cached_property
-from typing import TYPE_CHECKING, Any, Callable, Collection, Sequence
+from typing import TYPE_CHECKING, Callable
from flask import g
-from sqlalchemy import select
from airflow.auth.managers.fab.security_manager.constants import EXISTING_ROLES as FAB_EXISTING_ROLES
-from airflow.auth.managers.fab.views.permissions import (
- ActionModelView,
- PermissionPairModelView,
- ResourceModelView,
-)
-from airflow.auth.managers.fab.views.roles_list import CustomRoleModelView
-from airflow.auth.managers.fab.views.user import (
- CustomUserDBModelView,
- CustomUserLDAPModelView,
- CustomUserOAuthModelView,
- CustomUserOIDModelView,
- CustomUserRemoteUserModelView,
-)
-from airflow.auth.managers.fab.views.user_edit import (
- CustomResetMyPasswordView,
- CustomResetPasswordView,
- CustomUserInfoEditView,
-)
-from airflow.auth.managers.fab.views.user_stats import CustomUserStatsChartView
from airflow.auth.managers.models.resource_details import AccessView, DagAccessEntity
from airflow.auth.managers.utils.fab import (
get_method_from_fab_action_map,
)
-from airflow.exceptions import AirflowException, RemovedInAirflow3Warning
-from airflow.models import DagModel
-from airflow.security import permissions
from airflow.security.permissions import (
ACTION_CAN_ACCESS_MENU,
ACTION_CAN_READ,
@@ -85,7 +61,6 @@ from airflow.www.utils import CustomSQLAInterface
EXISTING_ROLES = FAB_EXISTING_ROLES
if TYPE_CHECKING:
- from airflow.auth.managers.fab.models import Permission, Resource
from airflow.auth.managers.models.base_user import BaseUser
@@ -95,137 +70,6 @@ class AirflowSecurityManagerV2(SecurityManager, LoggingMixin):
It's named V2 to differentiate it from the obsolete airflow.www.security.AirflowSecurityManager.
"""
- ###########################################################################
- # PERMISSIONS
- ###########################################################################
-
- # [START security_viewer_perms]
- VIEWER_PERMISSIONS = [
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_AUDIT_LOG),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_DEPENDENCIES),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_CODE),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_RUN),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_DATASET),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_CLUSTER_ACTIVITY),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_IMPORT_ERROR),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_WARNING),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_JOB),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PASSWORD),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_MY_PASSWORD),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PROFILE),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_MY_PROFILE),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_PLUGIN),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_SLA_MISS),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_INSTANCE),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_LOG),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_XCOM),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_BROWSE_MENU),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DAG),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DAG_DEPENDENCIES),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DAG_RUN),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DATASET),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_CLUSTER_ACTIVITY),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DOCS),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_DOCS_MENU),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_JOB),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_AUDIT_LOG),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_PLUGIN),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_SLA_MISS),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_TASK_INSTANCE),
- ]
- # [END security_viewer_perms]
-
- # [START security_user_perms]
- USER_PERMISSIONS = [
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG),
- (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_DAG),
- (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_TASK_INSTANCE),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_TASK_INSTANCE),
- (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_TASK_INSTANCE),
- (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_DAG_RUN),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG_RUN),
- (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_DAG_RUN),
- ]
- # [END security_user_perms]
-
- # [START security_op_perms]
- OP_PERMISSIONS = [
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_CONFIG),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_ADMIN_MENU),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_CONFIG),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_CONNECTION),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_POOL),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_VARIABLE),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_PROVIDER),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_XCOM),
- (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_CONNECTION),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_CONNECTION),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_CONNECTION),
- (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_CONNECTION),
- (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_POOL),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_POOL),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_POOL),
- (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_POOL),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_PROVIDER),
- (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_VARIABLE),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_VARIABLE),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_VARIABLE),
- (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_VARIABLE),
- (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_XCOM),
- ]
- # [END security_op_perms]
-
- ADMIN_PERMISSIONS = [
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_RESCHEDULE),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_TASK_RESCHEDULE),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_TRIGGER),
- (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_TRIGGER),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_PASSWORD),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_PASSWORD),
- (permissions.ACTION_CAN_READ, permissions.RESOURCE_ROLE),
- (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_ROLE),
- ]
-
- # global resource for dag-level access
- DAG_ACTIONS = permissions.DAG_ACTIONS
-
- ###########################################################################
- # DEFAULT ROLE CONFIGURATIONS
- ###########################################################################
-
- ROLE_CONFIGS: list[dict[str, Any]] = [
- {"role": "Public", "perms": []},
- {"role": "Viewer", "perms": VIEWER_PERMISSIONS},
- {
- "role": "User",
- "perms": VIEWER_PERMISSIONS + USER_PERMISSIONS,
- },
- {
- "role": "Op",
- "perms": VIEWER_PERMISSIONS + USER_PERMISSIONS + OP_PERMISSIONS,
- },
- {
- "role": "Admin",
- "perms": VIEWER_PERMISSIONS + USER_PERMISSIONS + OP_PERMISSIONS + ADMIN_PERMISSIONS,
- },
- ]
-
- actionmodelview = ActionModelView
- permissionmodelview = PermissionPairModelView
- rolemodelview = CustomRoleModelView
- resourcemodelview = ResourceModelView
- userdbmodelview = CustomUserDBModelView
- resetmypasswordview = CustomResetMyPasswordView
- resetpasswordview = CustomResetPasswordView
- userinfoeditview = CustomUserInfoEditView
- userldapmodelview = CustomUserLDAPModelView
- useroauthmodelview = CustomUserOAuthModelView
- userremoteusermodelview = CustomUserRemoteUserModelView
- useroidmodelview = CustomUserOIDModelView
- userstatschartview = CustomUserStatsChartView
-
def __init__(self, appbuilder) -> None:
super().__init__(appbuilder=appbuilder)
@@ -237,44 +81,6 @@ class AirflowSecurityManagerV2(SecurityManager, LoggingMixin):
view = getattr(self, attr, None)
if view and getattr(view, "datamodel", None):
view.datamodel = CustomSQLAInterface(view.datamodel.obj)
- self.perms = None
-
- def _get_root_dag_id(self, dag_id: str) -> str:
- if "." in dag_id:
- dm = self.appbuilder.get_session.execute(
- select(DagModel.dag_id, DagModel.root_dag_id).where(DagModel.dag_id == dag_id)
- ).one()
- return dm.root_dag_id or dm.dag_id
- return dag_id
-
- @staticmethod
- def get_user_roles(user=None):
- """
- Get all the roles associated with the user.
-
- :param user: the ab_user in FAB model.
- :return: a list of roles associated with the user.
- """
- if user is None:
- user = g.user
- return user.roles
-
- def prefixed_dag_id(self, dag_id: str) -> str:
- """Return the permission name for a DAG id."""
- warnings.warn(
- "`prefixed_dag_id` has been deprecated. "
- "Please use `airflow.security.permissions.resource_name_for_dag` instead.",
- RemovedInAirflow3Warning,
- stacklevel=2,
- )
- root_dag_id = self._get_root_dag_id(dag_id)
- return permissions.resource_name_for_dag(root_dag_id)
-
- def is_dag_resource(self, resource_name: str) -> bool:
- """Determine if a resource belongs to a DAG or all DAGs."""
- if resource_name == permissions.RESOURCE_DAG:
- return True
- return resource_name.startswith(permissions.RESOURCE_DAG_PREFIX)
def has_access(self, action_name: str, resource_name: str, user=None) -> bool:
"""
@@ -320,103 +126,6 @@ class AirflowSecurityManagerV2(SecurityManager, LoggingMixin):
"""
return None, None
- def sync_perm_for_dag(
- self,
- dag_id: str,
- access_control: dict[str, Collection[str]] | None = None,
- ) -> None:
- """
- Sync permissions for given dag id.
-
- The dag id surely exists in our dag bag as only / refresh button or DagBag will call this function.
-
- :param dag_id: the ID of the DAG whose permissions should be updated
- :param access_control: a dict where each key is a rolename and
- each value is a set() of action names (e.g.,
- {'can_read'}
- :return:
- """
- dag_resource_name = permissions.resource_name_for_dag(dag_id)
- for dag_action_name in self.DAG_ACTIONS:
- self.create_permission(dag_action_name, dag_resource_name)
-
- if access_control is not None:
- self.log.debug("Syncing DAG-level permissions for DAG '%s'", dag_resource_name)
- self._sync_dag_view_permissions(dag_resource_name, access_control)
- else:
- self.log.debug(
- "Not syncing DAG-level permissions for DAG '%s' as access control is unset.",
- dag_resource_name,
- )
-
- def _sync_dag_view_permissions(self, dag_id: str, access_control: dict[str, Collection[str]]) -> None:
- """
- Set the access policy on the given DAG's ViewModel.
-
- :param dag_id: the ID of the DAG whose permissions should be updated
- :param access_control: a dict where each key is a rolename and
- each value is a set() of action names (e.g. {'can_read'})
- """
- dag_resource_name = permissions.resource_name_for_dag(dag_id)
-
- def _get_or_create_dag_permission(action_name: str) -> Permission | None:
- perm = self.get_permission(action_name, dag_resource_name)
- if not perm:
- self.log.info("Creating new action '%s' on resource '%s'", action_name, dag_resource_name)
- perm = self.create_permission(action_name, dag_resource_name)
-
- return perm
-
- def _revoke_stale_permissions(resource: Resource):
- existing_dag_perms = self.get_resource_permissions(resource)
- for perm in existing_dag_perms:
- non_admin_roles = [role for role in perm.role if role.name != "Admin"]
- for role in non_admin_roles:
- target_perms_for_role = access_control.get(role.name, ())
- if perm.action.name not in target_perms_for_role:
- self.log.info(
- "Revoking '%s' on DAG '%s' for role '%s'",
- perm.action,
- dag_resource_name,
- role.name,
- )
- self.remove_permission_from_role(role, perm)
-
- resource = self.get_resource(dag_resource_name)
- if resource:
- _revoke_stale_permissions(resource)
-
- for rolename, action_names in access_control.items():
- role = self.find_role(rolename)
- if not role:
- raise AirflowException(
- f"The access_control mapping for DAG '{dag_id}' includes a role named "
- f"'{rolename}', but that role does not exist"
- )
-
- action_names = set(action_names)
- invalid_action_names = action_names - self.DAG_ACTIONS
- if invalid_action_names:
- raise AirflowException(
- f"The access_control map for DAG '{dag_resource_name}' includes "
- f"the following invalid permissions: {invalid_action_names}; "
- f"The set of valid permissions is: {self.DAG_ACTIONS}"
- )
-
- for action_name in action_names:
- dag_perm = _get_or_create_dag_permission(action_name)
- if dag_perm:
- self.add_permission_to_role(role, dag_perm)
-
- def check_authorization(
- self,
- perms: Sequence[tuple[str, str]] | None = None,
- dag_id: str | None = None,
- ) -> bool:
- raise NotImplementedError(
- "The method 'check_authorization' is only available with the auth manager FabAuthManager"
- )
-
@cached_property
def _auth_manager_is_authorized_map(self) -> dict[str, Callable[[str, BaseUser | None], bool]]:
"""
diff --git a/docs/apache-airflow/security/access-control.rst b/docs/apache-airflow/security/access-control.rst
index bef8b34f42..9c75821e91 100644
--- a/docs/apache-airflow/security/access-control.rst
+++ b/docs/apache-airflow/security/access-control.rst
@@ -51,7 +51,7 @@ Viewer
^^^^^^
``Viewer`` users have limited read permissions:
-.. exampleinclude:: /../../airflow/www/security_manager.py
+.. exampleinclude:: /../../airflow/auth/managers/fab/security_manager/override.py
:language: python
:start-after: [START security_viewer_perms]
:end-before: [END security_viewer_perms]
@@ -60,7 +60,7 @@ User
^^^^
``User`` users have ``Viewer`` permissions plus additional permissions:
-.. exampleinclude:: /../../airflow/www/security_manager.py
+.. exampleinclude:: /../../airflow/auth/managers/fab/security_manager/override.py
:language: python
:start-after: [START security_user_perms]
:end-before: [END security_user_perms]
@@ -69,7 +69,7 @@ Op
^^
``Op`` users have ``User`` permissions plus additional permissions:
-.. exampleinclude:: /../../airflow/www/security_manager.py
+.. exampleinclude:: /../../airflow/auth/managers/fab/security_manager/override.py
:language: python
:start-after: [START security_op_perms]
:end-before: [END security_op_perms]