Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions apps/application/tests/test_application_advanced_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

from application.views.application import ApplicationAPI
from application.views.application_api_key import ApplicationApiKeyEnable
from common.auth.constants.group_constants import Group
from common.auth.constants.operate_constants import Operate
from common.auth.constants.permission_constants import PermissionConstants
from common.auth.struct.permission import Role
from common.auth.struct.permission import Permission, Role
from common.exception.app_exception import AppUnauthorizedFailed


Expand All @@ -20,13 +21,24 @@ def kwargs(self):

def request_with_permission(self, permission_constant):
permission = permission_constant.get_workspace_application_permission()(None, self.kwargs())
permissions = {permission.get_resource_permission_key(permission.resource_id): permission.bit()}
return self.request_with_permissions(permission)

def request_with_permissions(self, *permission_values, roles=None):
permissions = {}
for permission in permission_values:
permissions[permission.get_resource_permission_key(permission.resource_id)] = permission.bit()
resolved_roles = [Role("USER", self.workspace_id)] if roles is None else roles
return SimpleNamespace(
auth=SimpleNamespace(roles=[Role("USER", self.workspace_id)], permissions=permissions),
auth=SimpleNamespace(roles=resolved_roles, permissions=permissions),
user=SimpleNamespace(id=self.application_id, type="test"),
META={"REMOTE_ADDR": "127.0.0.1"},
path="/test",
data={"enable_api_key": True},
data={
"enable_api_key": True,
"id_list": [self.application_id],
"clean_time": 30,
"file_clean_time": 7,
},
query_params={},
build_absolute_uri=lambda _: "http://localhost/",
)
Expand Down Expand Up @@ -68,3 +80,41 @@ def test_mcp_permission_bits_exist_for_role_tree(self):
PermissionConstants.APPLICATION_OVERVIEW_BACKEND_API_ACCESS.value.operate,
Operate.BACKEND_API_ACCESS,
)

def test_batch_clean_time_rejects_application_read_permission(self):
request = self.request_with_permissions(
PermissionConstants.APPLICATION_READ.get_workspace_permission()(None, self.kwargs()),
PermissionConstants.APPLICATION_CHAT_LOG_CLEAR_POLICY.get_workspace_application_permission()(
None, self.kwargs()
),
roles=[],
)
with (
patch("application.views.application.ApplicationBatchOperateSerializer"),
patch("application.views.application.result.success"),
self.assertRaises(AppUnauthorizedFailed),
):
ApplicationAPI.BatchCleanTime.put(ApplicationAPI.BatchCleanTime(), request, workspace_id=self.workspace_id)

def test_batch_clean_time_accepts_dedicated_workspace_permission(self):
request = self.request_with_permissions(
Permission(
group=Group.APPLICATION,
sub_group=Group.APPLICATION,
operate=Operate.CLEAR_POLICY,
bit_index=42,
workspace_id=self.workspace_id,
),
PermissionConstants.APPLICATION_CHAT_LOG_CLEAR_POLICY.get_workspace_application_permission()(
None, self.kwargs()
),
roles=[],
)
with (
patch("application.views.application.ApplicationBatchOperateSerializer") as serializer,
patch("application.views.application.get_application_operation_object_batch", return_value=[]),
patch("application.views.application.result.success"),
):
serializer.return_value.batch_clean_time.return_value = True
ApplicationAPI.BatchCleanTime.put(ApplicationAPI.BatchCleanTime(), request, workspace_id=self.workspace_id)
serializer.return_value.batch_clean_time.assert_called_once()
2 changes: 1 addition & 1 deletion apps/application/views/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ class BatchCleanTime(APIView):
tags=[_("Application")], # type: ignore
)
@has_permissions(
PermissionConstants.APPLICATION_READ.get_workspace_permission(),
PermissionConstants.APPLICATION_CLEAR_POLICY.get_workspace_permission(),
RoleConstants.USER.get_workspace_role(),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
)
Expand Down
10 changes: 10 additions & 0 deletions apps/common/auth/constants/permission_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,16 @@ class PermissionConstants(Enum):
),
)

APPLICATION_CLEAR_POLICY = (
Permission(group=Group.APPLICATION, sub_group=Group.APPLICATION, operate=Operate.CLEAR_POLICY, bit_index=42),
PermissionMeta(
role_list=[RoleConstants.WORKSPACE_MANAGE, RoleConstants.USER],
category=Category.WORKSPACE,
scope=[PermissionScopeConstants.WORKSPACE_RESOURCE, PermissionScopeConstants.WORKSPACE],
resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANAGE],
),
)

