From a7f548648bc9930770bdc783f06a6eec97f0316f Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 11 May 2026 14:08:17 +0800 Subject: [PATCH] fix: fix permission key format and fix role return format --- api/controllers/console/workspace/members.py | 10 +-- api/controllers/console/workspace/rbac.py | 69 +++++-------------- api/fields/member_fields.py | 2 +- api/services/enterprise/rbac_service.py | 7 +- .../console/workspace/test_members.py | 12 +++- .../console/workspace/test_rbac.py | 42 ++++------- .../services/enterprise/test_rbac_service.py | 6 +- 7 files changed, 54 insertions(+), 94 deletions(-) diff --git a/api/controllers/console/workspace/members.py b/api/controllers/console/workspace/members.py index 8e21506d66..f8a3005dd6 100644 --- a/api/controllers/console/workspace/members.py +++ b/api/controllers/console/workspace/members.py @@ -73,11 +73,11 @@ register_enum_models(console_ns, TenantAccountRole) register_schema_models(console_ns, AccountWithRole, AccountWithRoleList) -def _serialize_member_roles(current_role: str | None, member_role_ids: list[str]) -> list[str]: - if member_role_ids: - return member_role_ids +def _serialize_member_roles(current_role: str | None, member_roles: list[enterprise_rbac_service.MemberRoleSummary]) -> list[dict[str, str]]: + if member_roles: + return [{"id": role.id, "name": role.name} for role in member_roles] if current_role: - return [current_role] + return [{"id": current_role, "name": current_role}] return [] @@ -106,7 +106,7 @@ class MemberListApi(Resource): current_user.id, member_ids, ) - roles_map = {item.account_id: [role.id for role in item.roles] for item in member_roles} + roles_map = {item.account_id: item.roles for item in member_roles} else: roles_map = {} diff --git a/api/controllers/console/workspace/rbac.py b/api/controllers/console/workspace/rbac.py index 0a93a456de..36184077cf 100644 --- a/api/controllers/console/workspace/rbac.py +++ b/api/controllers/console/workspace/rbac.py @@ -16,34 +16,24 @@ from services.enterprise import rbac_service as svc _LEGACY_WORKSPACE_PERMISSION_KEYS: list[str] = [ - "inviteMembers", - "removeMembers", - "assignRoles", - "workspaceSettings", - "manageBilling", - "transferOwnership", + # These keys are copied from the enterprise RBAC catalog examples in + # `dify-rbac.md` so the legacy workspace roles stay in the same key format + # as the enterprise RBAC surface. + "workspace.member.manage", + "workspace.role.manage", ] _LEGACY_APP_PERMISSION_KEYS: list[str] = [ - "createApps", - "editApps", - "useApps", + "app.acl.view_layout", + "app.acl.test_and_run", + "app.acl.edit", + "app.acl.access_config", ] _LEGACY_DATASET_PERMISSION_KEYS: list[str] = [ - "createDatasets", - "editDatasets", - "manageDatasets", -] - -_LEGACY_ENTERPRISE_PERMISSION_KEYS: list[str] = [ - "workspace.member.manage", - "workspace.settings.manage", - "workspace.billing.manage", - "workspace.owner.transfer", - "app.acl.edit", - "app.acl.test_and_run", + "dataset.acl.readonly", "dataset.acl.edit", + "dataset.acl.use", ] _LEGACY_ROLE_PERMISSION_KEYS: dict[str, list[str]] = { @@ -55,45 +45,22 @@ _LEGACY_ROLE_PERMISSION_KEYS: dict[str, list[str]] = { *_LEGACY_WORKSPACE_PERMISSION_KEYS, *_LEGACY_APP_PERMISSION_KEYS, *_LEGACY_DATASET_PERMISSION_KEYS, - *_LEGACY_ENTERPRISE_PERMISSION_KEYS, ], "admin": [ - "inviteMembers", - "removeMembers", - "assignRoles", - "workspaceSettings", - "manageBilling", - "workspace.member.manage", - "workspace.settings.manage", - "workspace.billing.manage", - "app.acl.edit", - "app.acl.test_and_run", - "dataset.acl.edit", - "createApps", - "editApps", - "useApps", - "createDatasets", - "editDatasets", - "manageDatasets", + *_LEGACY_WORKSPACE_PERMISSION_KEYS, + *_LEGACY_APP_PERMISSION_KEYS, + *_LEGACY_DATASET_PERMISSION_KEYS, ], "editor": [ - "createApps", - "editApps", - "useApps", - "createDatasets", - "editDatasets", - "workspace.member.manage", - "app.acl.edit", - "app.acl.test_and_run", - "dataset.acl.edit", + *_LEGACY_APP_PERMISSION_KEYS, + *_LEGACY_DATASET_PERMISSION_KEYS, ], "normal": [ - "useApps", + "app.acl.view_layout", "app.acl.test_and_run", ], "dataset_operator": [ - "manageDatasets", - "dataset.acl.edit", + *_LEGACY_DATASET_PERMISSION_KEYS, ], } diff --git a/api/fields/member_fields.py b/api/fields/member_fields.py index ce8f27a28e..8356bc7d66 100644 --- a/api/fields/member_fields.py +++ b/api/fields/member_fields.py @@ -70,7 +70,7 @@ class AccountWithRole(_AccountAvatar): last_active_at: int | None = None created_at: int | None = None role: str - roles: list[str] = Field(default_factory=list) + roles: list[dict[str, str]] = Field(default_factory=list) status: str @field_validator("last_login_at", "last_active_at", "created_at", mode="before") diff --git a/api/services/enterprise/rbac_service.py b/api/services/enterprise/rbac_service.py index 0008e37753..6f1752ead2 100644 --- a/api/services/enterprise/rbac_service.py +++ b/api/services/enterprise/rbac_service.py @@ -74,6 +74,11 @@ class RBACRole(_RBACModel): return value +class MemberRoleSummary(_RBACModel): + id: str + name: str + + class AccessPolicy(_RBACModel): id: str tenant_id: str = "" @@ -146,7 +151,7 @@ class MemberBindingsResponse(_RBACModel): class MemberRolesResponse(_RBACModel): account_id: str - roles: list[RBACRole] = Field(default_factory=list) + roles: list[MemberRoleSummary] = Field(default_factory=list) class MemberRolesBatchResponse(_RBACModel): diff --git a/api/tests/unit_tests/controllers/console/workspace/test_members.py b/api/tests/unit_tests/controllers/console/workspace/test_members.py index 85cab7189c..c207aedd23 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_members.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_members.py @@ -61,7 +61,7 @@ class TestMemberListApi: assert status == 200 assert len(result["accounts"]) == 1 assert result["accounts"][0]["role"] == "admin" - assert result["accounts"][0]["roles"] == ["admin"] + assert result["accounts"][0]["roles"] == [{"id": "admin", "name": "admin"}] def test_get_with_rbac_enabled_fetches_roles_in_batch(self, app): api = MemberListApi() @@ -82,7 +82,10 @@ class TestMemberListApi: ) role_item = SimpleNamespace( account_id="m1", - roles=[SimpleNamespace(id="workspace.owner"), SimpleNamespace(id="workspace.editor")], + roles=[ + SimpleNamespace(id="workspace.owner", name="Owner"), + SimpleNamespace(id="workspace.editor", name="Editor"), + ], ) with ( @@ -99,7 +102,10 @@ class TestMemberListApi: assert status == 200 assert result["accounts"][0]["role"] == "editor" - assert result["accounts"][0]["roles"] == ["workspace.owner", "workspace.editor"] + assert result["accounts"][0]["roles"] == [ + {"id": "workspace.owner", "name": "Owner"}, + {"id": "workspace.editor", "name": "Editor"}, + ] mock_batch_get.assert_called_once_with("tenant-1", "acct-1", ["m1"]) def test_get_no_tenant(self, app: Flask): diff --git a/api/tests/unit_tests/controllers/console/workspace/test_rbac.py b/api/tests/unit_tests/controllers/console/workspace/test_rbac.py index 561b533e25..762ba28506 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_rbac.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_rbac.py @@ -137,25 +137,15 @@ class TestPaginationMapping: "description": "", "is_builtin": True, "permission_keys": [ - "inviteMembers", - "removeMembers", - "assignRoles", - "workspaceSettings", - "manageBilling", - "transferOwnership", - "createApps", - "editApps", - "useApps", - "createDatasets", - "editDatasets", - "manageDatasets", "workspace.member.manage", - "workspace.settings.manage", - "workspace.billing.manage", - "workspace.owner.transfer", - "app.acl.edit", + "workspace.role.manage", + "app.acl.view_layout", "app.acl.test_and_run", + "app.acl.edit", + "app.acl.access_config", + "dataset.acl.readonly", "dataset.acl.edit", + "dataset.acl.use", ], }, { @@ -167,23 +157,15 @@ class TestPaginationMapping: "description": "", "is_builtin": True, "permission_keys": [ - "inviteMembers", - "removeMembers", - "assignRoles", - "workspaceSettings", - "manageBilling", "workspace.member.manage", - "workspace.settings.manage", - "workspace.billing.manage", - "app.acl.edit", + "workspace.role.manage", + "app.acl.view_layout", "app.acl.test_and_run", + "app.acl.edit", + "app.acl.access_config", + "dataset.acl.readonly", "dataset.acl.edit", - "createApps", - "editApps", - "useApps", - "createDatasets", - "editDatasets", - "manageDatasets", + "dataset.acl.use", ], }, ] diff --git a/api/tests/unit_tests/services/enterprise/test_rbac_service.py b/api/tests/unit_tests/services/enterprise/test_rbac_service.py index 079bc7d73d..de9b861f39 100644 --- a/api/tests/unit_tests/services/enterprise/test_rbac_service.py +++ b/api/tests/unit_tests/services/enterprise/test_rbac_service.py @@ -312,7 +312,7 @@ class TestMyPermissions: def test_get_without_payload_uses_get(self, mock_send: MagicMock): mock_send.return_value = { "workspace": {"permission_keys": ["workspace.member.manage"]}, - "app": {"default_permission_keys": ["app.acl.test_and_run"], "overrides": []}, + "app": {"default_permission_keys": ["app.acl.view_layout", "app.acl.test_and_run"], "overrides": []}, "dataset": {"default_permission_keys": [], "overrides": []}, } @@ -378,8 +378,8 @@ class TestMemberRoles: { "account_id": "acct-2", "roles": [ - {"id": "role-1", "type": "workspace", "name": "Admin"}, - {"id": "role-2", "type": "workspace", "name": "Editor"}, + {"id": "role-1", "name": "Admin"}, + {"id": "role-2", "name": "Editor"}, ], }, {