1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-30 18:13:54 +08:00

more serious CORS tests.

this commit introduces more serious CORS tests that check
status response codes, and run real HTTP requests.

License: MIT
Signed-off-by: Juan Batiz-Benet <juan@benet.ai>
This commit is contained in:
Juan Batiz-Benet
2015-07-28 22:59:36 -07:00
parent d5f94be474
commit 8f35c3bcd9
2 changed files with 336 additions and 45 deletions

View File

@ -3,79 +3,338 @@ package http
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"testing" "testing"
cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors" cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
"github.com/ipfs/go-ipfs/commands" cmds "github.com/ipfs/go-ipfs/commands"
ipfscmd "github.com/ipfs/go-ipfs/core/commands"
coremock "github.com/ipfs/go-ipfs/core/mock"
) )
func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) { func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) {
for name, value := range reqHeaders { for name, value := range reqHeaders {
if resHeaders.Get(name) != value { if resHeaders.Get(name) != value {
t.Errorf("Invalid header `%s', wanted `%s', got `%s'", name, value, resHeaders.Get(name)) t.Errorf("Invalid header '%s', wanted '%s', got '%s'", name, value, resHeaders.Get(name))
} }
} }
} }
func originCfg(origin string) *ServerConfig { func assertStatus(t *testing.T, actual, expected int) {
if actual != expected {
t.Errorf("Expected status: %d got: %d", expected, actual)
}
}
func originCfg(origins []string) *ServerConfig {
return &ServerConfig{ return &ServerConfig{
CORSOpts: &cors.Options{ CORSOpts: &cors.Options{
AllowedOrigins: []string{origin}, AllowedOrigins: origins,
}, },
} }
} }
func TestDisallowedOrigin(t *testing.T) { type testCase struct {
res := httptest.NewRecorder() Method string
req, _ := http.NewRequest("GET", "http://example.com/foo", nil) Path string
req.Header.Add("Origin", "http://barbaz.com") Code int
Origin string
Referer string
AllowOrigins []string
ReqHeaders map[string]string
ResHeaders map[string]string
}
handler := NewHandler(commands.Context{}, nil, originCfg("")) func getTestServer(t *testing.T, origins []string) *httptest.Server {
handler.ServeHTTP(res, req) cmdsCtx, err := coremock.MockCmdsCtx()
if err != nil {
t.Error("failure to initialize mock cmds ctx", err)
return nil
}
assertHeaders(t, res.Header(), map[string]string{ cmdRoot := &cmds.Command{
"Access-Control-Allow-Origin": "", Subcommands: map[string]*cmds.Command{
"Access-Control-Allow-Methods": "", "version": ipfscmd.VersionCmd,
"Access-Control-Allow-Credentials": "", },
"Access-Control-Max-Age": "", }
"Access-Control-Expose-Headers": "",
}) handler := NewHandler(cmdsCtx, cmdRoot, originCfg(origins))
return httptest.NewServer(handler)
}
func (tc *testCase) test(t *testing.T) {
// defaults
method := tc.Method
if method == "" {
method = "GET"
}
path := tc.Path
if path == "" {
path = "/api/v0/version"
}
expectCode := tc.Code
if expectCode == 0 {
expectCode = 200
}
// request
req, err := http.NewRequest(method, path, nil)
if err != nil {
t.Error(err)
return
}
for k, v := range tc.ReqHeaders {
req.Header.Add(k, v)
}
if tc.Origin != "" {
req.Header.Add("Origin", tc.Origin)
}
if tc.Referer != "" {
req.Header.Add("Referer", tc.Referer)
}
// server
server := getTestServer(t, tc.AllowOrigins)
if server == nil {
return
}
defer server.Close()
req.URL, err = url.Parse(server.URL + path)
if err != nil {
t.Error(err)
return
}
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Error(err)
return
}
// checks
t.Log("GET", server.URL+path, req.Header, res.Header)
assertHeaders(t, res.Header, tc.ResHeaders)
assertStatus(t, res.StatusCode, expectCode)
}
func TestDisallowedOrigins(t *testing.T) {
gtc := func(origin string, allowedOrigins []string) testCase {
return testCase{
Origin: origin,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: "",
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusForbidden,
}
}
tcs := []testCase{
gtc("http://barbaz.com", nil),
gtc("http://barbaz.com", []string{"http://localhost"}),
gtc("http://127.0.0.1", []string{"http://localhost"}),
gtc("http://localhost", []string{"http://127.0.0.1"}),
gtc("http://127.0.0.1:1234", nil),
gtc("http://localhost:1234", nil),
}
for _, tc := range tcs {
tc.test(t)
}
}
func TestAllowedOrigins(t *testing.T) {
gtc := func(origin string, allowedOrigins []string) testCase {
return testCase{
Origin: origin,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: origin,
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusOK,
}
}
tcs := []testCase{
gtc("http://barbaz.com", []string{"http://barbaz.com", "http://localhost"}),
gtc("http://localhost", []string{"http://barbaz.com", "http://localhost"}),
gtc("http://localhost", nil),
gtc("http://127.0.0.1", nil),
}
for _, tc := range tcs {
tc.test(t)
}
} }
func TestWildcardOrigin(t *testing.T) { func TestWildcardOrigin(t *testing.T) {
res := httptest.NewRecorder() gtc := func(origin string, allowedOrigins []string) testCase {
req, _ := http.NewRequest("GET", "http://example.com/foo", nil) return testCase{
req.Header.Add("Origin", "http://foobar.com") Origin: origin,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: origin,
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusOK,
}
}
handler := NewHandler(commands.Context{}, nil, originCfg("*")) tcs := []testCase{
handler.ServeHTTP(res, req) gtc("http://barbaz.com", []string{"*"}),
gtc("http://barbaz.com", []string{"http://localhost", "*"}),
gtc("http://127.0.0.1", []string{"http://localhost", "*"}),
gtc("http://localhost", []string{"http://127.0.0.1", "*"}),
gtc("http://127.0.0.1", []string{"*"}),
gtc("http://localhost", []string{"*"}),
gtc("http://127.0.0.1:1234", []string{"*"}),
gtc("http://localhost:1234", []string{"*"}),
}
assertHeaders(t, res.Header(), map[string]string{ for _, tc := range tcs {
"Access-Control-Allow-Origin": "http://foobar.com", tc.test(t)
"Access-Control-Allow-Methods": "", }
"Access-Control-Allow-Headers": "", }
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "", func TestDisallowedReferer(t *testing.T) {
"Access-Control-Expose-Headers": "", gtc := func(referer string, allowedOrigins []string) testCase {
}) return testCase{
Origin: "http://localhost",
Referer: referer,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: "http://localhost",
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusForbidden,
}
}
tcs := []testCase{
gtc("http://foobar.com", nil),
gtc("http://localhost:1234", nil),
gtc("http://127.0.0.1:1234", nil),
}
for _, tc := range tcs {
tc.test(t)
}
}
func TestAllowedReferer(t *testing.T) {
gtc := func(referer string, allowedOrigins []string) testCase {
return testCase{
Origin: "http://localhost",
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: "http://localhost",
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusOK,
}
}
tcs := []testCase{
gtc("http://barbaz.com", []string{"http://barbaz.com", "http://localhost"}),
gtc("http://localhost", []string{"http://barbaz.com", "http://localhost"}),
gtc("http://localhost", nil),
gtc("http://127.0.0.1", nil),
}
for _, tc := range tcs {
tc.test(t)
}
}
func TestWildcardReferer(t *testing.T) {
gtc := func(origin string, allowedOrigins []string) testCase {
return testCase{
Origin: origin,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: origin,
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusOK,
}
}
tcs := []testCase{
gtc("http://barbaz.com", []string{"*"}),
gtc("http://barbaz.com", []string{"http://localhost", "*"}),
gtc("http://127.0.0.1", []string{"http://localhost", "*"}),
gtc("http://localhost", []string{"http://127.0.0.1", "*"}),
gtc("http://127.0.0.1", []string{"*"}),
gtc("http://localhost", []string{"*"}),
gtc("http://127.0.0.1:1234", []string{"*"}),
gtc("http://localhost:1234", []string{"*"}),
}
for _, tc := range tcs {
tc.test(t)
}
} }
func TestAllowedMethod(t *testing.T) { func TestAllowedMethod(t *testing.T) {
res := httptest.NewRecorder() gtc := func(method string, ok bool) testCase {
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) code := http.StatusOK
req.Header.Add("Origin", "http://www.foobar.com") hdrs := map[string]string{
req.Header.Add("Access-Control-Request-Method", "PUT") ACAOrigin: "http://localhost",
ACAMethods: method,
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
}
handler := NewHandler(commands.Context{}, nil, originCfg("http://www.foobar.com")) if !ok {
handler.ServeHTTP(res, req) hdrs[ACAOrigin] = ""
hdrs[ACAMethods] = ""
}
assertHeaders(t, res.Header(), map[string]string{ return testCase{
"Access-Control-Allow-Origin": "http://www.foobar.com", Method: "OPTIONS",
"Access-Control-Allow-Methods": "PUT", Origin: "http://localhost",
"Access-Control-Allow-Headers": "", AllowOrigins: []string{"*"},
"Access-Control-Allow-Credentials": "", ReqHeaders: map[string]string{
"Access-Control-Max-Age": "", "Access-Control-Request-Method": method,
"Access-Control-Expose-Headers": "", },
}) ResHeaders: hdrs,
Code: code,
}
}
tcs := []testCase{
gtc("PUT", true),
gtc("GET", true),
gtc("FOOBAR", false),
}
for _, tc := range tcs {
tc.test(t)
}
} }

