diff --git a/saas/backend/api/admin/constants.py b/saas/backend/api/admin/constants.py index 89c00bdca..9e934441f 100644 --- a/saas/backend/api/admin/constants.py +++ b/saas/backend/api/admin/constants.py @@ -26,12 +26,19 @@ class AdminAPIEnum(BaseAPIEnum): # 用户组成员 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() @@ -52,11 +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, "获取超级管理员成员列表"), diff --git a/saas/backend/api/admin/serializers.py b/saas/backend/api/admin/serializers.py index 72e0dca94..734703944 100644 --- a/saas/backend/api/admin/serializers.py +++ b/saas/backend/api/admin/serializers.py @@ -12,8 +12,10 @@ 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 @@ -40,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) @@ -78,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 diff --git a/saas/backend/api/admin/urls.py b/saas/backend/api/admin/urls.py index a83497719..0f1c7c2e0 100644 --- a/saas/backend/api/admin/urls.py +++ b/saas/backend/api/admin/urls.py @@ -27,6 +27,14 @@ views.AdminGroupMemberViewSet.as_view({"get": "list"}), name="open.admin.group_member", ), + # 用户组授权 + path( + "groups//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///groups/", @@ -39,6 +47,12 @@ views.AdminSystemViewSet.as_view({"get": "list"}), name="open.admin.system", ), + # 系统回调信息 + path( + "systems//provider_config/", + views.AdminSystemProviderConfigViewSet.as_view({"get": "list"}), + name="open.admin.system_provider_config", + ), # 超级管理员成员列表 get path( "roles/super_managers/members/", diff --git a/saas/backend/api/admin/views/__init__.py b/saas/backend/api/admin/views/__init__.py index d22975fc8..f3d4763e8 100644 --- a/saas/backend/api/admin/views/__init__.py +++ b/saas/backend/api/admin/views/__init__.py @@ -9,7 +9,7 @@ specific language governing permissions and limitations under the License. """ from .audit import AdminAuditEventViewSet -from .group import AdminGroupInfoViewSet, AdminGroupMemberViewSet, AdminGroupViewSet +from .group import AdminGroupInfoViewSet, AdminGroupMemberViewSet, AdminGroupPolicyViewSet, AdminGroupViewSet from .role import AdminSuperManagerMemberViewSet, AdminSystemManagerMemberViewSet from .subject import ( AdminSubjectFreezeViewSet, @@ -18,7 +18,8 @@ AdminSubjectPermissionExistsViewSet, AdminSubjectRoleViewSet, ) -from .system import AdminSystemViewSet +from .system import AdminSystemProviderConfigViewSet, AdminSystemViewSet +from .template import AdminTemplateViewSet __all__ = [ "AdminGroupViewSet", @@ -33,4 +34,7 @@ "AdminSubjectFreezeViewSet", "AdminSubjectPermissionCleanupViewSet", "AdminSubjectPermissionExistsViewSet", + "AdminTemplateViewSet", + "AdminGroupPolicyViewSet", + "AdminSystemProviderConfigViewSet", ] diff --git a/saas/backend/api/admin/views/group.py b/saas/backend/api/admin/views/group.py index 3d4fcad6e..f9879d99f 100644 --- a/saas/backend/api/admin/views/group.py +++ b/saas/backend/api/admin/views/group.py @@ -19,18 +19,27 @@ 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, AdminGroupCreateSLZ, 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 +from backend.apps.group.audit import GroupCreateAuditProvider, GroupTemplateCreateAuditProvider +from backend.apps.group.constants import OperateEnum from backend.apps.group.models import Group +from backend.apps.group.views import check_readonly_group from backend.apps.role.models import Role -from backend.audit.audit import add_audit +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): @@ -143,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) diff --git a/saas/backend/api/admin/views/system.py b/saas/backend/api/admin/views/system.py index 54fa22dcc..117d621ab 100644 --- a/saas/backend/api/admin/views/system.py +++ b/saas/backend/api/admin/views/system.py @@ -8,11 +8,17 @@ 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 drf_yasg.utils import swagger_auto_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet from backend.api.admin.constants import AdminAPIEnum from backend.api.admin.permissions import AdminAPIPermission +from backend.api.admin.serializers import AdminSystemProviderConfigSLZ from backend.api.authentication import ESBAuthentication from backend.apps.system.views import SystemViewSet +from backend.service.resource import SystemProviderConfigService class AdminSystemViewSet(SystemViewSet): @@ -30,3 +36,20 @@ def list(self, request, *args, **kwargs): request.query_params["hidden"] = False return super().list(request, *args, **kwargs) + + +class AdminSystemProviderConfigViewSet(GenericViewSet): + authentication_classes = [ESBAuthentication] + permission_classes = [AdminAPIPermission] + admin_api_permission = {"list": AdminAPIEnum.SYSTEM_PROVIDER_CONFIG_LIST.value} + + @swagger_auto_schema( + operation_description="系统回调信息", + responses={status.HTTP_200_OK: AdminSystemProviderConfigSLZ(label="系统回调信息")}, + tags=["admin.system.provider_config"], + ) + def list(self, request, *args, **kwargs): + system_id = kwargs["system_id"] + system_provider_config = SystemProviderConfigService().get_provider_config(system_id=system_id) + + return Response(AdminSystemProviderConfigSLZ(system_provider_config).data) diff --git a/saas/backend/api/admin/views/template.py b/saas/backend/api/admin/views/template.py new file mode 100644 index 000000000..c499d68b8 --- /dev/null +++ b/saas/backend/api/admin/views/template.py @@ -0,0 +1,60 @@ +from drf_yasg.utils import swagger_auto_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet + +from backend.api.admin.constants import AdminAPIEnum +from backend.api.admin.permissions import AdminAPIPermission +from backend.api.admin.serializers import AdminTemplateCreateSLZ, AdminTemplateIdSLZ +from backend.api.authentication import ESBAuthentication +from backend.apps.role.models import Role +from backend.apps.template.audit import TemplateCreateAuditProvider +from backend.audit.audit import audit_context_setter, view_audit_decorator +from backend.biz.role import RoleAuthorizationScopeChecker +from backend.biz.template import TemplateBiz, TemplateCheckBiz, TemplateCreateBean +from backend.common.lock import gen_template_upsert_lock +from backend.service.constants import RoleType + + +class AdminTemplateViewSet(GenericViewSet): + """模板""" + + authentication_classes = [ESBAuthentication] + permission_classes = [AdminAPIPermission] + + admin_api_permission = {"create": AdminAPIEnum.TEMPLATE_CREATE.value} + + template_biz = TemplateBiz() + template_check_biz = TemplateCheckBiz() + + @swagger_auto_schema( + operation_description="创建模板", + request_body=AdminTemplateCreateSLZ(label="模板"), + responses={status.HTTP_201_CREATED: AdminTemplateIdSLZ(label="模板ID")}, + tags=["admin.template"], + ) + @view_audit_decorator(TemplateCreateAuditProvider) + def create(self, request, *args, **kwargs): + """ + 创建模板 + """ + serializer = AdminTemplateCreateSLZ(data=request.data) + serializer.is_valid(raise_exception=True) + + user_id = request.user.username + data = serializer.validated_data + role = Role.objects.get(type=RoleType.SUPER_MANAGER.value) + + # 检查模板的授权是否满足管理员的授权范围 + scope_checker = RoleAuthorizationScopeChecker(role) + scope_checker.check_actions(data["system_id"], data["action_ids"]) + + with gen_template_upsert_lock(role.id, data["name"]): + # 检查权限模板是否在角色内唯一 + self.template_check_biz.check_role_template_name_exists(role.id, data["name"]) + + template = self.template_biz.create(role.id, TemplateCreateBean.parse_obj(data), user_id) + + audit_context_setter(template=template) + + return Response({"id": template.id}, status=status.HTTP_201_CREATED)