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>
Juan Batiz-Benet committed
Jul 28, 2015 at 22:59 UTC
8f35c3bcd922b8e46cb0e4dba98e5a98ff382cea
2 files changed
+342
-51
commands/http/handler_test.go
+309
-50
@@ -3,79 +3,338 @@ package http
3
import (
4
"net/http"
5
"net/http/httptest"
6
+ "net/url"
7
"testing"
8
9
cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
10
10
- "github.com/ipfs/go-ipfs/commands"
11
+ cmds "github.com/ipfs/go-ipfs/commands"
12
+ ipfscmd "github.com/ipfs/go-ipfs/core/commands"
13
+ coremock "github.com/ipfs/go-ipfs/core/mock"
14
)
15
16
func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) {
17
for name, value := range reqHeaders {
18
if resHeaders.Get(name) != value {
16
- t.Errorf("Invalid header `%s', wanted `%s', got `%s'", name, value, resHeaders.Get(name))
19
+ t.Errorf("Invalid header '%s', wanted '%s', got '%s'", name, value, resHeaders.Get(name))
20
}
21
}
22
}
23
21
-func originCfg(origin string) *ServerConfig {
24
+func assertStatus(t *testing.T, actual, expected int) {
25
+ if actual != expected {
26
+ t.Errorf("Expected status: %d got: %d", expected, actual)
27
+ }
28
+}
29
+
30
+func originCfg(origins []string) *ServerConfig {
31
return &ServerConfig{
32
CORSOpts: &cors.Options{
24
- AllowedOrigins: []string{origin},
33
+ AllowedOrigins: origins,
34
+ },
35
+ }
36
+}
37
+
38
+type testCase struct {
39
+ Method string
40
+ Path string
41
+ Code int
42
+ Origin string
43
+ Referer string
44
+ AllowOrigins []string
45
+ ReqHeaders map[string]string
46
+ ResHeaders map[string]string
47
+}
48
+
49
+func getTestServer(t *testing.T, origins []string) *httptest.Server {
50
+ cmdsCtx, err := coremock.MockCmdsCtx()
51
+ if err != nil {
52
+ t.Error("failure to initialize mock cmds ctx", err)
53
+ return nil
54
+ }
55
+
56
+ cmdRoot := &cmds.Command{
57
+ Subcommands: map[string]*cmds.Command{
58
+ "version": ipfscmd.VersionCmd,
59
},
60
}
61
+
62
+ handler := NewHandler(cmdsCtx, cmdRoot, originCfg(origins))
63
+ return httptest.NewServer(handler)
64
}
65
29
-func TestDisallowedOrigin(t *testing.T) {
30
- res := httptest.NewRecorder()
31
- req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
32
- req.Header.Add("Origin", "http://barbaz.com")
33
-
34
- handler := NewHandler(commands.Context{}, nil, originCfg(""))
35
- handler.ServeHTTP(res, req)
36
-
37
- assertHeaders(t, res.Header(), map[string]string{
38
- "Access-Control-Allow-Origin": "",
39
- "Access-Control-Allow-Methods": "",
40
- "Access-Control-Allow-Credentials": "",
41
- "Access-Control-Max-Age": "",
42
- "Access-Control-Expose-Headers": "",
43
- })
66
+func (tc *testCase) test(t *testing.T) {
67
+ // defaults
68
+ method := tc.Method
69
+ if method == "" {
70
+ method = "GET"
71
+ }
72
+
73
+ path := tc.Path
74
+ if path == "" {
75
+ path = "/api/v0/version"
76
+ }
77
+
78
+ expectCode := tc.Code
79
+ if expectCode == 0 {
80
+ expectCode = 200
81
+ }
82
+
83
+ // request
84
+ req, err := http.NewRequest(method, path, nil)
85
+ if err != nil {
86
+ t.Error(err)
87
+ return
88
+ }
89
+
90
+ for k, v := range tc.ReqHeaders {
91
+ req.Header.Add(k, v)
92
+ }
93
+ if tc.Origin != "" {
94
+ req.Header.Add("Origin", tc.Origin)
95
+ }
96
+ if tc.Referer != "" {
97
+ req.Header.Add("Referer", tc.Referer)
98
+ }
99
+
100
+ // server
101
+ server := getTestServer(t, tc.AllowOrigins)
102
+ if server == nil {
103
+ return
104
+ }
105
+ defer server.Close()
106
+
107
+ req.URL, err = url.Parse(server.URL + path)
108
+ if err != nil {
109
+ t.Error(err)
110
+ return
111
+ }
112
+
113
+ res, err := http.DefaultClient.Do(req)
114
+ if err != nil {
115
+ t.Error(err)
116
+ return
117
+ }
118
+
119
+ // checks
120
+ t.Log("GET", server.URL+path, req.Header, res.Header)
121
+ assertHeaders(t, res.Header, tc.ResHeaders)
122
+ assertStatus(t, res.StatusCode, expectCode)
123
+}
124
+
125
+func TestDisallowedOrigins(t *testing.T) {
126
+ gtc := func(origin string, allowedOrigins []string) testCase {
127
+ return testCase{
128
+ Origin: origin,
129
+ AllowOrigins: allowedOrigins,
130
+ ResHeaders: map[string]string{
131
+ ACAOrigin: "",
132
+ ACAMethods: "",
133
+ ACACredentials: "",
134
+ "Access-Control-Max-Age": "",
135
+ "Access-Control-Expose-Headers": "",
136
+ },
137
+ Code: http.StatusForbidden,
138
+ }
139
+ }
140
+
141
+ tcs := []testCase{
142
+ gtc("http://barbaz.com", nil),
143
+ gtc("http://barbaz.com", []string{"http://localhost"}),
144
+ gtc("http://127.0.0.1", []string{"http://localhost"}),
145
+ gtc("http://localhost", []string{"http://127.0.0.1"}),
146
+ gtc("http://127.0.0.1:1234", nil),
147
+ gtc("http://localhost:1234", nil),
148
+ }
149
+
150
+ for _, tc := range tcs {
151
+ tc.test(t)
152
+ }
153
+}
154
+
155
+func TestAllowedOrigins(t *testing.T) {
156
+ gtc := func(origin string, allowedOrigins []string) testCase {
157
+ return testCase{
158
+ Origin: origin,
159
+ AllowOrigins: allowedOrigins,
160
+ ResHeaders: map[string]string{
161
+ ACAOrigin: origin,
162
+ ACAMethods: "",
163
+ ACACredentials: "",
164
+ "Access-Control-Max-Age": "",
165
+ "Access-Control-Expose-Headers": "",
166
+ },
167
+ Code: http.StatusOK,
168
+ }
169
+ }
170
+
171
+ tcs := []testCase{
172
+ gtc("http://barbaz.com", []string{"http://barbaz.com", "http://localhost"}),
173
+ gtc("http://localhost", []string{"http://barbaz.com", "http://localhost"}),
174
+ gtc("http://localhost", nil),
175
+ gtc("http://127.0.0.1", nil),
176
+ }
177
+
178
+ for _, tc := range tcs {
179
+ tc.test(t)
180
+ }
181
}
182
183
func TestWildcardOrigin(t *testing.T) {
47
- res := httptest.NewRecorder()
48
- req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
49
- req.Header.Add("Origin", "http://foobar.com")
50
-
51
- handler := NewHandler(commands.Context{}, nil, originCfg("*"))
52
- handler.ServeHTTP(res, req)
53
-
54
- assertHeaders(t, res.Header(), map[string]string{
55
- "Access-Control-Allow-Origin": "http://foobar.com",
56
- "Access-Control-Allow-Methods": "",
57
- "Access-Control-Allow-Headers": "",
58
- "Access-Control-Allow-Credentials": "",
59
- "Access-Control-Max-Age": "",
60
- "Access-Control-Expose-Headers": "",
61
- })
184
+ gtc := func(origin string, allowedOrigins []string) testCase {
185
+ return testCase{
186
+ Origin: origin,
187
+ AllowOrigins: allowedOrigins,
188
+ ResHeaders: map[string]string{
189
+ ACAOrigin: origin,
190
+ ACAMethods: "",
191
+ ACACredentials: "",
192
+ "Access-Control-Max-Age": "",
193
+ "Access-Control-Expose-Headers": "",
194
+ },
195
+ Code: http.StatusOK,
196
+ }
197
+ }
198
+
199
+ tcs := []testCase{
200
+ gtc("http://barbaz.com", []string{"*"}),
201
+ gtc("http://barbaz.com", []string{"http://localhost", "*"}),
202
+ gtc("http://127.0.0.1", []string{"http://localhost", "*"}),
203
+ gtc("http://localhost", []string{"http://127.0.0.1", "*"}),
204
+ gtc("http://127.0.0.1", []string{"*"}),
205
+ gtc("http://localhost", []string{"*"}),
206
+ gtc("http://127.0.0.1:1234", []string{"*"}),
207
+ gtc("http://localhost:1234", []string{"*"}),
208
+ }
209
+
210
+ for _, tc := range tcs {
211
+ tc.test(t)
212
+ }
213
+}
214
+
215
+func TestDisallowedReferer(t *testing.T) {
216
+ gtc := func(referer string, allowedOrigins []string) testCase {
217
+ return testCase{
218
+ Origin: "http://localhost",
219
+ Referer: referer,
220
+ AllowOrigins: allowedOrigins,
221
+ ResHeaders: map[string]string{
222
+ ACAOrigin: "http://localhost",
223
+ ACAMethods: "",
224
+ ACACredentials: "",
225
+ "Access-Control-Max-Age": "",
226
+ "Access-Control-Expose-Headers": "",
227
+ },
228
+ Code: http.StatusForbidden,
229
+ }
230
+ }
231
+
232
+ tcs := []testCase{
233
+ gtc("http://foobar.com", nil),
234
+ gtc("http://localhost:1234", nil),
235
+ gtc("http://127.0.0.1:1234", nil),
236
+ }
237
+
238
+ for _, tc := range tcs {
239
+ tc.test(t)
240
+ }
241
+}
242
+
243
+func TestAllowedReferer(t *testing.T) {
244
+ gtc := func(referer string, allowedOrigins []string) testCase {
245
+ return testCase{
246
+ Origin: "http://localhost",
247
+ AllowOrigins: allowedOrigins,
248
+ ResHeaders: map[string]string{
249
+ ACAOrigin: "http://localhost",
250
+ ACAMethods: "",
251
+ ACACredentials: "",
252
+ "Access-Control-Max-Age": "",
253
+ "Access-Control-Expose-Headers": "",
254
+ },
255
+ Code: http.StatusOK,
256
+ }
257
+ }
258
+
259
+ tcs := []testCase{
260
+ gtc("http://barbaz.com", []string{"http://barbaz.com", "http://localhost"}),
261
+ gtc("http://localhost", []string{"http://barbaz.com", "http://localhost"}),
262
+ gtc("http://localhost", nil),
263
+ gtc("http://127.0.0.1", nil),
264
+ }
265
+
266
+ for _, tc := range tcs {
267
+ tc.test(t)
268
+ }
269
+}
270
+
271
+func TestWildcardReferer(t *testing.T) {
272
+ gtc := func(origin string, allowedOrigins []string) testCase {
273
+ return testCase{
274
+ Origin: origin,
275
+ AllowOrigins: allowedOrigins,
276
+ ResHeaders: map[string]string{
277
+ ACAOrigin: origin,
278
+ ACAMethods: "",
279
+ ACACredentials: "",
280
+ "Access-Control-Max-Age": "",
281
+ "Access-Control-Expose-Headers": "",
282
+ },
283
+ Code: http.StatusOK,
284
+ }
285
+ }
286
+
287
+ tcs := []testCase{
288
+ gtc("http://barbaz.com", []string{"*"}),
289
+ gtc("http://barbaz.com", []string{"http://localhost", "*"}),
290
+ gtc("http://127.0.0.1", []string{"http://localhost", "*"}),
291
+ gtc("http://localhost", []string{"http://127.0.0.1", "*"}),
292
+ gtc("http://127.0.0.1", []string{"*"}),
293
+ gtc("http://localhost", []string{"*"}),
294
+ gtc("http://127.0.0.1:1234", []string{"*"}),
295
+ gtc("http://localhost:1234", []string{"*"}),
296
+ }
297
+
298
+ for _, tc := range tcs {
299
+ tc.test(t)
300
+ }
301
}
302
303
func TestAllowedMethod(t *testing.T) {
65
- res := httptest.NewRecorder()
66
- req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
67
- req.Header.Add("Origin", "http://www.foobar.com")
68
- req.Header.Add("Access-Control-Request-Method", "PUT")
69
-
70
- handler := NewHandler(commands.Context{}, nil, originCfg("http://www.foobar.com"))
71
- handler.ServeHTTP(res, req)
72
-
73
- assertHeaders(t, res.Header(), map[string]string{
74
- "Access-Control-Allow-Origin": "http://www.foobar.com",
75
- "Access-Control-Allow-Methods": "PUT",
76
- "Access-Control-Allow-Headers": "",
77
- "Access-Control-Allow-Credentials": "",
78
- "Access-Control-Max-Age": "",
79
- "Access-Control-Expose-Headers": "",
80
- })
304
+ gtc := func(method string, ok bool) testCase {
305
+ code := http.StatusOK
306
+ hdrs := map[string]string{
307
+ ACAOrigin: "http://localhost",
308
+ ACAMethods: method,
309
+ ACACredentials: "",
310
+ "Access-Control-Max-Age": "",
311
+ "Access-Control-Expose-Headers": "",
312
+ }
313
+
314
+ if !ok {
315
+ hdrs[ACAOrigin] = ""
316
+ hdrs[ACAMethods] = ""
317
+ }
318
+
319
+ return testCase{
320
+ Method: "OPTIONS",
321
+ Origin: "http://localhost",
322
+ AllowOrigins: []string{"*"},
323
+ ReqHeaders: map[string]string{
324
+ "Access-Control-Request-Method": method,
325
+ },
326
+ ResHeaders: hdrs,
327
+ Code: code,
328
+ }
329
+ }
330
+
331
+ tcs := []testCase{
332
+ gtc("PUT", true),
333
+ gtc("GET", true),
334
+ gtc("FOOBAR", false),
335
+ }
336
+
337
+ for _, tc := range tcs {
338
+ tc.test(t)
339
+ }
340
}
core/mock/mock.go
+33
-1
@@ -6,6 +6,7 @@ import (
6
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
7
"github.com/ipfs/go-ipfs/blocks/blockstore"
8
blockservice "github.com/ipfs/go-ipfs/blockservice"
9
+ commands "github.com/ipfs/go-ipfs/commands"
10
core "github.com/ipfs/go-ipfs/core"
11
"github.com/ipfs/go-ipfs/exchange/offline"
12
mdag "github.com/ipfs/go-ipfs/merkledag"
@@ -27,7 +28,7 @@ import (
28
29
// NewMockNode constructs an IpfsNode for use in tests.
30
func NewMockNode() (*core.IpfsNode, error) {
30
- ctx := context.TODO()
31
+ ctx := context.Background()
32
33
// Generate Identity
34
ident, err := testutil.RandIdentity()
@@ -82,3 +83,34 @@ func NewMockNode() (*core.IpfsNode, error) {
83
84
return nd, nil
85
}
86
+
87
+func MockCmdsCtx() (commands.Context, error) {
88
+ // Generate Identity
89
+ ident, err := testutil.RandIdentity()
90
+ if err != nil {
91
+ return commands.Context{}, err
92
+ }
93
+ p := ident.ID()
94
+
95
+ conf := config.Config{
96
+ Identity: config.Identity{
97
+ PeerID: p.String(),
98
+ },
99
+ }
100
+
101
+ node, err := core.NewIPFSNode(context.Background(), core.Offline(&repo.Mock{
102
+ D: ds2.CloserWrap(syncds.MutexWrap(datastore.NewMapDatastore())),
103
+ C: conf,
104
+ }))
105
+
106
+ return commands.Context{
107
+ Online: true,
108
+ ConfigRoot: "/tmp/.mockipfsconfig",
109
+ LoadConfig: func(path string) (*config.Config, error) {
110
+ return &conf, nil
111
+ },
112
+ ConstructNode: func() (*core.IpfsNode, error) {
113
+ return node, nil
114
+ },
115
+ }, nil
116
+}