diff --git a/src/backend/apps/permission/handlers/drf.py b/src/backend/apps/permission/handlers/drf.py index 3b47b65a..01c62aa5 100644 --- a/src/backend/apps/permission/handlers/drf.py +++ b/src/backend/apps/permission/handlers/drf.py @@ -112,7 +112,7 @@ def has_permission(self, request, view): if not self.actions: return True - client = Permission() + client = Permission(request=request) return any([client.is_allowed(action=action) for action in self.actions]) diff --git a/src/backend/locale/en/LC_MESSAGES/django.mo b/src/backend/locale/en/LC_MESSAGES/django.mo index 13348301..06a265ee 100644 Binary files a/src/backend/locale/en/LC_MESSAGES/django.mo and b/src/backend/locale/en/LC_MESSAGES/django.mo differ diff --git a/src/backend/locale/en/LC_MESSAGES/django.po b/src/backend/locale/en/LC_MESSAGES/django.po index d187febc..7ddbd0fc 100644 --- a/src/backend/locale/en/LC_MESSAGES/django.po +++ b/src/backend/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 16:23+0800\n" +"POT-Creation-Date: 2025-01-20 20:33+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -3647,6 +3647,9 @@ msgstr "List Tables" msgid "Get RT Fields" msgstr "Get RT Fields" +msgid "Bulk Get RT Fields" +msgstr "Bulk Get RT Fields" + msgid "Get Strategy Status" msgstr "Get Strategy Status" @@ -3853,6 +3856,12 @@ msgstr "Plan variables" msgid "Table ID" msgstr "Table ID" +msgid "Table IDS" +msgstr "Table IDS" + +msgid "Multiple separated by commas" +msgstr "Multiple separated by commas" + msgid "value" msgstr "value" @@ -3917,6 +3926,9 @@ msgstr "Link Table Configuration" msgid "逗号分隔的标签ID列表" msgstr "Comma-separated tag ID list" +msgid "Need Update Strategy" +msgstr "Need Update Strategy" + msgid "Link Table Count" msgstr "Link Table Count" diff --git a/src/backend/locale/zh_CN/LC_MESSAGES/django.mo b/src/backend/locale/zh_CN/LC_MESSAGES/django.mo index a76ab485..81440f45 100644 Binary files a/src/backend/locale/zh_CN/LC_MESSAGES/django.mo and b/src/backend/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/src/backend/locale/zh_CN/LC_MESSAGES/django.po b/src/backend/locale/zh_CN/LC_MESSAGES/django.po index cdc632b7..00f061cb 100644 --- a/src/backend/locale/zh_CN/LC_MESSAGES/django.po +++ b/src/backend/locale/zh_CN/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 16:23+0800\n" +"POT-Creation-Date: 2025-01-20 20:33+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -3647,6 +3647,9 @@ msgstr "获取结果表列表" msgid "Get RT Fields" msgstr "获取RT字段" +msgid "Bulk Get RT Fields" +msgstr "批量获取RT字段" + msgid "Get Strategy Status" msgstr "获取策略状态" @@ -3853,6 +3856,12 @@ msgstr "方案参数" msgid "Table ID" msgstr "结果表ID" +msgid "Table IDS" +msgstr "结果表IDS" + +msgid "Multiple separated by commas" +msgstr "多个以逗号分隔" + msgid "value" msgstr "值" @@ -3901,6 +3910,9 @@ msgstr "联表配置" msgid "逗号分隔的标签ID列表" msgstr "逗号分隔的标签ID列表" +msgid "Need Update Strategy" +msgstr "是否存在需要更新的策略" + msgid "Link Table Count" msgstr "联表数" diff --git a/src/backend/services/web/risk/constants.py b/src/backend/services/web/risk/constants.py index bcd36a03..b89bbec0 100644 --- a/src/backend/services/web/risk/constants.py +++ b/src/backend/services/web/risk/constants.py @@ -368,13 +368,6 @@ def fields(self) -> List[Field]: RISK_DATA = Field(field_name="risk_data", alias_name=gettext_lazy("拓展数据")) -# 事件排除字段 -EVENT_BASIC_EXCLUDE_FIELDS = [ - EventMappingFields.EVENT_ID, - EventMappingFields.EVENT_EVIDENCE, - EventMappingFields.EVENT_DATA, -] - # 事件基础字段中需要映射的字段 EVENT_BASIC_MAP_FIELDS = [ EventMappingFields.RAW_EVENT_ID, diff --git a/src/backend/services/web/strategy_v2/models.py b/src/backend/services/web/strategy_v2/models.py index a701b82f..269e2b03 100644 --- a/src/backend/services/web/strategy_v2/models.py +++ b/src/backend/services/web/strategy_v2/models.py @@ -206,3 +206,35 @@ class Meta: verbose_name = gettext_lazy("Link Table Tag") verbose_name_plural = verbose_name ordering = ["-id"] + + +class LinkTableAuditInstance(AuditInstance): + """ + Link Table Audit Instance + """ + + @property + def instance_id(self): + """ + 实例ID + @rtype: str + """ + return getattr(self.instance, "uid", DEFAULT_EMPTY_VALUE) + + @property + def instance_name(self): + """ + 实例名 + @rtype: str + """ + return getattr(self.instance, "name", DEFAULT_EMPTY_VALUE) + + @property + def instance_data(self): + """ + 实例信息 JSON + @rtype: dict + """ + from services.web.strategy_v2.serializers import LinkTableInfoSerializer + + return LinkTableInfoSerializer(self.instance).data diff --git a/src/backend/services/web/strategy_v2/resources.py b/src/backend/services/web/strategy_v2/resources.py index 50564372..05b2daa6 100644 --- a/src/backend/services/web/strategy_v2/resources.py +++ b/src/backend/services/web/strategy_v2/resources.py @@ -23,13 +23,15 @@ from functools import cached_property from typing import List, Optional -from bk_resource import api, resource +from bk_resource import CacheResource, api, resource from bk_resource.base import Empty from bk_resource.exceptions import APIRequestError +from bk_resource.utils.cache import CacheTypeItem from blueapps.utils.request_provider import get_local_request, get_request_username from django.conf import settings from django.db import transaction from django.db.models import Count, Q, QuerySet +from django.db.models.aggregates import Min from django.http import Http404 from django.shortcuts import get_object_or_404 from django.utils.translation import gettext, gettext_lazy @@ -61,7 +63,7 @@ ) from services.web.analyze.controls.base import BaseControl from services.web.analyze.tasks import call_controller -from services.web.risk.constants import EVENT_BASIC_EXCLUDE_FIELDS, EventMappingFields +from services.web.risk.constants import EventMappingFields from services.web.risk.models import Risk from services.web.risk.permissions import RiskViewPermission from services.web.strategy_v2.constants import ( @@ -96,12 +98,15 @@ from services.web.strategy_v2.handlers.rule_audit import RuleAuditSQLBuilder from services.web.strategy_v2.models import ( LinkTable, + LinkTableAuditInstance, LinkTableTag, Strategy, StrategyAuditInstance, StrategyTag, ) from services.web.strategy_v2.serializers import ( + BulkGetRTFieldsRequestSerializer, + BulkGetRTFieldsResponseSerializer, CreateLinkTableRequestSerializer, CreateLinkTableResponseSerializer, CreateStrategyRequestSerializer, @@ -117,6 +122,7 @@ GetStrategyFieldValueRequestSerializer, GetStrategyFieldValueResponseSerializer, GetStrategyStatusRequestSerializer, + LinkTableInfoSerializer, ListLinkTableAllResponseSerializer, ListLinkTableRequestSerializer, ListLinkTableResponseSerializer, @@ -679,21 +685,12 @@ def perform_request(self, validated_request_data): } -class ListTables(StrategyV2Base): +class ListTables(StrategyV2Base, CacheResource): name = gettext_lazy("List Tables") RequestSerializer = ListTablesRequestSerializer + cache_type = CacheTypeItem(key="ListTables", timeout=60, user_related=False) def perform_request(self, validated_request_data): - # check permission - if not ActionPermission( - actions=[ - ActionEnum.CREATE_STRATEGY, - ActionEnum.LIST_STRATEGY, - ActionEnum.EDIT_STRATEGY, - ActionEnum.DELETE_STRATEGY, - ] - ).has_permission(request=get_local_request(), view=self): - return [] return TableHandler(**validated_request_data).list_tables() @@ -704,16 +701,6 @@ class GetRTFields(StrategyV2Base): many_response_data = True def perform_request(self, validated_request_data): - # check permission - if not ActionPermission( - actions=[ - ActionEnum.CREATE_STRATEGY, - ActionEnum.LIST_STRATEGY, - ActionEnum.EDIT_STRATEGY, - ActionEnum.DELETE_STRATEGY, - ] - ).has_permission(request=get_local_request(), view=self): - return [] fields = api.bk_base.get_rt_fields(result_table_id=validated_request_data["table_id"]) return [ { @@ -725,6 +712,25 @@ def perform_request(self, validated_request_data): ] +class BulkGetRTFields(StrategyV2Base): + name = gettext_lazy("Bulk Get RT Fields") + RequestSerializer = BulkGetRTFieldsRequestSerializer + ResponseSerializer = BulkGetRTFieldsResponseSerializer + many_response_data = True + + def perform_request(self, validated_request_data): + table_ids = validated_request_data["table_ids"] + bulk_request_params = [{"table_id": table_id} for table_id in table_ids] + bulk_resp = resource.strategy_v2.get_rt_fields.bulk_request(bulk_request_params) + return [ + { + "table_id": params["table_id"], + "fields": resp, + } + for params, resp in zip(bulk_request_params, bulk_resp) + ] + + class GetStrategyStatus(StrategyV2Base): name = gettext_lazy("Get Strategy Status") RequestSerializer = GetStrategyStatusRequestSerializer @@ -754,8 +760,15 @@ def get_event_basic_field_configs(self, risk: Optional[Risk], has_permission: bo description="", example=getattr(risk, field.field_name, "") if risk and has_permission else "", ) - for field in EventMappingFields().fields - if field not in EVENT_BASIC_EXCLUDE_FIELDS + for field in [ + EventMappingFields.RAW_EVENT_ID, + EventMappingFields.OPERATOR, + EventMappingFields.EVENT_TIME, + EventMappingFields.EVENT_SOURCE, + EventMappingFields.STRATEGY_ID, + EventMappingFields.EVENT_CONTENT, + EventMappingFields.EVENT_TYPE, + ] ] def get_event_data_field_configs(self, risk: Optional[Risk], has_permission: bool) -> List[EventInfoField]: @@ -825,6 +838,11 @@ def perform_request(self, validated_request_data): class LinkTableBase(AuditMixinResource, abc.ABC): tags = ["LinkTable"] + def add_audit_instance_to_context(self, link_table: LinkTable, old_link_table: Optional[dict] = None): + if old_link_table: + setattr(link_table, "instance_origin_data", old_link_table) + super().add_audit_instance_to_context(instance=LinkTableAuditInstance(link_table)) + def _save_tags(self, link_table_uid: str, tag_names: list) -> None: LinkTableTag.objects.filter(link_table_uid=link_table_uid).delete() if not tag_names: @@ -852,6 +870,8 @@ def create_link_table(self, validated_request_data) -> LinkTable: def perform_request(self, validated_request_data): link_table = self.create_link_table(validated_request_data) + # audit + self.add_audit_instance_to_context(link_table) # auth username = get_request_username() if username: @@ -877,6 +897,8 @@ def update_link_table(self, validated_request_data) -> LinkTable: link_table = LinkTable.last_version_link_table(uid=uid) if not link_table: raise Http404(gettext("LinkTable not found: %s") % uid) + # old link_table + old_link_table = LinkTableInfoSerializer(link_table).data # 更新或创建新版本联表 need_update_config = "config" in validated_request_data.keys() if not need_update_config: @@ -899,6 +921,8 @@ def update_link_table(self, validated_request_data) -> LinkTable: # save tag if not isinstance(tags, Empty): self._save_tags(link_table_uid=link_table.uid, tag_names=tags) + # audit + self.add_audit_instance_to_context(link_table, old_link_table) return link_table def perform_request(self, validated_request_data): @@ -914,6 +938,12 @@ def perform_request(self, validated_request_data): # 如果有策略使用了该联表则不能删除 if Strategy.objects.filter(strategy_type=StrategyType.RULE, link_table_uid=uid).exists(): raise LinkTableHasStrategy() + link_table = LinkTable.last_version_link_table(uid=uid) + if not link_table: + raise Http404(gettext("LinkTable not found: %s") % uid) + # audit + self.add_audit_instance_to_context(link_table) + # 删除联表 LinkTableTag.objects.filter(link_table_uid=uid).delete() LinkTable.objects.filter(uid=uid).delete() @@ -921,6 +951,8 @@ def perform_request(self, validated_request_data): class ListLinkTable(LinkTableBase): name = gettext_lazy("查询联表列表") RequestSerializer = ListLinkTableRequestSerializer + ResponseSerializer = ListLinkTableResponseSerializer + many_response_data = True bind_request = True def perform_request(self, validated_request_data): @@ -949,20 +981,29 @@ def perform_request(self, validated_request_data): for t in all_tags: tag_map[t.link_table_uid].append(t.tag_id) # 填充关联的策略数 - strategies = { + strategies = Strategy.objects.filter(strategy_type=StrategyType.RULE, link_table_uid__in=link_table_uids) + strategy_cnt_map = { strategy["link_table_uid"]: strategy["count"] - for strategy in Strategy.objects.filter(strategy_type=StrategyType.RULE, link_table_uid__in=link_table_uids) - .values("link_table_uid") - .annotate(count=Count("link_table_uid")) - .order_by() + for strategy in strategies.values("link_table_uid").annotate(count=Count("link_table_uid")).order_by() + } + # 填充关联的最小联表版本 + strategy_version_map = { + strategy["link_table_uid"]: strategy["version"] + for strategy in strategies.values("link_table_uid").annotate(version=Min("link_table_version")).order_by() } for link_table in link_tables: # 填充关联的策略数 - setattr(link_table, "strategy_count", strategies.get(link_table.uid, 0)) + setattr(link_table, "strategy_count", strategy_cnt_map.get(link_table.uid, 0)) # 填充标签 setattr(link_table, "tags", tag_map.get(link_table.uid, [])) + # 填充是否存在需要更新的策略 + setattr( + link_table, + "need_update_strategy", + strategy_version_map.get(link_table.uid, link_table.version) < link_table.version, + ) # 响应 - return page.get_paginated_response(data=ListLinkTableResponseSerializer(instance=link_tables, many=True).data) + return link_tables class ListLinkTableAll(LinkTableBase): diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index b748ce11..ad905f3e 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -604,6 +604,21 @@ class GetRTFieldsRequestSerializer(serializers.Serializer): table_id = serializers.CharField(label=gettext_lazy("Table ID")) +class BulkGetRTFieldsRequestSerializer(serializers.Serializer): + """ + Bulk Get RT Fields + """ + + table_ids = serializers.CharField( + label=gettext_lazy("Table IDS"), help_text=gettext_lazy("Multiple separated by commas") + ) + + def validate(self, attrs): + attrs = super().validate(attrs) + attrs['table_ids'] = list(set(attrs['table_ids'].split(','))) + return attrs + + class GetRTFieldsResponseSerializer(serializers.Serializer): """ Get RT Fields @@ -614,6 +629,15 @@ class GetRTFieldsResponseSerializer(serializers.Serializer): field_type = serializers.CharField(label=gettext_lazy("Field Type")) +class BulkGetRTFieldsResponseSerializer(serializers.Serializer): + """ + Bulk Get RT Fields + """ + + table_id = serializers.CharField(label=gettext_lazy("Table ID")) + fields = serializers.ListField(child=GetRTFieldsResponseSerializer()) + + class GetStrategyStatusRequestSerializer(serializers.Serializer): """ Get Strategy Status @@ -833,6 +857,7 @@ class ListLinkTableResponseSerializer(serializers.ModelSerializer): label=gettext_lazy("Tags"), child=serializers.IntegerField(label=gettext_lazy("Tag ID")) ) strategy_count = serializers.IntegerField(label=gettext_lazy("Strategy Count")) + need_update_strategy = serializers.BooleanField(label=gettext_lazy("Need Update Strategy")) class Meta: model = LinkTable diff --git a/src/backend/services/web/strategy_v2/views.py b/src/backend/services/web/strategy_v2/views.py index 3d7a520a..78671550 100644 --- a/src/backend/services/web/strategy_v2/views.py +++ b/src/backend/services/web/strategy_v2/views.py @@ -21,6 +21,7 @@ from apps.permission.handlers.actions import ActionEnum from apps.permission.handlers.drf import ( + ActionPermission, IAMPermission, InstanceActionPermission, insert_permission_field, @@ -90,9 +91,22 @@ def get_permissions(self): class StrategyTableViewSet(ResourceViewSet): + def get_permissions(self): + return [ + ActionPermission( + actions=[ + ActionEnum.CREATE_STRATEGY, + ActionEnum.LIST_STRATEGY, + ActionEnum.EDIT_STRATEGY, + ActionEnum.DELETE_STRATEGY, + ] + ) + ] + resource_routes = [ ResourceRoute("GET", resource.strategy_v2.list_tables), ResourceRoute("GET", resource.strategy_v2.get_rt_fields, endpoint="rt_fields"), + ResourceRoute("GET", resource.strategy_v2.bulk_get_rt_fields, endpoint="bulk_rt_fields"), ]