Skip to content

Commit

Permalink
feat: 超管接口支持创建、更新、删除用户组 #2264 (#2605)
Browse files Browse the repository at this point in the history
* feat: 超管接口支持创建、更新、删除用户组 #2264

* feat: 超管接口支持创建、更新、删除用户组 #2264

* feat:超管类API增加授权工具接口 #2409
  • Loading branch information
Canway-shiisa authored Apr 25, 2024
1 parent a32a857 commit 730624c
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 9 deletions.
26 changes: 25 additions & 1 deletion saas/backend/api/admin/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,37 @@
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
from aenum import auto, skip
from aenum import LowerStrEnum, auto, skip

from backend.api.constants import BaseAPIEnum
from backend.util.enum import ChoicesEnum


class AdminAPIEnum(BaseAPIEnum):
"""枚举每个Admin API"""

# 用户组
GROUP_LIST = auto()
GROUP_BATCH_CREATE = auto()
GROUP_UPDATE = auto()
GROUP_DELETE = auto()

# 用户组成员
GROUP_MEMBER_LIST = auto()

# 用户组权限
GROUP_POLICY_GRANT = auto()

# 模板
TEMPLATE_CREATE = auto()

# Subject
SUBJECT_JOINED_GROUP_LIST = auto()
SUBJECT_ROLE_LIST = auto()

# System
SYSTEM_LIST = auto()
SYSTEM_PROVIDER_CONFIG_LIST = auto()

# 角色
ROLE_SUPER_MANAGER_MEMBER_LIST = auto()
Expand All @@ -47,8 +59,14 @@ class AdminAPIEnum(BaseAPIEnum):
_choices_labels = skip(
(
(SYSTEM_LIST, "获取系统列表"),
(SYSTEM_PROVIDER_CONFIG_LIST, "获取系统回调信息"),
(GROUP_LIST, "获取用户组列表"),
(GROUP_BATCH_CREATE, "批量创建用户组"),
(GROUP_UPDATE, "更新用户组"),
(GROUP_DELETE, "删除用户组"),
(GROUP_MEMBER_LIST, "获取用户组成员列表"),
(GROUP_POLICY_GRANT, "授权用户组"),
(TEMPLATE_CREATE, "新建模板"),
(SUBJECT_JOINED_GROUP_LIST, "获取Subject加入的用户组列表"),
(SUBJECT_ROLE_LIST, "获取Subject角色列表"),
(ROLE_SUPER_MANAGER_MEMBER_LIST, "获取超级管理员成员列表"),
Expand All @@ -59,3 +77,9 @@ class AdminAPIEnum(BaseAPIEnum):
(SUBJECT_PERMISSION_EXISTS, "权限是否存在"),
)
)


class VerifyApiParamLocationEnum(ChoicesEnum, LowerStrEnum):
GROUP_IN_PATH = auto()

