Fix repositories.get_all_permissions, add tests. closes #3546
[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         "encoding/json"
15         "fmt"
16         "github.com/gorilla/mux"
17         "net/http"
18         "net/http/httptest"
19         "os"
20         "regexp"
21         "strings"
22         "testing"
23         "time"
24 )
25
26 // A RequestTester represents the parameters for an HTTP request to
27 // be issued on behalf of a unit test.
28 type RequestTester struct {
29         uri          string
30         api_token    string
31         method       string
32         request_body []byte
33 }
34
35 // Test GetBlockHandler on the following situations:
36 //   - permissions off, unauthenticated request, unsigned locator
37 //   - permissions on, authenticated request, signed locator
38 //   - permissions on, authenticated request, unsigned locator
39 //   - permissions on, unauthenticated request, signed locator
40 //   - permissions on, authenticated request, expired locator
41 //
42 func TestGetHandler(t *testing.T) {
43         defer teardown()
44
45         // Prepare two test Keep volumes. Our block is stored on the second volume.
46         KeepVM = MakeTestVolumeManager(2)
47         defer KeepVM.Quit()
48
49         vols := KeepVM.Volumes()
50         if err := vols[0].Put(TEST_HASH, TEST_BLOCK); err != nil {
51                 t.Error(err)
52         }
53
54         // Set up a REST router for testing the handlers.
55         rest := MakeRESTRouter()
56
57         // Create locators for testing.
58         // Turn on permission settings so we can generate signed locators.
59         enforce_permissions = true
60         PermissionSecret = []byte(known_key)
61         permission_ttl = time.Duration(300) * time.Second
62
63         var (
64                 unsigned_locator  = "/" + TEST_HASH
65                 valid_timestamp   = time.Now().Add(permission_ttl)
66                 expired_timestamp = time.Now().Add(-time.Hour)
67                 signed_locator    = "/" + SignLocator(TEST_HASH, known_token, valid_timestamp)
68                 expired_locator   = "/" + SignLocator(TEST_HASH, known_token, expired_timestamp)
69         )
70
71         // -----------------
72         // Test unauthenticated request with permissions off.
73         enforce_permissions = false
74
75         // Unauthenticated request, unsigned locator
76         // => OK
77         response := IssueRequest(rest,
78                 &RequestTester{
79                         method: "GET",
80                         uri:    unsigned_locator,
81                 })
82         ExpectStatusCode(t,
83                 "Unauthenticated request, unsigned locator", http.StatusOK, response)
84         ExpectBody(t,
85                 "Unauthenticated request, unsigned locator",
86                 string(TEST_BLOCK),
87                 response)
88         received_xbs := response.Header().Get("X-Block-Size")
89         expected_xbs := fmt.Sprintf("%d", len(TEST_BLOCK))
90         if received_xbs != expected_xbs {
91                 t.Errorf("expected X-Block-Size %s, got %s", expected_xbs, received_xbs)
92         }
93
94         // ----------------
95         // Permissions: on.
96         enforce_permissions = true
97
98         // Authenticated request, signed locator
99         // => OK
100         response = IssueRequest(rest, &RequestTester{
101                 method:    "GET",
102                 uri:       signed_locator,
103                 api_token: known_token,
104         })
105         ExpectStatusCode(t,
106                 "Authenticated request, signed locator", http.StatusOK, response)
107         ExpectBody(t,
108                 "Authenticated request, signed locator", string(TEST_BLOCK), response)
109         received_xbs = response.Header().Get("X-Block-Size")
110         expected_xbs = fmt.Sprintf("%d", len(TEST_BLOCK))
111         if received_xbs != expected_xbs {
112                 t.Errorf("expected X-Block-Size %s, got %s", expected_xbs, received_xbs)
113         }
114
115         // Authenticated request, unsigned locator
116         // => PermissionError
117         response = IssueRequest(rest, &RequestTester{
118                 method:    "GET",
119                 uri:       unsigned_locator,
120                 api_token: known_token,
121         })
122         ExpectStatusCode(t, "unsigned locator", PermissionError.HTTPCode, response)
123
124         // Unauthenticated request, signed locator
125         // => PermissionError
126         response = IssueRequest(rest, &RequestTester{
127                 method: "GET",
128                 uri:    signed_locator,
129         })
130         ExpectStatusCode(t,
131                 "Unauthenticated request, signed locator",
132                 PermissionError.HTTPCode, response)
133
134         // Authenticated request, expired locator
135         // => ExpiredError
136         response = IssueRequest(rest, &RequestTester{
137                 method:    "GET",
138                 uri:       expired_locator,
139                 api_token: known_token,
140         })
141         ExpectStatusCode(t,
142                 "Authenticated request, expired locator",
143                 ExpiredError.HTTPCode, response)
144 }
145
146 // Test PutBlockHandler on the following situations:
147 //   - no server key
148 //   - with server key, authenticated request, unsigned locator
149 //   - with server key, unauthenticated request, unsigned locator
150 //
151 func TestPutHandler(t *testing.T) {
152         defer teardown()
153
154         // Prepare two test Keep volumes.
155         KeepVM = MakeTestVolumeManager(2)
156         defer KeepVM.Quit()
157
158         // Set up a REST router for testing the handlers.
159         rest := MakeRESTRouter()
160
161         // --------------
162         // No server key.
163
164         // Unauthenticated request, no server key
165         // => OK (unsigned response)
166         unsigned_locator := "/" + TEST_HASH
167         response := IssueRequest(rest,
168                 &RequestTester{
169                         method:       "PUT",
170                         uri:          unsigned_locator,
171                         request_body: TEST_BLOCK,
172                 })
173
174         ExpectStatusCode(t,
175                 "Unauthenticated request, no server key", http.StatusOK, response)
176         ExpectBody(t,
177                 "Unauthenticated request, no server key",
178                 TEST_HASH_PUT_RESPONSE, response)
179
180         // ------------------
181         // With a server key.
182
183         PermissionSecret = []byte(known_key)
184         permission_ttl = time.Duration(300) * time.Second
185
186         // When a permission key is available, the locator returned
187         // from an authenticated PUT request will be signed.
188
189         // Authenticated PUT, signed locator
190         // => OK (signed response)
191         response = IssueRequest(rest,
192                 &RequestTester{
193                         method:       "PUT",
194                         uri:          unsigned_locator,
195                         request_body: TEST_BLOCK,
196                         api_token:    known_token,
197                 })
198
199         ExpectStatusCode(t,
200                 "Authenticated PUT, signed locator, with server key",
201                 http.StatusOK, response)
202         response_locator := strings.TrimSpace(response.Body.String())
203         if !VerifySignature(response_locator, known_token) {
204                 t.Errorf("Authenticated PUT, signed locator, with server key:\n"+
205                         "response '%s' does not contain a valid signature",
206                         response_locator)
207         }
208
209         // Unauthenticated PUT, unsigned locator
210         // => OK
211         response = IssueRequest(rest,
212                 &RequestTester{
213                         method:       "PUT",
214                         uri:          unsigned_locator,
215                         request_body: TEST_BLOCK,
216                 })
217
218         ExpectStatusCode(t,
219                 "Unauthenticated PUT, unsigned locator, with server key",
220                 http.StatusOK, response)
221         ExpectBody(t,
222                 "Unauthenticated PUT, unsigned locator, with server key",
223                 TEST_HASH_PUT_RESPONSE, response)
224 }
225
226 // Test /index requests:
227 //   - enforce_permissions off | unauthenticated /index request
228 //   - enforce_permissions off | unauthenticated /index/prefix request
229 //   - enforce_permissions off | authenticated /index request        | non-superuser
230 //   - enforce_permissions off | authenticated /index/prefix request | non-superuser
231 //   - enforce_permissions off | authenticated /index request        | superuser
232 //   - enforce_permissions off | authenticated /index/prefix request | superuser
233 //   - enforce_permissions on  | unauthenticated /index request
234 //   - enforce_permissions on  | unauthenticated /index/prefix request
235 //   - enforce_permissions on  | authenticated /index request        | non-superuser
236 //   - enforce_permissions on  | authenticated /index/prefix request | non-superuser
237 //   - enforce_permissions on  | authenticated /index request        | superuser
238 //   - enforce_permissions on  | authenticated /index/prefix request | superuser
239 //
240 // The only /index requests that should succeed are those issued by the
241 // superuser when enforce_permissions = true.
242 //
243 func TestIndexHandler(t *testing.T) {
244         defer teardown()
245
246         // Set up Keep volumes and populate them.
247         // Include multiple blocks on different volumes, and
248         // some metadata files (which should be omitted from index listings)
249         KeepVM = MakeTestVolumeManager(2)
250         defer KeepVM.Quit()
251
252         vols := KeepVM.Volumes()
253         vols[0].Put(TEST_HASH, TEST_BLOCK)
254         vols[1].Put(TEST_HASH_2, TEST_BLOCK_2)
255         vols[0].Put(TEST_HASH+".meta", []byte("metadata"))
256         vols[1].Put(TEST_HASH_2+".meta", []byte("metadata"))
257
258         // Set up a REST router for testing the handlers.
259         rest := MakeRESTRouter()
260
261         data_manager_token = "DATA MANAGER TOKEN"
262
263         unauthenticated_req := &RequestTester{
264                 method: "GET",
265                 uri:    "/index",
266         }
267         authenticated_req := &RequestTester{
268                 method:    "GET",
269                 uri:       "/index",
270                 api_token: known_token,
271         }
272         superuser_req := &RequestTester{
273                 method:    "GET",
274                 uri:       "/index",
275                 api_token: data_manager_token,
276         }
277         unauth_prefix_req := &RequestTester{
278                 method: "GET",
279                 uri:    "/index/" + TEST_HASH[0:3],
280         }
281         auth_prefix_req := &RequestTester{
282                 method:    "GET",
283                 uri:       "/index/" + TEST_HASH[0:3],
284                 api_token: known_token,
285         }
286         superuser_prefix_req := &RequestTester{
287                 method:    "GET",
288                 uri:       "/index/" + TEST_HASH[0:3],
289                 api_token: data_manager_token,
290         }
291
292         // ----------------------------
293         // enforce_permissions disabled
294         // All /index requests should fail.
295         enforce_permissions = false
296
297         // unauthenticated /index request
298         // => PermissionError
299         response := IssueRequest(rest, unauthenticated_req)
300         ExpectStatusCode(t,
301                 "enforce_permissions off, unauthenticated request",
302                 PermissionError.HTTPCode,
303                 response)
304
305         // unauthenticated /index/prefix request
306         // => PermissionError
307         response = IssueRequest(rest, unauth_prefix_req)
308         ExpectStatusCode(t,
309                 "enforce_permissions off, unauthenticated /index/prefix request",
310                 PermissionError.HTTPCode,
311                 response)
312
313         // authenticated /index request, non-superuser
314         // => PermissionError
315         response = IssueRequest(rest, authenticated_req)
316         ExpectStatusCode(t,
317                 "enforce_permissions off, authenticated request, non-superuser",
318                 PermissionError.HTTPCode,
319                 response)
320
321         // authenticated /index/prefix request, non-superuser
322         // => PermissionError
323         response = IssueRequest(rest, auth_prefix_req)
324         ExpectStatusCode(t,
325                 "enforce_permissions off, authenticated /index/prefix request, non-superuser",
326                 PermissionError.HTTPCode,
327                 response)
328
329         // authenticated /index request, superuser
330         // => PermissionError
331         response = IssueRequest(rest, superuser_req)
332         ExpectStatusCode(t,
333                 "enforce_permissions off, superuser request",
334                 PermissionError.HTTPCode,
335                 response)
336
337         // superuser /index/prefix request
338         // => PermissionError
339         response = IssueRequest(rest, superuser_prefix_req)
340         ExpectStatusCode(t,
341                 "enforce_permissions off, superuser /index/prefix request",
342                 PermissionError.HTTPCode,
343                 response)
344
345         // ---------------------------
346         // enforce_permissions enabled
347         // Only the superuser should be allowed to issue /index requests.
348         enforce_permissions = true
349
350         // unauthenticated /index request
351         // => PermissionError
352         response = IssueRequest(rest, unauthenticated_req)
353         ExpectStatusCode(t,
354                 "enforce_permissions on, unauthenticated request",
355                 PermissionError.HTTPCode,
356                 response)
357
358         // unauthenticated /index/prefix request
359         // => PermissionError
360         response = IssueRequest(rest, unauth_prefix_req)
361         ExpectStatusCode(t,
362                 "permissions on, unauthenticated /index/prefix request",
363                 PermissionError.HTTPCode,
364                 response)
365
366         // authenticated /index request, non-superuser
367         // => PermissionError
368         response = IssueRequest(rest, authenticated_req)
369         ExpectStatusCode(t,
370                 "permissions on, authenticated request, non-superuser",
371                 PermissionError.HTTPCode,
372                 response)
373
374         // authenticated /index/prefix request, non-superuser
375         // => PermissionError
376         response = IssueRequest(rest, auth_prefix_req)
377         ExpectStatusCode(t,
378                 "permissions on, authenticated /index/prefix request, non-superuser",
379                 PermissionError.HTTPCode,
380                 response)
381
382         // superuser /index request
383         // => OK
384         response = IssueRequest(rest, superuser_req)
385         ExpectStatusCode(t,
386                 "permissions on, superuser request",
387                 http.StatusOK,
388                 response)
389
390         expected := `^` + TEST_HASH + `\+\d+ \d+\n` +
391                 TEST_HASH_2 + `\+\d+ \d+\n$`
392         match, _ := regexp.MatchString(expected, response.Body.String())
393         if !match {
394                 t.Errorf(
395                         "permissions on, superuser request: expected %s, got:\n%s",
396                         expected, response.Body.String())
397         }
398
399         // superuser /index/prefix request
400         // => OK
401         response = IssueRequest(rest, superuser_prefix_req)
402         ExpectStatusCode(t,
403                 "permissions on, superuser request",
404                 http.StatusOK,
405                 response)
406
407         expected = `^` + TEST_HASH + `\+\d+ \d+\n$`
408         match, _ = regexp.MatchString(expected, response.Body.String())
409         if !match {
410                 t.Errorf(
411                         "permissions on, superuser /index/prefix request: expected %s, got:\n%s",
412                         expected, response.Body.String())
413         }
414 }
415
416 // TestDeleteHandler
417 //
418 // Cases tested:
419 //
420 //   With no token and with a non-data-manager token:
421 //   * Delete existing block
422 //     (test for 403 Forbidden, confirm block not deleted)
423 //
424 //   With data manager token:
425 //
426 //   * Delete existing block
427 //     (test for 200 OK, response counts, confirm block deleted)
428 //
429 //   * Delete nonexistent block
430 //     (test for 200 OK, response counts)
431 //
432 //   TODO(twp):
433 //
434 //   * Delete block on read-only and read-write volume
435 //     (test for 200 OK, response with copies_deleted=1,
436 //     copies_failed=1, confirm block deleted only on r/w volume)
437 //
438 //   * Delete block on read-only volume only
439 //     (test for 200 OK, response with copies_deleted=0, copies_failed=1,
440 //     confirm block not deleted)
441 //
442 func TestDeleteHandler(t *testing.T) {
443         defer teardown()
444
445         // Set up Keep volumes and populate them.
446         // Include multiple blocks on different volumes, and
447         // some metadata files (which should be omitted from index listings)
448         KeepVM = MakeTestVolumeManager(2)
449         defer KeepVM.Quit()
450
451         vols := KeepVM.Volumes()
452         vols[0].Put(TEST_HASH, TEST_BLOCK)
453
454         // Set up a REST router for testing the handlers.
455         rest := MakeRESTRouter()
456
457         var user_token = "NOT DATA MANAGER TOKEN"
458         data_manager_token = "DATA MANAGER TOKEN"
459
460         unauth_req := &RequestTester{
461                 method: "DELETE",
462                 uri:    "/" + TEST_HASH,
463         }
464
465         user_req := &RequestTester{
466                 method:    "DELETE",
467                 uri:       "/" + TEST_HASH,
468                 api_token: user_token,
469         }
470
471         superuser_existing_block_req := &RequestTester{
472                 method:    "DELETE",
473                 uri:       "/" + TEST_HASH,
474                 api_token: data_manager_token,
475         }
476
477         superuser_nonexistent_block_req := &RequestTester{
478                 method:    "DELETE",
479                 uri:       "/" + TEST_HASH_2,
480                 api_token: data_manager_token,
481         }
482
483         // Unauthenticated request returns PermissionError.
484         var response *httptest.ResponseRecorder
485         response = IssueRequest(rest, unauth_req)
486         ExpectStatusCode(t,
487                 "unauthenticated request",
488                 PermissionError.HTTPCode,
489                 response)
490
491         // Authenticated non-admin request returns PermissionError.
492         response = IssueRequest(rest, user_req)
493         ExpectStatusCode(t,
494                 "authenticated non-admin request",
495                 PermissionError.HTTPCode,
496                 response)
497
498         // Authenticated admin request for nonexistent block.
499         type deletecounter struct {
500                 Deleted int `json:"copies_deleted"`
501                 Failed  int `json:"copies_failed"`
502         }
503         var response_dc, expected_dc deletecounter
504
505         response = IssueRequest(rest, superuser_nonexistent_block_req)
506         ExpectStatusCode(t,
507                 "data manager request, nonexistent block",
508                 http.StatusNotFound,
509                 response)
510
511         // Authenticated admin request for existing block while never_delete is set.
512         never_delete = true
513         response = IssueRequest(rest, superuser_existing_block_req)
514         ExpectStatusCode(t,
515                 "authenticated request, existing block, method disabled",
516                 MethodDisabledError.HTTPCode,
517                 response)
518         never_delete = false
519
520         // Authenticated admin request for existing block.
521         response = IssueRequest(rest, superuser_existing_block_req)
522         ExpectStatusCode(t,
523                 "data manager request, existing block",
524                 http.StatusOK,
525                 response)
526         // Expect response {"copies_deleted":1,"copies_failed":0}
527         expected_dc = deletecounter{1, 0}
528         json.NewDecoder(response.Body).Decode(&response_dc)
529         if response_dc != expected_dc {
530                 t.Errorf("superuser_existing_block_req\nexpected: %+v\nreceived: %+v",
531                         expected_dc, response_dc)
532         }
533         // Confirm the block has been deleted
534         _, err := vols[0].Get(TEST_HASH)
535         var block_deleted = os.IsNotExist(err)
536         if !block_deleted {
537                 t.Error("superuser_existing_block_req: block not deleted")
538         }
539 }
540
541 // ====================
542 // Helper functions
543 // ====================
544
545 // IssueTestRequest executes an HTTP request described by rt, to a
546 // specified REST router.  It returns the HTTP response to the request.
547 func IssueRequest(router *mux.Router, rt *RequestTester) *httptest.ResponseRecorder {
548         response := httptest.NewRecorder()
549         body := bytes.NewReader(rt.request_body)
550         req, _ := http.NewRequest(rt.method, rt.uri, body)
551         if rt.api_token != "" {
552                 req.Header.Set("Authorization", "OAuth2 "+rt.api_token)
553         }
554         router.ServeHTTP(response, req)
555         return response
556 }
557
558 // ExpectStatusCode checks whether a response has the specified status code,
559 // and reports a test failure if not.
560 func ExpectStatusCode(
561         t *testing.T,
562         testname string,
563         expected_status int,
564         response *httptest.ResponseRecorder) {
565         if response.Code != expected_status {
566                 t.Errorf("%s: expected status %s, got %+v",
567                         testname, expected_status, response)
568         }
569 }
570
571 func ExpectBody(
572         t *testing.T,
573         testname string,
574         expected_body string,
575         response *httptest.ResponseRecorder) {
576         if response.Body.String() != expected_body {
577                 t.Errorf("%s: expected response body '%s', got %+v",
578                         testname, expected_body, response)
579         }
580 }