@cryptotaxi247 / kubo / commits / 8f35c3bcd

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 +}