_choices_labels = skip(((GROUP_IN_PATH, "在URL里的group id参数"),))
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2024-04-09 06:55

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api_admin', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='adminapiallowlistconfig',
name='api',
field=models.CharField(choices=[('system_list', '获取系统列表'), ('group_list', '获取用户组列表'), ('group_batch_create', '批量创建用户组'), ('group_update', '更新用户组'), ('group_delete', '删除用户组'), ('group_member_list', '获取用户组成员列表'), ('subject_joined_group_list', '获取Subject加入的用户组列表'), ('subject_role_list', '获取Subject角色列表'), ('role_super_manager_member_list', '获取超级管理员成员列表'), ('role_system_manager_member_list', '获取系统管理员及成员列表'), ('audit_event_list', '获取审计事件列表'), ('subject_freeze_unfreeze', '冻结/解冻Subject'), ('subject_permission_cleanup', '权限清理'), ('subject_permission_exists', '权限是否存在')], help_text='*代表任意', max_length=32, verbose_name='API'),
),
]
24 changes: 24 additions & 0 deletions saas/backend/api/admin/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
"""
from rest_framework import serializers

from backend.api.management.v2.serializers import ManagementGradeManagerGroupCreateSLZ
from backend.apps.group.models import Group
from backend.apps.group.serializers import GroupAuthorizationSLZ
from backend.apps.role.models import Role
from backend.apps.role.serializers import BaseGradeMangerSLZ
from backend.apps.template.serializers import TemplateCreateSLZ, TemplateIdSLZ
from backend.service.constants import GroupMemberType, RoleType


Expand All @@ -22,6 +25,10 @@ class Meta:
fields = ("id", "name", "description")


class AdminGroupCreateSLZ(ManagementGradeManagerGroupCreateSLZ):
pass


class AdminGroupMemberSLZ(serializers.Serializer):
type = serializers.ChoiceField(label="成员类型", choices=GroupMemberType.get_choices())
id = serializers.CharField(label="成员id")
Expand All @@ -35,6 +42,15 @@ class AdminSubjectGroupSLZ(serializers.Serializer):
expired_at = serializers.IntegerField(label="过期时间戳(单位秒)")


class AdminGroupAuthorizationSLZ(GroupAuthorizationSLZ):
pass


class AdminSystemProviderConfigSLZ(serializers.Serializer):
token = serializers.CharField(label="回调token")
host = serializers.CharField(label="回调地址")


class SystemManageSLZ(serializers.Serializer):
managers = serializers.ListField(child=serializers.CharField(label="成员"), max_length=100)

Expand Down Expand Up @@ -73,3 +89,11 @@ class SubjectSLZ(serializers.Serializer):
class FreezeSubjectResponseSLZ(serializers.Serializer):
type = serializers.CharField(label="SubjectType")
id = serializers.CharField(label="SubjectID")


class AdminTemplateCreateSLZ(TemplateCreateSLZ):
pass


class AdminTemplateIdSLZ(TemplateIdSLZ):
pass
22 changes: 21 additions & 1 deletion saas/backend/api/admin/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,27 @@

urlpatterns = [
# 用户组
path("groups/", views.AdminGroupViewSet.as_view({"get": "list"}), name="open.admin.group"),
path("groups/", views.AdminGroupViewSet.as_view({"get": "list", "post": "create"}), name="open.admin.group"),
# 用户组基本信息更新 & 删除
path(
"groups/<int:id>/",
views.AdminGroupInfoViewSet.as_view({"get": "update", "delete": "destroy"}),
name="open.admin.group",
),
# 用户组成员
path(
"groups/<int:id>/members/",
views.AdminGroupMemberViewSet.as_view({"get": "list"}),
name="open.admin.group_member",
),
# 用户组授权
path(
"groups/<str:id>/policies/",
views.AdminGroupPolicyViewSet.as_view({"post": "create"}),
name="open.admin.group_policy",
),
# 模板
path("templates/", views.AdminTemplateViewSet.as_view({"post": "create"}), name="open.admin.template"),
# Subject
path(
"subjects/<str:subject_type>/<str:subject_id>/groups/",
Expand All @@ -33,6 +47,12 @@
views.AdminSystemViewSet.as_view({"get": "list"}),
name="open.admin.system",
),
# 系统回调信息
path(
"systems/<str:system_id>/provider_config/",
views.AdminSystemProviderConfigViewSet.as_view({"get": "list"}),
name="open.admin.system_provider_config",
),
# 超级管理员成员列表 get
path(
"roles/super_managers/members/",
Expand Down
9 changes: 7 additions & 2 deletions saas/backend/api/admin/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
specific language governing permissions and limitations under the License.
"""
from .audit import AdminAuditEventViewSet
from .group import AdminGroupMemberViewSet, AdminGroupViewSet
from .group import AdminGroupInfoViewSet, AdminGroupMemberViewSet, AdminGroupPolicyViewSet, AdminGroupViewSet
from .role import AdminSuperManagerMemberViewSet, AdminSystemManagerMemberViewSet
from .subject import (
AdminSubjectFreezeViewSet,
Expand All @@ -18,10 +18,12 @@
AdminSubjectPermissionExistsViewSet,
AdminSubjectRoleViewSet,
)
from .system import AdminSystemViewSet
from .system import AdminSystemProviderConfigViewSet, AdminSystemViewSet
from .template import AdminTemplateViewSet

