diff --git a/saas/backend/api/management/constants.py b/saas/backend/api/management/constants.py index 2eb402766..2c788571d 100644 --- a/saas/backend/api/management/constants.py +++ b/saas/backend/api/management/constants.py @@ -55,6 +55,7 @@ class ManagementAPIEnum(BaseAPIEnum): V2_GROUP_MEMBER_ADD = auto() V2_GROUP_MEMBER_DELETE = auto() V2_GROUP_MEMBER_EXPIRED_AT_UPDATE = auto() + V2_GROUP_MEMBER_EXPIRED_AT_BATCH = auto() V2_GROUP_SUBJECT_TEMPLATE_LIST = auto() # 用户组权限 V2_GROUP_POLICY_GRANT = auto() @@ -64,6 +65,7 @@ class ManagementAPIEnum(BaseAPIEnum): # 用户组申请单 V2_GROUP_APPLICATION_CREATE = auto() V2_GROUP_APPLICATION_RENEW = auto() + V2_GROUP_APPLICATION_RENEW_BATCH = auto() # 用户组归属 V2_USER_GROUPS_BELONG_CHECK = auto() V2_DEPARTMENT_GROUPS_BELONG_CHECK = auto() @@ -125,6 +127,7 @@ class ManagementAPIEnum(BaseAPIEnum): (V2_GROUP_MEMBER_ADD, "[V2]添加用户组成员"), (V2_GROUP_MEMBER_DELETE, "[V2]删除用户组成员"), (V2_GROUP_MEMBER_EXPIRED_AT_UPDATE, "[V2]用户组成员续期"), + (V2_GROUP_MEMBER_EXPIRED_AT_BATCH, "[V2]用户组成员批量续期"), (V2_GROUP_SUBJECT_TEMPLATE_LIST, "[V2]获取用户组人员模板列表"), # 用户组权限 (V2_GROUP_POLICY_GRANT, "[V2]授权用户组"), @@ -134,6 +137,7 @@ class ManagementAPIEnum(BaseAPIEnum): # 用户组申请单 (V2_GROUP_APPLICATION_CREATE, "[V2]创建用户组申请单"), (V2_GROUP_APPLICATION_RENEW, "[V2]用户组续期申请单"), + (V2_GROUP_APPLICATION_RENEW_BATCH, "[V2]用户组批量续期申请单"), # 用户组归属 (V2_USER_GROUPS_BELONG_CHECK, "[V2]判断用户与用户组归属"), (V2_DEPARTMENT_GROUPS_BELONG_CHECK, "[V2]判断部门与用户组归属"), @@ -168,6 +172,7 @@ class VerifyApiParamLocationEnum(ChoicesEnum, LowerStrEnum): SYSTEM_IN_BODY = auto() SYSTEM_IN_QUERY = auto() GROUP_IDS_IN_BODY = auto() + GROUPS_IN_BODY = auto() SYSTEM_IN_PATH = auto() GROUP_IDS_IN_QUERY = auto() @@ -178,6 +183,7 @@ class VerifyApiParamLocationEnum(ChoicesEnum, LowerStrEnum): (SYSTEM_IN_BODY, "在body data里的system参数"), (SYSTEM_IN_QUERY, "在get请求query里的system参数"), (GROUP_IDS_IN_BODY, "在body data里的groups_ids参数"), + (GROUPS_IN_BODY, "在body data里的groups参数"), (SYSTEM_IN_PATH, "在路径里的system参数"), (GROUP_IDS_IN_QUERY, "在get请求query里的groups_ids参数"), ) diff --git a/saas/backend/api/management/v2/permissions.py b/saas/backend/api/management/v2/permissions.py index e111ad9c3..60f16dec2 100644 --- a/saas/backend/api/management/v2/permissions.py +++ b/saas/backend/api/management/v2/permissions.py @@ -20,7 +20,10 @@ VerifyAPIParamSourceToObjectTypeMap, ) from backend.api.management.mixins import ManagementAPIPermissionCheckMixin -from backend.api.management.v2.serializers import ManagementGroupIDsSLZ +from backend.api.management.v2.serializers import ( + ManagementGroupIDsSLZ, + ManagementGroupApplicationBatchSLZ, +) from backend.apps.role.models import Role, RoleRelatedObject, RoleSource from backend.service.constants import RoleRelatedObjectType, RoleSourceType, RoleType @@ -114,6 +117,15 @@ def _verify_api(self, view, request, param_source, api): self._verify_api_by_groups(app_code, group_ids, api, system_id) return + # 参数来自body data - groups + if param_source == VerifyApiParamLocationEnum.GROUPS_IN_BODY: + slz = ManagementGroupApplicationBatchSLZ(data=request.data) + slz.is_valid(raise_exception=True) + group_ids = [group["id"] for group in slz.validated_data["groups"]] + + self._verify_api_by_groups(app_code, group_ids, api, system_id) + return + def _verify_api_by_role(self, app_code: str, role_id: int, api: ManagementAPIEnum, system_id: str): """ 由于管理员API有很大一部分都是在需要在分级管理员角色下操作的 diff --git a/saas/backend/api/management/v2/serializers.py b/saas/backend/api/management/v2/serializers.py index eaad5f30b..42baf4021 100644 --- a/saas/backend/api/management/v2/serializers.py +++ b/saas/backend/api/management/v2/serializers.py @@ -415,3 +415,19 @@ class ManagementSubjectTemplateSLZ(serializers.ModelSerializer): class Meta: model = SubjectTemplate fields = ("id", "name", "description", "readonly", "source_group_id", "creator", "created_time") + + +class ManagementGroupBatchSLZ(ExpiredAtSLZ): + id = serializers.IntegerField(help_text="用户组 ID") + + +class ManagementGroupApplicationBatchSLZ(ReasonSLZ): + groups = serializers.ListField( + child=ManagementGroupBatchSLZ(label="用户组信息")) + applicant = serializers.CharField(label="申请者的用户名", max_length=32) + content_template = serializers.DictField(label="审批单内容模板", required=False, + allow_empty=True, default=dict) + group_content = serializers.DictField(label="审批单内容", required=False, + allow_empty=True, default=dict) + title_prefix = serializers.CharField(label="审批单标题前缀", required=False, + allow_blank=True, default="") diff --git a/saas/backend/api/management/v2/urls.py b/saas/backend/api/management/v2/urls.py index 7b40c24e6..fdd151144 100644 --- a/saas/backend/api/management/v2/urls.py +++ b/saas/backend/api/management/v2/urls.py @@ -57,6 +57,12 @@ views.ManagementGroupMemberExpiredAtViewSet.as_view({"put": "update"}), name="open.management.v2.group_member.expired_at", ), + # 用户组成员有效期批量更新(不支持人员模板) + path( + "groups//members/batch/expired_at/", + views.ManagementGroupMemberExpiredAtViewSet.as_view({"post": "batch"}), + name="open.management.v2.group_member.batch_expired_at", + ), # 用户组下类型为人员模板的成员 path( "groups//subject_templates/", @@ -88,6 +94,13 @@ views.ManagementGroupRenewApplicationViewSet.as_view({"post": "create"}), name="open.management.v2.group_renew_application", ), + # 用户组批量续期申请单 + path( + "groups/-/batch/applications/", + views.ManagementGroupRenewApplicationViewSet. + as_view({"post": "batch"}), + name="open.management.v2.group_batch_renew_application", + ), # 用户组申请单 path( "groups/-/applications/", diff --git a/saas/backend/api/management/v2/views/application.py b/saas/backend/api/management/v2/views/application.py index 44e7add03..3d9ea8253 100644 --- a/saas/backend/api/management/v2/views/application.py +++ b/saas/backend/api/management/v2/views/application.py @@ -26,6 +26,7 @@ ManagementGradeManagerApplicationResultSLZ, ManagementGradeManagerCreateApplicationSLZ, ManagementGroupApplicationCreateSLZ, + ManagementGroupApplicationBatchSLZ, ) from backend.apps.application.models import Application from backend.apps.organization.models import User as UserModel @@ -279,6 +280,10 @@ class ManagementGroupRenewApplicationViewSet(GenericViewSet): VerifyApiParamLocationEnum.GROUP_IDS_IN_BODY.value, ManagementAPIEnum.V2_GROUP_APPLICATION_RENEW.value, ), + "batch": ( + VerifyApiParamLocationEnum.GROUPS_IN_BODY.value, + ManagementAPIEnum.V2_GROUP_APPLICATION_RENEW_BATCH.value, + ), } biz = ApplicationBiz() @@ -327,3 +332,52 @@ def create(self, request, *args, **kwargs): ) return Response({"ids": [a.id for a in applications]}) + + @swagger_auto_schema( + operation_description="用户组批量续期申请单", + request_body=ManagementGroupApplicationBatchSLZ(label="用户组批量续期申请单"), + responses={status.HTTP_200_OK: ManagementApplicationIDSLZ( + label="单据ID列表")}, + tags=["management.group.application"], + ) + def batch(self, request, *args, **kwargs): + """ + 用户组批量续期申请单,支持不同过期时间 + """ + serializer = ManagementGroupApplicationBatchSLZ(data=request.data) + serializer.is_valid(raise_exception=True) + data = serializer.validated_data + + # 判断用户加入的用户组数与申请的数是否超过最大限制 + user_id = data["applicant"] + + # 转换为ApplicationBiz创建申请单所需数据结构 + user = UserModel.objects.filter(username=user_id).first() + if not user: + raise (error_codes.INVALID_ARGS. + format(f"user: {user_id} not exists")) + + source_system_id = kwargs["system_id"] + + # 检查用户组数量是否超限 + self.group_biz.check_subject_groups_quota( + Subject.from_username(user_id), data["groups"]) + + # 创建申请 + applications = self.biz.create_for_group( + ApplicationType.RENEW_GROUP.value, + GroupApplicationDataBean( + applicant=user.username, + reason=data["reason"], + groups=parse_obj_as( + List[ApplicationGroupInfoBean], + data["groups"], + ), + applicants=[Applicant(type=SubjectType.USER.value, + id=user.username, + display_name=user.display_name)], + ), + source_system_id=source_system_id, + ) + + return Response({"ids": [a.id for a in applications]}) diff --git a/saas/backend/api/management/v2/views/group.py b/saas/backend/api/management/v2/views/group.py index 3f948eaa5..7f0ab2612 100644 --- a/saas/backend/api/management/v2/views/group.py +++ b/saas/backend/api/management/v2/views/group.py @@ -45,7 +45,10 @@ GroupUpdateAuditProvider, ) from backend.apps.group.models import Group -from backend.apps.group.serializers import GroupAddMemberSLZ +from backend.apps.group.serializers import ( + GroupAddMemberSLZ, + GroupBatchUpdateMemberSLZ, +) from backend.apps.group.views import split_members_to_subject_and_template from backend.apps.policy.models import Policy from backend.apps.policy.serializers import PolicySLZ @@ -450,6 +453,10 @@ class ManagementGroupMemberExpiredAtViewSet(GenericViewSet): VerifyApiParamLocationEnum.GROUP_IN_PATH.value, ManagementAPIEnum.V2_GROUP_MEMBER_EXPIRED_AT_UPDATE.value, ), + "batch": ( + VerifyApiParamLocationEnum.GROUP_IN_PATH.value, + ManagementAPIEnum.V2_GROUP_MEMBER_EXPIRED_AT_BATCH.value, + ), } lookup_field = "id" @@ -484,6 +491,34 @@ def update(self, request, *args, **kwargs): return Response({}) + @swagger_auto_schema( + operation_description="用户组成员有效期批量更新", + request_body=GroupBatchUpdateMemberSLZ(label="用户组成员"), + responses={status.HTTP_200_OK: serializers.Serializer()}, + tags=["management.role.group.member"], + ) + @view_audit_decorator(GroupMemberRenewAuditProvider) + def batch(self, request, *args, **kwargs): + group = self.get_object() + + serializer = GroupBatchUpdateMemberSLZ(data=request.data) + serializer.is_valid(raise_exception=True) + data = serializer.validated_data + + members = [ + GroupMemberExpiredAtBean(type=m["type"], id=m["id"], + expired_at=m["expired_at"]) + for m in data["members"] + ] + + # 更新有效期 + self.biz.update_members_expired_at(group.id, members) + + # 写入审计上下文 + audit_context_setter(group=group, members=data["members"]) + + return Response({}) + class ManagementGroupSubjectTemplateViewSet(GenericViewSet): """用户组类型为人员模板的成员""" diff --git a/saas/backend/apps/group/serializers.py b/saas/backend/apps/group/serializers.py index a499f9eed..09d604a59 100644 --- a/saas/backend/apps/group/serializers.py +++ b/saas/backend/apps/group/serializers.py @@ -402,3 +402,29 @@ def get_created_time(self, obj): class SearchTemplateGroupMemberSLZ(SearchMemberSLZ): template_id = serializers.IntegerField(label="模板ID", required=True) + + +class GroupMemberExpiredSLZ(GroupMemberSLZ): + expired_at = serializers.IntegerField(label="过期时间", + max_value=PERMANENT_SECONDS) + + +class GroupBatchUpdateMemberSLZ(serializers.Serializer): + members = serializers.ListField(label="成员列表", + child=GroupMemberExpiredSLZ(label="成员"), + allow_empty=False) + + def validate_members(self, value): + members = [] + + for m in value: + # 过期时间不能小于当前时间 + if m["expired_at"] <= int(time.time()): + raise (serializers. + ValidationError("expired_at must more then now")) + # 屏蔽admin授权 + if not (m["type"] == GroupMemberType.USER.value + and m["id"] == ADMIN_USER): + members.append(m) + + return members