diff --git a/airflow/auth/managers/base_auth_manager.py b/airflow/auth/managers/base_auth_manager.py index f64c803e6a54..0b6096976858 100644 --- a/airflow/auth/managers/base_auth_manager.py +++ b/airflow/auth/managers/base_auth_manager.py @@ -55,7 +55,7 @@ from airflow.www.extensions.init_appbuilder import AirflowAppBuilder from airflow.www.security_manager import AirflowSecurityManagerV2 -ResourceMethod = Literal["GET", "POST", "PUT", "DELETE"] +ResourceMethod = Literal["GET", "POST", "PUT", "DELETE", "MENU"] class BaseAuthManager(LoggingMixin): diff --git a/airflow/auth/managers/fab/fab_auth_manager.py b/airflow/auth/managers/fab/fab_auth_manager.py index 2e4014af2e8b..80624ef4a5f5 100644 --- a/airflow/auth/managers/fab/fab_auth_manager.py +++ b/airflow/auth/managers/fab/fab_auth_manager.py @@ -53,8 +53,6 @@ from airflow.models import DagModel from airflow.security import permissions from airflow.security.permissions import ( - ACTION_CAN_ACCESS_MENU, - ACTION_CAN_READ, RESOURCE_AUDIT_LOG, RESOURCE_CLUSTER_ACTIVITY, RESOURCE_CONFIG, @@ -266,8 +264,10 @@ def is_authorized_variable( return self._is_authorized(method=method, resource_type=RESOURCE_VARIABLE, user=user) def is_authorized_view(self, *, access_view: AccessView, user: BaseUser | None = None) -> bool: + # "Docs" are only links in the menu, there is no page associated + method: ResourceMethod = "MENU" if access_view == AccessView.DOCS else "GET" return self._is_authorized( - method="GET", resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], user=user + method=method, resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], user=user ) def is_authorized_custom_view( @@ -472,18 +472,11 @@ def _get_user_permissions(user: BaseUser): """ Return the user permissions. - ACTION_CAN_READ and ACTION_CAN_ACCESS_MENU are merged into because they are very similar. - We can assume that if a user has permissions to read variables, they also have permissions to access - the menu "Variables". - :param user: the user to get permissions for :meta private: """ - perms = getattr(user, "perms") or [] - return [ - (ACTION_CAN_READ if perm[0] == ACTION_CAN_ACCESS_MENU else perm[0], perm[1]) for perm in perms - ] + return getattr(user, "perms") or [] def _get_root_dag_id(self, dag_id: str) -> str: """ diff --git a/airflow/auth/managers/utils/fab.py b/airflow/auth/managers/utils/fab.py index 316e5ecff165..5065f930feb0 100644 --- a/airflow/auth/managers/utils/fab.py +++ b/airflow/auth/managers/utils/fab.py @@ -36,6 +36,7 @@ "GET": ACTION_CAN_READ, "PUT": ACTION_CAN_EDIT, "DELETE": ACTION_CAN_DELETE, + "MENU": ACTION_CAN_ACCESS_MENU, } @@ -48,5 +49,4 @@ def get_method_from_fab_action_map(): """Returns the map associating a FAB action to a method.""" return { **{v: k for k, v in _MAP_METHOD_NAME_TO_FAB_ACTION_NAME.items()}, - ACTION_CAN_ACCESS_MENU: "GET", } diff --git a/airflow/www/security_manager.py b/airflow/www/security_manager.py index 13342ff7f7d5..772e672ff6b2 100644 --- a/airflow/www/security_manager.py +++ b/airflow/www/security_manager.py @@ -40,8 +40,6 @@ from airflow.exceptions import AirflowException from airflow.models import Connection, DagRun, Pool, TaskInstance, Variable from airflow.security.permissions import ( - ACTION_CAN_ACCESS_MENU, - ACTION_CAN_READ, RESOURCE_ADMIN_MENU, RESOURCE_AUDIT_LOG, RESOURCE_BROWSE_MENU, @@ -335,7 +333,7 @@ def _get_auth_manager_is_authorized_method(self, fab_resource_name: str) -> Call # This means the page the user is trying to access is specific to the auth manager used # Example: the user list view in FabAuthManager return lambda action, resource_pk, user: get_auth_manager().is_authorized_custom_view( - fab_action_name=ACTION_CAN_READ if action == ACTION_CAN_ACCESS_MENU else action, + fab_action_name=action, fab_resource_name=fab_resource_name, user=user, ) diff --git a/tests/auth/managers/fab/test_fab_auth_manager.py b/tests/auth/managers/fab/test_fab_auth_manager.py index bb9d507cbdd9..0d249a86035c 100644 --- a/tests/auth/managers/fab/test_fab_auth_manager.py +++ b/tests/auth/managers/fab/test_fab_auth_manager.py @@ -40,6 +40,7 @@ RESOURCE_DAG, RESOURCE_DAG_RUN, RESOURCE_DATASET, + RESOURCE_DOCS, RESOURCE_JOB, RESOURCE_PLUGIN, RESOURCE_PROVIDER, @@ -143,10 +144,10 @@ def test_is_logged_in(self, mock_get_user, auth_manager): [(ACTION_CAN_DELETE, resource_type), (ACTION_CAN_CREATE, "resource_test")], True, ), - # With permission (testing that ACTION_CAN_ACCESS_MENU gives GET permissions) + # With permission ( api_name, - "GET", + "MENU", [(ACTION_CAN_ACCESS_MENU, resource_type)], True, ), @@ -349,6 +350,18 @@ def test_is_authorized_dag( [(ACTION_CAN_READ, RESOURCE_TRIGGER)], False, ), + # Docs (positive) + ( + AccessView.DOCS, + [(ACTION_CAN_ACCESS_MENU, RESOURCE_DOCS)], + True, + ), + # Without permission + ( + AccessView.DOCS, + [(ACTION_CAN_READ, RESOURCE_DOCS)], + False, + ), ], ) def test_is_authorized_view(self, access_view, user_permissions, expected_result, auth_manager):