Merge branch 'master' of git.curoverse.com:arvados
[arvados.git] / services / keep / src / keep / handler_test.go
1 // Tests for Keep HTTP handlers:
2 //
3 //     GetBlockHandler
4 //     PutBlockHandler
5 //     IndexHandler
6 //
7 // The HTTP handlers are responsible for enforcing permission policy,
8 // so these tests must exercise all possible permission permutations.
9
10 package main
11
12 import (
13         "bytes"
14         "github.com/gorilla/mux"
15         "net/http"
16         "net/http/httptest"
17         "regexp"
18         "testing"
19         "time"
20 )
21
22 // A RequestTester represents the parameters for an HTTP request to
23 // be issued on behalf of a unit test.
24 type RequestTester struct {
25         uri          string
26         api_token    string
27         method       string
28         request_body []byte
29 }
30
31 // Test GetBlockHandler on the following situations:
32 //   - permissions off, unauthenticated request, unsigned locator
33 //   - permissions on, authenticated request, signed locator
34 //   - permissions on, authenticated request, unsigned locator
35 //   - permissions on, unauthenticated request, signed locator
36 //   - permissions on, authenticated request, expired locator
37 //
38 func TestGetHandler(t *testing.T) {
39         defer teardown()
40
41         // Prepare two test Keep volumes. Our block is stored on the second volume.
42         KeepVM = MakeTestVolumeManager(2)
43         defer func() { KeepVM.Quit() }()
44
45         vols := KeepVM.Volumes()
46         if err := vols[0].Put(TEST_HASH, TEST_BLOCK); err != nil {
47                 t.Error(err)
48         }
49
50         // Set up a REST router for testing the handlers.
51         rest := MakeRESTRouter()
52
53         // Create locators for testing.
54         // Turn on permission settings so we can generate signed locators.
55         enforce_permissions = true
56         PermissionSecret = []byte(known_key)
57         permission_ttl = time.Duration(300) * time.Second
58
59         var (
60                 unsigned_locator  = "http://localhost:25107/" + TEST_HASH
61                 valid_timestamp   = time.Now().Add(permission_ttl)
62                 expired_timestamp = time.Now().Add(-time.Hour)
63                 signed_locator    = "http://localhost:25107/" + SignLocator(TEST_HASH, known_token, valid_timestamp)
64                 expired_locator   = "http://localhost:25107/" + SignLocator(TEST_HASH, known_token, expired_timestamp)
65         )
66
67         // -----------------
68         // Test unauthenticated request with permissions off.
69         enforce_permissions = false
70
71         // Unauthenticated request, unsigned locator
72         // => OK
73         response := IssueRequest(rest,
74                 &RequestTester{
75                         method: "GET",
76                         uri:    unsigned_locator,
77                 })
78         ExpectStatusCode(t,
79                 "Unauthenticated request, unsigned locator", http.StatusOK, response)
80         ExpectBody(t,
81                 "Unauthenticated request, unsigned locator",
82                 string(TEST_BLOCK),
83                 response)
84
85         // ----------------
86         // Permissions: on.
87         enforce_permissions = true
88
89         // Authenticated request, signed locator
90         // => OK
91         response = IssueRequest(rest, &RequestTester{
92                 method:    "GET",
93                 uri:       signed_locator,
94                 api_token: known_token,
95         })
96         ExpectStatusCode(t,
97                 "Authenticated request, signed locator", http.StatusOK, response)
98         ExpectBody(t,
99                 "Authenticated request, signed locator", string(TEST_BLOCK), response)
100
101         // Authenticated request, unsigned locator
102         // => PermissionError
103         response = IssueRequest(rest, &RequestTester{
104                 method:    "GET",
105                 uri:       unsigned_locator,
106                 api_token: known_token,
107         })
108         ExpectStatusCode(t, "unsigned locator", PermissionError.HTTPCode, response)
109
110         // Unauthenticated request, signed locator
111         // => PermissionError
112         response = IssueRequest(rest, &RequestTester{
113                 method: "GET",
114                 uri:    signed_locator,
115         })
116         ExpectStatusCode(t,
117                 "Unauthenticated request, signed locator",
118                 PermissionError.HTTPCode, response)
119
120         // Authenticated request, expired locator
121         // => ExpiredError
122         response = IssueRequest(rest, &RequestTester{
123                 method:    "GET",
124                 uri:       expired_locator,
125                 api_token: known_token,
126         })
127         ExpectStatusCode(t,
128                 "Authenticated request, expired locator",
129                 ExpiredError.HTTPCode, response)
130 }
131
132 // Test PutBlockHandler on the following situations:
133 //   - no server key
134 //   - with server key, authenticated request, unsigned locator
135 //   - with server key, unauthenticated request, unsigned locator
136 //
137 func TestPutHandler(t *testing.T) {
138         defer teardown()
139
140         // Prepare two test Keep volumes.
141         KeepVM = MakeTestVolumeManager(2)
142         defer func() { KeepVM.Quit() }()
143
144         // Set up a REST router for testing the handlers.
145         rest := MakeRESTRouter()
146
147         // --------------
148         // No server key.
149
150         // Unauthenticated request, no server key
151         // => OK (unsigned response)
152         unsigned_locator := "http://localhost:25107/" + TEST_HASH
153         response := IssueRequest(rest,
154                 &RequestTester{
155                         method:       "PUT",
156                         uri:          unsigned_locator,
157                         request_body: TEST_BLOCK,
158                 })
159
160         ExpectStatusCode(t,
161                 "Unauthenticated request, no server key", http.StatusOK, response)
162         ExpectBody(t, "Unauthenticated request, no server key", TEST_HASH, response)
163
164         // ------------------
165         // With a server key.
166
167         PermissionSecret = []byte(known_key)
168         permission_ttl = time.Duration(300) * time.Second
169
170         // When a permission key is available, the locator returned
171         // from an authenticated PUT request will be signed.
172
173         // Authenticated PUT, signed locator
174         // => OK (signed response)
175         response = IssueRequest(rest,
176                 &RequestTester{
177                         method:       "PUT",
178                         uri:          unsigned_locator,
179                         request_body: TEST_BLOCK,
180                         api_token:    known_token,
181                 })
182
183         ExpectStatusCode(t,
184                 "Authenticated PUT, signed locator, with server key",
185                 http.StatusOK, response)
186         if !VerifySignature(response.Body.String(), known_token) {
187                 t.Errorf("Authenticated PUT, signed locator, with server key:\n"+
188                         "response '%s' does not contain a valid signature",
189                         response.Body.String())
190         }
191
192         // Unauthenticated PUT, unsigned locator
193         // => OK
194         response = IssueRequest(rest,
195                 &RequestTester{
196                         method:       "PUT",
197                         uri:          unsigned_locator,
198                         request_body: TEST_BLOCK,
199                 })
200
201         ExpectStatusCode(t,
202                 "Unauthenticated PUT, unsigned locator, with server key",
203                 http.StatusOK, response)
204         ExpectBody(t,
205                 "Unauthenticated PUT, unsigned locator, with server key",
206                 TEST_HASH, response)
207 }
208
209 // Test /index requests:
210 //   - enforce_permissions off | unauthenticated /index request
211 //   - enforce_permissions off | unauthenticated /index/prefix request
212 //   - enforce_permissions off | authenticated /index request        | non-superuser
213 //   - enforce_permissions off | authenticated /index/prefix request | non-superuser
214 //   - enforce_permissions off | authenticated /index request        | superuser
215 //   - enforce_permissions off | authenticated /index/prefix request | superuser
216 //   - enforce_permissions on  | unauthenticated /index request
217 //   - enforce_permissions on  | unauthenticated /index/prefix request
218 //   - enforce_permissions on  | authenticated /index request        | non-superuser
219 //   - enforce_permissions on  | authenticated /index/prefix request | non-superuser
220 //   - enforce_permissions on  | authenticated /index request        | superuser
221 //   - enforce_permissions on  | authenticated /index/prefix request | superuser
222 //
223 // The only /index requests that should succeed are those issued by the
224 // superuser when enforce_permissions = true.
225 //
226 func TestIndexHandler(t *testing.T) {
227         defer teardown()
228
229         // Set up Keep volumes and populate them.
230         // Include multiple blocks on different volumes, and
231         // some metadata files (which should be omitted from index listings)
232         KeepVM = MakeTestVolumeManager(2)
233         defer func() { KeepVM.Quit() }()
234
235         vols := KeepVM.Volumes()
236         vols[0].Put(TEST_HASH, TEST_BLOCK)
237         vols[1].Put(TEST_HASH_2, TEST_BLOCK_2)
238         vols[0].Put(TEST_HASH+".meta", []byte("metadata"))
239         vols[1].Put(TEST_HASH_2+".meta", []byte("metadata"))
240
241         // Set up a REST router for testing the handlers.
242         rest := MakeRESTRouter()
243
244         data_manager_token = "DATA MANAGER TOKEN"
245
246         unauthenticated_req := &RequestTester{
247                 method: "GET",
248                 uri:    "http://localhost:25107/index",
249         }
250         authenticated_req := &RequestTester{
251                 method:    "GET",
252                 uri:       "http://localhost:25107/index",
253                 api_token: known_token,
254         }
255         superuser_req := &RequestTester{
256                 method:    "GET",
257                 uri:       "http://localhost:25107/index",
258                 api_token: data_manager_token,
259         }
260         unauth_prefix_req := &RequestTester{
261                 method: "GET",
262                 uri:    "http://localhost:25107/index/" + TEST_HASH[0:3],
263         }
264         auth_prefix_req := &RequestTester{
265                 method:    "GET",
266                 uri:       "http://localhost:25107/index/" + TEST_HASH[0:3],
267                 api_token: known_token,
268         }
269         superuser_prefix_req := &RequestTester{
270                 method:    "GET",
271                 uri:       "http://localhost:25107/index/" + TEST_HASH[0:3],
272                 api_token: data_manager_token,
273         }
274
275         // ----------------------------
276         // enforce_permissions disabled
277         // All /index requests should fail.
278         enforce_permissions = false
279
280         // unauthenticated /index request
281         // => PermissionError
282         response := IssueRequest(rest, unauthenticated_req)
283         ExpectStatusCode(t,
284                 "enforce_permissions off, unauthenticated request",
285                 PermissionError.HTTPCode,
286                 response)
287
288         // unauthenticated /index/prefix request
289         // => PermissionError
290         response = IssueRequest(rest, unauth_prefix_req)
291         ExpectStatusCode(t,
292                 "enforce_permissions off, unauthenticated /index/prefix request",
293                 PermissionError.HTTPCode,
294                 response)
295
296         // authenticated /index request, non-superuser
297         // => PermissionError
298         response = IssueRequest(rest, authenticated_req)
299         ExpectStatusCode(t,
300                 "enforce_permissions off, authenticated request, non-superuser",
301                 PermissionError.HTTPCode,
302                 response)
303
304         // authenticated /index/prefix request, non-superuser
305         // => PermissionError
306         response = IssueRequest(rest, auth_prefix_req)
307         ExpectStatusCode(t,
308                 "enforce_permissions off, authenticated /index/prefix request, non-superuser",
309                 PermissionError.HTTPCode,
310                 response)
311
312         // authenticated /index request, superuser
313         // => PermissionError
314         response = IssueRequest(rest, superuser_req)
315         ExpectStatusCode(t,
316                 "enforce_permissions off, superuser request",
317                 PermissionError.HTTPCode,
318                 response)
319
320         // superuser /index/prefix request
321         // => PermissionError
322         response = IssueRequest(rest, superuser_prefix_req)
323         ExpectStatusCode(t,
324                 "enforce_permissions off, superuser /index/prefix request",
325                 PermissionError.HTTPCode,
326                 response)
327
328         // ---------------------------
329         // enforce_permissions enabled
330         // Only the superuser should be allowed to issue /index requests.
331         enforce_permissions = true
332
333         // unauthenticated /index request
334         // => PermissionError
335         response = IssueRequest(rest, unauthenticated_req)
336         ExpectStatusCode(t,
337                 "enforce_permissions on, unauthenticated request",
338                 PermissionError.HTTPCode,
339                 response)
340
341         // unauthenticated /index/prefix request
342         // => PermissionError
343         response = IssueRequest(rest, unauth_prefix_req)
344         ExpectStatusCode(t,
345                 "permissions on, unauthenticated /index/prefix request",
346                 PermissionError.HTTPCode,
347                 response)
348
349         // authenticated /index request, non-superuser
350         // => PermissionError
351         response = IssueRequest(rest, authenticated_req)
352         ExpectStatusCode(t,
353                 "permissions on, authenticated request, non-superuser",
354                 PermissionError.HTTPCode,
355                 response)
356
357         // authenticated /index/prefix request, non-superuser
358         // => PermissionError
359         response = IssueRequest(rest, auth_prefix_req)
360         ExpectStatusCode(t,
361                 "permissions on, authenticated /index/prefix request, non-superuser",
362                 PermissionError.HTTPCode,
363                 response)
364
365         // superuser /index request
366         // => OK
367         response = IssueRequest(rest, superuser_req)
368         ExpectStatusCode(t,
369                 "permissions on, superuser request",
370                 http.StatusOK,
371                 response)
372
373         expected := `^` + TEST_HASH + `\+\d+ \d+\n` +
374                 TEST_HASH_2 + `\+\d+ \d+\n$`
375         match, _ := regexp.MatchString(expected, response.Body.String())
376         if !match {
377                 t.Errorf(
378                         "permissions on, superuser request: expected %s, got:\n%s",
379                         expected, response.Body.String())
380         }
381
382         // superuser /index/prefix request
383         // => OK
384         response = IssueRequest(rest, superuser_prefix_req)
385         ExpectStatusCode(t,
386                 "permissions on, superuser request",
387                 http.StatusOK,
388                 response)
389
390         expected = `^` + TEST_HASH + `\+\d+ \d+\n$`
391         match, _ = regexp.MatchString(expected, response.Body.String())
392         if !match {
393                 t.Errorf(
394                         "permissions on, superuser /index/prefix request: expected %s, got:\n%s",
395                         expected, response.Body.String())
396         }
397 }
398
399 // ====================
400 // Helper functions
401 // ====================
402
403 // IssueTestRequest executes an HTTP request described by rt, to a
404 // specified REST router.  It returns the HTTP response to the request.
405 func IssueRequest(router *mux.Router, rt *RequestTester) *httptest.ResponseRecorder {
406         response := httptest.NewRecorder()
407         body := bytes.NewReader(rt.request_body)
408         req, _ := http.NewRequest(rt.method, rt.uri, body)
409         if rt.api_token != "" {
410                 req.Header.Set("Authorization", "OAuth "+rt.api_token)
411         }
412         router.ServeHTTP(response, req)
413         return response
414 }
415
416 // ExpectStatusCode checks whether a response has the specified status code,
417 // and reports a test failure if not.
418 func ExpectStatusCode(
419         t *testing.T,
420         testname string,
421         expected_status int,
422         response *httptest.ResponseRecorder) {
423         if response.Code != expected_status {
424                 t.Errorf("%s: expected status %s, got %+v",
425                         testname, expected_status, response)
426         }
427 }
428
429 func ExpectBody(
430         t *testing.T,
431         testname string,
432         expected_body string,
433         response *httptest.ResponseRecorder) {
434         if response.Body.String() != expected_body {
435                 t.Errorf("%s: expected response body '%s', got %+v",
436                         testname, expected_body, response)
437         }
438 }