From 96bfcf20cda18bbb227bd877ee5c83d060eb51c9 Mon Sep 17 00:00:00 2001 From: WH-2099 Date: Fri, 14 Aug 2026 12:03:54 +0000 Subject: [PATCH] fix(api): protect workspace credential mutations (#40790) --- .../console/datasets/data_source.py | 3 ++ .../console/workspace/tool_providers.py | 2 ++ ...rkspace_credential_mutation_permissions.py | 28 +++++++++++++++++++ .../console/workspace/test_tool_providers.py | 1 + 4 files changed, 34 insertions(+) create mode 100644 api/tests/unit_tests/controllers/console/test_workspace_credential_mutation_permissions.py diff --git a/api/controllers/console/datasets/data_source.py b/api/controllers/console/datasets/data_source.py index 9cf8420c251..e0f857c1582 100644 --- a/api/controllers/console/datasets/data_source.py +++ b/api/controllers/console/datasets/data_source.py @@ -36,6 +36,7 @@ from ..wraps import ( RBACPermission, RBACResourceScope, account_initialization_required, + is_admin_or_owner_required, model_validate, rbac_permission_required, setup_required, @@ -187,6 +188,8 @@ class DataSourceApi(Resource): @setup_required @login_required + @is_admin_or_owner_required + @rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False) @account_initialization_required @console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) @with_current_tenant_id diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py index 222ffd99a31..badfb21eed6 100644 --- a/api/controllers/console/workspace/tool_providers.py +++ b/api/controllers/console/workspace/tool_providers.py @@ -580,6 +580,8 @@ class ToolBuiltinProviderAddApi(Resource): ) @setup_required @login_required + @is_admin_or_owner_required + @rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_CREATE, resource_required=False) @account_initialization_required @with_current_user @with_current_tenant_id diff --git a/api/tests/unit_tests/controllers/console/test_workspace_credential_mutation_permissions.py b/api/tests/unit_tests/controllers/console/test_workspace_credential_mutation_permissions.py new file mode 100644 index 00000000000..b7a6212cc75 --- /dev/null +++ b/api/tests/unit_tests/controllers/console/test_workspace_credential_mutation_permissions.py @@ -0,0 +1,28 @@ +from inspect import getclosurevars, unwrap +from types import FunctionType + +import pytest + +from controllers.common.wraps import RBACPermission, RBACResourceScope +from controllers.console.datasets.data_source import DataSourceApi +from controllers.console.workspace.tool_providers import ToolBuiltinProviderAddApi + + +@pytest.mark.parametrize( + ("method", "permission"), + [ + (ToolBuiltinProviderAddApi.post, RBACPermission.CREDENTIAL_CREATE), + (DataSourceApi.patch, RBACPermission.CREDENTIAL_MANAGE), + ], +) +def test_workspace_credential_mutations_require_management_permission( + method: FunctionType, permission: RBACPermission +) -> None: + legacy_wrapper = unwrap(method, stop=lambda wrapper: "is_admin_or_owner_required" in wrapper.__code__.co_qualname) + assert "is_admin_or_owner_required" in legacy_wrapper.__code__.co_qualname + + rbac_wrapper = unwrap(method, stop=lambda wrapper: "rbac_permission_required" in wrapper.__code__.co_qualname) + rbac_config = getclosurevars(rbac_wrapper).nonlocals + assert rbac_config["resource_type"] == RBACResourceScope.WORKSPACE + assert rbac_config["scene"] == permission + assert rbac_config["resource_required"] is False diff --git a/api/tests/unit_tests/controllers/console/workspace/test_tool_providers.py b/api/tests/unit_tests/controllers/console/workspace/test_tool_providers.py index 5d5f47333c0..8184adaad97 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_tool_providers.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_tool_providers.py @@ -336,6 +336,7 @@ def test_builtin_provider_add_passes_payload( app: Flask, controller_module: ModuleType, monkeypatch: pytest.MonkeyPatch ): user = _mock_account() + user.role = TenantAccountRole.ADMIN _set_current_account(monkeypatch, controller_module, user, "tenant-456") service_mock = MagicMock(return_value={"result": "success"})