mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-28 01:03:50 +08:00
在 agent 下发意图(add_intent)与插入资产(insert_assets)两处接入资产闸门;
并新增任务级规则(拦截 block + 允许 allow 白名单),创建任务时录入、任务详情
总览里可编辑。
- 匹配/执行(db/asset_intercept_match.go):MatchAssetInterceptRules 纯匹配
(exact/fuzzy 的 域名·IP·URL + CIDR,URL host 会拆出归类);EvaluateAssetGate
「先拦截后允许」闸门——命中拦截即禁止;未命中但存在启用的允许规则且都不命中→
不允许测试;无启用允许规则则不启用白名单。附单元测试。
- 执行点:add_intent 命中即不下发意图;insert_assets 命中即跳过 Upsert 及
AddAutoScope 副作用。均合并 全局∪任务级 规则。
- 任务级规则:表 task_intercept_rules(task_id FK CASCADE + action block/allow,
幂等 ALTER 补旧库);db/task_intercept.go 提供 List/Split/CRUD/事务插入;
创建任务随 intercept_rules 提交;CRUD 路由 /api/tasks/{id}/intercept-rules。
- 前端:新建任务抽屉多行录入(拦截/允许 + 类型,用 NativeSelect 避免 Sheet 内
下拉 portal 导致误关抽屉);任务详情总览新增规则管理卡片(增删改+启用开关)。
190 lines
4.6 KiB
Go
190 lines
4.6 KiB
Go
package server
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
|
||
"github.com/Autumn-27/artex/db"
|
||
)
|
||
|
||
// 任务级资产拦截/允许规则的 CRUD。规则按 task_id 归属,仅对该任务生效:
|
||
// action=block 拦截(禁止测试),action=allow 允许(白名单)。执行判定见 db.EvaluateAssetGate。
|
||
|
||
type taskInterceptRuleReq struct {
|
||
Enabled bool `json:"enabled"`
|
||
Action string `json:"action"` // block | allow
|
||
Kind string `json:"kind"`
|
||
Pattern string `json:"pattern"`
|
||
Note string `json:"note"`
|
||
}
|
||
|
||
// validateTaskInterceptRuleReq 归一并校验;复用全局规则的 kind/pattern 校验器。
|
||
func validateTaskInterceptRuleReq(req *taskInterceptRuleReq) error {
|
||
if req.Action == "" {
|
||
req.Action = "block"
|
||
}
|
||
if req.Action != "block" && req.Action != "allow" {
|
||
return fmt.Errorf("action 必须是 block 或 allow")
|
||
}
|
||
v := assetInterceptRuleReq{Enabled: req.Enabled, Kind: req.Kind, Pattern: req.Pattern, Note: req.Note}
|
||
if err := validateAssetInterceptRuleReq(&v); err != nil {
|
||
return err
|
||
}
|
||
req.Pattern = v.Pattern // 已 trim
|
||
return nil
|
||
}
|
||
|
||
// buildTaskInterceptRules 校验创建任务时录入的任务级规则并转换为 db 输入形态。
|
||
func buildTaskInterceptRules(reqs []taskInterceptRuleReq) ([]db.TaskInterceptRuleInput, error) {
|
||
if len(reqs) == 0 {
|
||
return nil, nil
|
||
}
|
||
out := make([]db.TaskInterceptRuleInput, 0, len(reqs))
|
||
for i := range reqs {
|
||
rq := reqs[i]
|
||
if err := validateTaskInterceptRuleReq(&rq); err != nil {
|
||
return nil, err
|
||
}
|
||
out = append(out, db.TaskInterceptRuleInput{
|
||
Enabled: rq.Enabled,
|
||
Action: rq.Action,
|
||
Kind: rq.Kind,
|
||
Pattern: rq.Pattern,
|
||
Note: rq.Note,
|
||
})
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func (s *Server) taskInterceptListRules(w http.ResponseWriter, r *http.Request) {
|
||
pg := s.pg(w)
|
||
if pg == nil {
|
||
return
|
||
}
|
||
taskID, ok := pathInt(r, "id")
|
||
if !ok || taskID <= 0 {
|
||
writeErr(w, 400, "bad task id")
|
||
return
|
||
}
|
||
rules, err := pg.Assets().ListTaskInterceptRules(taskID)
|
||
if err != nil {
|
||
writeErr(w, 500, err.Error())
|
||
return
|
||
}
|
||
if rules == nil {
|
||
rules = []db.AssetInterceptRule{}
|
||
}
|
||
writeJSON(w, 200, map[string]any{"rules": rules})
|
||
}
|
||
|
||
func (s *Server) taskInterceptCreateRule(w http.ResponseWriter, r *http.Request) {
|
||
pg := s.pg(w)
|
||
if pg == nil {
|
||
return
|
||
}
|
||
taskID, ok := pathInt(r, "id")
|
||
if !ok || taskID <= 0 {
|
||
writeErr(w, 400, "bad task id")
|
||
return
|
||
}
|
||
var req taskInterceptRuleReq
|
||
if err := decode(r, &req); err != nil {
|
||
writeErr(w, 400, err.Error())
|
||
return
|
||
}
|
||
if err := validateTaskInterceptRuleReq(&req); err != nil {
|
||
writeErr(w, 400, err.Error())
|
||
return
|
||
}
|
||
rule, err := pg.Assets().CreateTaskInterceptRule(taskID, req.Action, req.Kind, req.Pattern, req.Note, req.Enabled)
|
||
if err != nil {
|
||
writeErr(w, 500, err.Error())
|
||
return
|
||
}
|
||
writeJSON(w, 200, rule)
|
||
}
|
||
|
||
func (s *Server) taskInterceptUpdateRule(w http.ResponseWriter, r *http.Request) {
|
||
pg := s.pg(w)
|
||
if pg == nil {
|
||
return
|
||
}
|
||
taskID, ok := pathInt(r, "id")
|
||
if !ok || taskID <= 0 {
|
||
writeErr(w, 400, "bad task id")
|
||
return
|
||
}
|
||
ruleID, ok := pathInt(r, "rid")
|
||
if !ok || ruleID <= 0 {
|
||
writeErr(w, 400, "bad rule id")
|
||
return
|
||
}
|
||
var req taskInterceptRuleReq
|
||
if err := decode(r, &req); err != nil {
|
||
writeErr(w, 400, err.Error())
|
||
return
|
||
}
|
||
if err := validateTaskInterceptRuleReq(&req); err != nil {
|
||
writeErr(w, 400, err.Error())
|
||
return
|
||
}
|
||
rule, err := pg.Assets().UpdateTaskInterceptRule(taskID, ruleID, req.Action, req.Kind, req.Pattern, req.Note, req.Enabled)
|
||
if err != nil {
|
||
writeErr(w, 500, err.Error())
|
||
return
|
||
}
|
||
writeJSON(w, 200, rule)
|
||
}
|
||
|
||
func (s *Server) taskInterceptDeleteRule(w http.ResponseWriter, r *http.Request) {
|
||
pg := s.pg(w)
|
||
if pg == nil {
|
||
return
|
||
}
|
||
taskID, ok := pathInt(r, "id")
|
||
if !ok || taskID <= 0 {
|
||
writeErr(w, 400, "bad task id")
|
||
return
|
||
}
|
||
ruleID, ok := pathInt(r, "rid")
|
||
if !ok || ruleID <= 0 {
|
||
writeErr(w, 400, "bad rule id")
|
||
return
|
||
}
|
||
deleted, err := pg.Assets().DeleteTaskInterceptRule(taskID, ruleID)
|
||
if err != nil {
|
||
writeErr(w, 500, err.Error())
|
||
return
|
||
}
|
||
writeJSON(w, 200, map[string]any{"deleted": deleted})
|
||
}
|
||
|
||
func (s *Server) taskInterceptToggleRule(w http.ResponseWriter, r *http.Request) {
|
||
pg := s.pg(w)
|
||
if pg == nil {
|
||
return
|
||
}
|
||
taskID, ok := pathInt(r, "id")
|
||
if !ok || taskID <= 0 {
|
||
writeErr(w, 400, "bad task id")
|
||
return
|
||
}
|
||
ruleID, ok := pathInt(r, "rid")
|
||
if !ok || ruleID <= 0 {
|
||
writeErr(w, 400, "bad rule id")
|
||
return
|
||
}
|
||
var req struct {
|
||
Enabled bool `json:"enabled"`
|
||
}
|
||
if err := decode(r, &req); err != nil {
|
||
writeErr(w, 400, err.Error())
|
||
return
|
||
}
|
||
if err := pg.Assets().ToggleTaskInterceptRule(taskID, ruleID, req.Enabled); err != nil {
|
||
writeErr(w, 500, err.Error())
|
||
return
|
||
}
|
||
writeJSON(w, 200, map[string]any{"ok": true, "enabled": req.Enabled})
|
||
}
|