From 039536defbfb7e741a268c656079e06d635364bd Mon Sep 17 00:00:00 2001 From: Patrik Segedy Date: Fri, 15 Dec 2023 12:12:45 +0100 Subject: [PATCH] RHINENG-3098: deprecate limit=-1 and limit>100 --- base/deprecations/base.go | 45 +++++++++++++++++++++++++------ base/deprecations/deprecations.go | 15 +++++++++++ base/utils/config.go | 2 ++ base/utils/gin.go | 5 ++++ deploy/clowdapp.yaml | 2 ++ manager/routes/routes.go | 1 + 6 files changed, 62 insertions(+), 8 deletions(-) diff --git a/base/deprecations/base.go b/base/deprecations/base.go index e235ecabc..9c2f5c74f 100644 --- a/base/deprecations/base.go +++ b/base/deprecations/base.go @@ -28,6 +28,30 @@ type apiDeprecation struct { message string } +type limitDeprecation struct { + shouldDeprecate func(c *gin.Context) bool + deprecationTimestamp time.Time + message string +} + +func (d limitDeprecation) Deprecate(c *gin.Context) { + if !utils.Cfg.LimitPageSize || !d.shouldDeprecate(c) { + return + } + + now := time.Now() + httpDate := d.deprecationTimestamp.Format(time.RFC1123) + setDeprecationHeader(c, httpDate, d.message) + + if now.Before(d.deprecationTimestamp) { + // allow unlimited `limit` + utils.Cfg.LimitPageSize = false + c.Next() + // reset LimitPageSize after all midllewares + utils.Cfg.LimitPageSize = true + } +} + func (d apiDeprecation) Deprecate(c *gin.Context) { if !d.shouldDeprecate(c) { return @@ -48,14 +72,7 @@ func (d apiDeprecation) Deprecate(c *gin.Context) { func (d *apiDeprecation) setDeprecationHeader(c *gin.Context) { // RFC1123 is HTTP-date format httpDate := d.deprecationTimestamp.Format(time.RFC1123) - - // set Deprecation header - // https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-deprecation-header - c.Header("Deprecation", httpDate) - - // set Sunset header - // https://datatracker.ietf.org/doc/html/rfc8594 - c.Header("Sunset", httpDate) + setDeprecationHeader(c, httpDate, d.message) } func (d *apiDeprecation) redirect(c *gin.Context) { @@ -79,3 +96,15 @@ func (d *apiDeprecation) redirect(c *gin.Context) { func (d *apiDeprecation) gone(c *gin.Context) { c.AbortWithStatusJSON(http.StatusGone, utils.ErrorResponse{Error: d.message}) } + +func setDeprecationHeader(c *gin.Context, httpDate string, message string) { + c.Header("Warning", message) + + // set Deprecation header + // https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-deprecation-header + c.Header("Deprecation", httpDate) + + // set Sunset header + // https://datatracker.ietf.org/doc/html/rfc8594 + c.Header("Sunset", httpDate) +} diff --git a/base/deprecations/deprecations.go b/base/deprecations/deprecations.go index 2052719ab..2907a046d 100644 --- a/base/deprecations/deprecations.go +++ b/base/deprecations/deprecations.go @@ -23,3 +23,18 @@ func DeprecateV1V2APIs() Deprecation { }, } } + +// Deprecate maximum `limit` +func DeprecateLimit() Deprecation { + return limitDeprecation{ + deprecationTimestamp: time.Date(2024, 3, 1, 0, 0, 0, 0, time.UTC), + message: "limit must be in [1, 100]", + shouldDeprecate: func(c *gin.Context) bool { + limit, err := utils.LoadParamInt(c, "limit", 20, true) + if err == nil && (limit < 1 || limit > 100) { + return true + } + return false + }, + } +} diff --git a/base/utils/config.go b/base/utils/config.go index 8f1f01be7..4f7acf157 100644 --- a/base/utils/config.go +++ b/base/utils/config.go @@ -41,6 +41,7 @@ type Config struct { MaxHeaderCount int MaxGinConnections int Ratelimit int + LimitPageSize bool // kafka KafkaServers []string @@ -177,6 +178,7 @@ func initAPIromClowder() { Cfg.MaxHeaderCount = GetIntEnvOrDefault("MAX_HEADER_COUNT", 50) Cfg.MaxGinConnections = GetIntEnvOrDefault("MAX_GIN_CONNECTIONS", 50) Cfg.Ratelimit = GetIntEnvOrDefault("RATELIMIT", 100) + Cfg.LimitPageSize = GetBoolEnvOrDefault("LIMIT_PAGE_SIZE", true) } func initKafkaFromClowder() { diff --git a/base/utils/gin.go b/base/utils/gin.go index 2e7f3001e..58ecaed33 100644 --- a/base/utils/gin.go +++ b/base/utils/gin.go @@ -64,6 +64,11 @@ func CheckLimitOffset(limit int, offset int) error { if offset < 0 { return errors.New("offset must not be negative") } + if Cfg.LimitPageSize { + if limit < 1 || limit > 100 { + return errors.New("limit must be in [1, 100]") + } + } if limit < 1 && limit != -1 { return errors.New("limit must not be less than 1, or should be -1 to return all items") } diff --git a/deploy/clowdapp.yaml b/deploy/clowdapp.yaml index b80fe556e..25052ad4e 100644 --- a/deploy/clowdapp.yaml +++ b/deploy/clowdapp.yaml @@ -127,6 +127,7 @@ objects: - {name: MAX_HEADER_COUNT, value: '${MAX_HEADER_COUNT}'} - {name: MAX_GIN_CONNECTIONS, value: '${MAX_GIN_CONNECTIONS}'} - {name: RATELIMIT, value: '${RATELIMIT}'} + - {name: LIMIT_PAGE_SIZE, value: '${LIMIT_PAGE_SIZE}'} resources: limits: {cpu: '${RES_LIMIT_CPU_MANAGER}', memory: '${RES_LIMIT_MEM_MANAGER}'} @@ -594,6 +595,7 @@ parameters: - {name: MAX_HEADER_COUNT, value: '50'} # limit number of request headers - {name: MAX_GIN_CONNECTIONS, value: '50'} - {name: RATELIMIT, value: '100'} # requests per second for leaky bucket rate limiter +- {name: LIMIT_PAGE_SIZE, value: 'true'} # page size is limited to 100 items per page, set to `false` to use any limit # Listener - {name: REPLICAS_LISTENER, value: '1'} diff --git a/manager/routes/routes.go b/manager/routes/routes.go index 840a5dad8..fc186f82d 100644 --- a/manager/routes/routes.go +++ b/manager/routes/routes.go @@ -17,6 +17,7 @@ func InitAPI(api *gin.RouterGroup, config docs.EndpointsConfig) { // nolint: fun api.Use(middlewares.CheckReferer()) api.Use(middlewares.SetAPIVersion(api.BasePath())) api.Use(middlewares.Deprecate(deprecations.DeprecateV1V2APIs())) + api.Use(middlewares.Deprecate(deprecations.DeprecateLimit())) api.Use(middlewares.DatabaseWithContext()) advisories := api.Group("/advisories")