From c3b3b1f44e532e596cf34019055c3372cb485008 Mon Sep 17 00:00:00 2001 From: waylon <1158341873@qq.com> Date: Mon, 3 Mar 2025 17:02:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=86=85=E7=BD=AE=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=B3=BB=E7=BB=9F=E7=BA=A7=E5=88=AB=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=88=B0=E7=89=B9=E5=AE=9A=E7=A9=BA=E9=97=B4=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20#104?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bkflow/plugin/models.py | 28 +++++++++++++++------------ bkflow/plugin/permissions.py | 15 ++++++++++++++ bkflow/plugin/serializers/comonent.py | 14 ++++++++++++++ bkflow/plugin/views/plugin.py | 23 ++++++++++++++++++++++ config/default.py | 3 +++ env.py | 3 +++ 6 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 bkflow/plugin/permissions.py diff --git a/bkflow/plugin/models.py b/bkflow/plugin/models.py index 19260ed..6e578d4 100644 --- a/bkflow/plugin/models.py +++ b/bkflow/plugin/models.py @@ -20,25 +20,30 @@ from django.db import models -from bkflow.plugin.space_plugin_config_parser import SpacePluginConfigParser + +class SpacePluginConfigManager(models.Manager): + def get_space_allow_list(self, space_id): + qs = self.filter(space_id=space_id) + if not qs.exists(): + return [] + return qs.first().allow_list class SpacePluginConfig(models.Model): """ - 空间插件配置, Deprecated, 使用 SpaceConfig 替代 - config格式如: - { - "default": {"mode": "allow_list", "plugin_codes": ["plugin1", "plugin2"]}, - "{scope_type}_{scope_id}": {"mode": "deny_list", "plugin_codes": ["plugin3"] - } - default 字段必须存在,表示默认配置,落库之前通过SpacePluginConfigParser进行校验 + 插件空间配置, 系统级配置,用于限制内置插件的空间使用范围 + config格式如: {"allow_list": ["plugin_code1", "plugin_code2"]} """ + ALLOW_LIST = "allow_list" + space_id = models.IntegerField(verbose_name="空间ID", db_index=True) config = models.JSONField(verbose_name="插件配置") create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") + objects = SpacePluginConfigManager() + class Meta: verbose_name = "空间插件配置" verbose_name_plural = "空间插件配置" @@ -46,7 +51,6 @@ class Meta: def __str__(self): return f"{self.space_id}: {self.config}" - def clean(self): - parser = SpacePluginConfigParser(self.config) - parser.is_valid(raise_exception=True) - return + @property + def allow_list(self): + return self.config.get(self.ALLOW_LIST, []) diff --git a/bkflow/plugin/permissions.py b/bkflow/plugin/permissions.py new file mode 100644 index 0000000..8c6383c --- /dev/null +++ b/bkflow/plugin/permissions.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from rest_framework import permissions + +from bkflow.permission.models import Token + + +class TokenPluginPermissions(permissions.BasePermission): + """根据 token 判断用户请求的空间是否对应""" + + def has_permission(self, request, view): + token = Token.objects.filter(token=request.token).first() + if not token or token.has_expired(): + return False + + return int(token.space_id) == int(request.query_params.get("space_id", -1)) diff --git a/bkflow/plugin/serializers/comonent.py b/bkflow/plugin/serializers/comonent.py index 0e17742..4885357 100644 --- a/bkflow/plugin/serializers/comonent.py +++ b/bkflow/plugin/serializers/comonent.py @@ -21,6 +21,7 @@ import re +from django.conf import settings from django.utils.translation import ugettext_lazy as _ from pipeline.component_framework.library import ComponentLibrary from pipeline.component_framework.models import ComponentModel @@ -28,6 +29,8 @@ from rest_framework import serializers from rest_framework.exceptions import NotFound +from bkflow.plugin.models import SpacePluginConfig as SpacePluginConfigModel + group_en_pattern = re.compile(r"(?:\()(.*)(?:\))") @@ -99,6 +102,17 @@ class ComponentListQuerySerializer(serializers.Serializer): scope_id = serializers.CharField(help_text="空间下scope ID", required=False) +class ComponentDetailQuerySerializer(serializers.Serializer): + space_id = serializers.CharField(help_text="空间ID") + + def validate_space_id(self, space_id): + plugin_code = self.context["plugin_code"] + space_allow_list = SpacePluginConfigModel.objects.get_space_allow_list(space_id) + if plugin_code in settings.SPACE_PLUGIN_LIST and plugin_code not in space_allow_list: + raise serializers.ValidationError(_("插件 {} 不在空间 {} 的插件白名单中").format(plugin_code, space_id)) + return space_id + + class ComponentModelDetailSerializer(ComponentModelSerializer): phase = None sort_key_group_en = None diff --git a/bkflow/plugin/views/plugin.py b/bkflow/plugin/views/plugin.py index 6721a8d..83cd451 100644 --- a/bkflow/plugin/views/plugin.py +++ b/bkflow/plugin/views/plugin.py @@ -19,19 +19,25 @@ """ import logging +from django.conf import settings from django_filters import FilterSet from drf_yasg.utils import swagger_auto_schema from pipeline.component_framework.models import ComponentModel +from bkflow.plugin.permissions import TokenPluginPermissions from bkflow.plugin.serializers.comonent import ( ComponentListQuerySerializer, ComponentModelDetailSerializer, ComponentModelListSerializer, + ComponentDetailQuerySerializer, ) from bkflow.plugin.space_plugin_config_parser import SpacePluginConfigParser +from bkflow.plugin.models import SpacePluginConfig as SpacePluginConfigModel from bkflow.space.configs import SpacePluginConfig from bkflow.space.models import SpaceConfig +from bkflow.space.permissions import SpaceSuperuserPermission from bkflow.utils.mixins import BKFLOWCommonMixin +from bkflow.utils.permissions import AdminPermission from bkflow.utils.views import ReadOnlyViewSet logger = logging.getLogger("root") @@ -51,10 +57,19 @@ class ComponentModelSetViewSet(BKFLOWCommonMixin, ReadOnlyViewSet): filterset_class = ComponentModelFilter pagination_class = None lookup_field = "code" + permission_classes = [AdminPermission | SpaceSuperuserPermission | TokenPluginPermissions] def get_queryset(self): queryset = super().get_queryset() + + # 过滤系统配置插件 space_id = self.request.query_params.get("space_id") + system_allow_list = SpacePluginConfigModel.objects.get_space_allow_list(space_id) + space_plugins = set(settings.SPACE_PLUGIN_LIST) - set(system_allow_list) + if space_plugins: + queryset = queryset.exclude(code__in=list(space_plugins)) + + # 过滤空间配置插件 scope_type = self.request.query_params.get("scope_type") scope_id = self.request.query_params.get("scope_id") scope_code = f"{scope_type}_{scope_id}" @@ -69,3 +84,11 @@ def list(self, request, *args, **kwargs): query_ser = ComponentListQuerySerializer(data=request.query_params) query_ser.is_valid(raise_exception=True) return super().list(request, *args, **kwargs) + + @swagger_auto_schema(query_serializer=ComponentDetailQuerySerializer) + def retrieve(self, request, *args, **kwargs): + query_ser = ComponentDetailQuerySerializer( + data=request.query_params, context={"plugin_code": kwargs[self.lookup_field]} + ) + query_ser.is_valid(raise_exception=True) + return super().retrieve(request, *args, **kwargs) diff --git a/config/default.py b/config/default.py index 6788a60..a529d80 100644 --- a/config/default.py +++ b/config/default.py @@ -209,6 +209,9 @@ # 忽略 example 插件 ENABLE_EXAMPLE_COMPONENTS = False +# 特定空间插件列表 +SPACE_PLUGIN_LIST = env.SPACE_PLUGIN_LIST_STR.split(",") if env.SPACE_PLUGIN_LIST_STR else [] + # 静态资源文件(js,css等)在APP上线更新后, 由于浏览器有缓存, # 可能会造成没更新的情况. 所以在引用静态资源的地方,都把这个加上 # Django 模板中: diff --git a/env.py b/env.py index dce95df..f766a8f 100644 --- a/env.py +++ b/env.py @@ -137,3 +137,6 @@ # APP 白名单 APP_WHITE_LIST_STR = os.getenv("BKAPP_APP_WHITE_LIST", "") # 逗号分隔的字符串 + +# 系统空间插件列表 +SPACE_PLUGIN_LIST_STR = os.getenv("SPACE_PLUGIN_LIST_STR", "") # 逗号分隔的字符串