__all__ = [
"AdminGroupViewSet",
"AdminGroupInfoViewSet",
"AdminGroupMemberViewSet",
"AdminSubjectGroupViewSet",
"AdminSuperManagerMemberViewSet",
Expand All @@ -32,4 +34,7 @@
"AdminSubjectFreezeViewSet",
"AdminSubjectPermissionCleanupViewSet",
"AdminSubjectPermissionExistsViewSet",
"AdminTemplateViewSet",
"AdminGroupPolicyViewSet",
"AdminSystemProviderConfigViewSet",
]
134 changes: 129 additions & 5 deletions saas/backend/api/admin/views/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,38 @@
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
from typing import List

from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from pydantic.tools import parse_obj_as
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, mixins

from backend.api.admin.constants import AdminAPIEnum
from backend.api.admin.constants import AdminAPIEnum, VerifyApiParamLocationEnum
from backend.api.admin.filters import GroupFilter
from backend.api.admin.permissions import AdminAPIPermission
from backend.api.admin.serializers import AdminGroupBasicSLZ, AdminGroupMemberSLZ
from backend.api.admin.serializers import (
AdminGroupAuthorizationSLZ,
AdminGroupBasicSLZ,
AdminGroupCreateSLZ,
AdminGroupMemberSLZ,
)
from backend.api.authentication import ESBAuthentication
from backend.api.management.v2.views import ManagementGroupViewSet
from backend.apps.group.audit import GroupCreateAuditProvider, GroupTemplateCreateAuditProvider
from backend.apps.group.constants import OperateEnum
from backend.apps.group.models import Group
from backend.biz.group import GroupBiz
from backend.apps.group.views import check_readonly_group
from backend.apps.role.models import Role
from backend.audit.audit import add_audit, audit_context_setter, view_audit_decorator
from backend.audit.constants import AuditSourceType
from backend.biz.group import GroupBiz, GroupCheckBiz, GroupCreationBean
from backend.biz.role import RoleBiz
from backend.common.lock import gen_group_upsert_lock
from backend.common.pagination import CompatiblePagination
from backend.service.constants import GroupSaaSAttributeEnum, RoleType
from backend.trans.group import GroupTrans


class AdminGroupViewSet(mixins.ListModelMixin, GenericViewSet):
Expand All @@ -29,13 +48,16 @@ class AdminGroupViewSet(mixins.ListModelMixin, GenericViewSet):
authentication_classes = [ESBAuthentication]
permission_classes = [AdminAPIPermission]

admin_api_permission = {"list": AdminAPIEnum.GROUP_LIST.value}
admin_api_permission = {"list": AdminAPIEnum.GROUP_LIST.value, "create": AdminAPIEnum.GROUP_BATCH_CREATE.value}

queryset = Group.objects.all()
serializer_class = AdminGroupBasicSLZ
filterset_class = GroupFilter
pagination_class = CompatiblePagination

group_biz = GroupBiz()
group_check_biz = GroupCheckBiz()

@swagger_auto_schema(
operation_description="用户组列表",
responses={status.HTTP_200_OK: AdminGroupBasicSLZ(label="用户组信息", many=True)},
Expand All @@ -44,6 +66,63 @@ class AdminGroupViewSet(mixins.ListModelMixin, GenericViewSet):
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)

@swagger_auto_schema(
operation_description="批量创建用户组",
request_body=AdminGroupCreateSLZ(label="用户组"),
responses={status.HTTP_200_OK: serializers.ListSerializer(child=serializers.IntegerField(label="用户组ID"))},
tags=["admin.group"],
)
def create(self, request, *args, **kwargs):
role = Role.objects.get(type=RoleType.SUPER_MANAGER.value)

