feat: 新增资产拦截规则(全局黑名单)增删改查
新增独立于命令拦截(intercept_rules)的资产拦截功能,仅提供规则的 存储与管理;具体匹配/拦截逻辑另行实现。 - db: 新增 asset_intercept_rules 表(schema.sql,幂等)与 CRUD 层 (db/asset_intercept.go);kind 支持全等/模糊的 域名·IP·URL 及 CIDR - db: 首启动 seed 内置模糊拦截政府(.gov/.gov.cn)与教育(.edu/.edu.cn) 网站,settings 标记位门控,可禁用/删除且重启不复活 - server: /api/asset-intercept/rules 的 list/create/update/delete/toggle; cidr、exact_ip 做格式校验 - web: 新增「资产拦截」页(/system/intercept/assets)与侧边栏入口、 api 与类型定义
This commit is contained in:
1 parent
a590b415ba
commit
dfc9d2fa2e
9 files changed
+687
No files matched your search
@@ -0,0 +1,156 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Autumn-27/artex/db"
|
||||
)
|
||||
|
||||
// --- asset intercept rule CRUD ---
|
||||
//
|
||||
// Global asset blocklist: exact/fuzzy domain·ip·url + CIDR. This layer only
|
||||
// persists rules; the actual match/enforcement logic lives elsewhere.
|
||||
|
||||
func (s *Server) assetInterceptListRules(w http.ResponseWriter, r *http.Request) {
|
||||
pg := s.pg(w)
|
||||
if pg == nil {
|
||||
return
|
||||
}
|
||||
rules, err := pg.ListAssetInterceptRules()
|
||||
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) assetInterceptCreateRule(w http.ResponseWriter, r *http.Request) {
|
||||
pg := s.pg(w)
|
||||
if pg == nil {
|
||||
return
|
||||
}
|
||||
var req assetInterceptRuleReq
|
||||
if err := decode(r, &req); err != nil {
|
||||
writeErr(w, 400, err.Error())
|
||||
return
|
||||
}
|
||||
if err := validateAssetInterceptRuleReq(&req); err != nil {
|
||||
writeErr(w, 400, err.Error())
|
||||
return
|
||||
}
|
||||
rule, err := pg.CreateAssetInterceptRule(req.Kind, req.Pattern, req.Note, req.Enabled)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, rule)
|
||||
}
|
||||
|
||||
func (s *Server) assetInterceptUpdateRule(w http.ResponseWriter, r *http.Request) {
|
||||
pg := s.pg(w)
|
||||
if pg == nil {
|
||||
return
|
||||
}
|
||||
id, ok := pathInt(r, "id")
|
||||
if !ok {
|
||||
writeErr(w, 400, "bad rule id")
|
||||
return
|
||||
}
|
||||
var req assetInterceptRuleReq
|
||||
if err := decode(r, &req); err != nil {
|
||||
writeErr(w, 400, err.Error())
|
||||
return
|
||||
}
|
||||
if err := validateAssetInterceptRuleReq(&req); err != nil {
|
||||
writeErr(w, 400, err.Error())
|
||||
return
|
||||
}
|
||||
rule, err := pg.UpdateAssetInterceptRule(id, req.Kind, req.Pattern, req.Note, req.Enabled)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, rule)
|
||||
}
|
||||
|
||||
func (s *Server) assetInterceptDeleteRule(w http.ResponseWriter, r *http.Request) {
|
||||
pg := s.pg(w)
|
||||
if pg == nil {
|
||||
return
|
||||
}
|
||||
id, ok := pathInt(r, "id")
|
||||
if !ok {
|
||||
writeErr(w, 400, "bad rule id")
|
||||
return
|
||||
}
|
||||
if err := pg.DeleteAssetInterceptRule(id); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"deleted": id})
|
||||
}
|
||||
|
||||
func (s *Server) assetInterceptToggleRule(w http.ResponseWriter, r *http.Request) {
|
||||
pg := s.pg(w)
|
||||
if pg == nil {
|
||||
return
|
||||
}
|
||||
id, ok := pathInt(r, "id")
|
||||
if !ok {
|
||||
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.ToggleAssetInterceptRule(id, req.Enabled); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"ok": true, "enabled": req.Enabled})
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
type assetInterceptRuleReq struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Kind string `json:"kind"`
|
||||
Pattern string `json:"pattern"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
// validateAssetInterceptRuleReq normalizes and validates a rule. It trims the
|
||||
// pattern and, for the strictly-formatted kinds (exact_ip / cidr), rejects
|
||||
// malformed values; fuzzy kinds and domain/url patterns are left as free text
|
||||
// (the enforcement layer decides how to interpret them).
|
||||
func validateAssetInterceptRuleReq(req *assetInterceptRuleReq) error {
|
||||
req.Pattern = strings.TrimSpace(req.Pattern)
|
||||
if req.Pattern == "" {
|
||||
return fmt.Errorf("pattern 不能为空")
|
||||
}
|
||||
switch req.Kind {
|
||||
case "exact_domain", "exact_url", "fuzzy_domain", "fuzzy_ip", "fuzzy_url":
|
||||
// free-form, no format check
|
||||
case "exact_ip":
|
||||
if net.ParseIP(req.Pattern) == nil {
|
||||
return fmt.Errorf("exact_ip 不是有效 IP 地址:%s", req.Pattern)
|
||||
}
|
||||
case "cidr":
|
||||
if _, _, err := net.ParseCIDR(req.Pattern); err != nil {
|
||||
return fmt.Errorf("cidr 不是有效网段(形如 192.168.0.0/16):%s", req.Pattern)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("kind 无效:%s", req.Kind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -890,6 +890,14 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("PUT /api/intercept/rules/{id}", s.interceptUpdateRule)
|
||||
mux.HandleFunc("DELETE /api/intercept/rules/{id}", s.interceptDeleteRule)
|
||||
mux.HandleFunc("POST /api/intercept/rules/{id}/toggle", s.interceptToggleRule)
|
||||
|
||||
// 资产拦截规则管理(全局黑名单:域名/IP/URL/CIDR)
|
||||
mux.HandleFunc("GET /api/asset-intercept/rules", s.assetInterceptListRules)
|
||||
mux.HandleFunc("POST /api/asset-intercept/rules", s.assetInterceptCreateRule)
|
||||
mux.HandleFunc("PUT /api/asset-intercept/rules/{id}", s.assetInterceptUpdateRule)
|
||||
mux.HandleFunc("DELETE /api/asset-intercept/rules/{id}", s.assetInterceptDeleteRule)
|
||||
mux.HandleFunc("POST /api/asset-intercept/rules/{id}/toggle", s.assetInterceptToggleRule)
|
||||
|
||||
mux.HandleFunc("GET /api/intercept/pending", s.interceptListPending)
|
||||
mux.HandleFunc("GET /api/intercept/pending/{id}", s.interceptGetOne)
|
||||
mux.HandleFunc("POST /api/intercept/pending/{id}/decide", s.interceptDecide)
|
||||
|
||||
Reference in new issue
Block a user