Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement api-level request size limit for oas (TT-11459) #6822

Merged
merged 6 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apidef/api_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ type VersionInfo struct {
GlobalResponseHeadersDisabled bool `bson:"global_response_headers_disabled" json:"global_response_headers_disabled"`
IgnoreEndpointCase bool `bson:"ignore_endpoint_case" json:"ignore_endpoint_case"`
GlobalSizeLimit int64 `bson:"global_size_limit" json:"global_size_limit"`
GlobalSizeLimitDisabled bool `bson:"global_size_limit_disabled" json:"global_size_limit_disabled"`
OverrideTarget string `bson:"override_target" json:"override_target"`
}

Expand Down
81 changes: 74 additions & 7 deletions apidef/oas/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ type Global struct {

// TrafficLogs contains the configurations related to API level log analytics.
TrafficLogs *TrafficLogs `bson:"trafficLogs,omitempty" json:"trafficLogs,omitempty"`

// RequestSizeLimit contains the configuration related to limiting the global request size.
RequestSizeLimit *GlobalRequestSizeLimit `bson:"requestSizeLimit,omitempty" json:"requestSizeLimit,omitempty"`
}

// MarshalJSON is a custom JSON marshaler for the Global struct. It is implemented
Expand Down Expand Up @@ -227,6 +230,8 @@ func (g *Global) Fill(api apidef.APIDefinition) {
g.fillContextVariables(api)

g.fillTrafficLogs(api)

g.fillRequestSizeLimit(api)
}

func (g *Global) fillTrafficLogs(api apidef.APIDefinition) {
Expand All @@ -240,6 +245,17 @@ func (g *Global) fillTrafficLogs(api apidef.APIDefinition) {
}
}

func (g *Global) fillRequestSizeLimit(api apidef.APIDefinition) {
if g.RequestSizeLimit == nil {
g.RequestSizeLimit = &GlobalRequestSizeLimit{}
}

g.RequestSizeLimit.Fill(api)
if ShouldOmit(g.RequestSizeLimit) {
g.RequestSizeLimit = nil
}
}

