diff --git a/api/controllers/console/workspace/rbac.py b/api/controllers/console/workspace/rbac.py index 878f91fe168..3a0f2fa6082 100644 --- a/api/controllers/console/workspace/rbac.py +++ b/api/controllers/console/workspace/rbac.py @@ -196,7 +196,7 @@ def _pagination_options() -> svc.ListOption: def _legacy_workspace_roles( - options: svc.ListOption | None = None, *, include_owner: int = 0 + options: svc.ListOption | None = None, *, include_owner: int = 0, billing_enabled: bool = True ) -> svc.Paginated[svc.RBACRole]: """Return the built-in legacy workspace roles in the RBAC list shape. @@ -207,6 +207,14 @@ def _legacy_workspace_roles( for role_name in ("owner", "admin", "editor", "normal", "dataset_operator"): if not dify_config.DATASET_OPERATOR_ENABLED and role_name == "dataset_operator": continue + + permission_keys = _LEGACY_ROLE_PERMISSION_KEYS[role_name] + valid_permission_keys = [] + for permission_key in permission_keys: + if not billing_enabled and "billing" in permission_key: + continue + valid_permission_keys.append(permission_key) + legacy_roles.append( svc.RBACRole( id=role_name, @@ -216,7 +224,7 @@ def _legacy_workspace_roles( name=role_name, description="", is_builtin=True, - permission_keys=list(dict.fromkeys(_LEGACY_ROLE_PERMISSION_KEYS[role_name])), + permission_keys=valid_permission_keys, role_tag="owner" if role_name == "owner" else "", ) ) @@ -307,10 +315,16 @@ class RBACRolesApi(Resource): query = _RolesListQuery.model_validate(request.args.to_dict(flat=True)) options = query.to_inner_options() if not dify_config.RBAC_ENABLED: - result = _legacy_workspace_roles(options, include_owner=query.include_owner) + result = _legacy_workspace_roles( + options, include_owner=query.include_owner, billing_enabled=dify_config.BILLING_ENABLED + ) else: result = svc.RBACService.Roles.list( - tenant_id, account_id, include_owner=query.include_owner, options=options + tenant_id, + account_id, + include_owner=query.include_owner, + biiling_enabled=dify_config.BILLING_ENABLED, + options=options, ) return _dump(result) @@ -336,7 +350,9 @@ class RBACRoleItemApi(Resource): @console_ns.response(200, "Success", console_ns.models[svc.RBACRole.__name__]) def get(self, role_id): tenant_id, account_id = _current_ids() - return _dump(svc.RBACService.Roles.get(tenant_id, account_id, str(role_id))) + return _dump( + svc.RBACService.Roles.get(tenant_id, account_id, role_id, billing_enabled=dify_config.BILLING_ENABLED) + ) @login_required @rbac_permission_required( diff --git a/api/services/enterprise/rbac_service.py b/api/services/enterprise/rbac_service.py index 8a6842538ee..5bfefd8a916 100644 --- a/api/services/enterprise/rbac_service.py +++ b/api/services/enterprise/rbac_service.py @@ -830,10 +830,13 @@ class RBACService: tenant_id: str, account_id: str | None = None, include_owner: int | None = None, + biiling_enabled: bool | None = None, *, options: ListOption | None = None, ) -> Paginated[RBACRole]: - params = (options or ListOption()).to_params({"include_owner": include_owner}) + params = (options or ListOption()).to_params( + {"include_owner": include_owner, "biiling_enabled": biiling_enabled} + ) params["dataset_operator_enabled"] = dify_config.DATASET_OPERATOR_ENABLED data = _inner_call( "GET", @@ -869,13 +872,13 @@ class RBACService: ) @staticmethod - def get(tenant_id: str, account_id: str | None, role_id: str) -> RBACRole: + def get(tenant_id: str, account_id: str | None, role_id: str, billing_enabled: bool = True) -> RBACRole: data = _inner_call( "GET", f"{_INNER_PREFIX}/roles/item", tenant_id=tenant_id, account_id=account_id, - params={"id": role_id}, + params={"id": role_id, "billing_enabled": billing_enabled}, ) return RBACRole.model_validate(data or {}) 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 a118f803a91..6508b7cc0dc 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_rbac.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_rbac.py @@ -24,6 +24,7 @@ from flask import Flask from pydantic import ValidationError from werkzeug.exceptions import Forbidden, NotFound +from configs import dify_config from controllers.console.workspace import rbac as rbac_mod @@ -176,6 +177,20 @@ class TestPaginationMapping: ): response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi()) + owner_permission_keys = rbac_mod._LEGACY_ROLE_PERMISSION_KEYS["owner"] + valid_owner_permission_keys = [] + for permission_key in owner_permission_keys: + if not dify_config.BILLING_ENABLED and "billing" in permission_key: + continue + valid_owner_permission_keys.append(permission_key) + + admin_permission_keys = rbac_mod._LEGACY_ROLE_PERMISSION_KEYS["admin"] + valid_admin_permission_keys = [] + for permission_key in admin_permission_keys: + if not dify_config.BILLING_ENABLED and "billing" in permission_key: + continue + valid_admin_permission_keys.append(permission_key) + assert response["data"] == [ { "id": "owner", @@ -185,7 +200,7 @@ class TestPaginationMapping: "name": "owner", "description": "", "is_builtin": True, - "permission_keys": list(dict.fromkeys(rbac_mod._LEGACY_ROLE_PERMISSION_KEYS["owner"])), + "permission_keys": valid_owner_permission_keys, "role_tag": "owner", }, { @@ -196,7 +211,7 @@ class TestPaginationMapping: "name": "admin", "description": "", "is_builtin": True, - "permission_keys": list(dict.fromkeys(rbac_mod._LEGACY_ROLE_PERMISSION_KEYS["admin"])), + "permission_keys": valid_admin_permission_keys, "role_tag": "", }, ] 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 96bd81aec6b..b50ba0e5edb 100644 --- a/api/tests/unit_tests/services/enterprise/test_rbac_service.py +++ b/api/tests/unit_tests/services/enterprise/test_rbac_service.py @@ -133,7 +133,7 @@ class TestRoles: call = _call_args(mock_send) assert call.method == "GET" assert call.endpoint == "/rbac/roles/item" - assert call.params == {"id": "role-1"} + assert call.params == {"billing_enabled": True, "id": "role-1"} def test_members_forwards_role_id_and_pagination(self, mock_send: MagicMock): mock_send.return_value = {