fix(platform): guard unscoped raw field queries
This commit is contained in:
@@ -186,6 +186,10 @@ func (h *Handler) handleOpsHealth(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *Handler) write(w http.ResponseWriter, r *http.Request, data any, err error) {
|
||||
if err != nil {
|
||||
if clientErr, ok := asClientError(err); ok {
|
||||
httpx.WriteError(w, http.StatusBadRequest, clientErr.Code, clientErr.Message, "", traceID(r))
|
||||
return
|
||||
}
|
||||
httpx.WriteError(w, http.StatusInternalServerError, "INTERNAL", "服务处理失败", err.Error(), traceID(r))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -941,6 +941,47 @@ func TestHandlerRawFramesPostAcceptsKeyword(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRawFramesRejectsUnscopedIncludeFields(t *testing.T) {
|
||||
handler := NewHandler(NewService(NewMockStore()))
|
||||
cases := []struct {
|
||||
name string
|
||||
req *http.Request
|
||||
}{
|
||||
{
|
||||
name: "get",
|
||||
req: httptest.NewRequest(http.MethodGet, "/api/history/raw-frames?limit=1&includeFields=true", nil),
|
||||
},
|
||||
{
|
||||
name: "post",
|
||||
req: httptest.NewRequest(http.MethodPost, "/api/history/raw-frames/query", strings.NewReader(`{"limit":1,"includeFields":true}`)),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, tc.req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var body struct {
|
||||
Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("response JSON should decode: %v body=%s", err, rec.Body.String())
|
||||
}
|
||||
if body.Error.Code != "RAW_FRAME_SCOPE_REQUIRED" {
|
||||
t.Fatalf("error code = %q body=%s", body.Error.Code, rec.Body.String())
|
||||
}
|
||||
if !strings.Contains(body.Error.Message, "车辆或时间范围") {
|
||||
t.Fatalf("error message should explain required scope: %s", rec.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRawFramesKeepsUnresolvedKeywordEmpty(t *testing.T) {
|
||||
handler := NewHandler(NewService(NewMockStore()))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -2,6 +2,7 @@ package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"net/url"
|
||||
"sort"
|
||||
@@ -56,6 +57,23 @@ type Service struct {
|
||||
runtime RuntimeInfo
|
||||
}
|
||||
|
||||
type clientError struct {
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e clientError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func asClientError(err error) (clientError, bool) {
|
||||
var target clientError
|
||||
if errors.As(err, &target) {
|
||||
return target, true
|
||||
}
|
||||
return clientError{}, false
|
||||
}
|
||||
|
||||
var canonicalVehicleProtocols = []string{"GB32960", "JT808", "YUTONG_MQTT"}
|
||||
|
||||
func completeProtocolStats(stats []ProtocolStat) []ProtocolStat {
|
||||
@@ -737,6 +755,12 @@ func (s *Service) RawFrames(ctx context.Context, query RawFrameQuery) (Page[RawF
|
||||
if query.Limit <= 0 || query.Limit > 500 {
|
||||
query.Limit = 100
|
||||
}
|
||||
if query.IncludeFields && !rawFrameQueryHasNarrowScope(query) {
|
||||
return Page[RawFrameRow]{}, clientError{
|
||||
Code: "RAW_FRAME_SCOPE_REQUIRED",
|
||||
Message: "RAW 解析字段查询需要提供车辆或时间范围",
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(query.VIN) == "" {
|
||||
query.VIN = query.Keyword
|
||||
}
|
||||
@@ -750,6 +774,14 @@ func (s *Service) RawFrames(ctx context.Context, query RawFrameQuery) (Page[RawF
|
||||
return s.store.RawFrames(ctx, query)
|
||||
}
|
||||
|
||||
func rawFrameQueryHasNarrowScope(query RawFrameQuery) bool {
|
||||
return strings.TrimSpace(query.VIN) != "" ||
|
||||
strings.TrimSpace(query.Keyword) != "" ||
|
||||
strings.TrimSpace(query.DateFrom) != "" ||
|
||||
strings.TrimSpace(query.DateTo) != "" ||
|
||||
len(query.Fields) > 0
|
||||
}
|
||||
|
||||
func (s *Service) MileageSummary(ctx context.Context, query url.Values) (MileageSummary, error) {
|
||||
resolvedQuery, err := s.resolveVehicleQuery(ctx, query)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user