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:
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user