Add CORS support to fn api (#455)

The Gin middleware is being used if one or more Origins are specified. Default setup for each Origin is as follows:

- GET,POST, PUT, HEAD methods allowed
- Credentials share disabled
- Preflight requests cached for 12 hours

Which are the defaults gin-contrib/cors comes with out of the box.

Gin-cors will return a 403 if it gets a request with an Origin header that isn't on its' list. If no Origin header is specified then it will just return the servers response.

Start fn with CORS enabled:

`API_CORS="http://localhost:4000, http://localhost:3000" make run`
This commit is contained in:
Alexander Bransby-Sharples
2017-11-16 15:37:26 +00:00
committed by GitHub
parent 8f7794c53a
commit c5ec0cc41e
12 changed files with 792 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ import (
"os" "os"
"path" "path"
"strconv" "strconv"
"strings"
"github.com/fnproject/fn/api" "github.com/fnproject/fn/api"
"github.com/fnproject/fn/api/agent" "github.com/fnproject/fn/api/agent"
@@ -23,6 +24,7 @@ import (
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/mqs" "github.com/fnproject/fn/api/mqs"
"github.com/fnproject/fn/api/version" "github.com/fnproject/fn/api/version"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/ext"
@@ -38,6 +40,7 @@ const (
EnvLOGDBURL = "logstore_url" EnvLOGDBURL = "logstore_url"
EnvPort = "port" // be careful, Gin expects this variable to be "port" EnvPort = "port" // be careful, Gin expects this variable to be "port"
EnvAPIURL = "api_url" EnvAPIURL = "api_url"
EnvAPICORS = "api_cors"
EnvZipkinURL = "zipkin_url" EnvZipkinURL = "zipkin_url"
) )
@@ -75,6 +78,22 @@ func NewFromEnv(ctx context.Context, opts ...ServerOption) *Server {
return New(ctx, ds, mq, logDB, opts...) return New(ctx, ds, mq, logDB, opts...)
} }
func optionalCorsWrap(r *gin.Engine) {
// By default no CORS are allowed unless one
// or more Origins are defined by the API_CORS
// environment variable.
if len(viper.GetString(EnvAPICORS)) > 0 {
origins := strings.Split(strings.Replace(viper.GetString(EnvAPICORS), " ", "", -1), ",")
corsConfig := cors.DefaultConfig()
corsConfig.AllowOrigins = origins
logrus.Infof("CORS enabled for domains: %s", origins)
r.Use(cors.New(corsConfig))
}
}
// New creates a new Functions server with the passed in datastore, message queue and API URL // New creates a new Functions server with the passed in datastore, message queue and API URL
func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, logDB models.LogStore, opts ...ServerOption) *Server { func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, logDB models.LogStore, opts ...ServerOption) *Server {
@@ -90,6 +109,8 @@ func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, logDB
setMachineID() setMachineID()
s.Router.Use(loggerWrap, traceWrap, panicWrap) s.Router.Use(loggerWrap, traceWrap, panicWrap)
optionalCorsWrap(s.Router)
s.bindHandlers(ctx) s.bindHandlers(ctx)
for _, opt := range opts { for _, opt := range opts {

2
glide.lock generated
View File

@@ -185,6 +185,8 @@ imports:
subpackages: subpackages:
- internal - internal
- redis - redis
- name: github.com/gin-contrib/cors
version: cf4846e6a636a76237a28d9286f163c132e841bc
- name: github.com/gin-contrib/sse - name: github.com/gin-contrib/sse
version: 22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae version: 22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae
- name: github.com/gin-gonic/gin - name: github.com/gin-gonic/gin

View File

@@ -2,8 +2,10 @@ package: github.com/fnproject/fn
excludeDirs: excludeDirs:
- cli - cli
import: import:
- package: golang.org/x/crypto/pkcs12 - package: golang.org/x/crypto
version: master version: master
subpackages:
- pkcs12
- package: github.com/fnproject/fn_go - package: github.com/fnproject/fn_go
version: ^0.2.0 version: ^0.2.0
subpackages: subpackages:
@@ -71,6 +73,7 @@ import:
- package: github.com/prometheus/common - package: github.com/prometheus/common
version: 2f17f4a9d485bf34b4bfaccc273805040e4f86c8 version: 2f17f4a9d485bf34b4bfaccc273805040e4f86c8
- package: github.com/prometheus/client_golang - package: github.com/prometheus/client_golang
- package: github.com/gin-contrib/cors
version: ~1.2.0
testImport: testImport:
- package: github.com/patrickmn/go-cache - package: github.com/patrickmn/go-cache
branch: master

23
vendor/github.com/gin-contrib/cors/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,23 @@
*.o
*.a
*.so
_obj
_test
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
coverage.out

22
vendor/github.com/gin-contrib/cors/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,22 @@
language: go
sudo: false
go:
- 1.6.x
- 1.7.x
- 1.8.x
- tip
script:
- go test -v -covermode=atomic -coverprofile=coverage.out
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/acc2c57482e94b44f557
on_success: change
on_failure: always
on_start: false

21
vendor/github.com/gin-contrib/cors/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Gin-Gonic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

92
vendor/github.com/gin-contrib/cors/README.md generated vendored Normal file
View File

@@ -0,0 +1,92 @@
# CORS gin's middleware
[![Build Status](https://travis-ci.org/gin-contrib/cors.svg)](https://travis-ci.org/gin-contrib/cors)
[![codecov](https://codecov.io/gh/gin-contrib/cors/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-contrib/cors)
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-contrib/cors)](https://goreportcard.com/report/github.com/gin-contrib/cors)
[![GoDoc](https://godoc.org/github.com/gin-contrib/cors?status.svg)](https://godoc.org/github.com/gin-contrib/cors)
[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin)
Gin middleware/handler to enable CORS support.
## Usage
### Start using it
Download and install it:
```sh
$ go get gopkg.in/gin-contrib/cors.v1
```
Import it in your code:
```go
import "gopkg.in/gin-contrib/cors.v1"
```
### Canonical example:
```go
package main
import (
"time"
"gopkg.in/gin-contrib/cors.v1"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// CORS for https://foo.com and https://github.com origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://foo.com"},
AllowMethods: []string{"PUT", "PATCH"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}))
router.Run()
}
```
### Using DefaultConfig as start point
```go
func main() {
router := gin.Default()
// - No origin allowed by default
// - GET,POST, PUT, HEAD methods
// - Credentials share disabled
// - Preflight requests cached for 12 hours
config := cors.DefaultConfig()
config.AllowOrigins = []string{"http://google.com"}
config.AddAllowOrigins("http://facebook.com")
// config.AllowOrigins == []string{"http://google.com", "http://facebook.com"}
router.Use(cors.New(config))
router.Run()
}
```
### Default() allows all origins
```go
func main() {
router := gin.Default()
// same as
// config := cors.DefaultConfig()
// config.AllowAllOrigins = true
// router.Use(cors.New(config))
router.Use(cors.Default())
router.Run()
}
```

83
vendor/github.com/gin-contrib/cors/config.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
package cors
import (
"net/http"
"github.com/gin-gonic/gin"
)
type cors struct {
allowAllOrigins bool
allowCredentials bool
allowOriginFunc func(string) bool
allowOrigins []string
exposeHeaders []string
normalHeaders http.Header
preflightHeaders http.Header
}
func newCors(config Config) *cors {
if err := config.Validate(); err != nil {
panic(err.Error())
}
return &cors{
allowOriginFunc: config.AllowOriginFunc,
allowAllOrigins: config.AllowAllOrigins,
allowCredentials: config.AllowCredentials,
allowOrigins: normalize(config.AllowOrigins),
normalHeaders: generateNormalHeaders(config),
preflightHeaders: generatePreflightHeaders(config),
}
}
func (cors *cors) applyCors(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
if len(origin) == 0 {
// request is not a CORS request
return
}
if !cors.validateOrigin(origin) {
c.AbortWithStatus(http.StatusForbidden)
return
}
if c.Request.Method == "OPTIONS" {
cors.handlePreflight(c)
defer c.AbortWithStatus(200)
} else {
cors.handleNormal(c)
}
if !cors.allowAllOrigins {
c.Header("Access-Control-Allow-Origin", origin)
}
}
func (cors *cors) validateOrigin(origin string) bool {
if cors.allowAllOrigins {
return true
}
for _, value := range cors.allowOrigins {
if value == origin {
return true
}
}
if cors.allowOriginFunc != nil {
return cors.allowOriginFunc(origin)
}
return false
}
func (cors *cors) handlePreflight(c *gin.Context) {
header := c.Writer.Header()
for key, value := range cors.preflightHeaders {
header[key] = value
}
}
func (cors *cors) handleNormal(c *gin.Context) {
header := c.Writer.Header()
for key, value := range cors.normalHeaders {
header[key] = value
}
}

102
vendor/github.com/gin-contrib/cors/cors.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
package cors
import (
"errors"
"strings"
"time"
"github.com/gin-gonic/gin"
)
// Config represents all available options for the middleware.
type Config struct {
AllowAllOrigins bool
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
// If the special "*" value is present in the list, all origins will be allowed.
// Default value is ["*"]
AllowOrigins []string
// AllowOriginFunc is a custom function to validate the origin. It take the origin
// as argument and returns true if allowed or false otherwise. If this option is
// set, the content of AllowedOrigins is ignored.
AllowOriginFunc func(origin string) bool
// AllowedMethods is a list of methods the client is allowed to use with
// cross-domain requests. Default value is simple methods (GET and POST)
AllowMethods []string
// AllowedHeaders is list 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.
// Default value is [] but "Origin" is always appended to the list.
AllowHeaders []string
// AllowCredentials indicates whether the request can include user credentials like
// cookies, HTTP authentication or client side SSL certificates.
AllowCredentials bool
// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
// API specification
ExposeHeaders []string
// MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached
MaxAge time.Duration
}
// AddAllowMethods is allowed to add custom methods
func (c *Config) AddAllowMethods(methods ...string) {
c.AllowMethods = append(c.AllowMethods, methods...)
}
// AddAllowHeaders is allowed to add custom headers
func (c *Config) AddAllowHeaders(headers ...string) {
c.AllowHeaders = append(c.AllowHeaders, headers...)
}
// AddExposeHeaders is allowed to add custom expose headers
func (c *Config) AddExposeHeaders(headers ...string) {
c.ExposeHeaders = append(c.ExposeHeaders, headers...)
}
// Validate is check configuration of user defined.
func (c Config) Validate() error {
if c.AllowAllOrigins && (c.AllowOriginFunc != nil || len(c.AllowOrigins) > 0) {
return errors.New("conflict settings: all origins are allowed. AllowOriginFunc or AllowedOrigins is not needed")
}
if !c.AllowAllOrigins && c.AllowOriginFunc == nil && len(c.AllowOrigins) == 0 {
return errors.New("conflict settings: all origins disabled")
}
for _, origin := range c.AllowOrigins {
if !strings.HasPrefix(origin, "http://") && !strings.HasPrefix(origin, "https://") {
return errors.New("bad origin: origins must include http:// or https://")
}
}
return nil
}
// DefaultConfig returns a generic default configuration mapped to localhost.
func DefaultConfig() Config {
return Config{
AllowMethods: []string{"GET", "POST", "PUT", "HEAD"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
AllowCredentials: false,
MaxAge: 12 * time.Hour,
}
}
// Default returns the location middleware with default configuration.
func Default() gin.HandlerFunc {
config := DefaultConfig()
config.AllowAllOrigins = true
return New(config)
}
// New returns the location middleware with user-defined custom configuration.
func New(config Config) gin.HandlerFunc {
cors := newCors(config)
return func(c *gin.Context) {
cors.applyCors(c)
}
}

307
vendor/github.com/gin-contrib/cors/cors_test.go generated vendored Normal file
View File

@@ -0,0 +1,307 @@
package cors
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func init() {
gin.SetMode(gin.TestMode)
}
func newTestRouter(config Config) *gin.Engine {
router := gin.New()
router.Use(New(config))
router.GET("/", func(c *gin.Context) {
c.String(200, "get")
})
router.POST("/", func(c *gin.Context) {
c.String(200, "post")
})
router.PATCH("/", func(c *gin.Context) {
c.String(200, "patch")
})
return router
}
func performRequest(r http.Handler, method, origin string) *httptest.ResponseRecorder {
req, _ := http.NewRequest(method, "/", nil)
if len(origin) > 0 {
req.Header.Set("Origin", origin)
}
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
func TestConfigAddAllow(t *testing.T) {
config := Config{}
config.AddAllowMethods("POST")
config.AddAllowMethods("GET", "PUT")
config.AddExposeHeaders()
config.AddAllowHeaders("Some", " cool")
config.AddAllowHeaders("header")
config.AddExposeHeaders()
config.AddExposeHeaders()
config.AddExposeHeaders("exposed", "header")
config.AddExposeHeaders("hey")
assert.Equal(t, config.AllowMethods, []string{"POST", "GET", "PUT"})
assert.Equal(t, config.AllowHeaders, []string{"Some", " cool", "header"})
assert.Equal(t, config.ExposeHeaders, []string{"exposed", "header", "hey"})
}
func TestBadConfig(t *testing.T) {
assert.Panics(t, func() { New(Config{}) })
assert.Panics(t, func() {
New(Config{
AllowAllOrigins: true,
AllowOrigins: []string{"http://google.com"},
})
})
assert.Panics(t, func() {
New(Config{
AllowAllOrigins: true,
AllowOriginFunc: func(origin string) bool { return false },
})
})
assert.Panics(t, func() {
New(Config{
AllowOrigins: []string{"google.com"},
})
})
}
func TestNormalize(t *testing.T) {
values := normalize([]string{
"http-Access ", "Post", "POST", " poSt ",
"HTTP-Access", "",
})
assert.Equal(t, values, []string{"http-access", "post", ""})
values = normalize(nil)
assert.Nil(t, values)
values = normalize([]string{})
assert.Equal(t, values, []string{})
}
func TestConvert(t *testing.T) {
methods := []string{"Get", "GET", "get"}
headers := []string{"X-CSRF-TOKEN", "X-CSRF-Token", "x-csrf-token"}
assert.Equal(t, []string{"GET", "GET", "GET"}, convert(methods, strings.ToUpper))
assert.Equal(t, []string{"X-Csrf-Token", "X-Csrf-Token", "X-Csrf-Token"}, convert(headers, http.CanonicalHeaderKey))
}
func TestGenerateNormalHeaders_AllowAllOrigins(t *testing.T) {
header := generateNormalHeaders(Config{
AllowAllOrigins: false,
})
assert.Equal(t, header.Get("Access-Control-Allow-Origin"), "")
assert.Equal(t, header.Get("Vary"), "Origin")
assert.Len(t, header, 1)
header = generateNormalHeaders(Config{
AllowAllOrigins: true,
})
assert.Equal(t, header.Get("Access-Control-Allow-Origin"), "*")
assert.Equal(t, header.Get("Vary"), "")
assert.Len(t, header, 1)
}
func TestGenerateNormalHeaders_AllowCredentials(t *testing.T) {
header := generateNormalHeaders(Config{
AllowCredentials: true,
})
assert.Equal(t, header.Get("Access-Control-Allow-Credentials"), "true")
assert.Equal(t, header.Get("Vary"), "Origin")
assert.Len(t, header, 2)
}
func TestGenerateNormalHeaders_ExposedHeaders(t *testing.T) {
header := generateNormalHeaders(Config{
ExposeHeaders: []string{"X-user", "xPassword"},
})
assert.Equal(t, header.Get("Access-Control-Expose-Headers"), "X-User,Xpassword")
assert.Equal(t, header.Get("Vary"), "Origin")
assert.Len(t, header, 2)
}
func TestGeneratePreflightHeaders(t *testing.T) {
header := generatePreflightHeaders(Config{
AllowAllOrigins: false,
})
assert.Equal(t, header.Get("Access-Control-Allow-Origin"), "")
assert.Equal(t, header.Get("Vary"), "Origin")
assert.Len(t, header, 1)
header = generateNormalHeaders(Config{
AllowAllOrigins: true,
})
assert.Equal(t, header.Get("Access-Control-Allow-Origin"), "*")
assert.Equal(t, header.Get("Vary"), "")
assert.Len(t, header, 1)
}
func TestGeneratePreflightHeaders_AllowCredentials(t *testing.T) {
header := generatePreflightHeaders(Config{
AllowCredentials: true,
})
assert.Equal(t, header.Get("Access-Control-Allow-Credentials"), "true")
assert.Equal(t, header.Get("Vary"), "Origin")
assert.Len(t, header, 2)
}
func TestGeneratePreflightHeaders_AllowedMethods(t *testing.T) {
header := generatePreflightHeaders(Config{
AllowMethods: []string{"GET ", "post", "PUT", " put "},
})
assert.Equal(t, header.Get("Access-Control-Allow-Methods"), "GET,POST,PUT")
assert.Equal(t, header.Get("Vary"), "Origin")
assert.Len(t, header, 2)
}
func TestGeneratePreflightHeaders_AllowedHeaders(t *testing.T) {
header := generatePreflightHeaders(Config{
AllowHeaders: []string{"X-user", "Content-Type"},
})
assert.Equal(t, header.Get("Access-Control-Allow-Headers"), "X-User,Content-Type")
assert.Equal(t, header.Get("Vary"), "Origin")
assert.Len(t, header, 2)
}
func TestGeneratePreflightHeaders_MaxAge(t *testing.T) {
header := generatePreflightHeaders(Config{
MaxAge: 12 * time.Hour,
})
assert.Equal(t, header.Get("Access-Control-Max-Age"), "43200") // 12*60*60
assert.Equal(t, header.Get("Vary"), "Origin")
assert.Len(t, header, 2)
}
func TestValidateOrigin(t *testing.T) {
cors := newCors(Config{
AllowAllOrigins: true,
})
assert.True(t, cors.validateOrigin("http://google.com"))
assert.True(t, cors.validateOrigin("https://google.com"))
assert.True(t, cors.validateOrigin("example.com"))
cors = newCors(Config{
AllowOrigins: []string{"https://google.com", "https://github.com"},
AllowOriginFunc: func(origin string) bool {
return (origin == "http://news.ycombinator.com")
},
})
assert.False(t, cors.validateOrigin("http://google.com"))
assert.True(t, cors.validateOrigin("https://google.com"))
assert.True(t, cors.validateOrigin("https://github.com"))
assert.True(t, cors.validateOrigin("http://news.ycombinator.com"))
assert.False(t, cors.validateOrigin("http://example.com"))
assert.False(t, cors.validateOrigin("google.com"))
}
func TestPassesAllowedOrigins(t *testing.T) {
router := newTestRouter(Config{
AllowOrigins: []string{"http://google.com"},
AllowMethods: []string{" GeT ", "get", "post", "PUT ", "Head", "POST"},
AllowHeaders: []string{"Content-type", "timeStamp "},
ExposeHeaders: []string{"Data", "x-User"},
AllowCredentials: false,
MaxAge: 12 * time.Hour,
AllowOriginFunc: func(origin string) bool {
return origin == "http://github.com"
},
})
// no CORS request, origin == ""
w := performRequest(router, "GET", "")
assert.Equal(t, "get", w.Body.String())
assert.Empty(t, w.Header().Get("Access-Control-Allow-Origin"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Credentials"))
assert.Empty(t, w.Header().Get("Access-Control-Expose-Headers"))
// allowed CORS request
w = performRequest(router, "GET", "http://google.com")
assert.Equal(t, "get", w.Body.String())
assert.Equal(t, "http://google.com", w.Header().Get("Access-Control-Allow-Origin"))
assert.Equal(t, "", w.Header().Get("Access-Control-Allow-Credentials"))
assert.Equal(t, "Data,X-User", w.Header().Get("Access-Control-Expose-Headers"))
w = performRequest(router, "GET", "http://github.com")
assert.Equal(t, "get", w.Body.String())
assert.Equal(t, "http://github.com", w.Header().Get("Access-Control-Allow-Origin"))
assert.Equal(t, "", w.Header().Get("Access-Control-Allow-Credentials"))
assert.Equal(t, "Data,X-User", w.Header().Get("Access-Control-Expose-Headers"))
// deny CORS request
w = performRequest(router, "GET", "https://google.com")
assert.Equal(t, 403, w.Code)
assert.Empty(t, w.Header().Get("Access-Control-Allow-Origin"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Credentials"))
assert.Empty(t, w.Header().Get("Access-Control-Expose-Headers"))
// allowed CORS prefligh request
w = performRequest(router, "OPTIONS", "http://github.com")
assert.Equal(t, 200, w.Code)
assert.Equal(t, "http://github.com", w.Header().Get("Access-Control-Allow-Origin"))
assert.Equal(t, "", w.Header().Get("Access-Control-Allow-Credentials"))
assert.Equal(t, "GET,POST,PUT,HEAD", w.Header().Get("Access-Control-Allow-Methods"))
assert.Equal(t, "Content-Type,Timestamp", w.Header().Get("Access-Control-Allow-Headers"))
assert.Equal(t, "43200", w.Header().Get("Access-Control-Max-Age"))
// deny CORS prefligh request
w = performRequest(router, "OPTIONS", "http://example.com")
assert.Equal(t, 403, w.Code)
assert.Empty(t, w.Header().Get("Access-Control-Allow-Origin"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Credentials"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Methods"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Headers"))
assert.Empty(t, w.Header().Get("Access-Control-Max-Age"))
}
func TestPassesAllowedAllOrigins(t *testing.T) {
router := newTestRouter(Config{
AllowAllOrigins: true,
AllowMethods: []string{" Patch ", "get", "post", "POST"},
AllowHeaders: []string{"Content-type", " testheader "},
ExposeHeaders: []string{"Data2", "x-User2"},
AllowCredentials: false,
MaxAge: 10 * time.Hour,
})
// no CORS request, origin == ""
w := performRequest(router, "GET", "")
assert.Equal(t, "get", w.Body.String())
assert.Empty(t, w.Header().Get("Access-Control-Allow-Origin"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Credentials"))
assert.Empty(t, w.Header().Get("Access-Control-Expose-Headers"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Credentials"))
// allowed CORS request
w = performRequest(router, "POST", "example.com")
assert.Equal(t, "post", w.Body.String())
assert.Equal(t, "*", w.Header().Get("Access-Control-Allow-Origin"))
assert.Equal(t, "Data2,X-User2", w.Header().Get("Access-Control-Expose-Headers"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Credentials"))
assert.Equal(t, "*", w.Header().Get("Access-Control-Allow-Origin"))
// allowed CORS prefligh request
w = performRequest(router, "OPTIONS", "https://facebook.com")
assert.Equal(t, 200, w.Code)
assert.Equal(t, "*", w.Header().Get("Access-Control-Allow-Origin"))
assert.Equal(t, "PATCH,GET,POST", w.Header().Get("Access-Control-Allow-Methods"))
assert.Equal(t, "Content-Type,Testheader", w.Header().Get("Access-Control-Allow-Headers"))
assert.Equal(t, "36000", w.Header().Get("Access-Control-Max-Age"))
assert.Empty(t, w.Header().Get("Access-Control-Allow-Credentials"))
}

29
vendor/github.com/gin-contrib/cors/examples/example.go generated vendored Normal file
View File

@@ -0,0 +1,29 @@
package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// CORS for https://foo.com and https://github.com origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://foo.com"},
AllowMethods: []string{"PUT", "PATCH"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}))
router.Run()
}

85
vendor/github.com/gin-contrib/cors/utils.go generated vendored Normal file
View File

@@ -0,0 +1,85 @@
package cors
import (
"net/http"
"strconv"
"strings"
"time"
)
type converter func(string) string
func generateNormalHeaders(c Config) http.Header {
headers := make(http.Header)
if c.AllowCredentials {
headers.Set("Access-Control-Allow-Credentials", "true")
}
if len(c.ExposeHeaders) > 0 {
exposeHeaders := convert(normalize(c.ExposeHeaders), http.CanonicalHeaderKey)
headers.Set("Access-Control-Expose-Headers", strings.Join(exposeHeaders, ","))
}
if c.AllowAllOrigins {
headers.Set("Access-Control-Allow-Origin", "*")
} else {
headers.Set("Vary", "Origin")
}
return headers
}
func generatePreflightHeaders(c Config) http.Header {
headers := make(http.Header)
if c.AllowCredentials {
headers.Set("Access-Control-Allow-Credentials", "true")
}
if len(c.AllowMethods) > 0 {
allowMethods := convert(normalize(c.AllowMethods), strings.ToUpper)
value := strings.Join(allowMethods, ",")
headers.Set("Access-Control-Allow-Methods", value)
}
if len(c.AllowHeaders) > 0 {
allowHeaders := convert(normalize(c.AllowHeaders), http.CanonicalHeaderKey)
value := strings.Join(allowHeaders, ",")
headers.Set("Access-Control-Allow-Headers", value)
}
if c.MaxAge > time.Duration(0) {
value := strconv.FormatInt(int64(c.MaxAge/time.Second), 10)
headers.Set("Access-Control-Max-Age", value)
}
if c.AllowAllOrigins {
headers.Set("Access-Control-Allow-Origin", "*")
} else {
// Always set Vary headers
// see https://github.com/rs/cors/issues/10,
// https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001
headers.Add("Vary", "Origin")
headers.Add("Vary", "Access-Control-Request-Method")
headers.Add("Vary", "Access-Control-Request-Headers")
}
return headers
}
func normalize(values []string) []string {
if values == nil {
return nil
}
distinctMap := make(map[string]bool, len(values))
normalized := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
value = strings.ToLower(value)
if _, seen := distinctMap[value]; !seen {
normalized = append(normalized, value)
distinctMap[value] = true
}
}
return normalized
}
func convert(s []string, c converter) []string {
var out []string
for _, i := range s {
out = append(out, c(i))
}
return out
}