package middleware import ( "apiserver/common" "apiserver/controllers" "apiserver/models" "apiserver/store" "apiserver/utils" logger "apiserver/utils" "bytes" "encoding/json" "errors" "io" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" "go.uber.org/zap" ) func ValidateHandler() gin.HandlerFunc { logger.Debug("Enter ValidateHandler() function") return func(ctx *gin.Context) { if isExcludedPath(ctx.Request.URL.Path) { ctx.Next() return } validateHeader()(ctx) if ctx.IsAborted() { return } flag := !strings.Contains("multipart/form-data", ctx.Request.Header.Get("Content-Type")) if ctx.Request.Method == http.MethodPost && flag == false { isValidBody, err := validateRequestBody(ctx) if err != nil || !isValidBody { controllers.ReturnError(ctx, common.INVALID_PARAMS_CODE, common.ERROR_READ_REQUEST_BODY_MSG+": "+err.Error(), nil) return } } if ctx.IsAborted() { return } ctx.Next() } } func isExcludedPath(path string) bool { allowed := false // define some excluded paths excludedPaths := []string{ "/ping", "/xce/v1/auth/logon", "/swagger/*any", "/xce/v1/auth/logout", } for _, excludedPath := range excludedPaths { if strings.HasPrefix(path, excludedPath[:len(excludedPath)-1]) { // 去掉最后的 * allowed = true break } } return allowed } // Token validation middleware func validateHeader() gin.HandlerFunc { logger.Debug("Enter validateHeader() function") return func(ctx *gin.Context) { //continue to validate token token := ctx.GetHeader("Authorization") if token == "" { logger.Error("Token is empty") controllers.ReturnError(ctx, common.ERROR_AUTH_CHECK_TOKEN_CODE, common.ERROR_AUTH_CHECK_TOKEN_EMPTY_MSG, nil) ctx.Abort() return } // Validate token (this is a placeholder for your actual token validation logic) isValid, err := isValidToken(strings.TrimPrefix(token, "Bearer "), ctx.ClientIP()) if err != nil { logger.Error("Validate token error", zap.Error(err)) controllers.ReturnError(ctx, common.ERROR_AUTH_CHECK_TOKEN_CODE, common.ERROR_AUTH_MSG+": "+err.Error(), nil) ctx.Abort() return } if !isValid { logger.Error("Token is invalid") controllers.ReturnError(ctx, common.ERROR_AUTH_CHECK_TOKEN_CODE, common.ERROR_AUTH_MSG, nil) ctx.Abort() return } } } func isValidToken(tokenString string, IP string) (bool, error) { // Service-account API keys are a distinct credential type from JWTs and // carry no IP binding (they're meant for long-lived automated clients // like the scheduler-agent MCP server, which may not have a stable IP). if strings.HasPrefix(tokenString, models.APIKeyPrefix) { _, ok := store.ValidateAPIKey(tokenString) return ok, nil } token, err := jwt.ParseWithClaims(tokenString, &models.JWTClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(utils.AuthenticationYamlConfig.Token.SecretKey), nil }) if err != nil { return false, err } if claims, ok := token.Claims.(*models.JWTClaims); ok && token.Valid { if claims.IP == IP { return true, nil } } return false, nil } func validateRequestBody(ctx *gin.Context) (bool, error) { logger.Debug("Enter validateRequestBody function") // read request body and resolve EOF error body, err := ctx.GetRawData() if err != nil { logger.Error("Failed to read request body", zap.Error(err)) ctx.Abort() return false, err } if len(body) == 0 { logger.Error("Request body is empty") ctx.Abort() return false, errors.New(common.ERROR_REQUEST_BODY_EMPTY_MSG) } // reset request body ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body)) // check request body is valid if !json.Valid(body) { logger.Error("Request body is not valid", zap.Error(err)) ctx.Abort() return false, errors.New(common.ERROR_REQUEST_BODY_NOT_JSON_MSG) } return true, nil }