优化结构
This commit is contained in:
@@ -27,6 +27,9 @@ func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, h
|
||||
}
|
||||
|
||||
func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessControlService, auth middleware.TokenAuthenticator, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string, routes *platformmodule.Runtime, ws *websocket.Server) *gin.Engine {
|
||||
if runtime == nil {
|
||||
runtime = conf.NewRuntime(nil, nil)
|
||||
}
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
engine := gin.New()
|
||||
if err := engine.SetTrustedProxies(nil); err != nil && logger != nil {
|
||||
|
||||
@@ -232,15 +232,24 @@ func failLogViewer(c *gin.Context, err error, logger *slog.Logger) {
|
||||
}
|
||||
|
||||
func (h *Audit) DeleteError(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
if err := h.service.DeleteErrors(c.Request.Context(), []uint{uint(id)}); err != nil {
|
||||
id, err := positiveUintQuery(c, "ID")
|
||||
if err != nil {
|
||||
Fail(c, "错误记录ID非法")
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteErrors(c.Request.Context(), []uint{id}); err != nil {
|
||||
Fail(c, "删除失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
Write(c, CodeSuccess, gin.H{}, "删除成功")
|
||||
}
|
||||
func (h *Audit) DeleteErrors(c *gin.Context) {
|
||||
if err := h.service.DeleteErrors(c.Request.Context(), IDsFromQuery(c)); err != nil {
|
||||
ids, err := IDsFromQuery(c)
|
||||
if err != nil {
|
||||
Fail(c, "错误记录ID非法")
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteErrors(c.Request.Context(), ids); err != nil {
|
||||
Fail(c, "批量删除失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
@@ -259,8 +268,12 @@ func (h *Audit) UpdateError(c *gin.Context) {
|
||||
Write(c, CodeSuccess, gin.H{}, "更新成功")
|
||||
}
|
||||
func (h *Audit) Error(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := h.service.Error(c.Request.Context(), uint(id))
|
||||
id, err := positiveUintQuery(c, "ID")
|
||||
if err != nil {
|
||||
Fail(c, "错误记录ID非法")
|
||||
return
|
||||
}
|
||||
item, err := h.service.Error(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
Fail(c, "查询失败:"+err.Error())
|
||||
return
|
||||
|
||||
@@ -142,7 +142,11 @@ func (h *Export) Preview(c *gin.Context) {
|
||||
Fail(c, "模板ID不能为空")
|
||||
return
|
||||
}
|
||||
params, _ := exportParams(c.Request.URL.Query())
|
||||
params, err := exportParams(c.Request.URL.Query())
|
||||
if err != nil {
|
||||
Fail(c, "解析 params 参数失败")
|
||||
return
|
||||
}
|
||||
sql, err := h.service.Preview(c.Request.Context(), templateID, params)
|
||||
if err != nil {
|
||||
Fail(c, "获取失败")
|
||||
|
||||
@@ -56,7 +56,11 @@ func (h *Media) Upload(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
defer opened.Close()
|
||||
category, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
|
||||
category, parseErr := strconv.Atoi(c.DefaultPostForm("classId", "0"))
|
||||
if parseErr != nil || category < 0 {
|
||||
Fail(c, "文件分类 ID 非法")
|
||||
return
|
||||
}
|
||||
save := c.DefaultQuery("noSave", "0") == "0"
|
||||
item, err := h.service.Upload(c.Request.Context(), claims.ID, header.Filename, header.Header.Get("Content-Type"), category, opened, save)
|
||||
if err != nil {
|
||||
@@ -276,12 +280,16 @@ func (h *Media) CompleteUpload(c *gin.Context) {
|
||||
}
|
||||
func (h *Media) CancelUpload(c *gin.Context) {
|
||||
claims := Claims(c)
|
||||
id, _ := strconv.ParseUint(c.Param("uploadId"), 10, 64)
|
||||
if claims == nil {
|
||||
Fail(c, "未登录")
|
||||
return
|
||||
}
|
||||
if err := h.service.CancelUpload(c.Request.Context(), claims.ID, uint(id)); err != nil {
|
||||
id, err := positiveUintParam(c, "uploadId")
|
||||
if err != nil {
|
||||
Fail(c, "上传会话 ID 非法")
|
||||
return
|
||||
}
|
||||
if err := h.service.CancelUpload(c.Request.Context(), claims.ID, id); err != nil {
|
||||
Fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var errInvalidPositiveUint = errors.New("参数必须是正整数")
|
||||
|
||||
func positiveUint(raw string) (uint, error) {
|
||||
parsed, err := strconv.ParseUint(strings.TrimSpace(raw), 10, 64)
|
||||
if err != nil || parsed == 0 || uint64(uint(parsed)) != parsed {
|
||||
return 0, errInvalidPositiveUint
|
||||
}
|
||||
return uint(parsed), nil
|
||||
}
|
||||
|
||||
func positiveUintQuery(c *gin.Context, key string) (uint, error) {
|
||||
return positiveUint(c.Query(key))
|
||||
}
|
||||
|
||||
func positiveUintParam(c *gin.Context, key string) (uint, error) {
|
||||
return positiveUint(c.Param(key))
|
||||
}
|
||||
|
||||
func page(c *gin.Context) (int, int, error) {
|
||||
var value, size int
|
||||
if raw, exists := c.GetQuery("page"); exists && raw != "" {
|
||||
@@ -25,14 +45,18 @@ func page(c *gin.Context) (int, int, error) {
|
||||
return value, size, nil
|
||||
}
|
||||
|
||||
func IDsFromQuery(c *gin.Context) []uint {
|
||||
func IDsFromQuery(c *gin.Context) ([]uint, error) {
|
||||
values := c.QueryArray("IDs[]")
|
||||
if len(values) == 0 {
|
||||
return nil, errInvalidPositiveUint
|
||||
}
|
||||
ids := make([]uint, 0, len(values))
|
||||
for _, value := range values {
|
||||
id, _ := strconv.ParseUint(value, 10, 64)
|
||||
if id > 0 {
|
||||
ids = append(ids, uint(id))
|
||||
id, err := positiveUint(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"kra/internal/server/middleware"
|
||||
"kra/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -10,9 +11,10 @@ type Session struct{ tokens *service.TokenService }
|
||||
|
||||
func NewSession(tokens *service.TokenService) *Session { return &Session{tokens: tokens} }
|
||||
func (h *Session) Logout(c *gin.Context) {
|
||||
token := c.GetHeader("x-token")
|
||||
token := middleware.RequestToken(c, false)
|
||||
if token == "" {
|
||||
token, _ = c.Cookie("x-token")
|
||||
NoAuth(c, "未登录或非法访问")
|
||||
return
|
||||
}
|
||||
if err := h.tokens.BlacklistToken(c.Request.Context(), token); err != nil {
|
||||
Fail(c, "jwt作废失败")
|
||||
|
||||
@@ -27,8 +27,12 @@ func versionStage(err error) (system.VersionStage, bool) {
|
||||
}
|
||||
|
||||
func (h *Version) Delete(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
if err := h.service.DeleteVersions(c.Request.Context(), []uint{uint(id)}); err != nil {
|
||||
id, err := positiveUintQuery(c, "ID")
|
||||
if err != nil {
|
||||
Fail(c, "版本ID非法")
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteVersions(c.Request.Context(), []uint{id}); err != nil {
|
||||
Fail(c, "删除失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
@@ -38,8 +42,16 @@ func (h *Version) DeleteMany(c *gin.Context) {
|
||||
raw := c.QueryArray("IDs[]")
|
||||
ids := make([]uint, 0, len(raw))
|
||||
for _, value := range raw {
|
||||
id, _ := strconv.ParseUint(value, 10, 64)
|
||||
ids = append(ids, uint(id))
|
||||
id, err := positiveUint(value)
|
||||
if err != nil {
|
||||
Fail(c, "版本ID非法")
|
||||
return
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
Fail(c, "版本ID不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteVersions(c.Request.Context(), ids); err != nil {
|
||||
Fail(c, "批量删除失败:"+err.Error())
|
||||
@@ -48,8 +60,12 @@ func (h *Version) DeleteMany(c *gin.Context) {
|
||||
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
|
||||
}
|
||||
func (h *Version) Find(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
|
||||
item, err := h.service.Version(c.Request.Context(), uint(id))
|
||||
id, err := positiveUintQuery(c, "ID")
|
||||
if err != nil {
|
||||
Fail(c, "版本ID非法")
|
||||
return
|
||||
}
|
||||
item, err := h.service.Version(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
Fail(c, "查询失败:"+err.Error())
|
||||
return
|
||||
@@ -112,8 +128,12 @@ func (h *Version) Download(c *gin.Context) {
|
||||
Fail(c, "版本ID不能为空")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(rawID, 10, 64)
|
||||
raw, code, err := h.service.VersionData(c.Request.Context(), uint(id))
|
||||
id, err := positiveUint(rawID)
|
||||
if err != nil {
|
||||
Fail(c, "版本ID非法")
|
||||
return
|
||||
}
|
||||
raw, code, err := h.service.VersionData(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
Fail(c, "获取版本记录失败:"+err.Error())
|
||||
return
|
||||
|
||||
@@ -22,6 +22,11 @@ func AccessControl(runtime *conf.Runtime, access accessController) gin.HandlerFu
|
||||
NoAuth(c, "未登录或非法访问")
|
||||
return
|
||||
}
|
||||
if access == nil {
|
||||
Write(c, CodeError, gin.H{}, "权限服务不可用")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
path := c.Request.URL.Path
|
||||
policyPath := path
|
||||
if config := runtime.Admin(); config != nil {
|
||||
|
||||
@@ -30,7 +30,10 @@ func AccessLog(runtime *conf.Runtime, logger *slog.Logger, version string) gin.H
|
||||
var requestBody []byte
|
||||
multipart := strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data")
|
||||
mediaUpload := multipart && isMediaUploadRoute(c.FullPath())
|
||||
config := runtime.Admin()
|
||||
var config *conf.AdminBackend
|
||||
if runtime != nil {
|
||||
config = runtime.Admin()
|
||||
}
|
||||
bodyLimit := defaultRequestBodyLimit
|
||||
if mediaUpload {
|
||||
bodyLimit = system.DefaultMaxMediaFileSize + (1 << 20)
|
||||
|
||||
@@ -23,6 +23,10 @@ const ctxOperationAuditPersistFailedKey = "operation_audit_persist_failed"
|
||||
|
||||
func OperationAudit(runtime *conf.Runtime, recorder *service.AuditRecorder) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if runtime == nil || recorder == nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
path := c.Request.URL.Path
|
||||
if !recordsOperation(c.Request.Method, path) {
|
||||
c.Next()
|
||||
@@ -95,12 +99,11 @@ func OperationAudit(runtime *conf.Runtime, recorder *service.AuditRecorder) gin.
|
||||
}
|
||||
|
||||
func operationQueryBody(raw string) []byte {
|
||||
query, _ := url.QueryUnescape(raw)
|
||||
values := make(map[string]string)
|
||||
for _, item := range strings.Split(query, "&") {
|
||||
parts := strings.Split(item, "=")
|
||||
if len(parts) == 2 {
|
||||
values[parts[0]] = parts[1]
|
||||
parsed, _ := url.ParseQuery(raw)
|
||||
values := make(map[string]string, len(parsed))
|
||||
for key, items := range parsed {
|
||||
if len(items) > 0 {
|
||||
values[key] = items[len(items)-1]
|
||||
}
|
||||
}
|
||||
body, _ := json.Marshal(&values)
|
||||
@@ -189,23 +192,6 @@ func operationPathMatches(pattern, path string) bool {
|
||||
return len(patternParts) == len(pathParts)
|
||||
}
|
||||
|
||||
func routeSuffix(path string) string {
|
||||
bestIndex := -1
|
||||
bestPath := path
|
||||
for _, marker := range []string{"/user/", "/api/", "/casbin/", "/authority/", "/menu/", "/department/", "/position/", "/sysDictionary/", "/sysDictionaryDetail/", "/sysParams/", "/securityConfig/", "/system/", "/sysApiToken/", "/sysVersion/", "/sysExportTemplate/", "/sysError/", "/sysLoginLog/", "/sysOperationRecord/", "/dataAccessLog/", "/timedTask/", "/info/", "/email/", "/integration/", "/payment/"} {
|
||||
if index := strings.Index(path, marker); index >= 0 {
|
||||
// Router prefixes may themselves contain a registered route marker
|
||||
// (for example /api/integration/...). Keep the deepest match so the
|
||||
// policy and audit route remain the actual application endpoint.
|
||||
if index > bestIndex {
|
||||
bestIndex = index
|
||||
bestPath = path[index:]
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestPath
|
||||
}
|
||||
|
||||
var operationRoutes = func() map[string]struct{} {
|
||||
values := []string{
|
||||
"POST /user/admin_register", "POST /user/changePassword", "POST /user/setUserAuthority", "DELETE /user/deleteUser", "PUT /user/setUserInfo", "PUT /user/setSelfInfo", "POST /user/setUserAuthorities", "POST /user/setUserDepartments", "POST /user/setUserPositions", "POST /user/resetPassword", "PUT /user/setSelfSetting",
|
||||
|
||||
@@ -68,7 +68,9 @@ func authenticate(c *gin.Context, auth TokenAuthenticator, allowQueryToken bool)
|
||||
return true
|
||||
}
|
||||
|
||||
func requestToken(c *gin.Context, allowQueryToken bool) string {
|
||||
// RequestToken extracts the authentication token accepted by HTTP handlers.
|
||||
// Query-string tokens are opt-in for WebSocket handshakes only.
|
||||
func RequestToken(c *gin.Context, allowQueryToken bool) string {
|
||||
token := strings.TrimSpace(c.GetHeader("x-token"))
|
||||
if token == "" {
|
||||
authorization := strings.TrimSpace(c.GetHeader("Authorization"))
|
||||
@@ -91,6 +93,10 @@ func requestToken(c *gin.Context, allowQueryToken bool) string {
|
||||
return token
|
||||
}
|
||||
|
||||
func requestToken(c *gin.Context, allowQueryToken bool) string {
|
||||
return RequestToken(c, allowQueryToken)
|
||||
}
|
||||
|
||||
func tokenErrorMessage(err error) string {
|
||||
message := "无法处理此token"
|
||||
switch {
|
||||
|
||||
@@ -19,6 +19,10 @@ const (
|
||||
// configuration reload takes effect without rebuilding the Gin engine.
|
||||
func CORS(runtime *conf.Runtime) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if runtime == nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
config := runtime.Admin()
|
||||
if config == nil || config.Cors == nil {
|
||||
c.Next()
|
||||
@@ -28,12 +32,14 @@ func CORS(runtime *conf.Runtime) gin.HandlerFunc {
|
||||
origin := c.GetHeader("Origin")
|
||||
corsHandled := false
|
||||
if mode == "allow-all" {
|
||||
setCORSHeaders(c, origin, defaultCORSHeaders, defaultCORSMethods, defaultCORSExpose, true)
|
||||
corsHandled = true
|
||||
if origin != "" {
|
||||
setCORSHeaders(c, origin, defaultCORSHeaders, defaultCORSMethods, defaultCORSExpose, true)
|
||||
corsHandled = true
|
||||
}
|
||||
} else if rule := matchingCORSRule(config.Cors.Whitelist, origin); rule != nil {
|
||||
setCORSHeaders(c, rule.AllowOrigin, rule.AllowHeaders, rule.AllowMethods, rule.ExposeHeaders, rule.AllowCredentials)
|
||||
corsHandled = true
|
||||
} else if mode == "strict-whitelist" && !(c.Request.Method == http.MethodGet && c.Request.URL.Path == "/health") {
|
||||
} else if mode == "strict-whitelist" && !(c.Request.Method == http.MethodGet && isHealthPath(c.Request.URL.Path)) {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -45,6 +51,11 @@ func CORS(runtime *conf.Runtime) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func isHealthPath(path string) bool {
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
return path == "/health" || strings.HasSuffix(path, "/health")
|
||||
}
|
||||
|
||||
func matchingCORSRule(rules []*conf.AdminBackend_CORSRule, origin string) *conf.AdminBackend_CORSRule {
|
||||
for _, rule := range rules {
|
||||
if rule != nil && origin == rule.AllowOrigin {
|
||||
@@ -55,6 +66,7 @@ func matchingCORSRule(rules []*conf.AdminBackend_CORSRule, origin string) *conf.
|
||||
}
|
||||
|
||||
func setCORSHeaders(c *gin.Context, origin, headers, methods, expose string, credentials bool) {
|
||||
c.Header("Vary", "Origin")
|
||||
c.Header("Access-Control-Allow-Origin", origin)
|
||||
c.Header("Access-Control-Allow-Headers", headers)
|
||||
c.Header("Access-Control-Allow-Methods", methods)
|
||||
|
||||
@@ -37,3 +37,9 @@ func TestCORSConsumesMatchedWhitelistPreflight(t *testing.T) {
|
||||
t.Fatalf("matched whitelist preflight status = %d, want %d", response.Code, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORSStrictWhitelistAllowsPrefixedHealth(t *testing.T) {
|
||||
if !isHealthPath("/admin/health") {
|
||||
t.Fatal("prefixed health endpoint was not recognized by strict whitelist")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -12,6 +13,16 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestOperationQueryBodyPreservesEncodedSeparators(t *testing.T) {
|
||||
var values map[string]string
|
||||
if err := json.Unmarshal(operationQueryBody("filter=one%3Dtwo%26three&repeat=first&repeat=last"), &values); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if values["filter"] != "one=two&three" || values["repeat"] != "last" {
|
||||
t.Fatalf("unexpected query body: %#v", values)
|
||||
}
|
||||
}
|
||||
|
||||
func runErrorAudit(t *testing.T, path, response string, logger *slog.Logger) {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
@@ -16,6 +16,10 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if settings == nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
config, err := settings.CurrentSecurity(c.Request.Context())
|
||||
if err != nil || config == nil {
|
||||
c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": CodeError, "msg": "安全服务暂不可用"})
|
||||
@@ -29,7 +33,11 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc {
|
||||
if window < 1 {
|
||||
window = 60
|
||||
}
|
||||
key := "KRA_SecLimit" + c.ClientIP() + c.FullPath()
|
||||
route := c.FullPath()
|
||||
if route == "" {
|
||||
route = path
|
||||
}
|
||||
key := "KRA_SecLimit:" + c.ClientIP() + ":" + route
|
||||
count, cacheErr := settings.IncrementRateLimit(c.Request.Context(), key, time.Duration(window)*time.Second)
|
||||
if cacheErr != nil {
|
||||
c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": CodeError, "msg": "安全服务暂不可用"})
|
||||
|
||||
@@ -57,6 +57,7 @@ func registerSwagger(engine *gin.Engine, prefix, version string, logger *slog.Lo
|
||||
}
|
||||
|
||||
func buildSwaggerDocument(routes []gin.RouteInfo, prefix, version string) string {
|
||||
routes = append([]gin.RouteInfo(nil), routes...)
|
||||
basePath := strings.TrimSuffix(prefix, "/")
|
||||
if basePath == "" {
|
||||
basePath = "/"
|
||||
@@ -159,8 +160,8 @@ func swaggerPathParameters(path string) []map[string]any {
|
||||
func swaggerPublicPath(path string) bool {
|
||||
for _, marker := range []string{
|
||||
"/health", "/base/login", "/base/captcha", "/init/checkdb", "/init/initdb",
|
||||
"/api/freshCasbin", "/sysExportTemplate/exportExcelByToken", "/sysExportTemplate/exportTemplateByToken",
|
||||
"/sysError/createSysError", "/info/getInfoDataSource", "/info/getInfoPublic",
|
||||
"/sysExportTemplate/exportExcelByToken", "/sysExportTemplate/exportTemplateByToken",
|
||||
"/sysError/createSysError", "/info/getInfoPublic",
|
||||
} {
|
||||
if path == marker {
|
||||
return true
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestSwaggerMarksOnlyPublicRoutesWithoutAuth(t *testing.T) {
|
||||
document := buildSwaggerDocument([]gin.RouteInfo{
|
||||
{Method: "GET", Path: "/api/freshCasbin"},
|
||||
{Method: "GET", Path: "/info/getInfoDataSource"},
|
||||
{Method: "GET", Path: "/info/getInfoPublic"},
|
||||
}, "", "test")
|
||||
var payload struct {
|
||||
Paths map[string]map[string]map[string]any `json:"paths"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(document), &payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, path := range []string{"/api/freshCasbin", "/info/getInfoDataSource"} {
|
||||
if _, ok := payload.Paths[path]["get"]["security"]; !ok {
|
||||
t.Fatalf("private route %s was marked public", path)
|
||||
}
|
||||
}
|
||||
if _, ok := payload.Paths["/info/getInfoPublic"]["get"]["security"]; ok {
|
||||
t.Fatal("public route /info/getInfoPublic requires authentication")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user