mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-30 09:59:13 +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:
@ -3,79 +3,338 @@ package http
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
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) {
|
||||
for name, value := range reqHeaders {
|
||||
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{
|
||||
CORSOpts: &cors.Options{
|
||||
AllowedOrigins: []string{origin},
|
||||
AllowedOrigins: origins,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisallowedOrigin(t *testing.T) {
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
|
||||
req.Header.Add("Origin", "http://barbaz.com")
|
||||
type testCase struct {
|
||||
Method string
|
||||
Path string
|
||||
Code int
|
||||
Origin string
|
||||
Referer string
|
||||
AllowOrigins []string
|
||||
ReqHeaders map[string]string
|
||||
ResHeaders map[string]string
|
||||
}
|
||||
|
||||
handler := NewHandler(commands.Context{}, nil, originCfg(""))
|
||||
handler.ServeHTTP(res, req)
|
||||
func getTestServer(t *testing.T, origins []string) *httptest.Server {
|
||||
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{
|
||||
"Access-Control-Allow-Origin": "",
|
||||
"Access-Control-Allow-Methods": "",
|
||||
"Access-Control-Allow-Credentials": "",
|
||||
"Access-Control-Max-Age": "",
|
||||
"Access-Control-Expose-Headers": "",
|
||||
})
|
||||
cmdRoot := &cmds.Command{
|
||||
Subcommands: map[string]*cmds.Command{
|
||||
"version": ipfscmd.VersionCmd,
|
||||
},
|
||||
}
|
||||
|
||||
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) {
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
|
||||
req.Header.Add("Origin", "http://foobar.com")
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
handler := NewHandler(commands.Context{}, nil, originCfg("*"))
|
||||
handler.ServeHTTP(res, req)
|
||||
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{"*"}),
|
||||
}
|
||||
|
||||
assertHeaders(t, res.Header(), map[string]string{
|
||||
"Access-Control-Allow-Origin": "http://foobar.com",
|
||||
"Access-Control-Allow-Methods": "",
|
||||
"Access-Control-Allow-Headers": "",
|
||||
"Access-Control-Allow-Credentials": "",
|
||||
"Access-Control-Max-Age": "",
|
||||
"Access-Control-Expose-Headers": "",
|
||||
})
|
||||
for _, tc := range tcs {
|
||||
tc.test(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisallowedReferer(t *testing.T) {
|
||||
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) {
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
|
||||
req.Header.Add("Origin", "http://www.foobar.com")
|
||||
req.Header.Add("Access-Control-Request-Method", "PUT")
|
||||
gtc := func(method string, ok bool) testCase {
|
||||
code := http.StatusOK
|
||||
hdrs := map[string]string{
|
||||
ACAOrigin: "http://localhost",
|
||||
ACAMethods: method,
|
||||
ACACredentials: "",
|
||||
"Access-Control-Max-Age": "",
|
||||
"Access-Control-Expose-Headers": "",
|
||||
}
|
||||
|
||||
handler := NewHandler(commands.Context{}, nil, originCfg("http://www.foobar.com"))
|
||||
handler.ServeHTTP(res, req)
|
||||
if !ok {
|
||||
hdrs[ACAOrigin] = ""
|
||||
hdrs[ACAMethods] = ""
|
||||
}
|
||||
|
||||
assertHeaders(t, res.Header(), map[string]string{
|
||||
"Access-Control-Allow-Origin": "http://www.foobar.com",
|
||||
"Access-Control-Allow-Methods": "PUT",
|
||||
"Access-Control-Allow-Headers": "",
|
||||
"Access-Control-Allow-Credentials": "",
|
||||
"Access-Control-Max-Age": "",
|
||||
"Access-Control-Expose-Headers": "",
|
||||
})
|
||||
return testCase{
|
||||
Method: "OPTIONS",
|
||||
Origin: "http://localhost",
|
||||
AllowOrigins: []string{"*"},
|
||||
ReqHeaders: map[string]string{
|
||||
"Access-Control-Request-Method": method,
|
||||
},
|
||||
ResHeaders: hdrs,
|
||||
Code: code,
|
||||
}
|
||||
}
|
||||
|
||||
tcs := []testCase{
|
||||
gtc("PUT", true),
|
||||
gtc("GET", true),
|
||||
gtc("FOOBAR", false),
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
tc.test(t)
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
|
||||
"github.com/ipfs/go-ipfs/blocks/blockstore"
|
||||
blockservice "github.com/ipfs/go-ipfs/blockservice"
|
||||
commands "github.com/ipfs/go-ipfs/commands"
|
||||
core "github.com/ipfs/go-ipfs/core"
|
||||
"github.com/ipfs/go-ipfs/exchange/offline"
|
||||
mdag "github.com/ipfs/go-ipfs/merkledag"
|
||||
@ -27,7 +28,7 @@ import (
|
||||
|
||||
// NewMockNode constructs an IpfsNode for use in tests.
|
||||
func NewMockNode() (*core.IpfsNode, error) {
|
||||
ctx := context.TODO()
|
||||
ctx := context.Background()
|
||||
|
||||
// Generate Identity
|
||||
ident, err := testutil.RandIdentity()
|
||||
@ -82,3 +83,34 @@ func NewMockNode() (*core.IpfsNode, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
Reference in New Issue
Block a user