func (g *Global) fillContextVariables(api apidef.APIDefinition) {
if g.ContextVariables == nil {
g.ContextVariables = &ContextVariables{}
Expand Down Expand Up @@ -312,12 +328,7 @@ func (g *Global) ExtractTo(api *apidef.APIDefinition) {
var resHeaderMeta apidef.HeaderInjectionMeta
g.TransformResponseHeaders.ExtractTo(&resHeaderMeta)

if len(api.VersionData.Versions) == 0 {
api.VersionData.Versions = map[string]apidef.VersionInfo{
Main: {},
}
}

requireMainVersion(api)
vInfo := api.VersionData.Versions[Main]
vInfo.GlobalHeadersDisabled = headerMeta.Disabled
vInfo.GlobalHeaders = headerMeta.AddHeaders
Expand All @@ -326,7 +337,9 @@ func (g *Global) ExtractTo(api *apidef.APIDefinition) {
vInfo.GlobalResponseHeadersDisabled = resHeaderMeta.Disabled
vInfo.GlobalResponseHeaders = resHeaderMeta.AddHeaders
vInfo.GlobalResponseHeadersRemove = resHeaderMeta.DeleteHeaders
api.VersionData.Versions[Main] = vInfo
updateMainVersion(api, vInfo)

g.extractRequestSizeLimitTo(api)
}

func (g *Global) extractTrafficLogsTo(api *apidef.APIDefinition) {
Expand All @@ -340,6 +353,17 @@ func (g *Global) extractTrafficLogsTo(api *apidef.APIDefinition) {
g.TrafficLogs.ExtractTo(api)
}

func (g *Global) extractRequestSizeLimitTo(api *apidef.APIDefinition) {
if g.RequestSizeLimit == nil {
g.RequestSizeLimit = &GlobalRequestSizeLimit{}
defer func() {
g.RequestSizeLimit = nil
}()
}

g.RequestSizeLimit.ExtractTo(api)
}

func (g *Global) extractContextVariablesTo(api *apidef.APIDefinition) {
if g.ContextVariables == nil {
g.ContextVariables = &ContextVariables{}
Expand Down Expand Up @@ -1570,6 +1594,49 @@ func (t *TrafficLogs) ExtractTo(api *apidef.APIDefinition) {
api.TagHeaders = t.TagHeaders
}

// GlobalRequestSizeLimit holds configuration about the global limits for request sizes.
type GlobalRequestSizeLimit struct {
// Enabled activates the Request Size Limit.
// Tyk classic API definition: `version_data.versions..global_size_limit_disabled`.
Enabled bool `bson:"enabled" json:"enabled"`
// Value contains the value of the request size limit.
// Tyk classic API definition: `version_data.versions..global_size_limit`.
Value int64 `bson:"value" json:"value"`
}

// Fill fills *GlobalRequestSizeLimit from apidef.APIDefinition.
func (g *GlobalRequestSizeLimit) Fill(api apidef.APIDefinition) {
ok := false
if api.VersionData.Versions != nil {
_, ok = api.VersionData.Versions[Main]
}
if !ok || api.VersionData.Versions[Main].GlobalSizeLimit == 0 {
g.Enabled = false
g.Value = 0
return
}

g.Enabled = !api.VersionData.Versions[Main].GlobalSizeLimitDisabled
g.Value = api.VersionData.Versions[Main].GlobalSizeLimit
}

// ExtractTo extracts *GlobalRequestSizeLimit into *apidef.APIDefinition.
func (g *GlobalRequestSizeLimit) ExtractTo(api *apidef.APIDefinition) {
mainVersion := requireMainVersion(api)
defer func() {
updateMainVersion(api, mainVersion)
}()

if g.Value == 0 {
mainVersion.GlobalSizeLimit = 0
mainVersion.GlobalSizeLimitDisabled = true
return
}

mainVersion.GlobalSizeLimitDisabled = !g.Enabled
mainVersion.GlobalSizeLimit = g.Value
}

// ContextVariables holds the configuration related to Tyk context variables.
type ContextVariables struct {
// Enabled enables context variables to be passed to Tyk middlewares.
Expand Down
144 changes: 144 additions & 0 deletions apidef/oas/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1086,3 +1086,147 @@ func TestContextVariables(t *testing.T) {
}
})
}

func TestGlobalRequestSizeLimit(t *testing.T) {
t.Parallel()
t.Run("fill", func(t *testing.T) {
t.Parallel()
testcases := []struct {
title string
input apidef.APIDefinition
expected *GlobalRequestSizeLimit
}{
{
title: "no versions",
input: apidef.APIDefinition{},
expected: nil,
},
{
title: "no main version",
input: apidef.APIDefinition{
VersionData: apidef.VersionData{
Versions: map[string]apidef.VersionInfo{
"NotMain": {},
},
},
},
expected: nil,
},
{
title: "request size limit set to 0 (disabled)",
input: apidef.APIDefinition{
VersionData: apidef.VersionData{
Versions: map[string]apidef.VersionInfo{
Main: {
GlobalSizeLimit: 0,
GlobalSizeLimitDisabled: false,
},
},
},
},
expected: nil,
},
{
title: "request size limit set to some value (enabled)",
input: apidef.APIDefinition{
VersionData: apidef.VersionData{
Versions: map[string]apidef.VersionInfo{
Main: {
GlobalSizeLimit: 5000,
GlobalSizeLimitDisabled: false,
},
},
},
},
expected: &GlobalRequestSizeLimit{
Enabled: true,
Value: 5000,
},
},
{
title: "request size limit set to some value but disabled",
input: apidef.APIDefinition{
VersionData: apidef.VersionData{
Versions: map[string]apidef.VersionInfo{
Main: {
GlobalSizeLimit: 5000,
GlobalSizeLimitDisabled: true,
},
},
},
},
expected: &GlobalRequestSizeLimit{
Enabled: false,
Value: 5000,
},
},
}

for _, tc := range testcases {
tc := tc
t.Run(tc.title, func(t *testing.T) {
t.Parallel()

g := new(Global)
g.Fill(tc.input)

assert.Equal(t, tc.expected, g.RequestSizeLimit)
})
}
})

t.Run("extractTo", func(t *testing.T) {
t.Parallel()

testcases := []struct {
title string
input *GlobalRequestSizeLimit
expectedGlobalRequestSizeLimit int64
expectedGlobalRequestSizeLimitDisabled bool
}{
{
title: "request size limit set to 0 (disabled)",
input: &GlobalRequestSizeLimit{
Enabled: true,
Value: 0,
},
expectedGlobalRequestSizeLimit: 0,
expectedGlobalRequestSizeLimitDisabled: true,
},
{
title: "request size limit set to a value (enabled)",
input: &GlobalRequestSizeLimit{
Enabled: true,
Value: 5000,
},
expectedGlobalRequestSizeLimit: 5000,
expectedGlobalRequestSizeLimitDisabled: false,
},
{
title: "request size limit set to a value and disabled",
input: &GlobalRequestSizeLimit{
Enabled: false,
Value: 5000,
},
expectedGlobalRequestSizeLimit: 5000,
expectedGlobalRequestSizeLimitDisabled: true,
},
}

for _, tc := range testcases {
tc := tc // Creating a new 'tc' scoped to the loop
t.Run(tc.title, func(t *testing.T) {
t.Parallel()

g := new(Global)
g.RequestSizeLimit = tc.input

var apiDef apidef.APIDefinition
g.ExtractTo(&apiDef)

assert.Equal(t, tc.expectedGlobalRequestSizeLimit, apiDef.VersionData.Versions[Main].GlobalSizeLimit)
assert.Equal(t, tc.expectedGlobalRequestSizeLimitDisabled, apiDef.VersionData.Versions[Main].GlobalSizeLimitDisabled)
})
}
})
}
2 changes: 1 addition & 1 deletion apidef/oas/oas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ func TestOAS_ExtractTo_ResetAPIDefinition(t *testing.T) {
vInfo.GlobalHeadersDisabled = false
vInfo.GlobalResponseHeadersDisabled = false
vInfo.UseExtendedPaths = false
vInfo.GlobalSizeLimitDisabled = false

vInfo.ExtendedPaths.Clear()

Expand Down Expand Up @@ -229,7 +230,6 @@ func TestOAS_ExtractTo_ResetAPIDefinition(t *testing.T) {
"APIDefinition.VersionData.Versions[0].ExtendedPaths.PersistGraphQL[0].Operation",
"APIDefinition.VersionData.Versions[0].ExtendedPaths.PersistGraphQL[0].Variables[0]",
"APIDefinition.VersionData.Versions[0].IgnoreEndpointCase",
"APIDefinition.VersionData.Versions[0].GlobalSizeLimit",
"APIDefinition.UptimeTests.CheckList[0].CheckURL",
"APIDefinition.UptimeTests.CheckList[0].Protocol",
"APIDefinition.UptimeTests.CheckList[0].Timeout",
Expand Down
19 changes: 19 additions & 0 deletions apidef/oas/oasutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package oas
import (
"encoding/json"

"github.com/TykTechnologies/tyk/apidef"
internalreflect "github.com/TykTechnologies/tyk/internal/reflect"
)

Expand All @@ -27,3 +28,21 @@ func toStructIfMap(input interface{}, val interface{}) bool {

// ShouldOmit is a compatibility alias. It may be removed in the future.
var ShouldOmit = internalreflect.IsEmpty

func requireMainVersion(api *apidef.APIDefinition) apidef.VersionInfo {
if len(api.VersionData.Versions) == 0 {
api.VersionData.Versions = map[string]apidef.VersionInfo{
Main: {},
}
}

if _, ok := api.VersionData.Versions[Main]; !ok {
api.VersionData.Versions[Main] = apidef.VersionInfo{}
}

return api.VersionData.Versions[Main]
}

func updateMainVersion(api *apidef.APIDefinition, updatedMainVersion apidef.VersionInfo) {
api.VersionData.Versions[Main] = updatedMainVersion
}
18 changes: 18 additions & 0 deletions apidef/oas/schema/x-tyk-api-gateway.json
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@
},
"trafficLogs": {
"$ref": "#/definitions/X-Tyk-TrafficLogs"
},
"requestSizeLimit": {
"$ref": "#/definitions/X-Tyk-GlobalRequestSizeLimit"
}
}
},
Expand Down Expand Up @@ -2000,6 +2003,21 @@
"enabled"
]
},
"X-Tyk-GlobalRequestSizeLimit": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"value": {
"type": "integer"
}
},
"required": [
"enabled",
"value"
]
},
"X-Tyk-UInt": {
"type": "integer",
"minimum": 0
Expand Down
19 changes: 19 additions & 0 deletions docs/dev/apidef-oas.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ if ShouldOmit(u.RateLimit) {

This ensures the field is reset to empty when not configured in the classic API definition.

### Working with `VersionData`
A `Main` version will be provided that can be used for `Fill`.
```go
api.VersionData.Versions[Main]
```

## Implement ExtractTo Method Pattern

Similarly, follow this pattern with `ExtractTo`:
Expand All @@ -94,6 +100,19 @@ if u.RateLimit == nil {
u.RateLimit.ExtractTo(api)
```

### Working with `VersionData`
There are 2 helper functions for `ExtractTo` that will help to handle `VersionData`. You can use them like this:
```go
func (g *GlobalRequestSizeLimit) ExtractTo(api *apidef.APIDefinition) {
mainVersion := requireMainVersion(api)
defer func() {
updateMainVersion(api, mainVersion)
}()

// manipulate the Main VersionInfo here
}
```

## Write Tests

Write tests for conversion functions. Refer to the example:
Expand Down
Loading
Loading