From 6eb1c5d256343e0d239547e13faf7b58eda05bb4 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Fri, 2 Aug 2024 12:00:17 +0800 Subject: [PATCH 01/64] =?UTF-8?q?feat:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 创建策略接口增加风险等级,风险危害,风险指引,处理人参数 2. 策略接口支持风险标题渲染 3. 增加事件信息字段获取接口 4. 风险处理支持从策略配置获取处理人 --- src/backend/api/user_manage/default.py | 26 +++++ src/backend/core/render.py | 45 ++++++++ .../services/web/risk/handlers/risk.py | 19 +++- .../services/web/risk/handlers/ticket.py | 30 ++++- .../web/risk/migrations/0023_risk_title.py | 18 +++ src/backend/services/web/risk/models.py | 1 + .../services/web/risk/resources/risk.py | 8 ++ .../services/web/strategy_v2/constants.py | 22 +++- .../migrations/0005_auto_20240802_0111.py | 105 ++++++++++++++++++ .../services/web/strategy_v2/models.py | 14 ++- .../services/web/strategy_v2/resources.py | 51 +++++++++ .../services/web/strategy_v2/serializers.py | 91 ++++++++++++++- src/backend/services/web/strategy_v2/views.py | 1 + src/backend/tests/core/__init__.py | 1 + src/backend/tests/core/test_render.py | 10 ++ .../tests/strategy_v2/test_strategy.py | 16 ++- 16 files changed, 447 insertions(+), 11 deletions(-) create mode 100644 src/backend/core/render.py create mode 100644 src/backend/services/web/risk/migrations/0023_risk_title.py create mode 100644 src/backend/services/web/strategy_v2/migrations/0005_auto_20240802_0111.py create mode 100644 src/backend/tests/core/__init__.py create mode 100644 src/backend/tests/core/test_render.py diff --git a/src/backend/api/user_manage/default.py b/src/backend/api/user_manage/default.py index d059b3a8..6e918748 100644 --- a/src/backend/api/user_manage/default.py +++ b/src/backend/api/user_manage/default.py @@ -19,6 +19,7 @@ import abc import base64 import os +from typing import Optional from bk_resource import BkApiResource from bk_resource.utils.cache import CacheTypeItem @@ -26,6 +27,7 @@ from django.utils.translation import gettext_lazy from api.domains import USER_MANAGE_URL +from core.constants import TimeEnum class UserManageResource(BkApiResource, abc.ABC): @@ -43,6 +45,30 @@ class RetrieveUser(UserManageResource): name = gettext_lazy("获取单个用户信息") method = "GET" action = "/retrieve_user/" + cache_type = CacheTypeItem(key="RetrieveUser", timeout=TimeEnum.ONE_HOUR_SECOND.value, user_related=False) + platform_authorization = True + + +class RetrieveLeaderResource(UserManageResource): + """获取单个用户的leader信息 .""" + + name = gettext_lazy("获取单个用户的leader信息") + platform_authorization = True + + @property + def action(self): + return + + def perform_request(self, validated_request_data) -> Optional[str]: + # 获取用户信息 + user_info = RetrieveUser().perform_request(validated_request_data) + # 解析出leader信息 + leader_infos = user_info.get("leader", []) + if leader_infos: + leader_info = leader_infos[0] + else: + leader_info = {} + return leader_info.get("username") class ListUserDepartments(UserManageResource): diff --git a/src/backend/core/render.py b/src/backend/core/render.py new file mode 100644 index 00000000..1bb5969d --- /dev/null +++ b/src/backend/core/render.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +import json +import logging +import re +from typing import Dict, List, Union + +from django.utils import translation +from jinja2 import Environment + +logger = logging.getLogger(__name__) + + +def jinja2_environment(**options: Dict) -> Environment: + """创建jinja2的环境执行环境 .""" + env = Environment(extensions=["jinja2.ext.i18n"], **options) + env.install_gettext_translations(translation, newstyle=True) + return env + + +class Jinja2Renderer: + """ + Jinja2渲染器 + """ + + @staticmethod + def render(template_value: str, context: dict) -> str: + """ + 只支持json和re函数 + """ + return jinja2_environment().from_string(template_value).render({"json": json, "re": re, **context}) + + +def jinja_render(template_value: Union[str, dict, list], context: dict) -> Union[str, Dict, List]: + """使用jinja渲染对象 .""" + if isinstance(template_value, str): + return Jinja2Renderer.render(template_value, context) or template_value + if isinstance(template_value, dict): + render_value = {} + for key, value in template_value.items(): + render_value[key] = jinja_render(value, context) + return render_value + if isinstance(template_value, list): + return [jinja_render(value, context) for value in template_value] + return template_value diff --git a/src/backend/services/web/risk/handlers/risk.py b/src/backend/services/web/risk/handlers/risk.py index d4e166a6..49f1e47b 100644 --- a/src/backend/services/web/risk/handlers/risk.py +++ b/src/backend/services/web/risk/handlers/risk.py @@ -20,7 +20,7 @@ import json import math import re -from typing import List, Union +from typing import List, Optional, Union from bk_resource import resource from blueapps.utils.logger import logger @@ -34,6 +34,7 @@ from apps.notice.constants import RelateType from apps.notice.handlers import ErrorMsgHandler from apps.notice.models import NoticeGroup +from core.render import jinja_render from services.web.risk.constants import ( EVENT_DATA_SORT_FIELD, EVENT_TYPE_SPLIT_REGEX, @@ -43,7 +44,7 @@ ) from services.web.risk.handlers import EventHandler from services.web.risk.models import Risk -from services.web.risk.serializers import CreateRiskSerializer +from services.web.risk.serializers import CreateRiskSerializer, RiskInfoSerializer from services.web.strategy_v2.models import Strategy, StrategyTag @@ -100,6 +101,19 @@ def load_events(self, start_time: datetime.datetime, end_time: datetime.datetime return data + def render_risk_title(self, risk: Risk) -> Optional[str]: + """ + 生成风险标题 + """ + + strategy: Strategy = Strategy.objects.filter(strategy_id=risk.strategy_id).first() + if not strategy or not strategy.risk_title: + return + risk_content = RiskInfoSerializer(risk).data + if risk_content.get("event_evidence"): + risk_content["event_evidence"] = json.loads(risk_content["event_evidence"])[0] + return jinja_render(strategy.risk_title, risk_content) + def create_risk(self, event: dict) -> (bool, Risk): """ 创建或更新风险 @@ -156,6 +170,7 @@ def create_risk(self, event: dict) -> (bool, Risk): event_source=event.get("event_source"), operator=self.parse_operator(event.get("operator")), tags=list(StrategyTag.objects.filter(strategy_id=event["strategy_id"]).values_list("tag_id", flat=True)), + title=self.render_risk_title(risk), ) def parse_operator(self, operator: str) -> List[str]: diff --git a/src/backend/services/web/risk/handlers/ticket.py b/src/backend/services/web/risk/handlers/ticket.py index 2be6fe7f..7e851e4c 100644 --- a/src/backend/services/web/risk/handlers/ticket.py +++ b/src/backend/services/web/risk/handlers/ticket.py @@ -47,6 +47,7 @@ from services.web.risk.handlers.risk import RiskHandler from services.web.risk.handlers.rule import RiskRuleHandler from services.web.risk.models import ProcessApplication, Risk, RiskRule, TicketNode +from services.web.strategy_v2.models import Strategy class RiskFlowBaseHandler: @@ -90,6 +91,21 @@ def load_security_person(cls) -> List[str]: return GlobalMetaConfig.get(config_key=SECURITY_PERSON_KEY) + def load_processor(self) -> List[str]: + """ + 获取处理人 + """ + + # 获取策略 + strategy: Strategy = Strategy.objects.filter(strategy_id=self.risk.strategy_id).first() + if not strategy: + return [] + # 获取处理组成员 + processor_groups: List[NoticeGroup] = list( + NoticeGroup.objects.filter(group_id__in=strategy.processor_groups or []) + ) + return list({member for processor_group in processor_groups for member in processor_group.group_member}) + def load_last_operator(self) -> List[str]: """ 获取上一个人工处理节点的处理人 @@ -247,7 +263,9 @@ def process(self, *args, **kwargs) -> dict: def update_operator(self, process_result: dict, *args, **kwargs) -> None: # 有处理套餐则当前处理人为空,否则为安全责任人 self.risk.current_operator = ( - [] if self.process_application and self.risk.operator else self.load_security_person() + [] + if self.process_application and self.risk.operator + else self.load_processor() or self.load_security_person() ) self.risk.save(update_fields=["current_operator"]) @@ -372,7 +390,9 @@ def update_operator(self, process_result: dict, **kwargs) -> None: if (current_status in TicketStatus.get_success_status() and not approve_result) or ( current_status in TicketStatus.get_failed_status() ): - self.risk.current_operator = self.load_last_operator() or self.load_security_person() + self.risk.current_operator = ( + self.load_last_operator() or self.load_processor() or self.load_security_person() + ) # 其他情况处理人为空 else: self.risk.current_operator = [] @@ -481,7 +501,9 @@ def update_operator(self, process_result: dict, *args, **kwargs) -> None: process_result["status"]["state"] in SOPSTaskStatus.get_failed_status() or (process_result["status"]["state"] in SOPSTaskStatus.get_success_status() and not auto_close_risk) ): - self.risk.current_operator = self.load_last_operator() or self.load_security_person() + self.risk.current_operator = ( + self.load_last_operator() or self.load_processor() or self.load_security_person() + ) # 其他情况处理人为空 else: self.risk.current_operator = [] @@ -717,7 +739,7 @@ def update_status(self, process_result: dict, *args, **kwargs) -> None: self.risk.save(update_fields=["status"]) def update_operator(self, process_result: dict, *args, **kwargs) -> None: - self.risk.current_operator = self.load_security_person() + self.risk.current_operator = self.load_processor() or self.load_security_person() self.risk.save(update_fields=["current_operator"]) def build_history(self, process_result: dict, *args, **kwargs) -> dict: diff --git a/src/backend/services/web/risk/migrations/0023_risk_title.py b/src/backend/services/web/risk/migrations/0023_risk_title.py new file mode 100644 index 00000000..eab51a45 --- /dev/null +++ b/src/backend/services/web/risk/migrations/0023_risk_title.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.23 on 2024-08-01 13:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("risk", "0022_risk_last_operate_time"), + ] + + operations = [ + migrations.AddField( + model_name="risk", + name="title", + field=models.TextField(blank=True, default=None, null=True, verbose_name="Risk Title"), + ), + ] diff --git a/src/backend/services/web/risk/models.py b/src/backend/services/web/risk/models.py index 88f52f1d..d3914b92 100644 --- a/src/backend/services/web/risk/models.py +++ b/src/backend/services/web/risk/models.py @@ -93,6 +93,7 @@ class Risk(models.Model): choices=RiskLabel.choices, ) last_operate_time = models.DateTimeField(gettext_lazy("Last Operate Time"), auto_now=True, db_index=True) + title = models.TextField(gettext_lazy("Risk Title"), null=True, blank=True, default=None) class Meta: verbose_name = gettext_lazy("Risk") diff --git a/src/backend/services/web/risk/resources/risk.py b/src/backend/services/web/risk/resources/risk.py index a9cb3be8..e22af11e 100644 --- a/src/backend/services/web/risk/resources/risk.py +++ b/src/backend/services/web/risk/resources/risk.py @@ -80,6 +80,7 @@ UpdateRiskLabelReqSerializer, ) from services.web.risk.tasks import process_one_risk, sync_auto_result +from services.web.strategy_v2.models import Strategy class RiskMeta(AuditMixinResource, abc.ABC): @@ -94,6 +95,7 @@ class RetrieveRisk(RiskMeta): def perform_request(self, validated_request_data): risk = get_object_or_404(Risk, risk_id=validated_request_data["risk_id"]) self.add_audit_instance_to_context(instance=RiskAuditInstance(risk)) + strategy: Strategy = Strategy.objects.filter(strategy_id=risk.strategy_id).first() data = RiskInfoSerializer(risk).data data = wrapper_permission_field( data, actions=[ActionEnum.EDIT_RISK], id_field=lambda risk: risk["risk_id"], many=False @@ -101,6 +103,12 @@ def perform_request(self, validated_request_data): risk = data[0] nodes = TicketNode.objects.filter(risk_id=risk["risk_id"]).order_by("timestamp") risk["ticket_history"] = TicketNodeSerializer(nodes, many=True).data + # 补充风险策略中的事件字段配置 + risk["event_field_configs"] = { + "event_basic_field_configs": strategy.event_basic_field_configs, + "event_data_field_configs": strategy.event_data_field_configs, + "event_evidence_field_configs": strategy.event_evidence_field_configs, + } return risk diff --git a/src/backend/services/web/strategy_v2/constants.py b/src/backend/services/web/strategy_v2/constants.py index 0aa109dd..13dc6023 100644 --- a/src/backend/services/web/strategy_v2/constants.py +++ b/src/backend/services/web/strategy_v2/constants.py @@ -28,7 +28,17 @@ HAS_UPDATE_TAG_NAME = gettext_lazy("Upgradable") # 本地更新字段,这些字段不会传递给后端策略,不会导致策略输出变化 -LOCAL_UPDATE_FIELDS = ["strategy_name", "tags", "notice_groups", "description"] +LOCAL_UPDATE_FIELDS = [ + "strategy_name", + "tags", + "notice_groups", + "description", + "risk_level", + "risk_hazard", + "risk_guidance", + "risk_title", + "processor_groups", +] # 策略可用的调度时间依赖于事件的查询周期 STRATEGY_SCHEDULE_TIME = max(1, RISK_EVENTS_SYNC_TIME - 1) # day @@ -146,3 +156,13 @@ class MappingType(TextChoices): PUBLIC = "public", gettext_lazy("Public Field") ACTION = "action", gettext_lazy("Extend Field") + + +class RiskLevel(TextChoices): + """ + 风险等级 + """ + + HIGH = "HIGH", gettext_lazy("高") + MIDDLE = "MIDDLE", gettext_lazy("中") + LOW = "LOW", gettext_lazy("低") diff --git a/src/backend/services/web/strategy_v2/migrations/0005_auto_20240802_0111.py b/src/backend/services/web/strategy_v2/migrations/0005_auto_20240802_0111.py new file mode 100644 index 00000000..b88e6a58 --- /dev/null +++ b/src/backend/services/web/strategy_v2/migrations/0005_auto_20240802_0111.py @@ -0,0 +1,105 @@ +# Generated by Django 3.2.23 on 2024-08-01 17:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("strategy_v2", "0004_auto_20240118_1018"), + ] + + operations = [ + migrations.AddField( + model_name="strategy", + name="event_basic_field_configs", + field=models.JSONField(blank=True, default=dict, null=True, verbose_name="Event field Configs"), + ), + migrations.AddField( + model_name="strategy", + name="event_data_field_configs", + field=models.JSONField(blank=True, default=dict, null=True, verbose_name="Event Data Configs"), + ), + migrations.AddField( + model_name="strategy", + name="event_evidence_field_configs", + field=models.JSONField(default=dict, null=True, verbose_name="Event Evidence Configs"), + ), + migrations.AddField( + model_name="strategy", + name="processor_groups", + field=models.JSONField(blank=True, default=dict, null=True, verbose_name="Processor"), + ), + migrations.AddField( + model_name="strategy", + name="risk_guidance", + field=models.TextField(blank=True, default=None, null=True, verbose_name="Risk Guidance"), + ), + migrations.AddField( + model_name="strategy", + name="risk_hazard", + field=models.TextField(blank=True, default=None, null=True, verbose_name="Risk Hazard"), + ), + migrations.AddField( + model_name="strategy", + name="risk_level", + field=models.CharField( + choices=[("HIGH", "高"), ("MIDDLE", "中"), ("LOW", "低")], + default=None, + max_length=16, + null=True, + verbose_name="Risk Level", + ), + ), + migrations.AddField( + model_name="strategy", + name="risk_title", + field=models.TextField(blank=True, default=None, null=True, verbose_name="Risk Title"), + ), + migrations.AlterField( + model_name="strategy", + name="backend_data", + field=models.JSONField(blank=True, default=dict, null=True, verbose_name="Backend Data"), + ), + migrations.AlterField( + model_name="strategy", + name="created_by", + field=models.CharField(blank=True, default="", max_length=32, null=True, verbose_name="创建者"), + ), + migrations.AlterField( + model_name="strategy", + name="status", + field=models.CharField( + choices=[ + ("disabled", "Disabled"), + ("failed", "Failed"), + ("start_failed", "Start Failed"), + ("update_failed", "Update Failed"), + ("stop_failed", "Stop Failed"), + ("delete_failed", "Delete Failed"), + ("starting", "Starting"), + ("updating", "Updating"), + ("stopping", "Stopping"), + ("running", "Running"), + ], + default="starting", + max_length=64, + verbose_name="Status", + ), + ), + migrations.AlterField( + model_name="strategy", + name="updated_by", + field=models.CharField(blank=True, default="", max_length=32, null=True, verbose_name="修改者"), + ), + migrations.AlterField( + model_name="strategytag", + name="created_by", + field=models.CharField(blank=True, default="", max_length=32, null=True, verbose_name="创建者"), + ), + migrations.AlterField( + model_name="strategytag", + name="updated_by", + field=models.CharField(blank=True, default="", max_length=32, null=True, verbose_name="修改者"), + ), + ] diff --git a/src/backend/services/web/strategy_v2/models.py b/src/backend/services/web/strategy_v2/models.py index 18680ea5..64894f65 100644 --- a/src/backend/services/web/strategy_v2/models.py +++ b/src/backend/services/web/strategy_v2/models.py @@ -23,7 +23,7 @@ from core.models import OperateRecordModel, SoftDeleteModel from services.web.analyze.models import Control, ControlVersion -from services.web.strategy_v2.constants import StrategyStatusChoices +from services.web.strategy_v2.constants import RiskLevel, StrategyStatusChoices class Strategy(SoftDeleteModel): @@ -47,6 +47,18 @@ class Strategy(SoftDeleteModel): backend_data = models.JSONField(gettext_lazy("Backend Data"), default=dict, null=True, blank=True) notice_groups = models.JSONField(gettext_lazy("Notice Groups"), default=list, null=True, blank=True) description = models.TextField(gettext_lazy("Description"), null=True, blank=True) + risk_level = models.CharField( + gettext_lazy("Risk Level"), choices=RiskLevel.choices, max_length=16, null=True, default=None + ) + risk_hazard = models.TextField(gettext_lazy("Risk Hazard"), null=True, blank=True, default=None) + risk_guidance = models.TextField(gettext_lazy("Risk Guidance"), null=True, blank=True, default=None) + risk_title = models.TextField(gettext_lazy("Risk Title"), null=True, blank=True, default=None) + processor_groups = models.JSONField(gettext_lazy("Processor"), default=dict, null=True, blank=True) + event_basic_field_configs = models.JSONField( + gettext_lazy("Event field Configs"), default=dict, null=True, blank=True + ) + event_data_field_configs = models.JSONField(gettext_lazy("Event Data Configs"), default=dict, null=True, blank=True) + event_evidence_field_configs = models.JSONField(gettext_lazy("Event Evidence Configs"), default=dict, null=True) class Meta: verbose_name = gettext_lazy("Strategy") diff --git a/src/backend/services/web/strategy_v2/resources.py b/src/backend/services/web/strategy_v2/resources.py index 0e5fe5f5..1957954d 100644 --- a/src/backend/services/web/strategy_v2/resources.py +++ b/src/backend/services/web/strategy_v2/resources.py @@ -18,6 +18,7 @@ import abc import datetime +import json from collections import defaultdict from typing import List @@ -58,11 +59,14 @@ from services.web.analyze.exceptions import ControlNotExist from services.web.analyze.models import Control from services.web.analyze.tasks import call_controller +from services.web.risk.constants import EventMappingFields +from services.web.risk.models import Risk from services.web.strategy_v2.constants import ( HAS_UPDATE_TAG_ID, HAS_UPDATE_TAG_NAME, LOCAL_UPDATE_FIELDS, MappingType, + RiskLevel, StrategyAlgorithmOperator, StrategyOperator, StrategyStatusChoices, @@ -75,6 +79,8 @@ BKMStrategySerializer, CreateStrategyRequestSerializer, CreateStrategyResponseSerializer, + GetEventInfoFieldsRequestSerializer, + GetEventInfoFieldsResponseSerializer, GetRTFieldsRequestSerializer, GetRTFieldsResponseSerializer, GetStrategyCommonResponseSerializer, @@ -524,6 +530,7 @@ def perform_request(self, validated_request_data): "strategy_status": choices_to_dict(StrategyStatusChoices, val="value", name="label"), "offset_unit": choices_to_dict(OffsetUnit, val="value", name="label"), "mapping_type": choices_to_dict(MappingType, val="value", name="label"), + "risk_level": choices_to_dict(RiskLevel, val="value", name="label"), } @@ -583,3 +590,47 @@ def perform_request(self, validated_request_data): s.strategy_id: {"status": s.status, "status_msg": s.status_msg} for s in Strategy.objects.filter(strategy_id__in=validated_request_data["strategy_ids"]) } + + +class GetEventInfoFields(StrategyV2Base): + name = gettext_lazy("Get Event Info Fields") + RequestSerializer = GetEventInfoFieldsRequestSerializer + ResponseSerializer = GetEventInfoFieldsResponseSerializer + + def get_event_basic_field_configs(self) -> List[dict]: + """ + 获取基础字段 + """ + + return [ + {"field_name": field.field_name, "display_name": field.alias_name, "description": str(field.description)} + for field in EventMappingFields().fields + ] + + def perform_request(self, validated_request_data): + # 获取基础字段 + result = {"event_basic_field_configs": self.get_event_basic_field_configs()} + strategy_id = validated_request_data.get("strategy_id") + if not strategy_id: + return result + # check permission + if not ActionPermission(actions=[ActionEnum.EDIT_STRATEGY]).has_permission( + request=get_local_request(), view=self + ): + return result + strategy: Strategy = get_object_or_404(Strategy, strategy_id=validated_request_data["strategy_id"]) + event_data_field_configs = [] + risk: Risk = Risk.objects.filter(strategy_id=strategy.strategy_id).order_by("-event_time").first() + # 获取事件数据字段 + event_data: dict = risk.event_data or {} + for key in event_data.keys(): + event_data_field_configs.append({"field_name": key, "display_name": key, "description": ""}) + result["event_data_field_configs"] = event_data_field_configs + # 获取事件证据字段 + event_evidence_field_configs = [] + event_evidence: List[dict] = json.loads(risk.event_evidence or "[]") + if event_evidence: + for key in event_evidence[0].keys(): + event_evidence_field_configs.append({"field_name": key, "display_name": key, "description": ""}) + result["event_evidence_field_configs"] = event_evidence_field_configs + return result diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index 0dce368f..b840eeb0 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -15,7 +15,6 @@ We undertake not to change the open source license (MIT license) applicable to the current version of the project delivered to anyone in the future. """ - from typing import List from django.utils.translation import gettext, gettext_lazy @@ -41,6 +40,13 @@ from services.web.strategy_v2.models import Strategy +class EventFieldSerializer(serializers.Serializer): + field_name = serializers.CharField(label=gettext_lazy("Field Name")) + display_name = serializers.CharField(label=gettext_lazy("Field Display Name")) + is_priority = serializers.BooleanField(label=gettext_lazy("Is Priority")) + description = serializers.CharField(label=gettext_lazy("Field Description")) + + class CreateStrategyRequestSerializer(serializers.ModelSerializer): """ Create Strategy @@ -49,6 +55,15 @@ class CreateStrategyRequestSerializer(serializers.ModelSerializer): tags = serializers.ListField( label=gettext_lazy("Tags"), child=serializers.CharField(label=gettext_lazy("Tag Name")), default=list ) + event_basic_field_configs = serializers.ListField( + label=gettext_lazy("Event Basic Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) + event_data_field_configs = serializers.ListField( + label=gettext_lazy("Event Data Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) + event_evidence_field_configs = serializers.ListField( + label=gettext_lazy("Event Evidence Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) class Meta: model = Strategy @@ -61,6 +76,14 @@ class Meta: "tags", "notice_groups", "description", + "risk_level", + "risk_hazard", + "risk_guidance", + "risk_title", + "processor_groups", + "event_basic_field_configs", + "event_data_field_configs", + "event_evidence_field_configs", ] def validate(self, attrs: dict) -> dict: @@ -96,6 +119,15 @@ class UpdateStrategyRequestSerializer(serializers.ModelSerializer): label=gettext_lazy("Tags"), child=serializers.CharField(label=gettext_lazy("Tag Name")), default=list ) strategy_id = serializers.IntegerField(label=gettext_lazy("Strategy ID")) + event_basic_field_configs = serializers.ListField( + label=gettext_lazy("Event Basic Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) + event_data_field_configs = serializers.ListField( + label=gettext_lazy("Event Data Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) + event_evidence_field_configs = serializers.ListField( + label=gettext_lazy("Event Evidence Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) class Meta: model = Strategy @@ -109,6 +141,14 @@ class Meta: "tags", "notice_groups", "description", + "risk_level", + "risk_hazard", + "risk_guidance", + "risk_title", + "processor_groups", + "event_basic_field_configs", + "event_data_field_configs", + "event_evidence_field_configs", ] def validate(self, attrs: dict) -> dict: @@ -193,7 +233,19 @@ class StrategyInfoSerializer(serializers.ModelSerializer): class Meta: model = Strategy - fields = ["namespace", "strategy_id", "strategy_name", "control_id", "control_version", "notice_groups"] + fields = [ + "namespace", + "strategy_id", + "strategy_name", + "control_id", + "control_version", + "notice_groups", + "risk_level", + "risk_hazard", + "risk_guidance", + "risk_title", + "processor_groups", + ] class ToggleStrategyRequestSerializer(serializers.Serializer): @@ -319,6 +371,7 @@ class GetStrategyCommonResponseSerializer(serializers.Serializer): strategy_status = ChoiceListSerializer(label=gettext_lazy("Strategy Status"), many=True) offset_unit = ChoiceListSerializer(label=gettext_lazy("Offset Unit"), many=True) mapping_type = ChoiceListSerializer(label=gettext_lazy("Mapping Type"), many=True) + risk_level = ChoiceListSerializer(label=gettext_lazy("Risk Level"), many=True) class AIOPSDataSourceFilterSerializer(serializers.Serializer): @@ -433,3 +486,37 @@ class RetryStrategyRequestSerializer(serializers.Serializer): """ strategy_id = serializers.IntegerField(label=gettext_lazy("Strategy ID")) + + +class GetEventInfoFieldsRequestSerializer(serializers.Serializer): + """ + Get Event Info Fields + """ + + strategy_id = serializers.IntegerField(label=gettext_lazy("Strategy ID"), required=False) + + +class EventInfoFieldSerializer(serializers.Serializer): + """ + Get Event Info Fields + """ + + field_name = serializers.CharField(label=gettext_lazy("Name")) + display_name = serializers.CharField(label=gettext_lazy("Display Name")) + description = serializers.CharField(label=gettext_lazy("Description")) + + +class GetEventInfoFieldsResponseSerializer(serializers.Serializer): + """ + Get Event Info Fields + """ + + event_basic_field_configs = serializers.ListField( + label=gettext_lazy("Event Basic Field Configs"), child=EventInfoFieldSerializer() + ) + event_data_field_configs = serializers.ListField( + label=gettext_lazy("Event Data Field Configs"), child=EventInfoFieldSerializer() + ) + event_evidence_field_configs = serializers.ListField( + label=gettext_lazy("Event Evidence Field Configs"), child=EventInfoFieldSerializer() + ) diff --git a/src/backend/services/web/strategy_v2/views.py b/src/backend/services/web/strategy_v2/views.py index 19353f9b..34251ba8 100644 --- a/src/backend/services/web/strategy_v2/views.py +++ b/src/backend/services/web/strategy_v2/views.py @@ -79,6 +79,7 @@ class StrategyFieldsViewSet(ResourceViewSet): resource_routes = [ ResourceRoute("GET", resource.strategy_v2.list_strategy_fields), ResourceRoute("GET", resource.strategy_v2.get_strategy_field_value, endpoint="value"), + ResourceRoute("GET", resource.strategy_v2.get_event_info_fields, endpoint="event"), ] diff --git a/src/backend/tests/core/__init__.py b/src/backend/tests/core/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/src/backend/tests/core/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/src/backend/tests/core/test_render.py b/src/backend/tests/core/test_render.py new file mode 100644 index 00000000..8559e354 --- /dev/null +++ b/src/backend/tests/core/test_render.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +from core.render import jinja_render + + +def test_jinja_render(): + template_value = "render: {{content.title}} {{content.content}} {{title}}" + context = {"content": {"title": "title", "content": "content"}, "title": "title"} + actual = jinja_render(template_value, context) + assert actual == "render: title content title" diff --git a/src/backend/tests/strategy_v2/test_strategy.py b/src/backend/tests/strategy_v2/test_strategy.py index 18bc6ef9..9a01586a 100644 --- a/src/backend/tests/strategy_v2/test_strategy.py +++ b/src/backend/tests/strategy_v2/test_strategy.py @@ -26,6 +26,7 @@ from apps.meta.models import GlobalMetaConfig from core.utils.tools import ordered_dict_to_json from services.web.analyze.models import Control, ControlVersion +from services.web.strategy_v2.constants import RiskLevel from tests.base import TestCase from tests.databus.collector_plugin.test_collector_plugin import CollectorPluginTest from tests.strategy_v2.constants import ( @@ -59,7 +60,16 @@ def test_create_bkm_strategy(self) -> None: @mock.patch("services.web.analyze.controls.bkm.api.bk_monitor.save_alarm_strategy", mock.Mock(return_value={})) def _create_bkm_strategy(self) -> dict: params = copy.deepcopy(BKM_STRATEGY_DATA) - params.update({"control_id": self.c_version.control_id, "control_version": self.c_version.control_version}) + params.update( + { + "control_id": self.c_version.control_id, + "control_version": self.c_version.control_version, + "risk_level": RiskLevel.HIGH.value, + "risk_hazard": "", + "risk_guidance": "", + "risk_title": "", + } + ) return resource.strategy_v2.create_strategy(**params) @mock.patch("services.web.analyze.controls.bkm.api.bk_monitor.save_alarm_strategy", mock.Mock(return_value={})) @@ -72,6 +82,10 @@ def test_update_bkm_strategy(self) -> None: "strategy_id": data["strategy_id"], "control_id": self.c_version.control_id, "control_version": self.c_version.control_version, + "risk_level": RiskLevel.HIGH.value, + "risk_hazard": "", + "risk_guidance": "", + "risk_title": "", } ) data = resource.strategy_v2.update_strategy(**params) From 1e7db562968c7214cecf3c4b7e1248bd0e1e6d4e Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Mon, 5 Aug 2024 19:57:51 +0800 Subject: [PATCH 02/64] =?UTF-8?q?feat:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 优化事件信息字段获取接口,增加样例字段,调整权限 2. 优化代码 --- src/backend/api/user_manage/default.py | 24 ----------- src/backend/core/utils/user_manage.py | 20 +++++++++ src/backend/services/web/risk/constants.py | 24 +++++++++++ .../services/web/strategy_v2/constants.py | 13 ++++++ .../services/web/strategy_v2/resources.py | 42 +++++++++++++------ .../services/web/strategy_v2/serializers.py | 10 +++++ src/backend/services/web/strategy_v2/views.py | 7 +++- .../tests/strategy_v2/test_strategy.py | 6 ++- 8 files changed, 107 insertions(+), 39 deletions(-) create mode 100644 src/backend/core/utils/user_manage.py diff --git a/src/backend/api/user_manage/default.py b/src/backend/api/user_manage/default.py index 6e918748..f238adc2 100644 --- a/src/backend/api/user_manage/default.py +++ b/src/backend/api/user_manage/default.py @@ -19,7 +19,6 @@ import abc import base64 import os -from typing import Optional from bk_resource import BkApiResource from bk_resource.utils.cache import CacheTypeItem @@ -46,29 +45,6 @@ class RetrieveUser(UserManageResource): method = "GET" action = "/retrieve_user/" cache_type = CacheTypeItem(key="RetrieveUser", timeout=TimeEnum.ONE_HOUR_SECOND.value, user_related=False) - platform_authorization = True - - -class RetrieveLeaderResource(UserManageResource): - """获取单个用户的leader信息 .""" - - name = gettext_lazy("获取单个用户的leader信息") - platform_authorization = True - - @property - def action(self): - return - - def perform_request(self, validated_request_data) -> Optional[str]: - # 获取用户信息 - user_info = RetrieveUser().perform_request(validated_request_data) - # 解析出leader信息 - leader_infos = user_info.get("leader", []) - if leader_infos: - leader_info = leader_infos[0] - else: - leader_info = {} - return leader_info.get("username") class ListUserDepartments(UserManageResource): diff --git a/src/backend/core/utils/user_manage.py b/src/backend/core/utils/user_manage.py new file mode 100644 index 00000000..e6e2bb1f --- /dev/null +++ b/src/backend/core/utils/user_manage.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from typing import Optional + +from bk_resource import api + + +def retrieve_leader(validated_request_data: dict) -> Optional[str]: + """ + 获取单个用户的leader信息 + """ + + # 获取用户信息 + user_info = api.user_manage.retrieve_user(validated_request_data) + # 解析出leader信息 + leader_infos = user_info.get("leader", []) + if leader_infos: + leader_info = leader_infos[0] + else: + leader_info = {} + return leader_info.get("username") diff --git a/src/backend/services/web/risk/constants.py b/src/backend/services/web/risk/constants.py index 34784de1..3db22b6f 100644 --- a/src/backend/services/web/risk/constants.py +++ b/src/backend/services/web/risk/constants.py @@ -110,6 +110,30 @@ class EventMappingFields: 审计事件标准字段 """ + @classmethod + def gen_example(cls, field: Field) -> str: + """ + 生成字段示例 + """ + + example = "" + match field: + case cls.EVENT_ID: + example = "" + case cls.EVENT_CONTENT: + example = "测试风险" + case cls.RAW_EVENT_ID: + example = "1234567890123456" + case cls.EVENT_TYPE: + example = "SuperPermission" + case cls.EVENT_TIME: + example = "2024-01-01 00:00:00" + case cls.EVENT_SOURCE: + example = EventSourceTypeChoices.BKM.value + case cls.OPERATOR: + example = "admin" + return example + @property def fields(self): return [ diff --git a/src/backend/services/web/strategy_v2/constants.py b/src/backend/services/web/strategy_v2/constants.py index 13dc6023..7b1607f0 100644 --- a/src/backend/services/web/strategy_v2/constants.py +++ b/src/backend/services/web/strategy_v2/constants.py @@ -15,6 +15,7 @@ We undertake not to change the open source license (MIT license) applicable to the current version of the project delivered to anyone in the future. """ +from typing import TypedDict from django.utils.translation import gettext_lazy @@ -114,6 +115,7 @@ class TableType(TextChoices): EVENT_LOG = "EventLog", gettext_lazy("Event Log") BUILD_ID_ASSET = "BuildIn", gettext_lazy("Asset Data") + # BIZ_ASSET = "BizAsset", gettext_lazy("Biz Asset") @classmethod @@ -166,3 +168,14 @@ class RiskLevel(TextChoices): HIGH = "HIGH", gettext_lazy("高") MIDDLE = "MIDDLE", gettext_lazy("中") LOW = "LOW", gettext_lazy("低") + + +class EventInfoField(TypedDict): + """ + 事件信息字段 + """ + + field_name: str + display_name: str + description: str + example: str diff --git a/src/backend/services/web/strategy_v2/resources.py b/src/backend/services/web/strategy_v2/resources.py index 1957954d..af81a52f 100644 --- a/src/backend/services/web/strategy_v2/resources.py +++ b/src/backend/services/web/strategy_v2/resources.py @@ -65,6 +65,7 @@ HAS_UPDATE_TAG_ID, HAS_UPDATE_TAG_NAME, LOCAL_UPDATE_FIELDS, + EventInfoField, MappingType, RiskLevel, StrategyAlgorithmOperator, @@ -592,20 +593,38 @@ def perform_request(self, validated_request_data): } -class GetEventInfoFields(StrategyV2Base): +class GetEventFieldsConfig(StrategyV2Base): name = gettext_lazy("Get Event Info Fields") RequestSerializer = GetEventInfoFieldsRequestSerializer ResponseSerializer = GetEventInfoFieldsResponseSerializer - def get_event_basic_field_configs(self) -> List[dict]: + @classmethod + def get_event_basic_field_configs(cls) -> List[EventInfoField]: """ 获取基础字段 """ - return [ - {"field_name": field.field_name, "display_name": field.alias_name, "description": str(field.description)} - for field in EventMappingFields().fields + field_configs = [] + basic_fields = [ + EventMappingFields.EVENT_ID, + EventMappingFields.EVENT_CONTENT, + EventMappingFields.RAW_EVENT_ID, + EventMappingFields.STRATEGY_ID, + EventMappingFields.EVENT_TYPE, + EventMappingFields.EVENT_TIME, + EventMappingFields.EVENT_SOURCE, + EventMappingFields.OPERATOR, ] + for field in basic_fields: + field_configs.append( + EventInfoField( + field_name=field.field_name, + display_name=field.alias_name, + description=str(field.description), + example=EventMappingFields.gen_example(field), + ) + ) + return field_configs def perform_request(self, validated_request_data): # 获取基础字段 @@ -613,24 +632,23 @@ def perform_request(self, validated_request_data): strategy_id = validated_request_data.get("strategy_id") if not strategy_id: return result - # check permission - if not ActionPermission(actions=[ActionEnum.EDIT_STRATEGY]).has_permission( - request=get_local_request(), view=self - ): - return result strategy: Strategy = get_object_or_404(Strategy, strategy_id=validated_request_data["strategy_id"]) event_data_field_configs = [] risk: Risk = Risk.objects.filter(strategy_id=strategy.strategy_id).order_by("-event_time").first() # 获取事件数据字段 event_data: dict = risk.event_data or {} for key in event_data.keys(): - event_data_field_configs.append({"field_name": key, "display_name": key, "description": ""}) + event_data_field_configs.append( + EventInfoField(field_name=key, display_name=key, description="", example="") + ) result["event_data_field_configs"] = event_data_field_configs # 获取事件证据字段 event_evidence_field_configs = [] event_evidence: List[dict] = json.loads(risk.event_evidence or "[]") if event_evidence: for key in event_evidence[0].keys(): - event_evidence_field_configs.append({"field_name": key, "display_name": key, "description": ""}) + event_evidence_field_configs.append( + EventInfoField(field_name=key, display_name=key, description="", example="") + ) result["event_evidence_field_configs"] = event_evidence_field_configs return result diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index b840eeb0..6ff04b7d 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -32,6 +32,7 @@ BKMONITOR_AGG_INTERVAL_MIN, STRATEGY_SCHEDULE_TIME, ConnectorChoices, + RiskLevel, StrategyAlgorithmOperator, StrategyOperator, TableType, @@ -64,6 +65,11 @@ class CreateStrategyRequestSerializer(serializers.ModelSerializer): event_evidence_field_configs = serializers.ListField( label=gettext_lazy("Event Evidence Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True ) + risk_level = serializers.ChoiceField(label=gettext_lazy("Risk Level"), choices=RiskLevel.choices) + risk_title = serializers.CharField(label=gettext_lazy("Risk Title")) + processor_groups = serializers.ListField( + label=gettext_lazy("Processor Groups"), child=serializers.CharField(label=gettext_lazy("Processor Group")) + ) class Meta: model = Strategy @@ -128,6 +134,10 @@ class UpdateStrategyRequestSerializer(serializers.ModelSerializer): event_evidence_field_configs = serializers.ListField( label=gettext_lazy("Event Evidence Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True ) + risk_title = serializers.CharField(label=gettext_lazy("Risk Title")) + processor_groups = serializers.ListField( + label=gettext_lazy("Processor Groups"), child=serializers.CharField(label=gettext_lazy("Processor Group")) + ) class Meta: model = Strategy diff --git a/src/backend/services/web/strategy_v2/views.py b/src/backend/services/web/strategy_v2/views.py index 34251ba8..c065c3f8 100644 --- a/src/backend/services/web/strategy_v2/views.py +++ b/src/backend/services/web/strategy_v2/views.py @@ -76,10 +76,15 @@ def get_permissions(self): class StrategyFieldsViewSet(ResourceViewSet): + def get_permissions(self): + if self.action in ["fields_config"]: + return [IAMPermission(actions=[ActionEnum.CREATE_STRATEGY])] + return [] + resource_routes = [ ResourceRoute("GET", resource.strategy_v2.list_strategy_fields), ResourceRoute("GET", resource.strategy_v2.get_strategy_field_value, endpoint="value"), - ResourceRoute("GET", resource.strategy_v2.get_event_info_fields, endpoint="event"), + ResourceRoute("GET", resource.strategy_v2.get_event_fields_config, endpoint="fields_config"), ] diff --git a/src/backend/tests/strategy_v2/test_strategy.py b/src/backend/tests/strategy_v2/test_strategy.py index 9a01586a..75744e39 100644 --- a/src/backend/tests/strategy_v2/test_strategy.py +++ b/src/backend/tests/strategy_v2/test_strategy.py @@ -67,7 +67,8 @@ def _create_bkm_strategy(self) -> dict: "risk_level": RiskLevel.HIGH.value, "risk_hazard": "", "risk_guidance": "", - "risk_title": "", + "risk_title": "risk title", + "processor_groups": ["123"], } ) return resource.strategy_v2.create_strategy(**params) @@ -85,7 +86,8 @@ def test_update_bkm_strategy(self) -> None: "risk_level": RiskLevel.HIGH.value, "risk_hazard": "", "risk_guidance": "", - "risk_title": "", + "risk_title": "risk_title", + "processor_groups": ["123"], } ) data = resource.strategy_v2.update_strategy(**params) From 0b0f84cf7ea47051148c86b09d7b4a0a2157dc9c Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Tue, 6 Aug 2024 17:02:45 +0800 Subject: [PATCH 03/64] =?UTF-8?q?feat:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/apps/notice/models.py | 9 ++++++++ src/backend/core/render.py | 3 --- .../services/web/risk/handlers/risk.py | 5 +--- .../services/web/risk/handlers/ticket.py | 23 +++++++------------ .../services/web/risk/resources/risk.py | 8 ------- .../migrations/0006_auto_20240806_1651.py | 23 +++++++++++++++++++ .../services/web/strategy_v2/models.py | 4 ++-- .../services/web/strategy_v2/serializers.py | 8 +++---- 8 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 src/backend/services/web/strategy_v2/migrations/0006_auto_20240806_1651.py diff --git a/src/backend/apps/notice/models.py b/src/backend/apps/notice/models.py index d5ed2c9e..0ecec7a6 100644 --- a/src/backend/apps/notice/models.py +++ b/src/backend/apps/notice/models.py @@ -15,6 +15,7 @@ We undertake not to change the open source license (MIT license) applicable to the current version of the project delivered to anyone in the future. """ +from typing import List from bk_audit.log.models import AuditInstance from bk_resource.utils.common_utils import get_md5 @@ -113,6 +114,14 @@ class Meta: def audit_instance(self): return NoticeGroupAuditInstance(self) + @classmethod + def parse_members(cls, processor_groups: List["NoticeGroup"]) -> List[str]: + """ + 解析处理组成员 + """ + + return list({member for processor_group in processor_groups for member in processor_group.group_member}) + class NoticeLog(OperateRecordModel): """ diff --git a/src/backend/core/render.py b/src/backend/core/render.py index 1bb5969d..1942319c 100644 --- a/src/backend/core/render.py +++ b/src/backend/core/render.py @@ -1,15 +1,12 @@ # -*- coding: utf-8 -*- import json -import logging import re from typing import Dict, List, Union from django.utils import translation from jinja2 import Environment -logger = logging.getLogger(__name__) - def jinja2_environment(**options: Dict) -> Environment: """创建jinja2的环境执行环境 .""" diff --git a/src/backend/services/web/risk/handlers/risk.py b/src/backend/services/web/risk/handlers/risk.py index 49f1e47b..8aa1b956 100644 --- a/src/backend/services/web/risk/handlers/risk.py +++ b/src/backend/services/web/risk/handlers/risk.py @@ -200,10 +200,7 @@ def send_risk_notice(self, risk: Risk) -> None: self.send_notice(risk=risk, notice_groups=notice_groups, is_todo=False) # 更新风险的通知人员名单 - notice_users = [] - for notice_group in notice_groups: - notice_users.extend(notice_group.group_member if isinstance(notice_group.group_member, list) else []) - risk.notice_users = notice_users + risk.notice_users = NoticeGroup.parse_members(notice_groups) risk.save(update_fields=["notice_users"]) @classmethod diff --git a/src/backend/services/web/risk/handlers/ticket.py b/src/backend/services/web/risk/handlers/ticket.py index 7e851e4c..9738b8df 100644 --- a/src/backend/services/web/risk/handlers/ticket.py +++ b/src/backend/services/web/risk/handlers/ticket.py @@ -66,6 +66,7 @@ def __init__(self, risk_id: str, operator: str): self.process_application: ProcessApplication = None self.init_rule() self.init_process_application() + self.strategy: Strategy = Strategy.objects.filter(strategy_id=self.risk.strategy_id).first() def init_rule(self) -> None: if self.risk.rule_id: @@ -93,18 +94,18 @@ def load_security_person(cls) -> List[str]: def load_processor(self) -> List[str]: """ - 获取处理人 + 获取处理人(用安全接口人进行兜底) """ # 获取策略 strategy: Strategy = Strategy.objects.filter(strategy_id=self.risk.strategy_id).first() if not strategy: - return [] + return self.load_security_person() # 获取处理组成员 processor_groups: List[NoticeGroup] = list( NoticeGroup.objects.filter(group_id__in=strategy.processor_groups or []) ) - return list({member for processor_group in processor_groups for member in processor_group.group_member}) + return NoticeGroup.parse_members(processor_groups=processor_groups) or self.load_security_person() def load_last_operator(self) -> List[str]: """ @@ -262,11 +263,7 @@ def process(self, *args, **kwargs) -> dict: def update_operator(self, process_result: dict, *args, **kwargs) -> None: # 有处理套餐则当前处理人为空,否则为安全责任人 - self.risk.current_operator = ( - [] - if self.process_application and self.risk.operator - else self.load_processor() or self.load_security_person() - ) + self.risk.current_operator = [] if self.process_application and self.risk.operator else self.load_processor() self.risk.save(update_fields=["current_operator"]) def match_risk_rule(self) -> None: @@ -390,9 +387,7 @@ def update_operator(self, process_result: dict, **kwargs) -> None: if (current_status in TicketStatus.get_success_status() and not approve_result) or ( current_status in TicketStatus.get_failed_status() ): - self.risk.current_operator = ( - self.load_last_operator() or self.load_processor() or self.load_security_person() - ) + self.risk.current_operator = self.load_last_operator() or self.load_processor() # 其他情况处理人为空 else: self.risk.current_operator = [] @@ -501,9 +496,7 @@ def update_operator(self, process_result: dict, *args, **kwargs) -> None: process_result["status"]["state"] in SOPSTaskStatus.get_failed_status() or (process_result["status"]["state"] in SOPSTaskStatus.get_success_status() and not auto_close_risk) ): - self.risk.current_operator = ( - self.load_last_operator() or self.load_processor() or self.load_security_person() - ) + self.risk.current_operator = self.load_last_operator() or self.load_processor() # 其他情况处理人为空 else: self.risk.current_operator = [] @@ -739,7 +732,7 @@ def update_status(self, process_result: dict, *args, **kwargs) -> None: self.risk.save(update_fields=["status"]) def update_operator(self, process_result: dict, *args, **kwargs) -> None: - self.risk.current_operator = self.load_processor() or self.load_security_person() + self.risk.current_operator = self.load_processor() self.risk.save(update_fields=["current_operator"]) def build_history(self, process_result: dict, *args, **kwargs) -> dict: diff --git a/src/backend/services/web/risk/resources/risk.py b/src/backend/services/web/risk/resources/risk.py index e22af11e..a9cb3be8 100644 --- a/src/backend/services/web/risk/resources/risk.py +++ b/src/backend/services/web/risk/resources/risk.py @@ -80,7 +80,6 @@ UpdateRiskLabelReqSerializer, ) from services.web.risk.tasks import process_one_risk, sync_auto_result -from services.web.strategy_v2.models import Strategy class RiskMeta(AuditMixinResource, abc.ABC): @@ -95,7 +94,6 @@ class RetrieveRisk(RiskMeta): def perform_request(self, validated_request_data): risk = get_object_or_404(Risk, risk_id=validated_request_data["risk_id"]) self.add_audit_instance_to_context(instance=RiskAuditInstance(risk)) - strategy: Strategy = Strategy.objects.filter(strategy_id=risk.strategy_id).first() data = RiskInfoSerializer(risk).data data = wrapper_permission_field( data, actions=[ActionEnum.EDIT_RISK], id_field=lambda risk: risk["risk_id"], many=False @@ -103,12 +101,6 @@ def perform_request(self, validated_request_data): risk = data[0] nodes = TicketNode.objects.filter(risk_id=risk["risk_id"]).order_by("timestamp") risk["ticket_history"] = TicketNodeSerializer(nodes, many=True).data - # 补充风险策略中的事件字段配置 - risk["event_field_configs"] = { - "event_basic_field_configs": strategy.event_basic_field_configs, - "event_data_field_configs": strategy.event_data_field_configs, - "event_evidence_field_configs": strategy.event_evidence_field_configs, - } return risk diff --git a/src/backend/services/web/strategy_v2/migrations/0006_auto_20240806_1651.py b/src/backend/services/web/strategy_v2/migrations/0006_auto_20240806_1651.py new file mode 100644 index 00000000..0740139e --- /dev/null +++ b/src/backend/services/web/strategy_v2/migrations/0006_auto_20240806_1651.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.23 on 2024-08-06 08:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("strategy_v2", "0005_auto_20240802_0111"), + ] + + operations = [ + migrations.AlterField( + model_name="strategy", + name="processor_groups", + field=models.JSONField(blank=True, default=list, null=True, verbose_name="Processor"), + ), + migrations.AlterField( + model_name="strategy", + name="risk_title", + field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name="Risk Title"), + ), + ] diff --git a/src/backend/services/web/strategy_v2/models.py b/src/backend/services/web/strategy_v2/models.py index 64894f65..d2d46bc6 100644 --- a/src/backend/services/web/strategy_v2/models.py +++ b/src/backend/services/web/strategy_v2/models.py @@ -52,8 +52,8 @@ class Strategy(SoftDeleteModel): ) risk_hazard = models.TextField(gettext_lazy("Risk Hazard"), null=True, blank=True, default=None) risk_guidance = models.TextField(gettext_lazy("Risk Guidance"), null=True, blank=True, default=None) - risk_title = models.TextField(gettext_lazy("Risk Title"), null=True, blank=True, default=None) - processor_groups = models.JSONField(gettext_lazy("Processor"), default=dict, null=True, blank=True) + risk_title = models.CharField(gettext_lazy("Risk Title"), max_length=255, null=True, blank=True, default=None) + processor_groups = models.JSONField(gettext_lazy("Processor"), default=list, null=True, blank=True) event_basic_field_configs = models.JSONField( gettext_lazy("Event field Configs"), default=dict, null=True, blank=True ) diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index 6ff04b7d..d49cb0fe 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -68,7 +68,7 @@ class CreateStrategyRequestSerializer(serializers.ModelSerializer): risk_level = serializers.ChoiceField(label=gettext_lazy("Risk Level"), choices=RiskLevel.choices) risk_title = serializers.CharField(label=gettext_lazy("Risk Title")) processor_groups = serializers.ListField( - label=gettext_lazy("Processor Groups"), child=serializers.CharField(label=gettext_lazy("Processor Group")) + label=gettext_lazy("Processor Groups"), child=serializers.IntegerField(label=gettext_lazy("Processor Group")) ) class Meta: @@ -136,7 +136,7 @@ class UpdateStrategyRequestSerializer(serializers.ModelSerializer): ) risk_title = serializers.CharField(label=gettext_lazy("Risk Title")) processor_groups = serializers.ListField( - label=gettext_lazy("Processor Groups"), child=serializers.CharField(label=gettext_lazy("Processor Group")) + label=gettext_lazy("Processor Groups"), child=serializers.IntegerField(label=gettext_lazy("Processor Group")) ) class Meta: @@ -525,8 +525,8 @@ class GetEventInfoFieldsResponseSerializer(serializers.Serializer): label=gettext_lazy("Event Basic Field Configs"), child=EventInfoFieldSerializer() ) event_data_field_configs = serializers.ListField( - label=gettext_lazy("Event Data Field Configs"), child=EventInfoFieldSerializer() + label=gettext_lazy("Event Data Field Configs"), child=EventInfoFieldSerializer(), required=False ) event_evidence_field_configs = serializers.ListField( - label=gettext_lazy("Event Evidence Field Configs"), child=EventInfoFieldSerializer() + label=gettext_lazy("Event Evidence Field Configs"), child=EventInfoFieldSerializer(), required=False ) From 250c7466bdb87bc2ab0daf16a5abc3a1285a7188 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Tue, 6 Aug 2024 18:08:35 +0800 Subject: [PATCH 04/64] =?UTF-8?q?feat:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/services/web/strategy_v2/resources.py | 2 ++ src/backend/services/web/strategy_v2/serializers.py | 1 + 2 files changed, 3 insertions(+) diff --git a/src/backend/services/web/strategy_v2/resources.py b/src/backend/services/web/strategy_v2/resources.py index af81a52f..67a28425 100644 --- a/src/backend/services/web/strategy_v2/resources.py +++ b/src/backend/services/web/strategy_v2/resources.py @@ -635,6 +635,8 @@ def perform_request(self, validated_request_data): strategy: Strategy = get_object_or_404(Strategy, strategy_id=validated_request_data["strategy_id"]) event_data_field_configs = [] risk: Risk = Risk.objects.filter(strategy_id=strategy.strategy_id).order_by("-event_time").first() + if not risk: + return result # 获取事件数据字段 event_data: dict = risk.event_data or {} for key in event_data.keys(): diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index d49cb0fe..60ecd3a6 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -514,6 +514,7 @@ class EventInfoFieldSerializer(serializers.Serializer): field_name = serializers.CharField(label=gettext_lazy("Name")) display_name = serializers.CharField(label=gettext_lazy("Display Name")) description = serializers.CharField(label=gettext_lazy("Description")) + example = serializers.CharField(label=gettext_lazy("Example")) class GetEventInfoFieldsResponseSerializer(serializers.Serializer): From 058791ff1b76be2675d25d02121d325ee1584b12 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Tue, 6 Aug 2024 19:16:14 +0800 Subject: [PATCH 05/64] =?UTF-8?q?feat:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/services/web/strategy_v2/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index 60ecd3a6..0af6c053 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -513,8 +513,8 @@ class EventInfoFieldSerializer(serializers.Serializer): field_name = serializers.CharField(label=gettext_lazy("Name")) display_name = serializers.CharField(label=gettext_lazy("Display Name")) - description = serializers.CharField(label=gettext_lazy("Description")) - example = serializers.CharField(label=gettext_lazy("Example")) + description = serializers.CharField(label=gettext_lazy("Description"), allow_blank=True) + example = serializers.CharField(label=gettext_lazy("Example"), allow_blank=True) class GetEventInfoFieldsResponseSerializer(serializers.Serializer): From 506adfc0d0238b1d585b190d7c61329191dcaba5 Mon Sep 17 00:00:00 2001 From: nanasikeai Date: Tue, 6 Aug 2024 20:22:56 +0800 Subject: [PATCH 06/64] =?UTF-8?q?feat:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/domain/model/strategy/common-data.ts | 2 + .../model/strategy/strategy-field-event.ts | 57 ++ .../src/domain/model/strategy/strategy.ts | 22 + .../src/domain/service/strategy-manage.ts | 34 +- .../src/domain/source/strategy-manage.ts | 108 ++- .../list/components/aiops/event-log.vue | 2 +- .../components/aiops/filter-condition.vue | 2 +- .../src/views/strategy-manage/routes.ts | 2 +- .../components/components/collapse-panel.vue | 2 +- .../components/components/field-cascader.vue | 0 .../components/components/field-input.vue | 0 .../components/components/field-select.vue | 0 .../components/filter-condition.vue | 0 .../components/components/model-empty.vue | 0 .../components/multiple-render-field.vue | 0 .../components/components/render-field.vue | 0 .../components/components/render-table.vue | 2 +- .../components/scheme-input/event-log.vue | 0 .../components/scheme-input/resource-data.vue | 0 .../components/scheme-paramenters/index.vue | 0 .../{ => step1/components}/aiops/index.vue | 0 .../{ => step1/components}/card-part.vue | 0 .../components}/control-description.vue | 0 .../{ => step1/components}/diff-condition.vue | 0 .../{ => step1/components}/normal/index.vue | 0 .../{ => step1/components}/plan-select.vue | 0 .../components/step1/components/card.vue | 0 .../upgrade/components/step1/index.vue | 4 +- .../upgrade/components/step2/index.vue | 2 +- .../{ => step1/components}/upgrade/index.vue | 0 .../components/step1/index.vue | 878 +++++++++++++++++ .../step2/components/event-info-table.vue | 219 +++++ .../step2/components/variable-table.vue | 115 +++ .../components/step2/index.vue | 171 ++++ .../components/step3/index.vue | 279 ++++++ .../strategy-manage/strategy-create/index.vue | 911 +++--------------- 36 files changed, 1985 insertions(+), 827 deletions(-) create mode 100644 src/frontend/src/domain/model/strategy/strategy-field-event.ts rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/collapse-panel.vue (98%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/field-cascader.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/field-input.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/field-select.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/filter-condition.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/model-empty.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/multiple-render-field.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/render-field.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/components/render-table.vue (97%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/scheme-input/event-log.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/scheme-input/resource-data.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/components/scheme-paramenters/index.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/aiops/index.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/card-part.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/control-description.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/diff-condition.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/normal/index.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/plan-select.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/upgrade/components/step1/components/card.vue (100%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/upgrade/components/step1/index.vue (99%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/upgrade/components/step2/index.vue (99%) rename src/frontend/src/views/strategy-manage/strategy-create/components/{ => step1/components}/upgrade/index.vue (100%) create mode 100644 src/frontend/src/views/strategy-manage/strategy-create/components/step1/index.vue create mode 100644 src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/event-info-table.vue create mode 100644 src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/variable-table.vue create mode 100644 src/frontend/src/views/strategy-manage/strategy-create/components/step2/index.vue create mode 100644 src/frontend/src/views/strategy-manage/strategy-create/components/step3/index.vue diff --git a/src/frontend/src/domain/model/strategy/common-data.ts b/src/frontend/src/domain/model/strategy/common-data.ts index 6e1ba33a..b589032a 100644 --- a/src/frontend/src/domain/model/strategy/common-data.ts +++ b/src/frontend/src/domain/model/strategy/common-data.ts @@ -29,6 +29,7 @@ export default class CommonData { filter_operator: Array; algorithm_operator: Array; strategy_status: Array; + risk_level: Array; constructor(payload = {} as CommonData) { this.offset_unit = payload.offset_unit || []; @@ -38,6 +39,7 @@ export default class CommonData { this.filter_operator = payload.filter_operator || []; this.algorithm_operator = payload.algorithm_operator || []; this.strategy_status = payload.strategy_status || []; + this.risk_level = payload.risk_level || []; } } diff --git a/src/frontend/src/domain/model/strategy/strategy-field-event.ts b/src/frontend/src/domain/model/strategy/strategy-field-event.ts new file mode 100644 index 00000000..f94ab9ed --- /dev/null +++ b/src/frontend/src/domain/model/strategy/strategy-field-event.ts @@ -0,0 +1,57 @@ +/* + TencentBlueKing is pleased to support the open source community by making + 蓝鲸智云 - 审计中心 (BlueKing - Audit Center) available. + Copyright (C) 2023 THL A29 Limited, + a Tencent company. All rights reserved. + Licensed under the MIT License (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at http://opensource.org/licenses/MIT + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on + 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. + We undertake not to change the open source license (MIT license) applicable + to the current version of the project delivered to anyone in the future. +*/ +type EventItem = { + field_name: string, + description: string + display_name: string; + is_priority: boolean; + group?: string; + example?: string +} + +export default class StrategyField { + event_basic_field_configs: Array; + event_data_field_configs: Array; + event_evidence_field_configs: Array; + + constructor(payload = {} as StrategyField) { + this.event_basic_field_configs = (payload.event_basic_field_configs + && payload.event_basic_field_configs.map(item => ({ + field_name: item.field_name, + display_name: item.display_name, + is_priority: item.is_priority || false, + description: item.description, + example: item.example, + }))) || []; + this.event_data_field_configs = (payload.event_data_field_configs + && payload.event_data_field_configs.map(item => ({ + field_name: item.field_name, + display_name: item.display_name, + is_priority: item.is_priority || false, + description: item.description, + example: item.example, + }))) || []; + this.event_evidence_field_configs = (payload.event_evidence_field_configs + && payload.event_evidence_field_configs.map(item => ({ + field_name: item.field_name, + display_name: item.display_name, + is_priority: item.is_priority || false, + description: item.description, + example: item.example, + }))) || []; + } +} diff --git a/src/frontend/src/domain/model/strategy/strategy.ts b/src/frontend/src/domain/model/strategy/strategy.ts index e245316d..05bccd16 100644 --- a/src/frontend/src/domain/model/strategy/strategy.ts +++ b/src/frontend/src/domain/model/strategy/strategy.ts @@ -15,6 +15,12 @@ to the current version of the project delivered to anyone in the future. */ +interface Variable { + field_name: string, + description: string + display_name: string; + is_priority: boolean; +} export default class Strategy { strategy_id: number; strategy_name: string; @@ -59,6 +65,14 @@ export default class Strategy { permission: Record; notice_groups: Array; description: string; + risk_level: string; + risk_hazard: string; + risk_guidance: string; + risk_title: string; + event_evidence_field_configs: Array; + event_data_field_configs: Array; + event_basic_field_configs: Array; + processor_groups: Array; constructor(payload = {} as Strategy) { this.strategy_id = payload.strategy_id; this.strategy_name = payload.strategy_name; @@ -77,6 +91,14 @@ export default class Strategy { this.permission = payload.permission; this.notice_groups = payload.notice_groups; this.description = payload.description; + this.risk_level = payload.risk_level; + this.risk_hazard = payload.risk_hazard; + this.risk_guidance = payload.risk_guidance; + this.risk_title = payload.risk_title; + this.event_evidence_field_configs = payload.event_evidence_field_configs; + this.event_data_field_configs = payload.event_data_field_configs; + this.event_basic_field_configs = payload.event_basic_field_configs; + this.processor_groups = payload.processor_groups; } get isFailed() { const failedStatusMap: Record = { diff --git a/src/frontend/src/domain/service/strategy-manage.ts b/src/frontend/src/domain/service/strategy-manage.ts index 6caa2ab0..07b31cd1 100644 --- a/src/frontend/src/domain/service/strategy-manage.ts +++ b/src/frontend/src/domain/service/strategy-manage.ts @@ -16,6 +16,7 @@ */ import SearchModel from '@model/es-query/search'; import StrategyModel from '@model/strategy/strategy'; +import StrategyFieldEvent from '@model/strategy/strategy-field-event'; import StrategySource from '../source/strategy-manage'; @@ -25,10 +26,10 @@ export default { * @param { Object } params */ fetchStrategyList(params: { - label?: string - name?: string, - page: number, - page_size : number + label?: string + name?: string, + page: number, + page_size: number }) { return StrategySource.getStrategyList( params, @@ -47,7 +48,7 @@ export default { */ saveStrategy(params: Record) { return StrategySource.saveStrategy(params) - .then(({ data }) => data); + .then(({ data }) => data); }, /** * @desc 获取所有策略下拉列表 @@ -94,7 +95,7 @@ export default { * @desc 删除告警策略 * @param { Number } id */ - remove(params: {id: number}) { + remove(params: { id: number }) { return StrategySource.deleteStrategy(params) .then(({ data }) => data); }, @@ -128,7 +129,7 @@ export default { * @param { String } field_name */ fetchStrategyFieldValue(params: { - field_name : string + field_name: string }) { return StrategySource.getStrategyFieldValue(params) .then(({ data }) => data); @@ -147,7 +148,7 @@ export default { * @desc 获取表格下的rt字段 */ fetchTableRtFields(params: { - table_id : string + table_id: string }) { return StrategySource.getTableRtFields(params) .then(({ data }) => data); @@ -195,7 +196,7 @@ export default { interval: number, conditions: Array>, page: number, - page_size : number + page_size: number }) { return StrategySource.getRecentLog(params) .then(({ data }) => ({ @@ -215,7 +216,7 @@ export default { * @desc 获取算法详情 * @param { String } id */ - fetchAiopsPlanById(params: { id: string}) { + fetchAiopsPlanById(params: { id: string }) { return StrategySource.getAiopsPlanById(params) .then(({ data }) => data); }, @@ -224,7 +225,7 @@ export default { * @desc 获取结果表字段 * @param { String } system_id */ - fetchRtfields(params: { system_id : string}) { + fetchRtfields(params: { system_id: string }) { return StrategySource.getRtfields(params) .then(({ data }) => data); }, @@ -233,8 +234,17 @@ export default { * @desc 获取aiops策略详情 * @param { String } id */ - fetchAiopsById(params: { id : string}) { + fetchAiopsById(params: { id: string }) { return StrategySource.getAiopsById(params) .then(({ data }) => data); }, + + /** + * @desc 获取策略事件信息 + * @param { String } id + */ + fetchStrategyEvent(params: { id: string }) { + return StrategySource.getStrategyEvent(params) + .then(({ data }) => new StrategyFieldEvent(data)); + }, }; diff --git a/src/frontend/src/domain/source/strategy-manage.ts b/src/frontend/src/domain/source/strategy-manage.ts index 347451c3..9957f85f 100644 --- a/src/frontend/src/domain/source/strategy-manage.ts +++ b/src/frontend/src/domain/source/strategy-manage.ts @@ -19,6 +19,7 @@ import type AiopsDetailModel from '@model/strategy/aiops-detail'; import type AiopsPlanModel from '@model/strategy/aiops-plan'; import type CommonDataModel from '@model/strategy/common-data'; import type StrategyModel from '@model/strategy/strategy'; +import type StrategyFieldEvent from '@model/strategy/strategy-field-event'; // import type StrategyFieldModel from '@model/strategy/strategy-field'; // import type StrategyConfigListModel from '@model/strategy/strategy-config-list'; import type StrategyTag from '@model/strategy/strategy-tag'; @@ -41,7 +42,7 @@ class Strategy extends ModuleBase { label?: string, name?: string, page: number, - page_size : number + page_size: number }, payload = {} as IRequestPayload) { return Request.get>(`${this.path}/strategy/`, { params, @@ -128,7 +129,7 @@ class Strategy extends ModuleBase { // 获取变量值 getStrategyFieldValue(params: { - field_name : string + field_name: string }) { return Request.get>, page: number, - page_size : number + page_size: number }, payload = {} as IRequestPayload) { return Request.post>(`${this.path}/unify_query/recent_log/`, { params, @@ -223,17 +224,108 @@ class Strategy extends ModuleBase { return Request.get>(`${this.path}/aiops_plan/`); } // 获取算法详情 - getAiopsPlanById(params: {id: string}) { + getAiopsPlanById(params: { id: string }) { return Request.get(`${this.path}/aiops_plan/${params.id}/`); } // 获取结果表字段 - getRtfields(params: {system_id: string}) { + getRtfields(params: { system_id: string }) { return Request.get>>(`${this.path}/aiops_meta/rt_fields/`, { params }); } // 获取aiops策略详情 - getAiopsById(params: {id: string}) { + getAiopsById(params: { id: string }) { return Request.get(`${this.path}/strategies/${params.id}/aiops_detail/`, { params }); } + // 获取aiops策略详情 + getStrategyEvent(params: { id: string }) { + return Request.get(`${this.path}/strategy_fields/fields_config/`, { params }); + // const data: Promise<{ + // data: StrategyFieldEvent + // }> = new Promise((resolve) => { + // resolve({ + // data: { + // event_evidence_field_configs: [ + // { + // field_name: 'case_id', + // display_name: '事件证据', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'case_id', + // display_name: '事件证据', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'case_id', + // display_name: '事件证据', + // is_priority: false, + // description: '', + // }, + // ], + // event_data_field_configs: [ + // { + // field_name: 'event_data', + // display_name: '事件数据字段', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'event_data', + // display_name: '事件数据字段', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'event_data', + // display_name: '事件数据字段', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'event_data', + // display_name: '事件数据字段', + // is_priority: false, + // description: '', + // }, + // ], + // event_basic_field_configs: [ + // { + // field_name: 'case_id', + // display_name: '事件ID', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'case_id', + // display_name: '事件ID', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'case_id', + // display_name: '事件ID', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'case_id', + // display_name: '事件ID', + // is_priority: false, + // description: '', + // }, + // { + // field_name: 'case_id', + // display_name: '事件ID', + // is_priority: false, + // description: '', + // }, + // ], + // }, + // }); + // }); + // return data; + } } export default new Strategy(); diff --git a/src/frontend/src/views/strategy-manage/list/components/aiops/event-log.vue b/src/frontend/src/views/strategy-manage/list/components/aiops/event-log.vue index 8a74b577..d2c059cd 100644 --- a/src/frontend/src/views/strategy-manage/list/components/aiops/event-log.vue +++ b/src/frontend/src/views/strategy-manage/list/components/aiops/event-log.vue @@ -90,7 +90,7 @@ import useRequest from '@hooks/use-request'; - import CollapsePanel from '@views/strategy-manage/strategy-create/components/aiops/components/components/collapse-panel.vue'; + import CollapsePanel from '@views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/collapse-panel.vue'; import RenderInfoBlock from '../render-info-block.vue'; import RenderInfoItem from '../render-info-item.vue'; diff --git a/src/frontend/src/views/strategy-manage/list/components/aiops/filter-condition.vue b/src/frontend/src/views/strategy-manage/list/components/aiops/filter-condition.vue index 20c88d98..d87861ce 100644 --- a/src/frontend/src/views/strategy-manage/list/components/aiops/filter-condition.vue +++ b/src/frontend/src/views/strategy-manage/list/components/aiops/filter-condition.vue @@ -67,7 +67,7 @@ import useRequest from '@hooks/use-request'; - import type { ConditionData } from '@views/strategy-manage/strategy-create/components/aiops/components/components/filter-condition.vue'; + import type { ConditionData } from '@views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/filter-condition.vue'; import RenderInfoBlock from '../render-info-block.vue'; import RenderInfoItem from '../render-info-item.vue'; diff --git a/src/frontend/src/views/strategy-manage/routes.ts b/src/frontend/src/views/strategy-manage/routes.ts index b7f5ae8c..e351d23d 100644 --- a/src/frontend/src/views/strategy-manage/routes.ts +++ b/src/frontend/src/views/strategy-manage/routes.ts @@ -63,7 +63,7 @@ export default { }, { path: 'upgrade/:strategyId/:controlId', - component: () => import('@views/strategy-manage/strategy-create/components/upgrade/index.vue'), + component: () => import('@views/strategy-manage/strategy-create/components/step1/components/upgrade/index.vue'), name: 'strategyUpgrade', meta: { title: '升级详情', diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/collapse-panel.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/collapse-panel.vue similarity index 98% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/collapse-panel.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/collapse-panel.vue index 6c4a81f7..cd9e83cd 100644 --- a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/collapse-panel.vue +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/collapse-panel.vue @@ -57,7 +57,7 @@ line-height: 40px; color: #313238; cursor: pointer; - background: #DCDEE5; + background: #dcdee5; } } diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/field-cascader.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/field-cascader.vue similarity index 100% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/field-cascader.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/field-cascader.vue diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/field-input.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/field-input.vue similarity index 100% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/field-input.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/field-input.vue diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/field-select.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/field-select.vue similarity index 100% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/field-select.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/field-select.vue diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/filter-condition.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/filter-condition.vue similarity index 100% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/filter-condition.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/filter-condition.vue diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/model-empty.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/model-empty.vue similarity index 100% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/model-empty.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/model-empty.vue diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/multiple-render-field.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/multiple-render-field.vue similarity index 100% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/multiple-render-field.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/multiple-render-field.vue diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/render-field.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/render-field.vue similarity index 100% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/render-field.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/render-field.vue diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/render-table.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/render-table.vue similarity index 97% rename from src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/render-table.vue rename to src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/render-table.vue index f65e35fa..775d8ad7 100644 --- a/src/frontend/src/views/strategy-manage/strategy-create/components/aiops/components/components/render-table.vue +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/components/aiops/components/components/render-table.vue @@ -19,7 +19,7 @@ class="table-panel" :is-active="isActive" :label="label" - style=" margin-bottom: 12px;background: #F0F1F5;"> + style=" margin-bottom: 12px;background: #f0f1f5;"> + + + + diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/event-info-table.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/event-info-table.vue new file mode 100644 index 00000000..938062cf --- /dev/null +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/event-info-table.vue @@ -0,0 +1,219 @@ + + + + diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/variable-table.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/variable-table.vue new file mode 100644 index 00000000..39457872 --- /dev/null +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/variable-table.vue @@ -0,0 +1,115 @@ + + + + diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/step2/index.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step2/index.vue new file mode 100644 index 00000000..a1bf03e6 --- /dev/null +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step2/index.vue @@ -0,0 +1,171 @@ + + + + diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/step3/index.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step3/index.vue new file mode 100644 index 00000000..9759a2b8 --- /dev/null +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step3/index.vue @@ -0,0 +1,279 @@ + + + + diff --git a/src/frontend/src/views/strategy-manage/strategy-create/index.vue b/src/frontend/src/views/strategy-manage/strategy-create/index.vue index 55c3213a..6f41b3a4 100644 --- a/src/frontend/src/views/strategy-manage/strategy-create/index.vue +++ b/src/frontend/src/views/strategy-manage/strategy-create/index.vue @@ -15,286 +15,55 @@ to the current version of the project delivered to anyone in the future. --> - - - From 414eeb43fe8e2356cda7b344a018f14c77a7bad3 Mon Sep 17 00:00:00 2001 From: nanasikeai Date: Tue, 6 Aug 2024 21:23:02 +0800 Subject: [PATCH 07/64] =?UTF-8?q?feat:=20=E5=8F=98=E9=87=8F=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E6=B7=BB=E5=8A=A0=E5=89=8D=E7=BC=80=20--story=3D11817?= =?UTF-8?q?9847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/strategy/strategy-field-event.ts | 7 ++++-- .../src/domain/model/strategy/strategy.ts | 14 ++++------- .../step2/components/variable-table.vue | 25 ++++++++----------- .../components/step2/index.vue | 14 +++-------- .../strategy-manage/strategy-create/index.vue | 14 +++-------- 5 files changed, 28 insertions(+), 46 deletions(-) diff --git a/src/frontend/src/domain/model/strategy/strategy-field-event.ts b/src/frontend/src/domain/model/strategy/strategy-field-event.ts index f94ab9ed..9836a8d6 100644 --- a/src/frontend/src/domain/model/strategy/strategy-field-event.ts +++ b/src/frontend/src/domain/model/strategy/strategy-field-event.ts @@ -14,12 +14,12 @@ We undertake not to change the open source license (MIT license) applicable to the current version of the project delivered to anyone in the future. */ -type EventItem = { +export type EventItem = { field_name: string, description: string display_name: string; is_priority: boolean; - group?: string; + prefix: string; example?: string } @@ -36,6 +36,7 @@ export default class StrategyField { is_priority: item.is_priority || false, description: item.description, example: item.example, + prefix: '', }))) || []; this.event_data_field_configs = (payload.event_data_field_configs && payload.event_data_field_configs.map(item => ({ @@ -44,6 +45,7 @@ export default class StrategyField { is_priority: item.is_priority || false, description: item.description, example: item.example, + prefix: 'event_data.', }))) || []; this.event_evidence_field_configs = (payload.event_evidence_field_configs && payload.event_evidence_field_configs.map(item => ({ @@ -52,6 +54,7 @@ export default class StrategyField { is_priority: item.is_priority || false, description: item.description, example: item.example, + prefix: 'event_evidence.', }))) || []; } } diff --git a/src/frontend/src/domain/model/strategy/strategy.ts b/src/frontend/src/domain/model/strategy/strategy.ts index 05bccd16..81d889f4 100644 --- a/src/frontend/src/domain/model/strategy/strategy.ts +++ b/src/frontend/src/domain/model/strategy/strategy.ts @@ -15,12 +15,8 @@ to the current version of the project delivered to anyone in the future. */ -interface Variable { - field_name: string, - description: string - display_name: string; - is_priority: boolean; -} +import type { EventItem } from '@model/strategy/strategy-field-event'; + export default class Strategy { strategy_id: number; strategy_name: string; @@ -69,9 +65,9 @@ export default class Strategy { risk_hazard: string; risk_guidance: string; risk_title: string; - event_evidence_field_configs: Array; - event_data_field_configs: Array; - event_basic_field_configs: Array; + event_evidence_field_configs: Array; + event_data_field_configs: Array; + event_basic_field_configs: Array; processor_groups: Array; constructor(payload = {} as Strategy) { this.strategy_id = payload.strategy_id; diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/variable-table.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/variable-table.vue index 39457872..37d5ec77 100644 --- a/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/variable-table.vue +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step2/components/variable-table.vue @@ -31,7 +31,7 @@ import StrategyManageService from '@service/strategy-manage'; - import StrategyFieldEvent from '@model/strategy/strategy-field-event'; + import StrategyFieldEvent, { type EventItem } from '@model/strategy/strategy-field-event'; import { execCopy, @@ -39,13 +39,6 @@ import useRequest from '@/hooks/use-request'; - interface Variable { - field_name: string, - description: string - display_name: string; - is_priority: boolean; - } - interface Props { strategyId: number } @@ -57,10 +50,10 @@ const tableColumn = ref([ { label: () => t('变量名称'), - render: ({ data }: {data: any}) =>
handleVariableCopy(data.field_name)}> - { data.field_name } + onClick={() => handleVariableCopy(data.prefix, data.field_name)}> + { `{{${data.field_name}}}` }
, }, { @@ -69,12 +62,14 @@ }, { label: () => t('实例'), - field: () => 'example', + render: ({ data }: {data: any}) =>
+ {data.example || '--'} +
, width: 160, }, ]); - const variableData = ref>([]); + const variableData = ref>([]); useRequest(StrategyManageService.fetchStrategyEvent, { defaultValue: new StrategyFieldEvent(), @@ -91,8 +86,8 @@ manual: true, }); - const handleVariableCopy = (value: string) => { - execCopy(`\${${value}}`, t(`变量${value}复制成功`)); + const handleVariableCopy = (prefix: string, value: string) => { + execCopy(`\${{${prefix}${value}}}`, t(`变量${value}复制成功`)); }; diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/step3/index.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step3/index.vue index 9759a2b8..f4762e11 100644 --- a/src/frontend/src/views/strategy-manage/strategy-create/components/step3/index.vue +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step3/index.vue @@ -159,12 +159,12 @@ @@ -128,6 +133,7 @@ interface Emits { (e: 'previousStep', step: number): void; (e: 'nextStep', step: number, params: IFormData): void; + (e: 'showPreview'): void; } interface Props { data: StrategyModel @@ -160,6 +166,13 @@ ], }; + const handlePreview = () => { + // 预览前更新一次formData,用于查看重点信息 + const params: IFormData = Object.assign({}, formData.value, eventRef.value.getData()); + emits('nextStep', 2, params); + emits('showPreview'); + }; + const handlePrevious = () => { emits('previousStep', 1); }; diff --git a/src/frontend/src/views/strategy-manage/strategy-create/index.vue b/src/frontend/src/views/strategy-manage/strategy-create/index.vue index 4b5ab32b..c68c22ab 100644 --- a/src/frontend/src/views/strategy-manage/strategy-create/index.vue +++ b/src/frontend/src/views/strategy-manage/strategy-create/index.vue @@ -33,8 +33,20 @@ @fetch-edit-data="handleEdit" @next-step="(step: any, params: any) => handleNextStep(step, params)" @previous-step="(step: any) => currentStep = step" + @show-preview="showPreview = true" @submit="handleSubmit" /> + + +
+ +
+
diff --git a/src/frontend/src/views/strategy-manage/list/components/detail.vue b/src/frontend/src/views/strategy-manage/list/components/detail.vue index fb842f8a..b0849367 100644 --- a/src/frontend/src/views/strategy-manage/list/components/detail.vue +++ b/src/frontend/src/views/strategy-manage/list/components/detail.vue @@ -16,148 +16,69 @@ --> - diff --git a/src/frontend/src/views/strategy-manage/list/components/risk-detection.vue b/src/frontend/src/views/strategy-manage/list/components/risk-detection.vue new file mode 100644 index 00000000..591c62a9 --- /dev/null +++ b/src/frontend/src/views/strategy-manage/list/components/risk-detection.vue @@ -0,0 +1,153 @@ + + + diff --git a/src/frontend/src/views/strategy-manage/list/components/risk-display.vue b/src/frontend/src/views/strategy-manage/list/components/risk-display.vue new file mode 100644 index 00000000..795614ec --- /dev/null +++ b/src/frontend/src/views/strategy-manage/list/components/risk-display.vue @@ -0,0 +1,204 @@ + + + + diff --git a/src/frontend/src/views/strategy-manage/list/components/risk-other.vue b/src/frontend/src/views/strategy-manage/list/components/risk-other.vue new file mode 100644 index 00000000..0ec07ec2 --- /dev/null +++ b/src/frontend/src/views/strategy-manage/list/components/risk-other.vue @@ -0,0 +1,105 @@ + + + + diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/preview/components/base-info.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/preview/components/base-info.vue index 4980492c..cb39382d 100644 --- a/src/frontend/src/views/strategy-manage/strategy-create/components/preview/components/base-info.vue +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/preview/components/base-info.vue @@ -55,13 +55,16 @@ :label="t('风险等级')" :label-width="labelWidth"> {{ riskLevelMap[data.risk_level].label }} + {{ t('以实际内容为准') }} {{ data.risk_hazard }} @@ -171,13 +174,16 @@ :label="t('风险等级')" :label-width="labelWidth"> {{ riskLevelMap[data.risk_level].label }} + {{ t('以实际内容为准') }} {{ data.risk_hazard }} From ce872e1e001a726f2d7fcb27fc6ad7b2d4e58c35 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Fri, 9 Aug 2024 13:10:50 +0800 Subject: [PATCH 17/64] =?UTF-8?q?refactor:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 优化权限判断 2. 增加更新策略风险等级字段 --- src/backend/services/web/risk/permissions.py | 25 ++++++++++++------- .../services/web/strategy_v2/resources.py | 17 ++++++------- .../services/web/strategy_v2/serializers.py | 2 ++ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/backend/services/web/risk/permissions.py b/src/backend/services/web/risk/permissions.py index b6d853dc..87668ef4 100644 --- a/src/backend/services/web/risk/permissions.py +++ b/src/backend/services/web/risk/permissions.py @@ -15,10 +15,11 @@ We undertake not to change the open source license (MIT license) applicable to the current version of the project delivered to anyone in the future. """ +from typing import List from django.shortcuts import get_object_or_404 -from apps.permission.handlers.actions import ActionEnum +from apps.permission.handlers.actions import ActionEnum, ActionMeta from apps.permission.handlers.drf import IAMPermission, InstanceActionPermission from apps.permission.handlers.resource_types import ResourceEnum from services.web.risk.models import Risk, TicketPermission @@ -29,16 +30,22 @@ class RiskViewPermission(InstanceActionPermission): 风险查看权限 """ - def has_permission(self, request, view): - risk_id = view.kwargs.get(self.lookup_field) or self.get_instance_id() - if all( + @classmethod + def has_risk_permission(cls, risk_id: str, actions: List[ActionMeta], operator: str) -> bool: + """ + 风险查看权限 + """ + + return all( [ - TicketPermission.objects.filter( - risk_id=risk_id, action=action.id, operator=request.user.username - ).exists() - for action in self.actions + TicketPermission.objects.filter(risk_id=risk_id, action=action.id, operator=operator).exists() + for action in actions ] - ): + ) + + def has_permission(self, request, view): + risk_id = view.kwargs.get(self.lookup_field) or self.get_instance_id() + if self.has_risk_permission(risk_id, self.actions, request.user.username): return True return super().has_permission(request, view) diff --git a/src/backend/services/web/strategy_v2/resources.py b/src/backend/services/web/strategy_v2/resources.py index a5cbc061..4996e60f 100644 --- a/src/backend/services/web/strategy_v2/resources.py +++ b/src/backend/services/web/strategy_v2/resources.py @@ -60,7 +60,8 @@ from services.web.analyze.models import Control from services.web.analyze.tasks import call_controller from services.web.risk.constants import EVENT_BASIC_EXCLUDE_FIELDS, EventMappingFields -from services.web.risk.models import Risk, TicketPermission +from services.web.risk.models import Risk +from services.web.risk.permissions import RiskViewPermission from services.web.strategy_v2.constants import ( HAS_UPDATE_TAG_ID, HAS_UPDATE_TAG_NAME, @@ -203,6 +204,9 @@ def update_db(self, strategy: Strategy, validated_request_data: dict) -> bool: # check control if strategy.control_id != validated_request_data["control_id"]: raise ControlChangeError() + # check risk_level + if strategy.risk_level is not None: + validated_request_data.pop("risk_level") # save strategy for key, val in validated_request_data.items(): inst_val = getattr(strategy, key, Empty()) @@ -650,14 +654,9 @@ def perform_request(self, validated_request_data): strategy_id = validated_request_data.get("strategy_id") risk: Optional[Risk] = Risk.objects.filter(strategy_id=strategy_id).order_by("-event_time").first() # 权限认证 - has_permission = False - if ( - risk - and TicketPermission.objects.filter( - risk_id=risk.risk_id, action=ActionEnum.LIST_RISK.id, operator=get_request_username() - ).exists() - ): - has_permission = True + has_permission = RiskViewPermission.has_risk_permission( + risk_id=risk.risk_id, actions=[ActionEnum.LIST_RISK], operator=get_request_username() + ) return { "event_basic_field_configs": self.get_event_basic_field_configs(risk, has_permission), "event_data_field_configs": self.get_event_data_field_configs(risk, has_permission), diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index eaf41c61..ea8267c3 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -142,6 +142,7 @@ class UpdateStrategyRequestSerializer(serializers.ModelSerializer): child=serializers.IntegerField(label=gettext_lazy("Processor Group")), allow_empty=False, ) + risk_level = serializers.ChoiceField(label=gettext_lazy("Risk Level"), choices=RiskLevel.choices) class Meta: model = Strategy @@ -155,6 +156,7 @@ class Meta: "tags", "notice_groups", "description", + "risk_level", "risk_hazard", "risk_guidance", "risk_title", From f30ce9a1a481a15c129805c8ad8d206e093afdd7 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Fri, 9 Aug 2024 15:26:37 +0800 Subject: [PATCH 18/64] =?UTF-8?q?refactor:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 优化权限判断 --- src/backend/services/web/risk/permissions.py | 21 +++++++++++++------ .../services/web/strategy_v2/resources.py | 13 ++++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/backend/services/web/risk/permissions.py b/src/backend/services/web/risk/permissions.py index 87668ef4..0cbea423 100644 --- a/src/backend/services/web/risk/permissions.py +++ b/src/backend/services/web/risk/permissions.py @@ -15,11 +15,10 @@ We undertake not to change the open source license (MIT license) applicable to the current version of the project delivered to anyone in the future. """ -from typing import List from django.shortcuts import get_object_or_404 -from apps.permission.handlers.actions import ActionEnum, ActionMeta +from apps.permission.handlers.actions import ActionEnum from apps.permission.handlers.drf import IAMPermission, InstanceActionPermission from apps.permission.handlers.resource_types import ResourceEnum from services.web.risk.models import Risk, TicketPermission @@ -30,22 +29,32 @@ class RiskViewPermission(InstanceActionPermission): 风险查看权限 """ - @classmethod - def has_risk_permission(cls, risk_id: str, actions: List[ActionMeta], operator: str) -> bool: + def has_risk_permission(self, risk_id: str, operator: str) -> bool: """ 风险查看权限 """ + if self._has_risk_permission(risk_id, operator): + return True + resource = self.resource_meta.create_instance(risk_id) + self.resources = [resource] + return IAMPermission.has_permission(self, None, None) + + def _has_risk_permission(self, risk_id: str, operator: str) -> bool: + """ + 校验风险权限 + """ + return all( [ TicketPermission.objects.filter(risk_id=risk_id, action=action.id, operator=operator).exists() - for action in actions + for action in self.actions ] ) def has_permission(self, request, view): risk_id = view.kwargs.get(self.lookup_field) or self.get_instance_id() - if self.has_risk_permission(risk_id, self.actions, request.user.username): + if self._has_risk_permission(risk_id, request.user.username): return True return super().has_permission(request, view) diff --git a/src/backend/services/web/strategy_v2/resources.py b/src/backend/services/web/strategy_v2/resources.py index 4996e60f..ffd2d3f4 100644 --- a/src/backend/services/web/strategy_v2/resources.py +++ b/src/backend/services/web/strategy_v2/resources.py @@ -49,6 +49,7 @@ from apps.permission.handlers.drf import ActionPermission from apps.permission.handlers.permission import Permission from apps.permission.handlers.resource_types import ResourceEnum +from core.exceptions import PermissionException from core.utils.tools import choices_to_dict from services.web.analyze.constants import ( ControlTypeChoices, @@ -653,10 +654,14 @@ def perform_request(self, validated_request_data): # 无权限: 字段样例为空 strategy_id = validated_request_data.get("strategy_id") risk: Optional[Risk] = Risk.objects.filter(strategy_id=strategy_id).order_by("-event_time").first() - # 权限认证 - has_permission = RiskViewPermission.has_risk_permission( - risk_id=risk.risk_id, actions=[ActionEnum.LIST_RISK], operator=get_request_username() - ) + has_permission = False + if risk: + # 权限认证 + permission = RiskViewPermission(actions=[ActionEnum.LIST_RISK], resource_meta=ResourceEnum.RISK) + try: + has_permission = permission.has_risk_permission(risk_id=risk.risk_id, operator=get_request_username()) + except PermissionException: + pass return { "event_basic_field_configs": self.get_event_basic_field_configs(risk, has_permission), "event_data_field_configs": self.get_event_data_field_configs(risk, has_permission), From 23ce80f853d55198a3181758f0f11faf26fc9fa0 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Fri, 9 Aug 2024 15:36:11 +0800 Subject: [PATCH 19/64] =?UTF-8?q?refactor:=20=E7=AD=96=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=8C=96=20--story=3D118179847?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 优化权限判断 --- src/backend/services/web/risk/permissions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/services/web/risk/permissions.py b/src/backend/services/web/risk/permissions.py index 0cbea423..21b4cc56 100644 --- a/src/backend/services/web/risk/permissions.py +++ b/src/backend/services/web/risk/permissions.py @@ -31,18 +31,18 @@ class RiskViewPermission(InstanceActionPermission): def has_risk_permission(self, risk_id: str, operator: str) -> bool: """ - 风险查看权限 + 校验风险权限 """ - if self._has_risk_permission(risk_id, operator): + if self.has_risk_local_permission(risk_id, operator): return True resource = self.resource_meta.create_instance(risk_id) self.resources = [resource] return IAMPermission.has_permission(self, None, None) - def _has_risk_permission(self, risk_id: str, operator: str) -> bool: + def has_risk_local_permission(self, risk_id: str, operator: str) -> bool: """ - 校验风险权限 + 校验本地风险权限 """ return all( @@ -54,7 +54,7 @@ def _has_risk_permission(self, risk_id: str, operator: str) -> bool: def has_permission(self, request, view): risk_id = view.kwargs.get(self.lookup_field) or self.get_instance_id() - if self._has_risk_permission(risk_id, request.user.username): + if self.has_risk_local_permission(risk_id, request.user.username): return True return super().has_permission(request, view) From f9b6de17655c03e8f051a96b6d199d3ec641b769 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Mon, 5 Aug 2024 22:48:14 +0800 Subject: [PATCH 20/64] =?UTF-8?q?feat:=20=E9=A3=8E=E9=99=A9=E5=8D=95?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA=E4=BA=8C=E6=9C=9F=20--stor?= =?UTF-8?q?y=3D118188959?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 优化模板渲染器 2. 风险标题生成 3. 风险详情新增:风险等级、风险危害、处理指引 --- src/backend/core/render.py | 39 ++++++++++--------- .../services/web/risk/handlers/risk.py | 24 +++++++----- src/backend/services/web/risk/render.py | 12 ++++++ src/backend/tests/core/test_render.py | 10 ++--- 4 files changed, 52 insertions(+), 33 deletions(-) create mode 100644 src/backend/services/web/risk/render.py diff --git a/src/backend/core/render.py b/src/backend/core/render.py index 1942319c..1c5ff76a 100644 --- a/src/backend/core/render.py +++ b/src/backend/core/render.py @@ -8,9 +8,9 @@ from jinja2 import Environment -def jinja2_environment(**options: Dict) -> Environment: +def jinja2_environment(*args, **kwargs) -> Environment: """创建jinja2的环境执行环境 .""" - env = Environment(extensions=["jinja2.ext.i18n"], **options) + env = Environment(extensions=["jinja2.ext.i18n"], *args, **kwargs) env.install_gettext_translations(translation, newstyle=True) return env @@ -20,23 +20,24 @@ class Jinja2Renderer: Jinja2渲染器 """ - @staticmethod - def render(template_value: str, context: dict) -> str: + def __init__(self, *args, **kwargs): + self.env = jinja2_environment(*args, **kwargs) + + def _render(self, template_value: str, context: dict) -> str: """ 只支持json和re函数 """ - return jinja2_environment().from_string(template_value).render({"json": json, "re": re, **context}) - - -def jinja_render(template_value: Union[str, dict, list], context: dict) -> Union[str, Dict, List]: - """使用jinja渲染对象 .""" - if isinstance(template_value, str): - return Jinja2Renderer.render(template_value, context) or template_value - if isinstance(template_value, dict): - render_value = {} - for key, value in template_value.items(): - render_value[key] = jinja_render(value, context) - return render_value - if isinstance(template_value, list): - return [jinja_render(value, context) for value in template_value] - return template_value + return self.env.from_string(template_value).render({"json": json, "re": re, **context}) + + def jinja_render(self, template_value: Union[str, dict, list], context: dict) -> Union[str, Dict, List]: + """使用jinja渲染对象 .""" + if isinstance(template_value, str): + return self._render(template_value, context) or template_value + if isinstance(template_value, dict): + render_value = {} + for key, value in template_value.items(): + render_value[key] = self.jinja_render(value, context) + return render_value + if isinstance(template_value, list): + return [self.jinja_render(value, context) for value in template_value] + return template_value diff --git a/src/backend/services/web/risk/handlers/risk.py b/src/backend/services/web/risk/handlers/risk.py index aded0f9b..88605c17 100644 --- a/src/backend/services/web/risk/handlers/risk.py +++ b/src/backend/services/web/risk/handlers/risk.py @@ -28,13 +28,14 @@ from django.db.models import Q, QuerySet from django.utils import timezone from django.utils.translation import gettext +from jinja2.exceptions import UndefinedError from rest_framework.settings import api_settings from apps.meta.models import GlobalMetaConfig from apps.notice.constants import RelateType from apps.notice.handlers import ErrorMsgHandler from apps.notice.models import NoticeGroup -from core.render import jinja_render +from core.render import Jinja2Renderer from services.web.risk.constants import ( EVENT_DATA_SORT_FIELD, EVENT_TYPE_SPLIT_REGEX, @@ -44,6 +45,7 @@ ) from services.web.risk.handlers import EventHandler from services.web.risk.models import Risk +from services.web.risk.render import RiskTitleUndefined from services.web.risk.serializers import CreateRiskSerializer, RiskInfoSerializer from services.web.strategy_v2.models import Strategy, StrategyTag @@ -110,15 +112,19 @@ def render_risk_title(self, risk: Risk) -> Optional[str]: if not strategy or not strategy.risk_title: return risk_content = RiskInfoSerializer(risk).data - event_evidence = {} if risk_content.get("event_evidence"): - # 事件证据为字符串需要转换成列表,并取第一条字典数据 - try: - event_evidence = json.loads(risk_content["event_evidence"])[0] - except json.JSONDecodeError: - pass - risk_content["event_evidence"] = event_evidence - return jinja_render(strategy.risk_title, risk_content) + risk_content["event_evidence"] = json.loads(risk_content["event_evidence"])[0] + try: + risk_title = Jinja2Renderer(undefined=RiskTitleUndefined).jinja_render(strategy.risk_title, risk_content) + return risk_title + except UndefinedError as err: + logger.exception( + "[RenderRiskTitleFailed] risk_title: %s; risk_content: %s; err: %s", + strategy.risk_title, + risk_content, + err, + ) + return strategy.risk_title def create_risk(self, event: dict) -> (bool, Risk): """ diff --git a/src/backend/services/web/risk/render.py b/src/backend/services/web/risk/render.py new file mode 100644 index 00000000..f03d9a94 --- /dev/null +++ b/src/backend/services/web/risk/render.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from django.utils.translation import gettext +from jinja2 import Undefined + + +class RiskTitleUndefined(Undefined): + def __str__(self) -> str: + """ + 未定义字段返回指定字符串 + """ + + return gettext("(未获取到变量值:%s)") % self._undefined_name diff --git a/src/backend/tests/core/test_render.py b/src/backend/tests/core/test_render.py index 8559e354..4634cf20 100644 --- a/src/backend/tests/core/test_render.py +++ b/src/backend/tests/core/test_render.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - -from core.render import jinja_render +from core.render import Jinja2Renderer +from services.web.risk.render import RiskTitleUndefined def test_jinja_render(): - template_value = "render: {{content.title}} {{content.content}} {{title}}" + template_value = "render: {{content.title}} {{content.content}} {{title}} {{test}}" context = {"content": {"title": "title", "content": "content"}, "title": "title"} - actual = jinja_render(template_value, context) - assert actual == "render: title content title" + actual = Jinja2Renderer(undefined=RiskTitleUndefined).jinja_render(template_value, context) + assert actual == "render: title content title (未获取到变量值:test)" From aa4c4a7329986388bc17d2e06427d227a55cfc66 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Wed, 7 Aug 2024 11:08:12 +0800 Subject: [PATCH 21/64] =?UTF-8?q?feat:=20=E9=A3=8E=E9=99=A9=E5=8D=95?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA=E4=BA=8C=E6=9C=9F=20--stor?= =?UTF-8?q?y=3D118188959?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增风险策略详情接口:风险等级、风险危害、处理指引 --- .../services/web/risk/resources/risk.py | 22 +++++++++++++++++++ src/backend/services/web/risk/serializers.py | 20 +++++++++++++++++ src/backend/services/web/risk/views.py | 3 ++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/backend/services/web/risk/resources/risk.py b/src/backend/services/web/risk/resources/risk.py index a9cb3be8..17adf4ba 100644 --- a/src/backend/services/web/risk/resources/risk.py +++ b/src/backend/services/web/risk/resources/risk.py @@ -74,6 +74,7 @@ ListRiskRequestSerializer, ListRiskResponseSerializer, ReopenRiskReqSerializer, + RetrieveRiskStrategyInfoResponseSerializer, RetryAutoProcessReqSerializer, RiskInfoSerializer, TicketNodeSerializer, @@ -104,6 +105,27 @@ def perform_request(self, validated_request_data): return risk +class RetrieveRiskStrategyInfo(RiskMeta): + name = gettext_lazy("获取风险策略信息") + audit_action = ActionEnum.LIST_RISK + ResponseSerializer = RetrieveRiskStrategyInfoResponseSerializer + + def perform_request(self, validated_request_data): + risk: Risk = get_object_or_404(Risk, risk_id=validated_request_data["risk_id"]) + self.add_audit_instance_to_context(instance=RiskAuditInstance(risk)) + strategy: Strategy = Strategy.objects.filter(strategy_id=risk.strategy_id).first() + if not strategy: + return {} + return { + "risk_level": strategy.risk_level, + "risk_hazard": strategy.risk_hazard, + "risk_guidance": strategy.risk_guidance, + "event_basic_field_configs": strategy.event_basic_field_configs, + "event_data_field_configs": strategy.event_data_field_configs, + "event_evidence_field_configs": strategy.event_evidence_field_configs, + } + + class RetrieveRiskAPIGW(RetrieveRisk): audit_action = None diff --git a/src/backend/services/web/risk/serializers.py b/src/backend/services/web/risk/serializers.py index 3a25dba0..150bf1ea 100644 --- a/src/backend/services/web/risk/serializers.py +++ b/src/backend/services/web/risk/serializers.py @@ -36,6 +36,8 @@ RiskRule, TicketNode, ) +from services.web.strategy_v2.constants import RiskLevel +from services.web.strategy_v2.serializers import EventFieldSerializer class CreateEventSerializer(serializers.Serializer): @@ -543,3 +545,21 @@ class GetRiskFieldsByStrategyResponseSerializer(serializers.Serializer): key = serializers.CharField() name = serializers.CharField() unique = serializers.BooleanField(default=False) + + +class RetrieveRiskStrategyInfoResponseSerializer(serializers.Serializer): + risk_level = serializers.ChoiceField( + label=gettext_lazy("Risk Level"), choices=RiskLevel.choices, required=False, allow_null=True + ) + risk_hazard = serializers.CharField(label=gettext_lazy("Risk Hazard"), required=False, allow_null=True) + risk_guidance = serializers.CharField(label=gettext_lazy("Risk Guidance"), required=False, allow_null=True) + + event_basic_field_configs = serializers.ListField( + label=gettext_lazy("Event Basic Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) + event_data_field_configs = serializers.ListField( + label=gettext_lazy("Event Data Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) + event_evidence_field_configs = serializers.ListField( + label=gettext_lazy("Event Evidence Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + ) diff --git a/src/backend/services/web/risk/views.py b/src/backend/services/web/risk/views.py index 15fc4a9a..da24c97f 100644 --- a/src/backend/services/web/risk/views.py +++ b/src/backend/services/web/risk/views.py @@ -84,7 +84,7 @@ def get_permissions(self): "process_risk_ticket", ]: return [RiskTicketPermission()] - if self.action in ["retrieve"]: + if self.action in ["retrieve", "strategy_info"]: return [ RiskViewPermission(actions=[ActionEnum.LIST_RISK], resource_meta=ResourceEnum.RISK, lookup_field="pk") ] @@ -92,6 +92,7 @@ def get_permissions(self): resource_routes = [ ResourceRoute("GET", resource.risk.retrieve_risk, pk_field="risk_id"), + ResourceRoute("GET", resource.risk.retrieve_risk_strategy_info, pk_field="risk_id", endpoint="strategy_info"), ResourceRoute( "GET", resource.risk.list_risk, From cb0f70f153bc6b9c01daf15fc9008f6866fb5b86 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Fri, 9 Aug 2024 16:19:23 +0800 Subject: [PATCH 22/64] =?UTF-8?q?feat:=20=E9=A3=8E=E9=99=A9=E5=8D=95?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA=E4=BA=8C=E6=9C=9F=20--stor?= =?UTF-8?q?y=3D118188959?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 优化代码逻辑 --- .../services/web/risk/handlers/risk.py | 8 +++-- .../services/web/risk/resources/risk.py | 16 ++-------- src/backend/services/web/risk/serializers.py | 30 ++++++++++++------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/backend/services/web/risk/handlers/risk.py b/src/backend/services/web/risk/handlers/risk.py index 88605c17..e3a592c8 100644 --- a/src/backend/services/web/risk/handlers/risk.py +++ b/src/backend/services/web/risk/handlers/risk.py @@ -112,8 +112,12 @@ def render_risk_title(self, risk: Risk) -> Optional[str]: if not strategy or not strategy.risk_title: return risk_content = RiskInfoSerializer(risk).data - if risk_content.get("event_evidence"): - risk_content["event_evidence"] = json.loads(risk_content["event_evidence"])[0] + # 事件证据为字符串需要转换成列表,并取第一条字典数据 + try: + event_evidence = json.loads(risk.event_evidence)[0] + except (json.JSONDecodeError, IndexError, KeyError): + event_evidence = {} + risk_content["event_evidence"] = event_evidence try: risk_title = Jinja2Renderer(undefined=RiskTitleUndefined).jinja_render(strategy.risk_title, risk_content) return risk_title diff --git a/src/backend/services/web/risk/resources/risk.py b/src/backend/services/web/risk/resources/risk.py index 17adf4ba..643b0047 100644 --- a/src/backend/services/web/risk/resources/risk.py +++ b/src/backend/services/web/risk/resources/risk.py @@ -81,6 +81,7 @@ UpdateRiskLabelReqSerializer, ) from services.web.risk.tasks import process_one_risk, sync_auto_result +from services.web.strategy_v2.models import Strategy class RiskMeta(AuditMixinResource, abc.ABC): @@ -107,23 +108,12 @@ def perform_request(self, validated_request_data): class RetrieveRiskStrategyInfo(RiskMeta): name = gettext_lazy("获取风险策略信息") - audit_action = ActionEnum.LIST_RISK ResponseSerializer = RetrieveRiskStrategyInfoResponseSerializer def perform_request(self, validated_request_data): risk: Risk = get_object_or_404(Risk, risk_id=validated_request_data["risk_id"]) - self.add_audit_instance_to_context(instance=RiskAuditInstance(risk)) - strategy: Strategy = Strategy.objects.filter(strategy_id=risk.strategy_id).first() - if not strategy: - return {} - return { - "risk_level": strategy.risk_level, - "risk_hazard": strategy.risk_hazard, - "risk_guidance": strategy.risk_guidance, - "event_basic_field_configs": strategy.event_basic_field_configs, - "event_data_field_configs": strategy.event_data_field_configs, - "event_evidence_field_configs": strategy.event_evidence_field_configs, - } + strategy = Strategy.objects.filter(strategy_id=risk.strategy_id).first() + return strategy or {} class RetrieveRiskAPIGW(RetrieveRisk): diff --git a/src/backend/services/web/risk/serializers.py b/src/backend/services/web/risk/serializers.py index 150bf1ea..63e52d02 100644 --- a/src/backend/services/web/risk/serializers.py +++ b/src/backend/services/web/risk/serializers.py @@ -36,7 +36,7 @@ RiskRule, TicketNode, ) -from services.web.strategy_v2.constants import RiskLevel +from services.web.strategy_v2.models import Strategy from services.web.strategy_v2.serializers import EventFieldSerializer @@ -547,19 +547,27 @@ class GetRiskFieldsByStrategyResponseSerializer(serializers.Serializer): unique = serializers.BooleanField(default=False) -class RetrieveRiskStrategyInfoResponseSerializer(serializers.Serializer): - risk_level = serializers.ChoiceField( - label=gettext_lazy("Risk Level"), choices=RiskLevel.choices, required=False, allow_null=True - ) - risk_hazard = serializers.CharField(label=gettext_lazy("Risk Hazard"), required=False, allow_null=True) - risk_guidance = serializers.CharField(label=gettext_lazy("Risk Guidance"), required=False, allow_null=True) - +class RetrieveRiskStrategyInfoResponseSerializer(serializers.ModelSerializer): event_basic_field_configs = serializers.ListField( - label=gettext_lazy("Event Basic Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + label=gettext_lazy("Event Basic Field Configs"), child=EventFieldSerializer(), required=False, allow_empty=True ) event_data_field_configs = serializers.ListField( - label=gettext_lazy("Event Data Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + label=gettext_lazy("Event Data Field Configs"), child=EventFieldSerializer(), required=False, allow_empty=True ) event_evidence_field_configs = serializers.ListField( - label=gettext_lazy("Event Evidence Field Configs"), child=EventFieldSerializer(), default=list, allow_empty=True + label=gettext_lazy("Event Evidence Field Configs"), + child=EventFieldSerializer(), + required=False, + allow_empty=True, ) + + class Meta: + model = Strategy + fields = [ + "risk_level", + "risk_hazard", + "risk_guidance", + "event_basic_field_configs", + "event_data_field_configs", + "event_evidence_field_configs", + ] From 3257165b8457b6c13de474d6b97de0f918704312 Mon Sep 17 00:00:00 2001 From: nanasikeai Date: Mon, 12 Aug 2024 10:13:11 +0800 Subject: [PATCH 23/64] =?UTF-8?q?feat:=20=E9=A3=8E=E9=99=A9=E5=8D=95?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA=E4=BA=8C=E6=9C=9F=20--stor?= =?UTF-8?q?y=3D118188959?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/domain/model/risk/risk.ts | 2 +- .../src/domain/model/risk/strategy-info.ts | 38 +++++++ .../src/domain/service/risk-manage.ts | 11 ++- .../src/domain/service/strategy-manage.ts | 10 ++ src/frontend/src/domain/source/risk-manage.ts | 7 ++ .../src/domain/source/strategy-manage.ts | 99 +++---------------- .../detail/components/base-info.vue | 98 +++++++++++++++++- .../detail/components/link-event.vue | 69 ++++++++++++- .../src/views/risk-manage/detail/index.vue | 26 ++++- .../list/components/risk-display.vue | 7 +- .../components/step1/index.vue | 2 +- 11 files changed, 267 insertions(+), 102 deletions(-) create mode 100644 src/frontend/src/domain/model/risk/strategy-info.ts diff --git a/src/frontend/src/domain/model/risk/risk.ts b/src/frontend/src/domain/model/risk/risk.ts index cbadfa98..8abda219 100644 --- a/src/frontend/src/domain/model/risk/risk.ts +++ b/src/frontend/src/domain/model/risk/risk.ts @@ -71,7 +71,7 @@ export default class Event { new_operators: string[], description: string, // 误报说明 - custom_action :string, // CustomProcess + custom_action: string, // CustomProcess pa_id: string, // 套餐id pa_params: Record// 套餐参数 diff --git a/src/frontend/src/domain/model/risk/strategy-info.ts b/src/frontend/src/domain/model/risk/strategy-info.ts new file mode 100644 index 00000000..4ceafe9f --- /dev/null +++ b/src/frontend/src/domain/model/risk/strategy-info.ts @@ -0,0 +1,38 @@ +/* + TencentBlueKing is pleased to support the open source community by making + 蓝鲸智云 - 审计中心 (BlueKing - Audit Center) available. + Copyright (C) 2023 THL A29 Limited, + a Tencent company. All rights reserved. + Licensed under the MIT License (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at http://opensource.org/licenses/MIT + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on + 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. + We undertake not to change the open source license (MIT license) applicable + to the current version of the project delivered to anyone in the future. +*/ + +import type { EventItem } from '../strategy/strategy-field-event'; + +export default class StrategyInfo { + risk_title: string; + risk_level: string; + risk_guidance: string; + risk_hazard: string; + event_evidence_field_configs: Array; + event_data_field_configs: Array; + event_basic_field_configs: Array; + + constructor(payload = {} as StrategyInfo) { + this.risk_title = payload.risk_title; + this.risk_level = payload.risk_level; + this.risk_guidance = payload.risk_guidance; + this.risk_hazard = payload.risk_hazard; + this.event_evidence_field_configs = payload.event_evidence_field_configs; + this.event_data_field_configs = payload.event_data_field_configs; + this.event_basic_field_configs = payload.event_basic_field_configs; + } +} diff --git a/src/frontend/src/domain/service/risk-manage.ts b/src/frontend/src/domain/service/risk-manage.ts index cedfe699..b0f8836e 100644 --- a/src/frontend/src/domain/service/risk-manage.ts +++ b/src/frontend/src/domain/service/risk-manage.ts @@ -77,6 +77,15 @@ export default { return RiskManageSource.getRiskById(params) .then(({ data }) => data); }, + /** + * @desc 获取风险策略信息 + */ + fetchRiskInfo(params: { + id: string + }) { + return RiskManageSource.getRiskInfo(params) + .then(({ data }) => data); + }, /** * @desc 人工执行处理套餐 * @params risk_id @@ -149,7 +158,7 @@ export default { risk_id: string, new_operators: string[], description: string - }) { + }) { return RiskManageSource.transRisk(params) .then(({ data }) => data); }, diff --git a/src/frontend/src/domain/service/strategy-manage.ts b/src/frontend/src/domain/service/strategy-manage.ts index 07b31cd1..c9177686 100644 --- a/src/frontend/src/domain/service/strategy-manage.ts +++ b/src/frontend/src/domain/service/strategy-manage.ts @@ -247,4 +247,14 @@ export default { return StrategySource.getStrategyEvent(params) .then(({ data }) => new StrategyFieldEvent(data)); }, + + /** +* @desc 获取风险单风险等级 +*/ + fetchRiskLevel(params: { + strategy_ids: string + }) { + return StrategySource.getRiskLevel(params) + .then(({ data }) => data); + }, }; diff --git a/src/frontend/src/domain/source/risk-manage.ts b/src/frontend/src/domain/source/risk-manage.ts index 199a59ad..0db6698c 100644 --- a/src/frontend/src/domain/source/risk-manage.ts +++ b/src/frontend/src/domain/source/risk-manage.ts @@ -15,6 +15,7 @@ to the current version of the project delivered to anyone in the future. */ import type RiskManageModel from '@model/risk/risk'; +import type StrategyInfo from '@model/risk/strategy-info'; import Request, { type IRequestPayload, @@ -76,6 +77,12 @@ class RiskManage extends ModuleBase { params, }); } + // 获取风险策略信息 + getRiskInfo(params: { + id: string, + }) { + return Request.get(`${this.module}/${params.id}/strategy_info/`); + } // 人工执行处理套餐 autoProcess(params: { risk_id: string diff --git a/src/frontend/src/domain/source/strategy-manage.ts b/src/frontend/src/domain/source/strategy-manage.ts index 9957f85f..e2e34010 100644 --- a/src/frontend/src/domain/source/strategy-manage.ts +++ b/src/frontend/src/domain/source/strategy-manage.ts @@ -238,93 +238,18 @@ class Strategy extends ModuleBase { // 获取aiops策略详情 getStrategyEvent(params: { id: string }) { return Request.get(`${this.path}/strategy_fields/fields_config/`, { params }); - // const data: Promise<{ - // data: StrategyFieldEvent - // }> = new Promise((resolve) => { - // resolve({ - // data: { - // event_evidence_field_configs: [ - // { - // field_name: 'case_id', - // display_name: '事件证据', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'case_id', - // display_name: '事件证据', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'case_id', - // display_name: '事件证据', - // is_priority: false, - // description: '', - // }, - // ], - // event_data_field_configs: [ - // { - // field_name: 'event_data', - // display_name: '事件数据字段', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'event_data', - // display_name: '事件数据字段', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'event_data', - // display_name: '事件数据字段', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'event_data', - // display_name: '事件数据字段', - // is_priority: false, - // description: '', - // }, - // ], - // event_basic_field_configs: [ - // { - // field_name: 'case_id', - // display_name: '事件ID', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'case_id', - // display_name: '事件ID', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'case_id', - // display_name: '事件ID', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'case_id', - // display_name: '事件ID', - // is_priority: false, - // description: '', - // }, - // { - // field_name: 'case_id', - // display_name: '事件ID', - // is_priority: false, - // description: '', - // }, - // ], - // }, - // }); - // }); - // return data; + } + // 获取风险单风险等级 + getRiskLevel(params: { + strategy_ids: string, + }) { + return Request.get<{ + [key: string]: { + risk_level: string + } + }>(`${this.path}/strategy/display_info/`, { + params, + }); } } diff --git a/src/frontend/src/views/risk-manage/detail/components/base-info.vue b/src/frontend/src/views/risk-manage/detail/components/base-info.vue index 5458c71b..17b7beba 100644 --- a/src/frontend/src/views/risk-manage/detail/components/base-info.vue +++ b/src/frontend/src/views/risk-manage/detail/components/base-info.vue @@ -16,6 +16,9 @@ -->
= { + HIGH: { + label: '高', + color: '#ea3636', + }, + MIDDLE: { + label: '中', + color: '#ff9c01', + }, + LOW: { + label: '低', + color: '#979ba5', + }, + }; const { t, locale } = useI18n(); const isShowMore = ref(false); diff --git a/src/frontend/src/views/risk-manage/detail/components/link-event.vue b/src/frontend/src/views/risk-manage/detail/components/link-event.vue index 67ec9190..67532da1 100644 --- a/src/frontend/src/views/risk-manage/detail/components/link-event.vue +++ b/src/frontend/src/views/risk-manage/detail/components/link-event.vue @@ -40,7 +40,34 @@
-
+
+
+ {{ t('重点信息') }} +
+ + + {{ t('暂无数据') }} + +
+
{{ t('基本信息') }}
, - data: RiskManageModel + data: RiskManageModel & StrategyInfo } interface Emits { (e: 'changeHeight', height: number): void @@ -348,6 +376,20 @@ immediate: true, }); + // 重点信息 + const importantInformation = computed(() => { + const arr = [ + ...props.data.event_basic_field_configs.filter(item => item.is_priority), + ...props.data.event_data_field_configs.filter(item => item.is_priority), + ...props.data.event_evidence_field_configs.filter(item => item.is_priority), + ]; + const groups = []; + for (let i = 0; i < arr.length; i += 2) { + groups.push(arr.slice(i, i + 2)); + } + return groups; + }); + onMounted(() => { const observer = new MutationObserver(() => { const detail = document.querySelector('.list-item-detail'); @@ -418,6 +460,25 @@ width: calc(100% - 164px); padding-left: 16px; + .important-information { + padding: 12px 0; + background-color: #fafbfd; + + .title { + padding-left: 16px; + border-left: 3px solid #3a84ff; + } + + .render-info-item { + width: 50%; + align-items: flex-start; + + .info-value { + word-break: break-all; + } + } + } + .base-info { .render-info-item { width: 50%; @@ -431,8 +492,7 @@ .data-info { margin: 16px 0; - border-top: 1px solid #ecedf1; - border-left: 1px solid #ecedf1; + border: 1px solid #ecedf1; .data-info-item { width: 50%; @@ -443,7 +503,6 @@ align-items: center; padding: 6px 12px; border-right: 1px solid #ecedf1; - border-bottom: 1px solid #ecedf1; } .data-info-item-key { diff --git a/src/frontend/src/views/risk-manage/detail/index.vue b/src/frontend/src/views/risk-manage/detail/index.vue index 05a9cdd2..3fbf1b3c 100644 --- a/src/frontend/src/views/risk-manage/detail/index.vue +++ b/src/frontend/src/views/risk-manage/detail/index.vue @@ -19,13 +19,13 @@
@@ -58,6 +58,7 @@ diff --git a/src/frontend/src/views/handle-manage/index.vue b/src/frontend/src/views/handle-manage/index.vue index a845a034..b21386c3 100644 --- a/src/frontend/src/views/handle-manage/index.vue +++ b/src/frontend/src/views/handle-manage/index.vue @@ -59,6 +59,7 @@ import Tooltips from '@components/show-tooltips-text/index.vue'; import MarkRiskLabel from './components/mark-risk-label.vue'; + import RiskLevel from './components/risk-level.vue'; import SearchBox from './search-box/index.vue'; const dataSource = RiskManageService.fetchTodoRiskList; @@ -128,6 +129,14 @@ minWidth: 320, render: ({ data }: { data: RiskManageModel }) => , }, + { + label: () => t('风险等级'), + field: () => 'risk_id', + minWidth: 320, + render: ({ data }: { data: RiskManageModel }) => <> + + , + }, { label: () => t('风险标签'), field: () => 'tags', @@ -375,10 +384,28 @@ }); + const { + data: levelData, + run: fetchRiskLevel, + } = useRequest(StrategyManageService.fetchRiskLevel, { + defaultValue: {}, + }); + // 记录轮训的数据 const pollingDataMap = ref>({}); const handleRequestSuccess = ({ results }: {results: Array}) => { startPolling(results); + if (!results.length) return; + // 获取对应风险等级 + const strategyIds = results.reduce((acc, item, index) => { + if (index === 0) { + return `${item.strategy_id}`; + } + return `${acc},${item.strategy_id}`; + }, ''); + fetchRiskLevel({ + strategy_ids: strategyIds, + }); }; // 开始轮训 const startPolling = (results: Array) => { @@ -438,6 +465,7 @@ operator: '', status: '', event_content: '', + risk_level: '', }; listRef.value.fetchData({ ...params, diff --git a/src/frontend/src/views/handle-manage/search-box/components/render-field-config/config.ts b/src/frontend/src/views/handle-manage/search-box/components/render-field-config/config.ts index 294a770e..f5b05e7a 100644 --- a/src/frontend/src/views/handle-manage/search-box/components/render-field-config/config.ts +++ b/src/frontend/src/views/handle-manage/search-box/components/render-field-config/config.ts @@ -21,7 +21,7 @@ export interface IFieldConfig { label: string, type: string, required: boolean, - validator?: (value: any)=> boolean, + validator?: (value: any) => boolean, message?: string, service?: (params?: Record) => Promise>, labelName?: string, @@ -70,4 +70,23 @@ export default { type: 'string', required: false, }, + risk_level: { + label: '风险等级', + type: 'select', + required: false, + service: () => new Promise(resolve => resolve([ + { + id: 'HIGH', + name: '高', + }, + { + id: 'MIDDLE', + name: '中', + }, + { + id: 'LOW', + name: '低', + }, + ])), + }, } as Record; diff --git a/src/frontend/src/views/risk-manage/list/components/risk-level.vue b/src/frontend/src/views/risk-manage/list/components/risk-level.vue new file mode 100644 index 00000000..371b07db --- /dev/null +++ b/src/frontend/src/views/risk-manage/list/components/risk-level.vue @@ -0,0 +1,63 @@ + + + + diff --git a/src/frontend/src/views/risk-manage/list/components/search-box/components/render-field-config/config.ts b/src/frontend/src/views/risk-manage/list/components/search-box/components/render-field-config/config.ts index c5562f72..0f9f1aa6 100644 --- a/src/frontend/src/views/risk-manage/list/components/search-box/components/render-field-config/config.ts +++ b/src/frontend/src/views/risk-manage/list/components/search-box/components/render-field-config/config.ts @@ -21,7 +21,7 @@ export interface IFieldConfig { label: string, type: string, required: boolean, - validator?: (value: any)=> boolean, + validator?: (value: any) => boolean, message?: string, service?: (params?: Record) => Promise>, labelName?: string, @@ -90,4 +90,23 @@ export default { type: 'string', required: false, }, + risk_level: { + label: '风险等级', + type: 'select', + required: false, + service: () => new Promise(resolve => resolve([ + { + id: 'HIGH', + name: '高', + }, + { + id: 'MIDDLE', + name: '中', + }, + { + id: 'LOW', + name: '低', + }, + ])), + }, } as Record; diff --git a/src/frontend/src/views/risk-manage/list/index.vue b/src/frontend/src/views/risk-manage/list/index.vue index 12f024f9..698a9644 100644 --- a/src/frontend/src/views/risk-manage/list/index.vue +++ b/src/frontend/src/views/risk-manage/list/index.vue @@ -64,6 +64,7 @@ import Tooltips from '@components/show-tooltips-text/index.vue'; import MarkRiskLabel from './components/mark-risk-label.vue'; + import RiskLevel from './components/risk-level.vue'; import SearchBox from './components/search-box/index.vue'; const dataSource = RiskManageService.fetchRiskList; @@ -134,6 +135,14 @@ minWidth: 320, render: ({ data }: { data: RiskManageModel }) => , }, + { + label: () => t('风险等级'), + field: () => 'risk_id', + minWidth: 320, + render: ({ data }: { data: RiskManageModel }) => <> + + , + }, { label: () => t('风险标签'), minWidth: 200, @@ -383,10 +392,28 @@ }, }); + const { + data: levelData, + run: fetchRiskLevel, + } = useRequest(StrategyManageService.fetchRiskLevel, { + defaultValue: {}, + }); + // 记录轮训的数据 const pollingDataMap = ref>({}); const handleRequestSuccess = ({ results }: {results: Array}) => { startPolling(results); + if (!results.length) return; + // 获取对应风险等级 + const strategyIds = results.reduce((acc, item, index) => { + if (index === 0) { + return `${item.strategy_id}`; + } + return `${acc},${item.strategy_id}`; + }, ''); + fetchRiskLevel({ + strategy_ids: strategyIds, + }); }; // // 开始轮训 const startPolling = (results: Array) => { @@ -447,6 +474,7 @@ status: '', risk_label: '', event_content: '', + risk_level: '', }; listRef.value.fetchData({ ...params, diff --git a/src/frontend/src/views/strategy-manage/strategy-create/components/step1/index.vue b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/index.vue index b3b88c9a..bf7d08c5 100644 --- a/src/frontend/src/views/strategy-manage/strategy-create/components/step1/index.vue +++ b/src/frontend/src/views/strategy-manage/strategy-create/components/step1/index.vue @@ -81,7 +81,7 @@ From d0a53024c43d094cf6dbe194af729abf153745c4 Mon Sep 17 00:00:00 2001 From: nanasikeai Date: Mon, 12 Aug 2024 18:43:40 +0800 Subject: [PATCH 26/64] =?UTF-8?q?feat:=20=E9=A3=8E=E9=99=A9=E5=8D=95?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA=E4=BA=8C=E6=9C=9F-?= =?UTF-8?q?=E9=87=8D=E7=82=B9=E4=BF=A1=E6=81=AF=E5=B1=95=E7=A4=BA=20--stor?= =?UTF-8?q?y=3D118188959?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/risk-manage/detail/components/link-event.vue | 9 +++++++-- .../risk-manage/detail/components/render-info-item.vue | 10 +++++++++- src/frontend/src/views/risk-manage/detail/index.vue | 3 --- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/frontend/src/views/risk-manage/detail/components/link-event.vue b/src/frontend/src/views/risk-manage/detail/components/link-event.vue index 67532da1..ab3e69dd 100644 --- a/src/frontend/src/views/risk-manage/detail/components/link-event.vue +++ b/src/frontend/src/views/risk-manage/detail/components/link-event.vue @@ -53,9 +53,14 @@ - {{ subItem.field_name }} + {{ + eventItem[subItem.field_name as keyof typeof eventItem] || + eventItem.event_data[subItem.field_name] || + (eventItemEvidence && eventItemEvidence.map(item => item[subItem.field_name]).join(';')) + }} diff --git a/src/frontend/src/views/risk-manage/detail/components/render-info-item.vue b/src/frontend/src/views/risk-manage/detail/components/render-info-item.vue index dff808bb..d7fd2973 100644 --- a/src/frontend/src/views/risk-manage/detail/components/render-info-item.vue +++ b/src/frontend/src/views/risk-manage/detail/components/render-info-item.vue @@ -19,7 +19,14 @@
- {{ label }}: + + {{ label }}: +
@@ -34,6 +41,7 @@ interface Props{ label: string, labelWidth?: number, + description?: string, } const props = defineProps(); diff --git a/src/frontend/src/views/risk-manage/detail/index.vue b/src/frontend/src/views/risk-manage/detail/index.vue index 3fbf1b3c..9f5b795d 100644 --- a/src/frontend/src/views/risk-manage/detail/index.vue +++ b/src/frontend/src/views/risk-manage/detail/index.vue @@ -138,9 +138,6 @@ id: route.params.riskId, }, manual: true, - onSuccess() { - console.log(strategyInfoData); - }, }); const handleUpdate = () => { From 56565c5f0e9b7516c5dac09e37f18aaa3b9c72c3 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Mon, 12 Aug 2024 18:54:21 +0800 Subject: [PATCH 27/64] =?UTF-8?q?feat:=20=E9=A3=8E=E9=99=A9API=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=A3=8E=E9=99=A9=E7=AD=89=E7=BA=A7=20--story=3D11833?= =?UTF-8?q?2270?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 增加风险等级查询条件 2. 增加策略信息查询接口 3. 优化字段返回格式序列化器 --- .../services/web/risk/resources/risk.py | 4 ++++ src/backend/services/web/risk/serializers.py | 3 +++ .../services/web/strategy_v2/resources.py | 21 +++++++++++++++--- .../services/web/strategy_v2/serializers.py | 22 ++++++++++++++----- src/backend/services/web/strategy_v2/views.py | 1 + 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/backend/services/web/risk/resources/risk.py b/src/backend/services/web/risk/resources/risk.py index 643b0047..e3d8c024 100644 --- a/src/backend/services/web/risk/resources/risk.py +++ b/src/backend/services/web/risk/resources/risk.py @@ -155,6 +155,10 @@ def perform_request(self, validated_request_data): def load_risks(self, validated_request_data: dict) -> QuerySet: # 构造表达式 q = Q() + # 风险等级 + risk_level = validated_request_data.pop("risk_level", None) + if risk_level: + q &= Q(strategy_id__in=Strategy.objects.filter(risk_level__in=risk_level).values("strategy_id")) for key, val in validated_request_data.items(): if not val: continue diff --git a/src/backend/services/web/risk/serializers.py b/src/backend/services/web/risk/serializers.py index 63e52d02..efe90a57 100644 --- a/src/backend/services/web/risk/serializers.py +++ b/src/backend/services/web/risk/serializers.py @@ -196,6 +196,9 @@ class ListRiskRequestSerializer(serializers.Serializer): order_type = serializers.ChoiceField( label=gettext_lazy("排序方式"), required=False, allow_null=True, allow_blank=True, choices=OrderTypeChoices.choices ) + risk_level = serializers.CharField( + label=gettext_lazy("Risk Level"), required=False, allow_blank=True, allow_null=True + ) def validate(self, attrs: dict) -> dict: # 校验 diff --git a/src/backend/services/web/strategy_v2/resources.py b/src/backend/services/web/strategy_v2/resources.py index ffd2d3f4..e0153126 100644 --- a/src/backend/services/web/strategy_v2/resources.py +++ b/src/backend/services/web/strategy_v2/resources.py @@ -28,7 +28,7 @@ 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 +from django.db.models import Count, Q, QuerySet from django.shortcuts import get_object_or_404 from django.utils.translation import gettext, gettext_lazy from pypinyin import lazy_pinyin @@ -87,6 +87,7 @@ GetRTFieldsRequestSerializer, GetRTFieldsResponseSerializer, GetStrategyCommonResponseSerializer, + GetStrategyDisplayInfoRequestSerializer, GetStrategyFieldValueRequestSerializer, GetStrategyFieldValueResponseSerializer, GetStrategyStatusRequestSerializer, @@ -611,8 +612,8 @@ def get_event_basic_field_configs(self, risk: Optional[Risk], has_permission: bo return [ EventInfoField( field_name=field.field_name, - display_name=field.alias_name, - description=str(field.description), + display_name=str(field.description), + description="", example=getattr(risk, field.field_name, "") if risk and has_permission else "", ) for field in EventMappingFields().fields @@ -667,3 +668,17 @@ def perform_request(self, validated_request_data): "event_data_field_configs": self.get_event_data_field_configs(risk, has_permission), "event_evidence_field_configs": self.get_event_evidence_field_configs(risk, has_permission), } + + +class GetStrategyDisplayInfo(StrategyV2Base): + """ + 获取策略展示信息 + """ + + name = gettext_lazy("获取策略展示信息") + RequestSerializer = GetStrategyDisplayInfoRequestSerializer + + def perform_request(self, validated_request_data): + strategy_ids = validated_request_data["strategy_ids"] + strategies: QuerySet[Strategy] = Strategy.objects.filter(strategy_id__in=strategy_ids).only("risk_level") + return {strategy.strategy_id: {"risk_level": strategy.risk_level} for strategy in strategies} diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index a7f6b41c..c24196ea 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -46,7 +46,7 @@ class EventFieldSerializer(serializers.Serializer): field_name = serializers.CharField(label=gettext_lazy("Field Name")) display_name = serializers.CharField(label=gettext_lazy("Field Display Name")) is_priority = serializers.BooleanField(label=gettext_lazy("Is Priority")) - description = serializers.CharField(label=gettext_lazy("Field Description")) + description = serializers.CharField(label=gettext_lazy("Field Description"), default="", allow_blank=True) class CreateStrategyRequestSerializer(serializers.ModelSerializer): @@ -525,11 +525,13 @@ class EventInfoFieldSerializer(serializers.Serializer): def to_internal_value(self, data): # example 可能是 list 或 bool,均转换为字符串进行展示 example = data["example"] - if isinstance(example, (bool, list, dict)): - try: + try: + if isinstance(example, (list, dict)): data["example"] = json.dumps(example) - except Exception: # NOCC:broad-except(需要处理所有错误) - data["example"] = str(example) + except Exception: # NOCC:broad-except(需要处理所有错误) + pass + finally: + data["example"] = str(example) return super().to_internal_value(data) @@ -547,3 +549,13 @@ class GetEventInfoFieldsResponseSerializer(serializers.Serializer): event_evidence_field_configs = serializers.ListField( label=gettext_lazy("Event Evidence Field Configs"), child=EventInfoFieldSerializer(), required=False ) + + +class GetStrategyDisplayInfoRequestSerializer(serializers.Serializer): + strategy_ids = serializers.CharField(label=gettext_lazy("Strategy IDs")) + + def validate_strategy_ids(self, strategy_ids: str) -> List[int]: + try: + return [int(s) for s in strategy_ids.split(",") if s] + except ValueError: + raise serializers.ValidationError(gettext("Strategy ID Invalid")) diff --git a/src/backend/services/web/strategy_v2/views.py b/src/backend/services/web/strategy_v2/views.py index 2854e965..8e8dc57e 100644 --- a/src/backend/services/web/strategy_v2/views.py +++ b/src/backend/services/web/strategy_v2/views.py @@ -61,6 +61,7 @@ def get_permissions(self): ResourceRoute("GET", resource.strategy_v2.get_strategy_common, endpoint="common"), ResourceRoute("GET", resource.strategy_v2.get_strategy_status, endpoint="status"), ResourceRoute("PUT", resource.strategy_v2.retry_strategy, pk_field="strategy_id", endpoint="retry"), + ResourceRoute("GET", resource.strategy_v2.get_strategy_display_info, endpoint="display_info"), ] From 7d654eb21beb3e0fba34215957061a237b5f39a7 Mon Sep 17 00:00:00 2001 From: raja <1647193241@qq.com> Date: Mon, 12 Aug 2024 20:16:05 +0800 Subject: [PATCH 28/64] =?UTF-8?q?feat:=20=E9=A3=8E=E9=99=A9=E5=8D=95?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA=E4=BA=8C=E6=9C=9F=20--stor?= =?UTF-8?q?y=3D118188959?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修复风险生成标题的异常 --- .../services/web/risk/handlers/risk.py | 50 ++++++++++--------- .../services/web/risk/resources/risk.py | 4 ++ src/backend/services/web/risk/serializers.py | 3 ++ .../services/web/strategy_v2/resources.py | 21 ++++++-- .../services/web/strategy_v2/serializers.py | 22 ++++++-- src/backend/services/web/strategy_v2/views.py | 1 + 6 files changed, 70 insertions(+), 31 deletions(-) diff --git a/src/backend/services/web/risk/handlers/risk.py b/src/backend/services/web/risk/handlers/risk.py index e3a592c8..bf9ff7b5 100644 --- a/src/backend/services/web/risk/handlers/risk.py +++ b/src/backend/services/web/risk/handlers/risk.py @@ -28,7 +28,7 @@ from django.db.models import Q, QuerySet from django.utils import timezone from django.utils.translation import gettext -from jinja2.exceptions import UndefinedError +from jinja2 import UndefinedError from rest_framework.settings import api_settings from apps.meta.models import GlobalMetaConfig @@ -46,7 +46,7 @@ from services.web.risk.handlers import EventHandler from services.web.risk.models import Risk from services.web.risk.render import RiskTitleUndefined -from services.web.risk.serializers import CreateRiskSerializer, RiskInfoSerializer +from services.web.risk.serializers import CreateRiskSerializer from services.web.strategy_v2.models import Strategy, StrategyTag @@ -103,33 +103,50 @@ def load_events(self, start_time: datetime.datetime, end_time: datetime.datetime return data - def render_risk_title(self, risk: Risk) -> Optional[str]: + @classmethod + def render_risk_title(cls, create_params: dict) -> Optional[str]: """ 生成风险标题 """ - strategy: Strategy = Strategy.objects.filter(strategy_id=risk.strategy_id).first() + strategy: Strategy = Strategy.objects.filter(strategy_id=create_params["strategy_id"]).first() if not strategy or not strategy.risk_title: return - risk_content = RiskInfoSerializer(risk).data # 事件证据为字符串需要转换成列表,并取第一条字典数据 try: - event_evidence = json.loads(risk.event_evidence)[0] + event_evidence = json.loads(create_params["event_evidence"])[0] except (json.JSONDecodeError, IndexError, KeyError): event_evidence = {} - risk_content["event_evidence"] = event_evidence + create_params["event_evidence"] = event_evidence try: - risk_title = Jinja2Renderer(undefined=RiskTitleUndefined).jinja_render(strategy.risk_title, risk_content) + risk_title = Jinja2Renderer(undefined=RiskTitleUndefined).jinja_render(strategy.risk_title, create_params) return risk_title except UndefinedError as err: logger.exception( "[RenderRiskTitleFailed] risk_title: %s; risk_content: %s; err: %s", strategy.risk_title, - risk_content, + create_params, err, ) return strategy.risk_title + def gen_risk_create_params(self, event: dict) -> dict: + create_params = { + "event_content": event.get("event_content"), + "raw_event_id": event["raw_event_id"], + "strategy_id": event["strategy_id"], + "event_evidence": event.get("event_evidence"), + "event_type": self.parse_event_type(event.get("event_type")), + "event_data": event.get("event_data"), + "event_time": datetime.datetime.fromtimestamp(event["event_time"] / 1000), + "event_end_time": datetime.datetime.fromtimestamp(event["event_time"] / 1000), + "event_source": event.get("event_source"), + "operator": self.parse_operator(event.get("operator")), + "tags": list(StrategyTag.objects.filter(strategy_id=event["strategy_id"]).values_list("tag_id", flat=True)), + } + create_params["title"] = self.render_risk_title(create_params) + return create_params + def create_risk(self, event: dict) -> (bool, Risk): """ 创建或更新风险 @@ -174,20 +191,7 @@ def create_risk(self, event: dict) -> (bool, Risk): return False, None # 不存在则创建 - return True, Risk.objects.create( - event_content=event.get("event_content"), - raw_event_id=event["raw_event_id"], - strategy_id=event["strategy_id"], - event_evidence=event.get("event_evidence"), - event_type=self.parse_event_type(event.get("event_type")), - event_data=event.get("event_data"), - event_time=datetime.datetime.fromtimestamp(event["event_time"] / 1000), - event_end_time=datetime.datetime.fromtimestamp(event["event_time"] / 1000), - event_source=event.get("event_source"), - operator=self.parse_operator(event.get("operator")), - tags=list(StrategyTag.objects.filter(strategy_id=event["strategy_id"]).values_list("tag_id", flat=True)), - title=self.render_risk_title(risk), - ) + return True, Risk.objects.create(**self.gen_risk_create_params(event)) def parse_operator(self, operator: str) -> List[str]: operator = operator or "" diff --git a/src/backend/services/web/risk/resources/risk.py b/src/backend/services/web/risk/resources/risk.py index 643b0047..e3d8c024 100644 --- a/src/backend/services/web/risk/resources/risk.py +++ b/src/backend/services/web/risk/resources/risk.py @@ -155,6 +155,10 @@ def perform_request(self, validated_request_data): def load_risks(self, validated_request_data: dict) -> QuerySet: # 构造表达式 q = Q() + # 风险等级 + risk_level = validated_request_data.pop("risk_level", None) + if risk_level: + q &= Q(strategy_id__in=Strategy.objects.filter(risk_level__in=risk_level).values("strategy_id")) for key, val in validated_request_data.items(): if not val: continue diff --git a/src/backend/services/web/risk/serializers.py b/src/backend/services/web/risk/serializers.py index 63e52d02..efe90a57 100644 --- a/src/backend/services/web/risk/serializers.py +++ b/src/backend/services/web/risk/serializers.py @@ -196,6 +196,9 @@ class ListRiskRequestSerializer(serializers.Serializer): order_type = serializers.ChoiceField( label=gettext_lazy("排序方式"), required=False, allow_null=True, allow_blank=True, choices=OrderTypeChoices.choices ) + risk_level = serializers.CharField( + label=gettext_lazy("Risk Level"), required=False, allow_blank=True, allow_null=True + ) def validate(self, attrs: dict) -> dict: # 校验 diff --git a/src/backend/services/web/strategy_v2/resources.py b/src/backend/services/web/strategy_v2/resources.py index ffd2d3f4..e0153126 100644 --- a/src/backend/services/web/strategy_v2/resources.py +++ b/src/backend/services/web/strategy_v2/resources.py @@ -28,7 +28,7 @@ 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 +from django.db.models import Count, Q, QuerySet from django.shortcuts import get_object_or_404 from django.utils.translation import gettext, gettext_lazy from pypinyin import lazy_pinyin @@ -87,6 +87,7 @@ GetRTFieldsRequestSerializer, GetRTFieldsResponseSerializer, GetStrategyCommonResponseSerializer, + GetStrategyDisplayInfoRequestSerializer, GetStrategyFieldValueRequestSerializer, GetStrategyFieldValueResponseSerializer, GetStrategyStatusRequestSerializer, @@ -611,8 +612,8 @@ def get_event_basic_field_configs(self, risk: Optional[Risk], has_permission: bo return [ EventInfoField( field_name=field.field_name, - display_name=field.alias_name, - description=str(field.description), + display_name=str(field.description), + description="", example=getattr(risk, field.field_name, "") if risk and has_permission else "", ) for field in EventMappingFields().fields @@ -667,3 +668,17 @@ def perform_request(self, validated_request_data): "event_data_field_configs": self.get_event_data_field_configs(risk, has_permission), "event_evidence_field_configs": self.get_event_evidence_field_configs(risk, has_permission), } + + +class GetStrategyDisplayInfo(StrategyV2Base): + """ + 获取策略展示信息 + """ + + name = gettext_lazy("获取策略展示信息") + RequestSerializer = GetStrategyDisplayInfoRequestSerializer + + def perform_request(self, validated_request_data): + strategy_ids = validated_request_data["strategy_ids"] + strategies: QuerySet[Strategy] = Strategy.objects.filter(strategy_id__in=strategy_ids).only("risk_level") + return {strategy.strategy_id: {"risk_level": strategy.risk_level} for strategy in strategies} diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index a7f6b41c..c24196ea 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -46,7 +46,7 @@ class EventFieldSerializer(serializers.Serializer): field_name = serializers.CharField(label=gettext_lazy("Field Name")) display_name = serializers.CharField(label=gettext_lazy("Field Display Name")) is_priority = serializers.BooleanField(label=gettext_lazy("Is Priority")) - description = serializers.CharField(label=gettext_lazy("Field Description")) + description = serializers.CharField(label=gettext_lazy("Field Description"), default="", allow_blank=True) class CreateStrategyRequestSerializer(serializers.ModelSerializer): @@ -525,11 +525,13 @@ class EventInfoFieldSerializer(serializers.Serializer): def to_internal_value(self, data): # example 可能是 list 或 bool,均转换为字符串进行展示 example = data["example"] - if isinstance(example, (bool, list, dict)): - try: + try: + if isinstance(example, (list, dict)): data["example"] = json.dumps(example) - except Exception: # NOCC:broad-except(需要处理所有错误) - data["example"] = str(example) + except Exception: # NOCC:broad-except(需要处理所有错误) + pass + finally: + data["example"] = str(example) return super().to_internal_value(data) @@ -547,3 +549,13 @@ class GetEventInfoFieldsResponseSerializer(serializers.Serializer): event_evidence_field_configs = serializers.ListField( label=gettext_lazy("Event Evidence Field Configs"), child=EventInfoFieldSerializer(), required=False ) + + +class GetStrategyDisplayInfoRequestSerializer(serializers.Serializer): + strategy_ids = serializers.CharField(label=gettext_lazy("Strategy IDs")) + + def validate_strategy_ids(self, strategy_ids: str) -> List[int]: + try: + return [int(s) for s in strategy_ids.split(",") if s] + except ValueError: + raise serializers.ValidationError(gettext("Strategy ID Invalid")) diff --git a/src/backend/services/web/strategy_v2/views.py b/src/backend/services/web/strategy_v2/views.py index 2854e965..8e8dc57e 100644 --- a/src/backend/services/web/strategy_v2/views.py +++ b/src/backend/services/web/strategy_v2/views.py @@ -61,6 +61,7 @@ def get_permissions(self): ResourceRoute("GET", resource.strategy_v2.get_strategy_common, endpoint="common"), ResourceRoute("GET", resource.strategy_v2.get_strategy_status, endpoint="status"), ResourceRoute("PUT", resource.strategy_v2.retry_strategy, pk_field="strategy_id", endpoint="retry"), + ResourceRoute("GET", resource.strategy_v2.get_strategy_display_info, endpoint="display_info"), ] From f519ddc65b8d545451263e94507e97e70b03759a Mon Sep 17 00:00:00 2001 From: nanasikeai Date: Tue, 13 Aug 2024 10:19:59 +0800 Subject: [PATCH 29/64] =?UTF-8?q?feat:=20=E9=A3=8E=E9=99=A9=E6=A0=87?= =?UTF-8?q?=E9=A2=98=20--story=3D118188959?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/domain/model/risk/risk.ts | 2 ++ .../views/risk-manage/detail/components/base-info.vue | 10 +++++++++- .../components/preview/components/base-info.vue | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/domain/model/risk/risk.ts b/src/frontend/src/domain/model/risk/risk.ts index 8abda219..13aa3d90 100644 --- a/src/frontend/src/domain/model/risk/risk.ts +++ b/src/frontend/src/domain/model/risk/risk.ts @@ -82,6 +82,7 @@ export default class Event { risk_label: string; permission: Record; experiences: number;// 风险总结 + title: string; constructor(payload = {} as Event) { this.risk_id = payload.risk_id; @@ -108,5 +109,6 @@ export default class Event { this.risk_label = payload.risk_label; this.permission = payload.permission; this.experiences = payload.experiences; + this.title = payload.title; } } diff --git a/src/frontend/src/views/risk-manage/detail/components/base-info.vue b/src/frontend/src/views/risk-manage/detail/components/base-info.vue index 17b7beba..49d32b61 100644 --- a/src/frontend/src/views/risk-manage/detail/components/base-info.vue +++ b/src/frontend/src/views/risk-manage/detail/components/base-info.vue @@ -17,7 +17,7 @@