Skip to content

Commit

Permalink
Corsadaptor support origin (#498)
Browse files Browse the repository at this point in the history
* Add MaxAge param

Co-authored-by: hanjuntao <[email protected]>

* Enable CORS requests

Co-authored-by: hanjuntao <[email protected]>

* fix test

Co-authored-by: hanjuntao <[email protected]>
  • Loading branch information
Samu Tamminen and jthann authored Feb 10, 2022
1 parent 2b9d889 commit c6b1670
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 38 deletions.
2 changes: 2 additions & 0 deletions doc/reference/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ allowedMethods: [GET]
| allowedHeaders | []string | An array of non-simple headers the client is allowed to use with cross-domain requests. If the special `*` value is present in the list, all headers will be allowed. The default value is [] but "Origin" is always appended to the list | No |
| allowCredentials | bool | Indicates whether the request can include user credentials like cookies, HTTP authentication, or client-side SSL certificates | No |
| exposedHeaders | []string | Indicates which headers are safe to expose to the API of a CORS API specification | No |
| maxAge | int | Indicates how long (in seconds) the results of a preflight request can be cached. The default is 0 stands for no max age | No |
| supportCORSRequest | bool | When true, support CORS request and CORS preflight requests. By default, support only preflight requests. | No |

### Results

Expand Down
28 changes: 27 additions & 1 deletion pkg/filter/corsadaptor/corsadaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ type (
cors *cors.Cors
}

// Spec is describes of CORSAdaptor.
// Spec describes of CORSAdaptor.
Spec struct {
AllowedOrigins []string `yaml:"allowedOrigins" jsonschema:"omitempty"`
AllowedMethods []string `yaml:"allowedMethods" jsonschema:"omitempty,uniqueItems=true,format=httpmethod-array"`
AllowedHeaders []string `yaml:"allowedHeaders" jsonschema:"omitempty"`
AllowCredentials bool `yaml:"allowCredentials" jsonschema:"omitempty"`
ExposedHeaders []string `yaml:"exposedHeaders" jsonschema:"omitempty"`
MaxAge int `yaml:"maxAge" jsonschema:"omitempty"`
// If true, handle requests with 'Origin' header. https://fetch.spec.whatwg.org/#http-requests
// By default, only CORS-preflight requests are handled.
SupportCORSRequest bool `yaml:"supportCORSRequest" jsonschema:"omitempty"`
}
)

Expand Down Expand Up @@ -98,11 +102,16 @@ func (a *CORSAdaptor) reload() {
AllowedHeaders: a.spec.AllowedHeaders,
AllowCredentials: a.spec.AllowCredentials,
ExposedHeaders: a.spec.ExposedHeaders,
MaxAge: a.spec.MaxAge,
})
}

// Handle handles simple cross-origin requests or directs.
func (a *CORSAdaptor) Handle(ctx context.HTTPContext) string {
if a.spec.SupportCORSRequest {
result := a.handleCORS(ctx)
return ctx.CallNextHandler(result)
}
result := a.handle(ctx)
return ctx.CallNextHandler(result)
}
Expand All @@ -119,6 +128,23 @@ func (a *CORSAdaptor) handle(ctx context.HTTPContext) string {
return ""
}

func (a *CORSAdaptor) handleCORS(ctx context.HTTPContext) string {
r := ctx.Request()
w := ctx.Response()
method := r.Method()
isCorsRequest := r.Header().Get("Origin") != ""
isPreflight := method == http.MethodOptions && r.Header().Get("Access-Control-Request-Method") != ""
// set CORS headers to response
a.cors.HandlerFunc(w.Std(), r.Std())
if !isCorsRequest {
return "" // next filter
}
if isPreflight {
return resultPreflighted // pipeline jumpIf skips following filters
}
return "" // next filter
}