serializer = AdminGroupCreateSLZ(data=request.data)
serializer.is_valid(raise_exception=True)
groups_data = serializer.validated_data["groups"]
sync_subject_template = serializer.validated_data["sync_subject_template"]

# 用户组数量在角色内是否超限
self.group_check_biz.check_role_group_limit(role, len(groups_data))

infos = parse_obj_as(List[GroupCreationBean], groups_data)

attrs = None
if serializer.validated_data["create_attributes"]:
attrs = {
GroupSaaSAttributeEnum.SOURCE_TYPE.value: AuditSourceType.OPENAPI.value,
}

with gen_group_upsert_lock(role.id):
# 用户组名称在角色内唯一
group_names = [g["name"] for g in groups_data]
self.group_check_biz.batch_check_role_group_names_unique(role.id, group_names)

groups = self.group_biz.batch_create(
role,
infos,
request.user.username,
attrs=attrs,
sync_subject_template=sync_subject_template,
)

# 添加审计信息
# TODO: 后续其他地方也需要批量添加审计时再抽象出一个batch_add_audit方法,将for循环逻辑放到方法里
for g in groups:
add_audit(GroupCreateAuditProvider, request, group=g)

return Response([group.id for group in groups])


class AdminGroupInfoViewSet(ManagementGroupViewSet):
"""用户组"""

authentication_classes = [ESBAuthentication]
permission_classes = [AdminAPIPermission]

admin_api_permission = {
"update": (VerifyApiParamLocationEnum.GROUP_IN_PATH.value, AdminAPIEnum.GROUP_UPDATE.value),
"destroy": (VerifyApiParamLocationEnum.GROUP_IN_PATH.value, AdminAPIEnum.GROUP_DELETE.value),
}


class AdminGroupMemberViewSet(GenericViewSet):
"""用户组成员"""
Expand Down Expand Up @@ -73,3 +152,48 @@ def list(self, request, *args, **kwargs):
count, group_members = self.biz.list_paging_thin_group_member(group.id, limit, offset)
results = [one.dict(include={"type", "id", "name", "expired_at"}) for one in group_members]
return Response({"count": count, "results": results})


class AdminGroupPolicyViewSet(GenericViewSet):
"""用户组授权"""

authentication_classes = [ESBAuthentication]
permission_classes = [AdminAPIPermission]

admin_api_permission = {"create": AdminAPIEnum.GROUP_POLICY_GRANT.value}

pagination_class = None # 去掉swagger中的limit offset参数
queryset = Group.objects.all()
lookup_field = "id"

group_biz = GroupBiz()
role_biz = RoleBiz()

group_trans = GroupTrans()

@swagger_auto_schema(
operation_description="用户组添加权限",
request_body=AdminGroupAuthorizationSLZ(label="授权信息"),
responses={status.HTTP_201_CREATED: serializers.Serializer()},
tags=["admin.group.policy"],
)
@view_audit_decorator(GroupTemplateCreateAuditProvider)
@check_readonly_group(operation=OperateEnum.GROUP_POLICY_CREATE.label)
def create(self, request, *args, **kwargs):
serializer = AdminGroupAuthorizationSLZ(data=request.data)
serializer.is_valid(raise_exception=True)

group = self.get_object()
data = serializer.validated_data

role = self.role_biz.get_role_by_group_id(group.id)
templates = self.group_trans.from_group_grant_data(data["templates"])
self.group_biz.grant(role, group, templates)

# 写入审计上下文
audit_context_setter(
group=group,
templates=[{"system_id": t["system_id"], "template_id": t["template_id"]} for t in data["templates"]],
)

return Response({}, status=status.HTTP_201_CREATED)
Loading

0 comments on commit 730624c

Please sign in to comment.