View File

@ -6,6 +6,7 @@ import (
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
"github.com/ipfs/go-ipfs/blocks/blockstore" "github.com/ipfs/go-ipfs/blocks/blockstore"
blockservice "github.com/ipfs/go-ipfs/blockservice" blockservice "github.com/ipfs/go-ipfs/blockservice"
commands "github.com/ipfs/go-ipfs/commands"
core "github.com/ipfs/go-ipfs/core" core "github.com/ipfs/go-ipfs/core"
"github.com/ipfs/go-ipfs/exchange/offline" "github.com/ipfs/go-ipfs/exchange/offline"
mdag "github.com/ipfs/go-ipfs/merkledag" mdag "github.com/ipfs/go-ipfs/merkledag"
@ -27,7 +28,7 @@ import (
// NewMockNode constructs an IpfsNode for use in tests. // NewMockNode constructs an IpfsNode for use in tests.
func NewMockNode() (*core.IpfsNode, error) { func NewMockNode() (*core.IpfsNode, error) {
ctx := context.TODO() ctx := context.Background()
// Generate Identity // Generate Identity
ident, err := testutil.RandIdentity() ident, err := testutil.RandIdentity()
@ -82,3 +83,34 @@ func NewMockNode() (*core.IpfsNode, error) {
return nd, nil return nd, nil
} }
func MockCmdsCtx() (commands.Context, error) {
// Generate Identity
ident, err := testutil.RandIdentity()
if err != nil {
return commands.Context{}, err
}
p := ident.ID()
conf := config.Config{
Identity: config.Identity{
PeerID: p.String(),
},
}
node, err := core.NewIPFSNode(context.Background(), core.Offline(&repo.Mock{
D: ds2.CloserWrap(syncds.MutexWrap(datastore.NewMapDatastore())),
C: conf,
}))
return commands.Context{
Online: true,
ConfigRoot: "/tmp/.mockipfsconfig",
LoadConfig: func(path string) (*config.Config, error) {
return &conf, nil
},
ConstructNode: func() (*core.IpfsNode, error) {
return node, nil
},
}, nil
}