// Status return status.
func (a *CORSAdaptor) Status() interface{} {
return nil
Expand Down
129 changes: 92 additions & 37 deletions pkg/filter/corsadaptor/corsadaptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,50 +28,105 @@ import (
)

func TestCORSAdaptor(t *testing.T) {
const yamlSpec = `
t.Run("CORS preflight-request", func(t *testing.T) {
const yamlSpec = `
kind: CORSAdaptor
name: cors
`
rawSpec := make(map[string]interface{})
yamltool.Unmarshal([]byte(yamlSpec), &rawSpec)
rawSpec := make(map[string]interface{})
yamltool.Unmarshal([]byte(yamlSpec), &rawSpec)

spec, e := httppipeline.NewFilterSpec(rawSpec, nil)
if e != nil {
t.Errorf("unexpected error: %v", e)
}
spec, e := httppipeline.NewFilterSpec(rawSpec, nil)
if e != nil {
t.Errorf("unexpected error: %v", e)
}

cors := &CORSAdaptor{}
cors.Init(spec)
cors := &CORSAdaptor{}
cors.Init(spec)

header := http.Header{}
ctx := &contexttest.MockedHTTPContext{}
ctx.MockedRequest.MockedMethod = func() string {
return http.MethodOptions
}
ctx.MockedRequest.MockedHeader = func() *httpheader.HTTPHeader {
return httpheader.New(header)
}
header := http.Header{}
ctx := &contexttest.MockedHTTPContext{}
ctx.MockedRequest.MockedMethod = func() string {
return http.MethodOptions
}
ctx.MockedRequest.MockedHeader = func() *httpheader.HTTPHeader {
return httpheader.New(header)
}

result := cors.Handle(ctx)
if result == resultPreflighted {
t.Error("request should not be preflighted")
}
result := cors.Handle(ctx)
if result == resultPreflighted {
t.Error("request should not be preflighted")
}

header.Add("Access-Control-Request-Method", "abc")
result = cors.Handle(ctx)
if result != resultPreflighted {
t.Error("request should be preflighted")
}
header.Add("Access-Control-Request-Method", "abc")
result = cors.Handle(ctx)
if result != resultPreflighted {
t.Error("request should be preflighted")
}

newCors := &CORSAdaptor{}
spec, _ = httppipeline.NewFilterSpec(rawSpec, nil)
newCors.Inherit(spec, cors)
cors.Close()
ctx.MockedRequest.MockedMethod = func() string {
return http.MethodGet
}
result = newCors.Handle(ctx)
if result == resultPreflighted {
t.Error("request should not be preflighted")
}
newCors := &CORSAdaptor{}
spec, _ = httppipeline.NewFilterSpec(rawSpec, nil)
newCors.Inherit(spec, cors)
cors.Close()
ctx.MockedRequest.MockedMethod = func() string {
return http.MethodGet
}
result = newCors.Handle(ctx)
if result == resultPreflighted {
t.Error("request should not be preflighted")
}
})
t.Run("CORS request", func(t *testing.T) {
const yamlSpec = `
kind: CORSAdaptor
name: cors
supportCORSRequest: true
allowedOrigins:
- test.orig.test
`
rawSpec := make(map[string]interface{})
yamltool.Unmarshal([]byte(yamlSpec), &rawSpec)

spec, e := httppipeline.NewFilterSpec(rawSpec, nil)
if e != nil {
t.Errorf("unexpected error: %v", e)
}

cors := &CORSAdaptor{}
cors.Init(spec)
cors.Description()
cors.Status()
header := http.Header{}
ctx := &contexttest.MockedHTTPContext{}
ctx.MockedRequest.MockedMethod = func() string {
return http.MethodOptions
}
ctx.MockedRequest.MockedHeader = func() *httpheader.HTTPHeader {
return httpheader.New(header)
}
result := cors.Handle(ctx)
if result == resultPreflighted {
t.Error("request should not be preflighted")
}
header.Add("Origin", "test.orig.test")
header.Add("Access-Control-Request-Method", "get")
result = cors.Handle(ctx)
if result != resultPreflighted {
t.Error("request should be preflighted")
}

header = http.Header{}
header.Add("Origin", "test.orig.test")
ctx = &contexttest.MockedHTTPContext{}
ctx.MockedRequest.MockedMethod = func() string {
return http.MethodGet
}
ctx.MockedRequest.MockedHeader = func() *httpheader.HTTPHeader {
return httpheader.New(header)
}
result = cors.Handle(ctx)
if result == resultPreflighted {
t.Error("request should not be preflighted")
}
})
}

0 comments on commit c6b1670

Please sign in to comment.