APPLICATION_RESOURCE_AUTHORIZATION = (
Permission(group=Group.APPLICATION, sub_group=Group.APPLICATION, operate=Operate.AUTH, bit_index=10),
PermissionMeta(
Expand Down
79 changes: 79 additions & 0 deletions apps/system_manage/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from common.auth.constants.permission_constants import PermissionConstants
from common.auth.struct.aggregate_permission import AggregatePermission
from common.auth.struct.permission import Permission
from common.exception.app_exception import AppUnauthorizedFailed
from system_manage.models import ChatUser, UserGroup, UserGroupRelation
from system_manage.serializers.chat_user import ChatUserSerializer
Expand All @@ -13,13 +14,45 @@


class ChatUserPermissionTests(SimpleTestCase):
workspace_id = "workspace-1"
resource_id = "resource-1"

@staticmethod
def build_permissions(items, request, kwargs, compare):
for item in items:
if callable(item) and not isinstance(item, AggregatePermission):
item(request, kwargs)
return SimpleNamespace(hasPermission=lambda *args, **kwargs: True)

@classmethod
def build_extended_user_request(cls, permission_constant):
source = permission_constant.value
permission = Permission(
group=source.group,
sub_group=source.sub_group,
operate=source.operate,
bit_index=source.bit_index,
workspace_id=cls.workspace_id,
resource_id=cls.resource_id,
)
return SimpleNamespace(
query_params={},
data=[],
auth=SimpleNamespace(
roles={f"EXTENDS_USER:w:{cls.workspace_id}"},
permissions={permission.get_resource_permission_key(cls.resource_id): permission.bit()},
),
)

@classmethod
def endpoint_kwargs(cls):
return {
"workspace_id": cls.workspace_id,
"resource_type": "APPLICATION",
"resource_id": cls.resource_id,
"user_group_id": "group-1",
}

def test_supported_resources_keep_existing_permissions(self):
self.assertEqual(
PermissionConstants.APPLICATION_CHAT_USER_READ,
Expand Down Expand Up @@ -61,6 +94,52 @@ def test_list_page_and_group_endpoints_reject_tool_permission(self):
resource_chat_user_group.ResourceChatUserGroup(), SimpleNamespace(), **kwargs
)

def test_extended_user_can_read_chat_users_and_groups(self):
request = self.build_extended_user_request(PermissionConstants.APPLICATION_CHAT_USER_READ)
kwargs = self.endpoint_kwargs()
group_kwargs = {key: value for key, value in kwargs.items() if key != "user_group_id"}

with (
patch("system_manage.views.resource_chat_user.ResourceChatUserQuerySerializer") as user_query,
patch("system_manage.views.resource_chat_user_group.ResourceChatUserGroupQuerySerializer") as group_query,
):
user_query.return_value.list.return_value = []
user_query.return_value.page.return_value = []
group_query.return_value.list.return_value = []
group_query.return_value.page.return_value = []

ResourceChatUser.get(ResourceChatUser(), request, **kwargs)
ResourceChatUser.Page.get(ResourceChatUser.Page(), request, **kwargs, current_page=1, page_size=20)
resource_chat_user_group.ResourceChatUserGroup.get(
resource_chat_user_group.ResourceChatUserGroup(), request, **group_kwargs
)
resource_chat_user_group.ResourceChatUserGroup.Page.get(
resource_chat_user_group.ResourceChatUserGroup.Page(),
request,
**group_kwargs,
current_page=1,
page_size=20,
)

def test_extended_user_can_edit_chat_users_and_groups(self):
request = self.build_extended_user_request(PermissionConstants.APPLICATION_CHAT_USER_EDIT)
kwargs = self.endpoint_kwargs()
group_kwargs = {key: value for key, value in kwargs.items() if key != "user_group_id"}

with (
patch("system_manage.views.resource_chat_user.ResourceChatUserSerializer") as user_edit,
patch("system_manage.views.resource_chat_user_group.ResourceChatUserGroupSerializer") as group_edit,
):
user_edit.return_value.edit.return_value = []
group_edit.return_value.edit.return_value = []
ResourceChatUser.put(ResourceChatUser(), request, **kwargs)
resource_chat_user_group.ResourceChatUserGroup.put(
resource_chat_user_group.ResourceChatUserGroup(), request, **group_kwargs
)

user_edit.return_value.edit.assert_called_once_with([])
group_edit.return_value.edit.assert_called_once_with([])


class ChatUserEditTests(TestCase):
def test_status_and_password_changes_preserve_user_groups(self):
Expand Down
75 changes: 29 additions & 46 deletions apps/system_manage/views/resource_chat_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ def get_chat_user_permission_constant(resource_type, operate):
return PermissionConstants[f"{normalized}_CHAT_USER_{operate}"]


def get_chat_user_role_permissions(operate):
def get_permission(request, kwargs):
constant = get_chat_user_permission_constant(kwargs.get("resource_type"), operate).value
return Permission(
group=constant.group,
sub_group=constant.sub_group,
operate=constant.operate,
bit_index=constant.bit_index,
workspace_id=kwargs.get("workspace_id"),
resource_id=kwargs.get("resource_id"),
)

return [
ViewPermission(
[RoleConstants.USER.get_workspace_role()],
[get_permission],
compare=CompareConstants.AND,
),
ViewPermission(
[RoleConstants.EXTENDS_USER.get_workspace_role()],
[get_permission],
compare=CompareConstants.AND,
),
]


class ResourceChatUser(APIView):
authentication_classes = [TokenAuth]

Expand All @@ -59,20 +85,7 @@ class ResourceChatUser(APIView):
lambda r, kwargs: get_chat_user_permission_constant(
kwargs.get("resource_type"), "READ"
).get_workspace_permission_workspace_manage_role(),
ViewPermission(
[RoleConstants.USER.get_workspace_role()],
[
lambda r, kwargs: Permission(
group=get_chat_user_permission_constant(kwargs.get("resource_type"), "READ").value.group,
sub_group=get_chat_user_permission_constant(kwargs.get("resource_type"), "READ").value.sub_group,
operate=get_chat_user_permission_constant(kwargs.get("resource_type"), "READ").value.operate,
bit_index=get_chat_user_permission_constant(kwargs.get("resource_type"), "READ").value.bit_index,
workspace_id=kwargs.get("workspace_id"),
resource_id=kwargs.get("resource_id"),
)
],
compare=CompareConstants.AND,
),
*get_chat_user_role_permissions("READ"),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
)
def get(self, request: Request, workspace_id: str, resource_type: str, resource_id: str, user_group_id: str):
Expand Down Expand Up @@ -107,20 +120,7 @@ def get(self, request: Request, workspace_id: str, resource_type: str, resource_
lambda r, kwargs: get_chat_user_permission_constant(
kwargs.get("resource_type"), "EDIT"
).get_workspace_permission_workspace_manage_role(),
ViewPermission(
[RoleConstants.USER.get_workspace_role()],
[
lambda r, kwargs: Permission(
group=get_chat_user_permission_constant(kwargs.get("resource_type"), "EDIT").value.group,
sub_group=get_chat_user_permission_constant(kwargs.get("resource_type"), "EDIT").value.sub_group,
operate=get_chat_user_permission_constant(kwargs.get("resource_type"), "EDIT").value.operate,
bit_index=get_chat_user_permission_constant(kwargs.get("resource_type"), "EDIT").value.bit_index,
workspace_id=kwargs.get("workspace_id"),
resource_id=kwargs.get("resource_id"),
)
],
compare=CompareConstants.AND,
),
*get_chat_user_role_permissions("EDIT"),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
)
def put(self, request: Request, workspace_id: str, resource_type, resource_id: str, user_group_id: str):
Expand Down Expand Up @@ -154,24 +154,7 @@ class Page(APIView):
lambda r, kwargs: get_chat_user_permission_constant(
kwargs.get("resource_type"), "READ"
).get_workspace_permission_workspace_manage_role(),
ViewPermission(
[RoleConstants.USER.get_workspace_role()],
[
lambda r, kwargs: Permission(
group=get_chat_user_permission_constant(kwargs.get("resource_type"), "READ").value.group,
sub_group=get_chat_user_permission_constant(
kwargs.get("resource_type"), "READ"
).value.sub_group,
operate=get_chat_user_permission_constant(kwargs.get("resource_type"), "READ").value.operate,
bit_index=get_chat_user_permission_constant(
kwargs.get("resource_type"), "READ"
).value.bit_index,
workspace_id=kwargs.get("workspace_id"),
resource_id=kwargs.get("resource_id"),
)
],
compare=CompareConstants.AND,
),
*get_chat_user_role_permissions("READ"),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
)
def get(
Expand Down
Loading
Loading