diff --git a/apps/common/constants/permission_constants.py b/apps/common/constants/permission_constants.py index b1d40debdbc..14db6931c9f 100644 --- a/apps/common/constants/permission_constants.py +++ b/apps/common/constants/permission_constants.py @@ -102,6 +102,9 @@ class Group(Enum): APPLICATION_FOLDER = "APPLICATION_FOLDER" KNOWLEDGE_FOLDER = "KNOWLEDGE_FOLDER" TOOL_FOLDER = "TOOL_FOLDER" + # 身份与权限 Identity & Access Management + IAM = "IAM" + class SystemGroup(Enum): @@ -124,6 +127,7 @@ class SystemGroup(Enum): SYSTEM_SETTING = "SYSTEM_SETTING" OPERATION_LOG = "OPERATION_LOG" OTHER = "OTHER" + USER_GROUP = "USER_GROUP" class WorkspaceGroup(Enum): @@ -145,6 +149,24 @@ class UserGroup(Enum): OTHER = "OTHER" +class TopLevelGroup(Enum): + """ + 一级目录(最顶层分类),用于在 parent_group 之上再加一层分类 + """ + # 身份与权限 + IAM = "IAM" + # 资源管理 + RESOURCE = "RESOURCE" + # 共享资源 + SHARED = "SHARED" + CHAT_CLIENT = "CHAT_CLIENT" + # 系统设置 + SYSTEM_SETTING = "SYSTEM_SETTING" + #操作日志 + OPERATION_LOG = "OPERATION_LOG" + OTHER = "OTHER" + + class Operate(Enum): """ 一个权限组的操作权限 @@ -323,14 +345,14 @@ def get_workspace_role(self): SystemGroup.USER_MANAGEMENT.value: _("User Management"), SystemGroup.ROLE.value: _("Role"), SystemGroup.WORKSPACE.value: _("Workspace"), - SystemGroup.RESOURCE_APPLICATION.value: _("Resource Application"), - SystemGroup.RESOURCE_KNOWLEDGE.value: _("Resource Knowledge"), - SystemGroup.RESOURCE_TOOL.value: _("Resource Tool"), - SystemGroup.RESOURCE_MODEL.value: _("Resource Model"), + SystemGroup.RESOURCE_APPLICATION.value: _("Application"), + SystemGroup.RESOURCE_KNOWLEDGE.value: _("Knowledge"), + SystemGroup.RESOURCE_TOOL.value: _("Tool"), + SystemGroup.RESOURCE_MODEL.value: _("Model"), SystemGroup.RESOURCE_PERMISSION.value: _("Resource Permission"), - SystemGroup.SHARED_KNOWLEDGE.value: _("Shared Knowledge"), - SystemGroup.SHARED_MODEL.value: _("Shared Model"), - SystemGroup.SHARED_TOOL.value: _("Shared Tool"), + SystemGroup.SHARED_KNOWLEDGE.value: _("Knowledge"), + SystemGroup.SHARED_MODEL.value: _("Model"), + SystemGroup.SHARED_TOOL.value: _("Tool"), SystemGroup.OPERATION_LOG.value: _("Operation Log"), SystemGroup.OTHER.value: _("Other"), WorkspaceGroup.SYSTEM_MANAGEMENT.value: _("System Management"), @@ -439,9 +461,48 @@ def get_workspace_role(self): Group.APPLICATION_FOLDER.value: _("Folder"), Group.KNOWLEDGE_FOLDER.value: _("Folder"), Group.TOOL_FOLDER.value: _("Folder"), - # SystemGroup.RESOURCE.value: _("Resource"), + TopLevelGroup.IAM.value: _("IAM"), + TopLevelGroup.RESOURCE.value: _("Resource"), + TopLevelGroup.SHARED.value: _("Shared"), + TopLevelGroup.CHAT_CLIENT.value: _("Chat Client"), +} +GROUPS = { + TopLevelGroup.IAM: ( + SystemGroup.USER_MANAGEMENT, + SystemGroup.ROLE, + SystemGroup.WORKSPACE, + SystemGroup.USER_GROUP, + SystemGroup.RESOURCE_PERMISSION, + ), + TopLevelGroup.RESOURCE: ( + SystemGroup.RESOURCE_APPLICATION, + SystemGroup.RESOURCE_KNOWLEDGE, + SystemGroup.RESOURCE_TOOL, + SystemGroup.RESOURCE_MODEL, + ), + TopLevelGroup.SHARED: ( + SystemGroup.SHARED_KNOWLEDGE, + SystemGroup.SHARED_MODEL, + SystemGroup.SHARED_TOOL, + ), + TopLevelGroup.CHAT_CLIENT: ( + SystemGroup.CHAT_USER, + ), + TopLevelGroup.SYSTEM_SETTING: ( + SystemGroup.SYSTEM_SETTING, + ), + TopLevelGroup.OPERATION_LOG: ( + SystemGroup.OPERATION_LOG, + ), + TopLevelGroup.OTHER: ( + SystemGroup.OTHER, + ) +} +TOP_LEVEL_GROUP_MAP = { + group: top_level + for top_level, groups in GROUPS.items() + for group in groups } - class Permission: """ @@ -449,7 +510,8 @@ class Permission: """ def __init__(self, group: Group, operate: Operate, resource_path=None, role_list=None, - resource_permission_group_list=None, parent_group=None, label=None, is_ee=True): + resource_permission_group_list=None, parent_group=None, label=None, is_ee=True, + top_group=None): if role_list is None: role_list = [] if resource_permission_group_list is None: @@ -464,6 +526,7 @@ def __init__(self, group: Group, operate: Operate, resource_path=None, role_list self.parent_group = parent_group # 新增字段:父级组 self.label = label self.is_ee = is_ee # 是否是企业版权限 + self.top_group = top_group # 一级目录分类 @staticmethod def new_instance(permission_str: str): @@ -521,6 +584,31 @@ class PermissionConstants(Enum): parent_group=[SystemGroup.USER_MANAGEMENT] ) + SYSTEM_USER_GROUP_READ = Permission(group=Group.USER_GROUP, operate=Operate.READ, + role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], + parent_group=[SystemGroup.USER_GROUP] + ) + SYSTEM_USER_GROUP_CREATE = Permission(group=Group.USER_GROUP, operate=Operate.CREATE, + role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], + parent_group=[SystemGroup.USER_GROUP] + ) + SYSTEM_USER_GROUP_EDIT = Permission(group=Group.USER_GROUP, operate=Operate.EDIT, + role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], + parent_group=[SystemGroup.USER_GROUP] + ) + SYSTEM_USER_GROUP_DELETE = Permission(group=Group.USER_GROUP, operate=Operate.DELETE, + role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], + parent_group=[SystemGroup.USER_GROUP] + ) + SYSTEM_USER_GROUP_ADD_MEMBER = Permission(group=Group.USER_GROUP, operate=Operate.ADD_MEMBER, + role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], + parent_group=[SystemGroup.USER_GROUP] + ) + SYSTEM_USER_GROUP_REMOVE_MEMBER = Permission(group=Group.USER_GROUP, operate=Operate.REMOVE_MEMBER, + role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], + parent_group=[SystemGroup.USER_GROUP] + ) + MODEL_READ = Permission( group=Group.MODEL, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL], diff --git a/apps/locales/en_US/LC_MESSAGES/django.po b/apps/locales/en_US/LC_MESSAGES/django.po index e75765b138d..ae2ad025b79 100644 --- a/apps/locales/en_US/LC_MESSAGES/django.po +++ b/apps/locales/en_US/LC_MESSAGES/django.po @@ -9381,4 +9381,34 @@ msgid "User Name" msgstr "User Name" msgid "New chat" -msgstr "A new conversation has been generated. Please ask your question again!" \ No newline at end of file +msgstr "A new conversation has been generated. Please ask your question again!" + +msgid "IAM" +msgstr "IAM" + +msgid "Chat Client" +msgstr "Chat Client" + +msgid "Shared" +msgstr "" + +msgid "IAM/User Group" +msgstr "" + +msgid "Create or update System User Group" +msgstr "" + +msgid "Get System User Group list by workspace id" +msgstr "" + +msgid "Delete System User Group" +msgstr "" + +msgid "Add members to System User Group" +msgstr "" + +msgid "Unauthorized users are present" +msgstr "" + +msgid "Remove members from System User Group" +msgstr "" \ No newline at end of file diff --git a/apps/locales/zh_CN/LC_MESSAGES/django.po b/apps/locales/zh_CN/LC_MESSAGES/django.po index 308cbc30791..1a6239727c5 100644 --- a/apps/locales/zh_CN/LC_MESSAGES/django.po +++ b/apps/locales/zh_CN/LC_MESSAGES/django.po @@ -9504,4 +9504,34 @@ msgid "User Name" msgstr "用户名称" msgid "New chat" -msgstr "已生成新对话,请重新提问!" \ No newline at end of file +msgstr "已生成新对话,请重新提问!" + +msgid "IAM" +msgstr "身份与权限" + +msgid "Chat Client" +msgstr "对话端管理" + +msgid "Shared" +msgstr "共享资源" + +msgid "IAM/User Group" +msgstr "身份与权限/用户组" + +msgid "Create or update System User Group" +msgstr "创建或更新系统用户组" + +msgid "Get System User Group list by workspace id" +msgstr "通过工作空间 ID 获取系统用户组列表" + +msgid "Delete System User Group" +msgstr "删除系统用户组" + +msgid "Add members to System User Group" +msgstr "添加成员到系统用户组" + +msgid "Unauthorized users are present" +msgstr "存在未授权的用户" + +msgid "Remove members from System User Group" +msgstr "从系统用户组中移除成员" \ No newline at end of file diff --git a/apps/locales/zh_Hant/LC_MESSAGES/django.po b/apps/locales/zh_Hant/LC_MESSAGES/django.po index 463dd0706c3..2af4aac193c 100644 --- a/apps/locales/zh_Hant/LC_MESSAGES/django.po +++ b/apps/locales/zh_Hant/LC_MESSAGES/django.po @@ -9504,4 +9504,34 @@ msgid "User Name" msgstr "用戶名稱" msgid "New chat" -msgstr "已生成新對話,請重新提問!" \ No newline at end of file +msgstr "已生成新對話,請重新提問!" + +msgid "IAM" +msgstr "身份與權限" + +msgid "Chat Client" +msgstr "對話端管理" + +msgid "Shared" +msgstr "共享資源" + +msgid "IAM/User Group" +msgstr "身份與權限/用戶組" + +msgid "Create or update System User Group" +msgstr "創建或更新系統用戶組" + +msgid "Get System User Group list by workspace id" +msgstr "通過工作空間 ID 獲取系統用戶組列表" + +msgid "Delete System User Group" +msgstr "刪除系統用戶組" + +msgid "Add members to System User Group" +msgstr "添加成員到系統用戶組" + +msgid "Unauthorized users are present" +msgstr "存在未授權的使用者" + +msgid "Remove members from System User Group" +msgstr "從系統用戶組中移除成員" \ No newline at end of file diff --git a/apps/users/api/user_group.py b/apps/users/api/user_group.py new file mode 100644 index 00000000000..ef30d147668 --- /dev/null +++ b/apps/users/api/user_group.py @@ -0,0 +1,197 @@ +from django.utils.translation import gettext_lazy as _ +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter +from rest_framework import serializers + +from common.mixins.api_mixin import APIMixin +from common.result import ResultSerializer, DefaultResultSerializer +from users.serializers.user_group import SystemUserGroupModelSerializer, SystemUserGroupCreateSerializer + + +class UserGroupResponse(ResultSerializer): + def get_data(self): + return SystemUserGroupModelSerializer() + + +class CreateUserGroupApi(APIMixin): + @staticmethod + def get_request(): + return SystemUserGroupCreateSerializer + + @staticmethod + def get_response(): + return UserGroupResponse + + +class DeleteUserGroupApi(APIMixin): + @staticmethod + def get_parameters(): + return [OpenApiParameter( + name='workspace_id', + type=OpenApiTypes.STR, + description=_('Workspace ID'), + required=True, + location=OpenApiParameter.PATH, # type: ignore + ), + OpenApiParameter( + name="user_group_id", + description=_("User Group ID"), + type=OpenApiTypes.STR, + location=OpenApiParameter.PATH, # type: ignore + required=True, + )] + + @staticmethod + def get_response(): + return DefaultResultSerializer() + + +class UserGroupListResponse(ResultSerializer): + def get_data(self): + return SystemUserGroupModelSerializer(many=True) + + +class UserGroupListApi(APIMixin): + @staticmethod + def get_response(): + return UserGroupListResponse + + def get_parameters(self): + return [ + OpenApiParameter( + name='workspace_id', + type=OpenApiTypes.STR, + description=_('Workspace ID'), + required=True, + location=OpenApiParameter.PATH, # type: ignore + )] + + +class UserGroupListPageResponse(ResultSerializer): + def get_data(self): + return SystemUserGroupModelSerializer(many=True) + + +class UserGroupListPageApi(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name='workspace_id', + type=OpenApiTypes.STR, + description=_('Workspace ID'), + required=True, + location=OpenApiParameter.PATH, # type: ignore + ), + OpenApiParameter( + name='user_group_id', + type=OpenApiTypes.STR, + description=_('Group ID'), + required=True, + location=OpenApiParameter.PATH, # type: ignore + ), + OpenApiParameter( + name='current_page', + type=OpenApiTypes.INT, + description=_('Current page'), + required=True, + location=OpenApiParameter.PATH, # type: ignore + ), + OpenApiParameter( + name='page_size', + type=OpenApiTypes.INT, + description=_('Page size'), + required=True, + location=OpenApiParameter.PATH, # type: ignore + ), + OpenApiParameter( + name='username', + type=OpenApiTypes.STR, + description=_('Username'), + required=False, + location=OpenApiParameter.QUERY, # type: ignore + ), + OpenApiParameter( + name='nick_name', + type=OpenApiTypes.STR, + description=_('Nickname'), + required=False, + location=OpenApiParameter.QUERY, # type: ignore + ), + ] + + @staticmethod + def get_response(): + return UserGroupListPageResponse + + +class AddMemberRequest(serializers.Serializer): + user_ids = serializers.ListField( + child=serializers.CharField(required=True), + required=True, + label=_('User IDs') + ) + + +class AddMemberApi(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name='workspace_id', + type=OpenApiTypes.STR, + description=_('Workspace ID'), + required=True, + location=OpenApiParameter.PATH, # type: ignore + ), + OpenApiParameter( + name="user_group_id", + description=_("User Group ID"), + type=OpenApiTypes.STR, + location=OpenApiParameter.PATH, # type: ignore + required=True, + )] + + @staticmethod + def get_request(): + return AddMemberRequest + + @staticmethod + def get_response(): + return DefaultResultSerializer() + + +class RemoveMemberRequest(serializers.Serializer): + group_relation_ids = serializers.ListField( + child=serializers.CharField(required=True), + required=True, + label=_('User group relation IDs') + ) + + +class RemoveMemberApi(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name='workspace_id', + type=OpenApiTypes.STR, + description=_('Workspace ID'), + required=True, + location=OpenApiParameter.PATH, # type: ignore + ), + OpenApiParameter( + name="user_group_id", + description=_("User Group ID"), + type=OpenApiTypes.STR, + location=OpenApiParameter.PATH, # type: ignore + required=True, + )] + + @staticmethod + def get_request(): + return RemoveMemberRequest + + @staticmethod + def get_response(): + return DefaultResultSerializer() diff --git a/apps/users/models/user_group.py b/apps/users/models/user_group.py new file mode 100644 index 00000000000..c94f36d30aa --- /dev/null +++ b/apps/users/models/user_group.py @@ -0,0 +1,34 @@ +# coding=utf-8 +import uuid_utils.compat as uuid + +from django.db import models + +from users.models import User + + +class SystemUserGroup(models.Model): + id = models.CharField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") + name = models.CharField(max_length=150, verbose_name="名称", unique=True, db_index=True) + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) + create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True, db_index=True) + update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, null=True, db_index=True) + + class Meta: + db_table = "system_user_group" + unique_together = ("workspace_id", "name") + + +class SystemUserGroupRelation(models.Model): + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户") + group = models.ForeignKey(SystemUserGroup, on_delete=models.CASCADE, verbose_name="用户组") + + class Meta: + db_table = "system_user_group_relation" + constraints = [ + models.UniqueConstraint( + fields=["user", "group"], + name="uniq_user_group_relation" + ) + ] + diff --git a/apps/users/serializers/user_group.py b/apps/users/serializers/user_group.py new file mode 100644 index 00000000000..848d3355aa9 --- /dev/null +++ b/apps/users/serializers/user_group.py @@ -0,0 +1,242 @@ +# coding=utf-8 + +import uuid_utils.compat as uuid +from django.db import transaction +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from common.constants.permission_constants import RoleConstants +from common.database_model_manage.database_model_manage import DatabaseModelManage +from common.db.search import page_search +from common.exception.app_exception import AppApiException +from system_manage.models import UserGroup, UserGroupRelation +from users.models.user_group import SystemUserGroup, SystemUserGroupRelation +from users.serializers.user import UserInstanceSerializer + + +@transaction.atomic +def add_or_edit_user_group_relation(user, user_group_ids): + UserGroupRelation.objects.filter(user=user).delete() + if not user_group_ids: + return + groups = UserGroup.objects.filter(id__in=user_group_ids) + if groups.count() != len(user_group_ids): + raise AppApiException(500, _('Some user groups do not exist')) + + UserGroupRelation.objects.bulk_create([ + UserGroupRelation(user=user, group=group) + for group in groups + ]) + + +class SystemUserGroupModelSerializer(serializers.ModelSerializer): + class Meta: + model = SystemUserGroup + fields = ['id', 'name', 'workspace_id'] + + +class SystemUserGroupCreateSerializer(serializers.Serializer): + id = serializers.CharField(required=False, label='ID') + name = serializers.CharField(required=True, label='User Group Name') + workspace_id = serializers.CharField(required=True, label='Workspace ID') + + def validate(self, data): + group_id = data.get('id') + name = data.get('name') + workspace_id = data.get('workspace_id') + if group_id: + if not SystemUserGroup.objects.filter(id=group_id, workspace_id=workspace_id).exists(): + raise AppApiException(500, _("User group does not exist")) + if name: + queryset = SystemUserGroup.objects.filter(name=name, workspace_id=workspace_id) + if group_id: + queryset = queryset.exclude(id=group_id) + if queryset.exists(): + raise AppApiException(500, _("User group name already exists")) + return data + + def create_or_update_group(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + data = self.validated_data + group_id = data.get('id') + name = data['name'] + workspace_id = data.get('workspace_id', 'default') + + if group_id: + SystemUserGroup.objects.filter(id=group_id, workspace_id=workspace_id).update(name=name) + group = SystemUserGroup.objects.get(id=group_id, workspace_id=workspace_id) + else: + group = SystemUserGroup.objects.create( + id=uuid.uuid7(), + name=name, + workspace_id=workspace_id, + ) + return SystemUserGroupModelSerializer(group).data + + class UserGroupDeleteSerializer(serializers.Serializer): + id = serializers.CharField(required=True, label='ID') + workspace_id = serializers.CharField(required=True, label='Workspace ID') + + group = None + + def validate(self, attrs): + self.group = SystemUserGroup.objects.filter( + id=attrs["id"], + workspace_id=attrs["workspace_id"], + ).first() + + if self.group is None: + raise AppApiException(500, _("User group does not exist")) + + return attrs + + @transaction.atomic + def delete(self, *, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + + self.group.delete() + return True + + +class UserGroupAddMemberSerializer(serializers.Serializer): + id = serializers.CharField(required=True, label='ID') + workspace_id = serializers.CharField(required=True, label='Workspace ID') + user_ids = serializers.ListField( + child=serializers.CharField(required=True), + required=True, + label=_('User IDs') + ) + + def validate_normal_users(self, workspace_id: str, user_ids: list[str]): + if not user_ids: + return + + license_is_valid = DatabaseModelManage.get_model("license_is_valid") or (lambda: False) + license_is_valid = license_is_valid() if license_is_valid() is not None else False + if not license_is_valid: + return + + user_id_set = set(user_ids) + + mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") + valid_user_ids = set( + mapping_model.objects.filter( + workspace_id=workspace_id, + user_id__in=user_id_set, + role__type=RoleConstants.USER.name, + ).values_list("user_id", flat=True) + ) + + invalid_user_ids = user_id_set - valid_user_ids + if invalid_user_ids: + raise AppApiException(500, _("Unauthorized users are present")) + + def validate(self, data): + id = data.get('id') + workspace_id = data.get('workspace_id') + user_ids = data.get('user_ids') + group = SystemUserGroup.objects.filter(id=id, workspace_id=workspace_id).first() + if not group: + raise AppApiException(500, _("User group does not exist")) + if not user_ids: + raise AppApiException(500, _("User IDs cannot be empty")) + + self.validate_normal_users(workspace_id, user_ids) + return data + + def add_member(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + user_ids = self.data.get('user_ids') + workspace_id = self.data.get('workspace_id') + + current_user_group_ids = set( + str(user_id) for user_id in + SystemUserGroupRelation.objects.filter(group__id=self.data.get('id'), + group__workspace_id=workspace_id).values_list('user_id', flat=True) + ) + to_add = set(user_ids).difference(current_user_group_ids) + if to_add: + SystemUserGroupRelation.objects.bulk_create([ + SystemUserGroupRelation( + id=uuid.uuid7(), + user_id=user_id, + group_id=self.data.get('id') + ) + for user_id in to_add + ]) + return True + + +class UserGroupRemoveMemberSerializer(serializers.Serializer): + id = serializers.CharField(required=True, label='ID') + workspace_id = serializers.CharField(required=True, label='Workspace ID') + group_relation_ids = serializers.ListField( + child=serializers.CharField(required=True), + required=True, + label=_('User group relation IDs') + ) + + def validate(self, data): + group_id = data.get('id') + relation_ids = data.get('group_relation_ids') + if not SystemUserGroup.objects.filter(id=group_id).exists(): + raise AppApiException(500, _("User group does not exist")) + if not relation_ids: + raise AppApiException(500, _("User group relation IDs cannot be empty")) + return data + + def remove_member(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + data = self.validated_data + relation_ids = data['group_relation_ids'] + SystemUserGroupRelation.objects.filter(id__in=relation_ids).delete() + return True + + +class UserGroupListPageSerializer(serializers.Serializer): + class Query(serializers.Serializer): + workspace_id = serializers.CharField(required=True, label='Workspace ID') + group_id = serializers.CharField(required=True, label=_('Group ID')) + username = serializers.CharField(required=False, label=_('Username'), allow_null=True) + nick_name = serializers.CharField(required=False, label=_('Nick Name'), allow_null=True) + source = serializers.CharField(required=False, label=_('Source'), allow_null=True) + + def is_valid(self, *, raise_exception=False): + super().is_valid(raise_exception=raise_exception) + group_id = self.data.get('group_id') + workspace_id = self.data.get('workspace_id') + if not SystemUserGroup.objects.filter(id=group_id, workspace_id=workspace_id).exists(): + raise AppApiException(500, _("User group does not exist")) + + def page(self, current_page, page_size): + self.is_valid() + query_set = self.get_query_set() + result = page_search( + current_page, + page_size, + query_set, + post_records_handler=lambda relation: { + **UserInstanceSerializer(relation.user).data, + 'system_user_group_relation_id': relation.id + } + ) + return result + + def get_query_set(self): + group_id = self.data.get('group_id') + username = self.data.get('username') + nick_name = self.data.get('nick_name') + source = self.data.get('source') + query_set = SystemUserGroupRelation.objects.filter(group_id=group_id).select_related('user') + + if username is not None: + query_set = query_set.filter(user__username__contains=username) + if nick_name is not None: + query_set = query_set.filter(user__nick_name__contains=nick_name) + if source is not None: + query_set = query_set.filter(user__source=source) + return query_set.order_by('-user__create_time') diff --git a/apps/users/urls.py b/apps/users/urls.py index 27d7f914946..3576171de45 100644 --- a/apps/users/urls.py +++ b/apps/users/urls.py @@ -27,4 +27,17 @@ path("user_manage/", views.UserManage.Operate.as_view(), name="user_manage_operate"), path("user_manage//re_password", views.UserManage.RePassword.as_view(), name="user_manage_re_password"), path("user_manage//", views.UserManage.Page.as_view(), name="user_manage_page"), + path('system/workspace//user_group', views.SystemUserGroupView.as_view()), + path('system/workspace//user_group/', views.SystemUserGroupView.Delete.as_view()), + path('system/workspace//user_group//add_member', views.SystemUserGroupView.AddMember.as_view()), + path('system/workspace//user_group//remove_member', views.SystemUserGroupView.RemoveMember.as_view()), + path('system/workspace//user_group//user_list//', views.SystemUserGroupView.UserList.as_view()), ] + +''' +用户组需要的接口 未完成的 +1.用户组添加成员 +2。用户组移除成员 还有批量删除 +3 根据空间id 用户组id 展示用户组下的成员 需要是分页的 还需要过滤 + +''' diff --git a/apps/users/views/__init__.py b/apps/users/views/__init__.py index 9ef4e79ce76..9b93c142583 100644 --- a/apps/users/views/__init__.py +++ b/apps/users/views/__init__.py @@ -8,3 +8,4 @@ """ from .login import * from .user import * +from .system_user_group import * diff --git a/apps/users/views/system_user_group.py b/apps/users/views/system_user_group.py new file mode 100644 index 00000000000..e1786c825c3 --- /dev/null +++ b/apps/users/views/system_user_group.py @@ -0,0 +1,180 @@ +# coding=utf-8 + +from django.utils.translation import gettext_lazy as _ +from drf_spectacular.utils import extend_schema +from rest_framework.request import Request +from rest_framework.views import APIView + +from common import result +from common.auth import TokenAuth +from common.auth.authentication import has_permissions +from common.constants.permission_constants import PermissionConstants, RoleConstants +from common.log.log import log +from models_provider.api.model import DefaultModelResponse +from users.api.user_group import ( + AddMemberApi, CreateUserGroupApi, DeleteUserGroupApi, + RemoveMemberApi, UserGroupListApi, UserGroupListPageApi +) +from users.models.user_group import SystemUserGroup +from users.serializers.user_group import ( + SystemUserGroupCreateSerializer, + UserGroupAddMemberSerializer, + UserGroupRemoveMemberSerializer, + UserGroupListPageSerializer +) + + +def _get_operation_object(request, kwargs): + try: + return {"name": request.data.get("name", None)} + except Exception: + return {} + + +def _get_group_operation_object(group_id): + try: + group = SystemUserGroup.objects.filter(id=group_id).values("name").first() + return group or {} + except Exception: + return {} + + +class SystemUserGroupView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=["POST"], + summary=_("Create or update System User Group"), + description=_("Create or update System User Group"), + operation_id=_("Create or update System User Group"), + request=CreateUserGroupApi.get_request(), + responses=CreateUserGroupApi.get_response(), + tags=["IAM/System User Group"], + ) + @has_permissions(PermissionConstants.SYSTEM_USER_GROUP_CREATE, + PermissionConstants.SYSTEM_USER_GROUP_EDIT, + RoleConstants.ADMIN) + @log( + menu="IAM/System User Group", + operate="Create or update System User Group", + get_operation_object=_get_operation_object, + ) + def post(self, request: Request, workspace_id: str): + serializer = SystemUserGroupCreateSerializer(request.data) + data = serializer.create_or_update_group(with_valid=True) + return result.success(data) + + @extend_schema( + methods=["GET"], + summary=_("Get System User Group list by workspace id"), + description=_("Get System User Group list by workspace id"), + operation_id=_("Get System User Group list by workspace id"), + request=UserGroupListApi.get_parameters(), + responses=UserGroupListApi.get_response(), + tags=["IAM/System User Group"], + ) + @has_permissions(PermissionConstants.SYSTEM_USER_GROUP_READ, RoleConstants.ADMIN) + def get(self, request: Request, workspace_id: str): + groups = SystemUserGroup.objects.filter(workspace_id=workspace_id).order_by("name") + return result.success(SystemUserGroupModelSerializer(groups, many=True).data) + + class Delete(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=["DELETE"], + summary=_("Delete System User Group"), + description=_("Delete System User Group"), + operation_id=_("Delete System User Group"), + parameters=DeleteUserGroupApi.get_parameters(), + responses=DefaultModelResponse.get_response(), + tags=["IAM/System User Group"], + ) + @has_permissions(PermissionConstants.SYSTEM_USER_GROUP_DELETE, RoleConstants.ADMIN) + @log( + menu="IAM/System User Group", + operate="Delete System User Group", + get_operation_object=lambda r, k: _get_group_operation_object(k.get("user_group_id")), + ) + def delete(self, request: Request, workspace_id, user_group_id: str): + return result.success( + SystemUserGroupCreateSerializer.UserGroupDeleteSerializer( + data={"id": user_group_id, "workspace_id": workspace_id}).delete() + ) + + class AddMember(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=["POST"], + summary=_("Add members to System User Group"), + description=_("Add members to System User Group"), + operation_id=_("Add members to System User Group"), + parameters=AddMemberApi.get_parameters(), + request=AddMemberApi.get_request(), + responses=DefaultModelResponse.get_response(), + tags=["IAM/System User Group"], + ) + @has_permissions(PermissionConstants.SYSTEM_USER_GROUP_ADD_MEMBER, RoleConstants.ADMIN) + @log( + menu="IAM/System User Group", + operate="Add members to System User Group", + ) + def post(self, request: Request, workspace_id: str, user_group_id: str): + return result.success( + UserGroupAddMemberSerializer( + data={"id": user_group_id, "workspace_id": workspace_id, + "user_ids": request.data.get("user_ids", [])} + ).add_member() + ) + + class RemoveMember(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=["DELETE"], + summary=_("Remove members from System User Group"), + description=_("Remove members from System User Group"), + operation_id=_("Remove members from System User Group"), + parameters=RemoveMemberApi.get_parameters(), + request=RemoveMemberApi.get_request(), + responses=DefaultModelResponse.get_response(), + tags=["IAM/System User Group"], + ) + @has_permissions(PermissionConstants.SYSTEM_USER_GROUP_REMOVE_MEMBER, RoleConstants.ADMIN) + @log( + menu="IAM/System User Group", + operate="Remove members from System User Group", + ) + def delete(self, request: Request, workspace_id: str, user_group_id: str): + return result.success( + UserGroupRemoveMemberSerializer( + data={"id": user_group_id, "workspace_id": workspace_id, + "user_ids": request.data.get("user_ids", [])} + ).remove_member() + ) + + class UserList(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=["GET"], + summary=_("Get user list by group"), + description=_("Get user list by group"), + operation_id=_("Get user list by group"), # type: ignore + tags=[_("IAM/System User Group")], # type: ignore + parameters=UserGroupListPageApi.get_parameters(), + responses=UserGroupListPageApi.get_response(), + ) + @has_permissions(PermissionConstants.SYSTEM_USER_GROUP_READ, RoleConstants.ADMIN) + def get(self, request: Request, workspace_id: str, user_group_id: str, current_page: int, page_size: int): + d = UserGroupListPageSerializer.Query( + data={ + "username": request.query_params.get("username", None), + "nick_name": request.query_params.get("nick_name", None), + "source": request.query_params.get("source", None), + "group_id": user_group_id, + "workspace_id": workspace_id, + } + ) + return result.success(d.page(current_page, page_size))