auth

Paddy 2015-01-18 Parent:fa8ee6a4507c Child:0a1e16b9c141

116:e000b1c24fc0 Go to Latest

auth/client_test.go

Make all tests that deal with the store interfaces go through the Context. This is mainly important so that pre- and post- save/retrieval/deletion/whatever transforms can be done without doing them in every single implementation of the store. Change the Endpoint URI property to be a string, not a *url.URL. This makes testing easier, JSON responses cleaner, and is all around just a better strategy. Just because we turn it into a URL every now and then doesn't mean that's how we need to store it. Add JSON tags to the Client type and Endpoint type. Create normalizeURI and normalizeURIString methods to... well, normalize the Endpoint URIs. This makes it so that we can compare them, and forgive some arbitrary user behaviour (like slashes, etc.) Add a NormalizedURI property to the Endpoint type. This is where we store the NormalizedURI, which is what we'll be using when we want to check if an endpoint is valid or not. For the sake of tests and predictability, however, we always want to redirect to the URI, not the NormalizedURI. Add checks to the Client creation API endpoint to give better errors. Now leaving out the Type won't be considered an invalid type, it will be considered a missing parameter. An empty name will be reported as a missing parameter, a name with too few characters will be reported as an insufficient name, and a name with too many characters will be reported as an overflow name. We gather as many of these errors as apply before returning. Check if an Endpoint URI is absolute before adding it as an endpoint, or return an invalid value error if it is not. Always return the errors array when creating a client. We could succeed in creating one or more things and still have errors. We should return anything that's created _as well as_ any errors encountered. Add unit testing for our CreateClientHandler. Fix our oauth2 tests so that if there's an error in the body, it's in the test logs. This should help debugging significantly. Fix our oauth2 tests so that the Profile only requires 1 iteration for its password hashing. This means each time we want to validate a session, it doesn't add a full second to our test runs. This is a big speed improvement for our tests. Add test helper methods for comparing API errors, API responses, and filling in server-generated information in a response that it's impossible to have an expectation around (e.g., IDs) so that we can use our comparison helpers to check if a response is as we expect it. Fix a typo in our Context helpers that was reporting no sessionStore being set _only_ when a sessionStore was set. So yes, the opposite of what we wanted. Oops. This was discovered by passing all our tests through the context. methods instead of operating on the stores themselves.

History
1 package auth
3 import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "io/ioutil"
8 "net/http"
9 "net/http/httptest"
10 "net/url"
11 "sort"
12 "strings"
13 "testing"
14 "time"
16 "code.secondbit.org/uuid.hg"
17 )
19 const (
20 clientChangeSecret = 1 << iota
21 clientChangeOwnerID
22 clientChangeName
23 clientChangeLogo
24 clientChangeWebsite
25 )
27 var clientStores = []clientStore{NewMemstore()}
29 func compareClients(client1, client2 Client) (success bool, field string, val1, val2 interface{}) {
30 if !client1.ID.Equal(client2.ID) {
31 return false, "ID", client1.ID, client2.ID
32 }
33 if client1.Secret != client2.Secret {
34 return false, "secret", client1.Secret, client2.Secret
35 }
36 if !client1.OwnerID.Equal(client2.OwnerID) {
37 return false, "owner ID", client1.OwnerID, client2.OwnerID
38 }
39 if client1.Name != client2.Name {
40 return false, "name", client1.Name, client2.Name
41 }
42 if client1.Logo != client2.Logo {
43 return false, "logo", client1.Logo, client2.Logo
44 }
45 if client1.Website != client2.Website {
46 return false, "website", client1.Website, client2.Website
47 }
48 if client1.Type != client2.Type {
49 return false, "type", client1.Type, client2.Type
50 }
51 return true, "", nil, nil
52 }
54 func compareEndpoints(endpoint1, endpoint2 Endpoint) (success bool, field string, val1, val2 interface{}) {
55 if !endpoint1.ID.Equal(endpoint2.ID) {
56 return false, "ID", endpoint1.ID, endpoint2.ID
57 }
58 if !endpoint1.ClientID.Equal(endpoint2.ClientID) {
59 return false, "OwnerID", endpoint1.ClientID, endpoint2.ClientID
60 }
61 if !endpoint1.Added.Equal(endpoint2.Added) {
62 return false, "Added", endpoint1.Added, endpoint2.Added
63 }
64 if endpoint1.URI != endpoint2.URI {
65 return false, "URI", endpoint1.URI, endpoint2.URI
66 }
67 return true, "", nil, nil
68 }
70 func TestClientStoreSuccess(t *testing.T) {
71 t.Parallel()
72 client := Client{
73 ID: uuid.NewID(),
74 Secret: "secret",
75 OwnerID: uuid.NewID(),
76 Name: "name",
77 Logo: "logo",
78 Website: "website",
79 }
80 for _, store := range clientStores {
81 context := Context{clients: store}
82 err := context.SaveClient(client)
83 if err != nil {
84 t.Fatalf("Error saving client to %T: %s", store, err)
85 }
86 err = context.SaveClient(client)
87 if err != ErrClientAlreadyExists {
88 t.Fatalf("Expected ErrClientAlreadyExists, got %v from %T", err, store)
89 }
90 retrieved, err := context.GetClient(client.ID)
91 if err != nil {
92 t.Fatalf("Error retrieving client from %T: %s", store, err)
93 }
94 success, field, expectation, result := compareClients(client, retrieved)
95 if !success {
96 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
97 }
98 clients, err := context.ListClientsByOwner(client.OwnerID, 25, 0)
99 if err != nil {
100 t.Fatalf("Error retrieving clients by owner from %T: %s", store, err)
101 }
102 if len(clients) != 1 {
103 t.Fatalf("Expected 1 client in response from %T, got %+v", store, clients)
104 }
105 success, field, expectation, result = compareClients(client, clients[0])
106 if !success {
107 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
108 }
109 err = context.DeleteClient(client.ID)
110 if err != nil {
111 t.Fatalf("Error deleting client from %T: %s", store, err)
112 }
113 err = context.DeleteClient(client.ID)
114 if err != ErrClientNotFound {
115 t.Fatalf("Expected ErrClientNotFound, got %s from %T", err, store)
116 }
117 retrieved, err = context.GetClient(client.ID)
118 if err != ErrClientNotFound {
119 t.Fatalf("Expected ErrClientNotFound from %T, got %+v and %s", store, retrieved, err)
120 }
121 clients, err = context.ListClientsByOwner(client.OwnerID, 25, 0)
122 if err != nil {
123 t.Fatalf("Error listing clients by owner from %T: %s", store, err)
124 }
125 if len(clients) != 0 {
126 t.Fatalf("Expected 0 clients in response from %T, got %+v", store, clients)
127 }
128 }
129 }
131 func TestEndpointStoreSuccess(t *testing.T) {
132 t.Parallel()
133 client := Client{
134 ID: uuid.NewID(),
135 Secret: "secret",
136 OwnerID: uuid.NewID(),
137 Name: "name",
138 Logo: "logo",
139 Website: "website",
140 }
141 endpoint1 := Endpoint{
142 ID: uuid.NewID(),
143 ClientID: client.ID,
144 Added: time.Now(),
145 URI: "https://www.example.com/",
146 }
147 endpoint2 := Endpoint{
148 ID: uuid.NewID(),
149 ClientID: client.ID,
150 Added: time.Now(),
151 URI: "https://www.example.com/my/full/path",
152 }
153 for _, store := range clientStores {
154 context := Context{clients: store}
155 err := context.SaveClient(client)
156 if err != nil {
157 t.Fatalf("Error saving client to %T: %s", store, err)
158 }
159 err = context.AddEndpoints(client.ID, []Endpoint{endpoint1})
160 if err != nil {
161 t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
162 }
163 endpoints, err := context.ListEndpoints(client.ID, 10, 0)
164 if err != nil {
165 t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
166 }
167 if len(endpoints) != 1 {
168 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store)
169 }
170 success, field, expectation, result := compareEndpoints(endpoint1, endpoints[0])
171 if !success {
172 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
173 }
174 err = context.AddEndpoints(client.ID, []Endpoint{endpoint2})
175 if err != nil {
176 t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
177 }
178 endpoints, err = context.ListEndpoints(client.ID, 10, 0)
179 if err != nil {
180 t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
181 }
182 if len(endpoints) != 2 {
183 t.Fatalf("Expected %d endpoints, got %+v from %T", 2, endpoints, store)
184 }
185 sortedEnd := sortedEndpoints(endpoints)
186 sort.Sort(sortedEnd)
187 endpoints = []Endpoint(sortedEnd)
188 success, field, expectation, result = compareEndpoints(endpoint1, endpoints[0])
189 if !success {
190 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
191 }
192 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[1])
193 if !success {
194 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
195 }
196 err = context.RemoveEndpoint(client.ID, endpoint1.ID)
197 if err != nil {
198 t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
199 }
200 endpoints, err = context.ListEndpoints(client.ID, 10, 0)
201 if err != nil {
202 t.Fatalf("Error listing endpoints in %T: %s", store, err)
203 }
204 if len(endpoints) != 1 {
205 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store)
206 }
207 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[0])
208 if !success {
209 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
210 }
211 err = context.RemoveEndpoint(client.ID, endpoint2.ID)
212 if err != nil {
213 t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
214 }
215 endpoints, err = context.ListEndpoints(client.ID, 10, 0)
216 if err != nil {
217 t.Fatalf("Error listing endpoints in %T: %s", store, err)
218 }
219 if len(endpoints) != 0 {
220 t.Fatalf("Expected %d endpoints, got %+v from %T", 0, endpoints, store)
221 }
222 }
223 }
225 func TestClientUpdates(t *testing.T) {
226 t.Parallel()
227 variations := 1 << 5
228 client := Client{
229 ID: uuid.NewID(),
230 Secret: "secret",
231 OwnerID: uuid.NewID(),
232 Name: "name",
233 Logo: "logo",
234 Website: "website",
235 }
236 for i := 0; i < variations; i++ {
237 var secret, name, logo, website string
238 change := ClientChange{}
239 expectation := client
240 result := client
241 if i&clientChangeSecret != 0 {
242 secret = fmt.Sprintf("secret-%d", i)
243 change.Secret = &secret
244 expectation.Secret = secret
245 }
246 if i&clientChangeOwnerID != 0 {
247 change.OwnerID = uuid.NewID()
248 expectation.OwnerID = change.OwnerID
249 }
250 if i&clientChangeName != 0 {
251 name = fmt.Sprintf("name-%d", i)
252 change.Name = &name
253 expectation.Name = name
254 }
255 if i&clientChangeLogo != 0 {
256 logo = fmt.Sprintf("logo-%d", i)
257 change.Logo = &logo
258 expectation.Logo = logo
259 }
260 if i&clientChangeWebsite != 0 {
261 website = fmt.Sprintf("website-%d", i)
262 change.Website = &website
263 expectation.Website = website
264 }
265 result.ApplyChange(change)
266 match, field, expected, got := compareClients(expectation, result)
267 if !match {
268 t.Fatalf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
269 }
270 for _, store := range clientStores {
271 context := Context{clients: store}
272 err := context.SaveClient(client)
273 if err != nil {
274 t.Fatalf("Error saving client in %T: %s", store, err)
275 }
276 err = context.UpdateClient(client.ID, change)
277 if err != nil {
278 t.Fatalf("Error updating client in %T: %s", store, err)
279 }
280 retrieved, err := context.GetClient(client.ID)
281 if err != nil {
282 t.Fatalf("Error getting client from %T: %s", store, err)
283 }
284 match, field, expected, got = compareClients(expectation, retrieved)
285 if !match {
286 t.Fatalf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
287 }
288 err = context.DeleteClient(client.ID)
289 if err != nil {
290 t.Fatalf("Error deleting client from %T: %s", store, err)
291 }
292 err = context.UpdateClient(client.ID, change)
293 if err != ErrClientNotFound {
294 t.Fatalf("Expected ErrClientNotFound, got %v from %T", err, store)
295 }
296 }
297 }
298 }
300 func TestClientEndpointChecks(t *testing.T) {
301 t.Parallel()
302 client := Client{
303 ID: uuid.NewID(),
304 Secret: "secret",
305 OwnerID: uuid.NewID(),
306 Name: "name",
307 Logo: "logo",
308 Website: "website",
309 }
310 endpoint1 := Endpoint{
311 ID: uuid.NewID(),
312 ClientID: client.ID,
313 Added: time.Now(),
314 URI: "https://www.example.com/first",
315 }
316 endpoint2 := Endpoint{
317 ID: uuid.NewID(),
318 ClientID: client.ID,
319 Added: time.Now(),
320 URI: "https://www.example.com/my/full/path",
321 }
322 candidates := map[string]bool{
323 "https://www.example.com/": false,
324 "https://www.example.com/first": true,
325 "https://www.example.com/first/extra/path": false,
326 "https://www.example.com/my": false,
327 "https://www.example.com/my/full/path": true,
328 }
329 for _, store := range clientStores {
330 context := Context{clients: store}
331 err := context.SaveClient(client)
332 if err != nil {
333 t.Fatalf("Error saving client in %T: %s", store, err)
334 }
335 err = context.AddEndpoints(client.ID, []Endpoint{endpoint1})
336 if err != nil {
337 t.Fatalf("Error saving endpoint in %T: %s", store, err)
338 }
339 err = context.AddEndpoints(client.ID, []Endpoint{endpoint2})
340 if err != nil {
341 t.Fatalf("Error saving endpoint in %T: %s", store, err)
342 }
343 for candidate, expectation := range candidates {
344 result, err := context.CheckEndpoint(client.ID, candidate)
345 if err != nil {
346 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
347 }
348 if result != expectation {
349 expectStr := "no"
350 resultStr := "a"
351 if expectation {
352 expectStr = "a"
353 resultStr = "no"
354 }
355 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr)
356 }
357 }
358 }
359 }
361 func TestClientEndpointChecksStrict(t *testing.T) {
362 t.Parallel()
363 client := Client{
364 ID: uuid.NewID(),
365 Secret: "secret",
366 OwnerID: uuid.NewID(),
367 Name: "name",
368 Logo: "logo",
369 Website: "website",
370 }
371 endpoint1 := Endpoint{
372 ID: uuid.NewID(),
373 ClientID: client.ID,
374 Added: time.Now(),
375 URI: "https://www.example.com/first",
376 }
377 endpoint2 := Endpoint{
378 ID: uuid.NewID(),
379 ClientID: client.ID,
380 Added: time.Now(),
381 URI: "https://www.example.com/my/full/path",
382 }
383 candidates := map[string]bool{
384 "https://www.example.com/": false,
385 "https://www.example.com/first": true,
386 "https://www.example.com/first/extra/path": false,
387 "https://www.example.com/my": false,
388 "https://www.example.com/my/full/path": true,
389 }
390 for _, store := range clientStores {
391 context := Context{clients: store}
392 err := context.SaveClient(client)
393 if err != nil {
394 t.Fatalf("Error saving client in %T: %s", store, err)
395 }
396 err = context.AddEndpoints(client.ID, []Endpoint{endpoint1})
397 if err != nil {
398 t.Fatalf("Error saving endpoint in %T: %s", store, err)
399 }
400 err = context.AddEndpoints(client.ID, []Endpoint{endpoint2})
401 if err != nil {
402 t.Fatalf("Error saving endpoint in %T: %s", store, err)
403 }
404 for candidate, expectation := range candidates {
405 result, err := context.CheckEndpoint(client.ID, candidate)
406 if err != nil {
407 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
408 }
409 if result != expectation {
410 expectStr := "no"
411 resultStr := "a"
412 if expectation {
413 expectStr = "a"
414 resultStr = "no"
415 }
416 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr)
417 }
418 }
419 }
420 }
422 func TestClientChangeValidation(t *testing.T) {
423 t.Parallel()
424 change := ClientChange{}
425 if err := change.Validate(); err != ErrEmptyChange {
426 t.Errorf("Expected %s to give an error of %s, gave %s", "empty change", ErrEmptyChange, err)
427 }
428 names := map[string]error{
429 "a": ErrClientNameTooShort,
430 "ab": nil,
431 "abc": nil,
432 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq": ErrClientNameTooLong,
433 }
434 for name, expectation := range names {
435 change = ClientChange{Name: &name}
436 if err := change.Validate(); err != expectation {
437 t.Errorf("Expected %s to give an error of %s, gave %s", name, expectation, err)
438 }
439 }
440 longPath := ""
441 for i := 0; i < 1025; i++ {
442 longPath = fmt.Sprintf("%s%d", longPath, i)
443 }
444 logos := map[string]error{
445 "https://www.example.com/" + longPath: ErrClientLogoTooLong,
446 "https://www.example.com/ab": nil,
447 "www.example.com/ab": ErrClientLogoNotURL,
448 "test": ErrClientLogoNotURL,
449 "": nil,
450 }
451 for logo, expectation := range logos {
452 change = ClientChange{Logo: &logo}
453 if err := change.Validate(); err != expectation {
454 t.Errorf("Expected %s to give an error of %s, gave %s", logo, expectation, err)
455 }
456 }
457 websites := map[string]error{
458 "https://www.example.com/" + longPath: ErrClientWebsiteTooLong,
459 "https://www.example.com/ab": nil,
460 "www.example.com/ab": ErrClientWebsiteNotURL,
461 "test": ErrClientWebsiteNotURL,
462 "": nil,
463 }
464 for website, expectation := range websites {
465 change = ClientChange{Website: &website}
466 if err := change.Validate(); err != expectation {
467 t.Errorf("Expected %s to give an error of %s, gave %s", website, expectation, err)
468 }
469 }
470 }
472 func TestVerifyClient(t *testing.T) {
473 t.Parallel()
474 memstore := NewMemstore()
475 context := Context{
476 clients: memstore,
477 }
478 client := Client{
479 ID: uuid.NewID(),
480 Secret: "super secret!",
481 OwnerID: uuid.NewID(),
482 Name: "My test client",
483 Logo: "https://secondbit.org/logo.png",
484 Website: "https://secondbit.org/",
485 Type: "confidential",
486 }
487 err := context.SaveClient(client)
488 if err != nil {
489 t.Fatal("Could not save client:", err)
490 }
491 publicClient := Client{
492 ID: uuid.NewID(),
493 Secret: "",
494 OwnerID: uuid.NewID(),
495 Name: "A public client",
496 Logo: "https://secondbit.org/logo.png",
497 Website: "https://secondbit.org/",
498 Type: "public",
499 }
500 err = context.SaveClient(publicClient)
501 if err != nil {
502 t.Fatal("Could not save client:", err)
503 }
505 // verifyClient with no auth header, no public clients
506 w := httptest.NewRecorder()
507 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
508 if err != nil {
509 t.Fatal("Can't build request:", err)
510 }
511 resp, success := verifyClient(w, r, false, context)
512 if success {
513 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
514 }
515 if resp != nil {
516 t.Error("Expected nil client ID, got", resp)
517 }
518 if w.Code != http.StatusBadRequest {
519 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
520 }
521 expectedBody := `{"error":"unauthorized_client"}`
522 if expectedBody != strings.TrimSpace(w.Body.String()) {
523 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
524 }
526 // verifyClient with no auth header, public clients, empty client_id
527 w = httptest.NewRecorder()
528 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
529 if err != nil {
530 t.Fatal("Can't build request:", err)
531 }
532 resp, success = verifyClient(w, r, true, context)
533 if success {
534 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
535 }
536 if resp != nil {
537 t.Error("Expected nil client ID, got", resp)
538 }
539 if w.Code != http.StatusUnauthorized {
540 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
541 }
542 expectedBody = `{"error":"invalid_client"}`
543 if expectedBody != strings.TrimSpace(w.Body.String()) {
544 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
545 }
547 // verifyClient with auth header, no public clients, empty client id
548 w = httptest.NewRecorder()
549 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
550 if err != nil {
551 t.Fatal("Can't build request:", err)
552 }
553 r.SetBasicAuth("", "no client ID set")
554 resp, success = verifyClient(w, r, false, context)
555 if success {
556 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
557 }
558 if resp != nil {
559 t.Error("Expected nil client ID, got", resp)
560 }
561 if w.Code != http.StatusUnauthorized {
562 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
563 }
564 expectedBody = `{"error":"invalid_client"}`
565 if expectedBody != strings.TrimSpace(w.Body.String()) {
566 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
567 }
568 if w.Header().Get("WWW-Authenticate") != "Basic" {
569 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
570 }
572 // verifyClient with auth header, public clients, empty client id
573 w = httptest.NewRecorder()
574 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
575 if err != nil {
576 t.Fatal("Can't build request:", err)
577 }
578 r.SetBasicAuth("", "no client ID set")
579 resp, success = verifyClient(w, r, true, context)
580 if success {
581 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
582 }
583 if resp != nil {
584 t.Error("Expected nil client ID, got", resp)
585 }
586 if w.Code != http.StatusUnauthorized {
587 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
588 }
589 expectedBody = `{"error":"invalid_client"}`
590 if expectedBody != strings.TrimSpace(w.Body.String()) {
591 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
592 }
593 if w.Header().Get("WWW-Authenticate") != "Basic" {
594 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
595 }
597 // verifyClient with auth header, no public clients, invalid client ID
598 w = httptest.NewRecorder()
599 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
600 if err != nil {
601 t.Fatal("Can't build request:", err)
602 }
603 r.SetBasicAuth("not an actual id", "invalid client ID set")
604 resp, success = verifyClient(w, r, false, context)
605 if success {
606 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
607 }
608 if resp != nil {
609 t.Error("Expected nil client ID, got", resp)
610 }
611 if w.Code != http.StatusUnauthorized {
612 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
613 }
614 expectedBody = `{"error":"invalid_client"}`
615 if expectedBody != strings.TrimSpace(w.Body.String()) {
616 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
617 }
618 if w.Header().Get("WWW-Authenticate") != "Basic" {
619 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
620 }
622 // verifyClient with auth header, public clients, invalid client ID
623 w = httptest.NewRecorder()
624 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
625 if err != nil {
626 t.Fatal("Can't build request:", err)
627 }
628 r.SetBasicAuth("not an actual id", "invalid client ID set")
629 resp, success = verifyClient(w, r, true, context)
630 if success {
631 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
632 }
633 if resp != nil {
634 t.Error("Expected nil client ID, got", resp)
635 }
636 if w.Code != http.StatusUnauthorized {
637 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
638 }
639 expectedBody = `{"error":"invalid_client"}`
640 if expectedBody != strings.TrimSpace(w.Body.String()) {
641 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
642 }
643 if w.Header().Get("WWW-Authenticate") != "Basic" {
644 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
645 }
647 // verifyClient with auth header, no public clients, client ID valid but not in clientStore
648 w = httptest.NewRecorder()
649 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
650 if err != nil {
651 t.Fatal("Can't build request:", err)
652 }
653 r.SetBasicAuth(uuid.NewID().String(), "non existent client ID set")
654 resp, success = verifyClient(w, r, false, context)
655 if success {
656 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
657 }
658 if resp != nil {
659 t.Error("Expected nil client ID, got", resp)
660 }
661 if w.Code != http.StatusUnauthorized {
662 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
663 }
664 expectedBody = `{"error":"invalid_client"}`
665 if expectedBody != strings.TrimSpace(w.Body.String()) {
666 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
667 }
668 if w.Header().Get("WWW-Authenticate") != "Basic" {
669 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
670 }
672 // verifyClient with auth header, public clients, client ID valid but not in clientStore
673 w = httptest.NewRecorder()
674 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
675 if err != nil {
676 t.Fatal("Can't build request:", err)
677 }
678 r.SetBasicAuth(uuid.NewID().String(), "non existent client ID set")
679 resp, success = verifyClient(w, r, true, context)
680 if success {
681 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
682 }
683 if resp != nil {
684 t.Error("Expected nil client ID, got", resp)
685 }
686 if w.Code != http.StatusUnauthorized {
687 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
688 }
689 expectedBody = `{"error":"invalid_client"}`
690 if expectedBody != strings.TrimSpace(w.Body.String()) {
691 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
692 }
693 if w.Header().Get("WWW-Authenticate") != "Basic" {
694 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
695 }
697 // verifyClient with auth header, no public clients, client secret wrong
698 w = httptest.NewRecorder()
699 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
700 if err != nil {
701 t.Fatal("Can't build request:", err)
702 }
703 r.SetBasicAuth(client.ID.String(), "not actually the secret")
704 resp, success = verifyClient(w, r, false, context)
705 if success {
706 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
707 }
708 if resp != nil {
709 t.Error("Expected nil client ID, got", resp)
710 }
711 if w.Code != http.StatusUnauthorized {
712 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
713 }
714 expectedBody = `{"error":"invalid_client"}`
715 if expectedBody != strings.TrimSpace(w.Body.String()) {
716 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
717 }
718 if w.Header().Get("WWW-Authenticate") != "Basic" {
719 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
720 }
722 // verifyClient with auth header, public clients, client secret wrong
723 w = httptest.NewRecorder()
724 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
725 if err != nil {
726 t.Fatal("Can't build request:", err)
727 }
728 r.SetBasicAuth(client.ID.String(), "not actually the secret")
729 resp, success = verifyClient(w, r, true, context)
730 if success {
731 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
732 }
733 if resp != nil {
734 t.Error("Expected nil client ID, got", resp)
735 }
736 if w.Code != http.StatusUnauthorized {
737 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
738 }
739 expectedBody = `{"error":"invalid_client"}`
740 if expectedBody != strings.TrimSpace(w.Body.String()) {
741 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
742 }
743 if w.Header().Get("WWW-Authenticate") != "Basic" {
744 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
745 }
747 // verifyClient with no auth header, public clients, invalid client_id post form value
748 w = httptest.NewRecorder()
749 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
750 if err != nil {
751 t.Fatal("Can't build request:", err)
752 }
753 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
754 params := url.Values{}
755 params.Set("client_id", "not an actual id")
756 body := bytes.NewBufferString(params.Encode())
757 r.Body = ioutil.NopCloser(body)
758 resp, success = verifyClient(w, r, true, context)
759 if success {
760 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
761 }
762 if resp != nil {
763 t.Error("Expected nil client ID, got", resp)
764 }
765 if w.Code != http.StatusUnauthorized {
766 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
767 }
768 expectedBody = `{"error":"invalid_client"}`
769 if expectedBody != strings.TrimSpace(w.Body.String()) {
770 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
771 }
773 // verifyClient with no auth header, public clients, client_id valid but not in clientStore
774 w = httptest.NewRecorder()
775 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
776 if err != nil {
777 t.Fatal("Can't build request:", err)
778 }
779 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
780 params = url.Values{}
781 params.Set("client_id", uuid.NewID().String())
782 body = bytes.NewBufferString(params.Encode())
783 r.Body = ioutil.NopCloser(body)
784 resp, success = verifyClient(w, r, true, context)
785 if success {
786 t.Error("Expected verification to fail, but succeeded with client ID:", resp)
787 }
788 if resp != nil {
789 t.Error("Expected nil client ID, got", resp)
790 }
791 if w.Code != http.StatusUnauthorized {
792 t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code)
793 }
794 expectedBody = `{"error":"invalid_client"}`
795 if expectedBody != strings.TrimSpace(w.Body.String()) {
796 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
797 }
799 // verifyClient with auth header, public clients, valid client_id and secret
800 w = httptest.NewRecorder()
801 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
802 if err != nil {
803 t.Fatal("Can't build request:", err)
804 }
805 r.SetBasicAuth(client.ID.String(), client.Secret)
806 resp, success = verifyClient(w, r, true, context)
807 if !success {
808 t.Error("Expected verification to succeed, but it failed")
809 }
810 if !client.ID.Equal(resp) {
811 t.Errorf("Expected client ID to be %s, got %s", client.ID, resp)
812 }
813 if w.Code != http.StatusOK {
814 t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code)
815 }
816 if w.Body.String() != "" {
817 t.Errorf(`Expected empty body, got "%s"`, w.Body.String())
818 }
820 // verifyClient with auth header, no public clients, valid client_id and secret
821 w = httptest.NewRecorder()
822 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
823 if err != nil {
824 t.Fatal("Can't build request:", err)
825 }
826 r.SetBasicAuth(client.ID.String(), client.Secret)
827 resp, success = verifyClient(w, r, true, context)
828 if !success {
829 t.Error("Expected verification to succeed, but it failed")
830 }
831 if !client.ID.Equal(resp) {
832 t.Errorf("Expected client ID to be %s, got %s", client.ID, resp)
833 }
834 if w.Code != http.StatusOK {
835 t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code)
836 }
837 if w.Body.String() != "" {
838 t.Errorf(`Expected empty body, got "%s"`, w.Body.String())
839 }
841 // verifyClient with no auth header, public clients, valid client_id and secret
842 w = httptest.NewRecorder()
843 r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
844 if err != nil {
845 t.Fatal("Can't build request:", err)
846 }
847 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
848 params = url.Values{}
849 params.Set("client_id", publicClient.ID.String())
850 body = bytes.NewBufferString(params.Encode())
851 r.Body = ioutil.NopCloser(body)
852 resp, success = verifyClient(w, r, true, context)
853 if !success {
854 t.Error("Expected verification to succeed, but it failed")
855 }
856 if !publicClient.ID.Equal(resp) {
857 t.Errorf("Expected client ID to be %s, got %s", publicClient.ID, resp)
858 }
859 if w.Code != http.StatusOK {
860 t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code)
861 }
862 if w.Body.String() != "" {
863 t.Errorf(`Expected empty body, got "%s"`, w.Body.String())
864 }
865 }
867 func TestCreateClientHandler(t *testing.T) {
868 t.Parallel()
869 memstore := NewMemstore()
870 c := Context{
871 clients: memstore,
872 profiles: memstore,
873 }
874 w := httptest.NewRecorder()
875 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/clients", nil)
876 if err != nil {
877 t.Fatal("Can't build request:", err)
878 }
879 r.Header.Set("Content-Type", "application/json")
880 CreateClientHandler(w, r, c)
881 if w.Code != http.StatusUnauthorized {
882 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
883 }
884 expected := `{"errors":[{"error":"access_denied"}]}`
885 result := strings.TrimSpace(w.Body.String())
886 if result != expected {
887 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
888 }
889 w = httptest.NewRecorder()
890 r.Header.Set("Authorization", "Not basic at all...")
891 CreateClientHandler(w, r, c)
892 if w.Code != http.StatusUnauthorized {
893 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
894 }
895 expected = `{"errors":[{"error":"access_denied"}]}`
896 result = strings.TrimSpace(w.Body.String())
897 if result != expected {
898 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
899 }
900 w = httptest.NewRecorder()
901 r.Header.Set("Authorization", "Basic TotallyNotBase64Encoded")
902 CreateClientHandler(w, r, c)
903 if w.Code != http.StatusUnauthorized {
904 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
905 }
906 expected = `{"errors":[{"error":"access_denied"}]}`
907 result = strings.TrimSpace(w.Body.String())
908 if result != expected {
909 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
910 }
911 w = httptest.NewRecorder()
912 r.Header.Set("Authorization", "Basic dGhpc2hhc25vY29sb24=")
913 CreateClientHandler(w, r, c)
914 if w.Code != http.StatusUnauthorized {
915 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
916 }
917 expected = `{"errors":[{"error":"access_denied"}]}`
918 result = strings.TrimSpace(w.Body.String())
919 if result != expected {
920 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
921 }
922 profile := Profile{
923 ID: uuid.NewID(),
924 Name: "Test User",
925 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0",
926 Iterations: 1,
927 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b",
928 PassphraseScheme: 1,
929 Compromised: false,
930 LockedUntil: time.Time{},
931 PassphraseReset: "",
932 PassphraseResetCreated: time.Time{},
933 Created: time.Now(),
934 LastSeen: time.Time{},
935 }
936 login := Login{
937 Type: "email",
938 Value: "test@example.com",
939 ProfileID: profile.ID,
940 Created: time.Now(),
941 LastUsed: time.Time{},
942 }
943 w = httptest.NewRecorder()
944 r.SetBasicAuth("test@example.com", "mysecurepassphrase")
945 CreateClientHandler(w, r, c)
946 if w.Code != http.StatusUnauthorized {
947 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
948 }
949 expected = `{"errors":[{"error":"access_denied"}]}`
950 result = strings.TrimSpace(w.Body.String())
951 if result != expected {
952 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
953 }
954 err = c.SaveProfile(profile)
955 if err != nil {
956 t.Error("Error saving profile:", err)
957 }
958 err = c.AddLogin(login)
959 if err != nil {
960 t.Error("Error adding login:", err)
961 }
962 r.SetBasicAuth("test@example.com", "mysecurepassphrase")
963 type testStruct struct {
964 request string
965 code int
966 resp response
967 }
968 tests := []testStruct{
969 {``, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/"}}}},
970 {`{}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}},
971 {`{"type":"notarealtype"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}},
972 {`{"type":"notarealtype","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrOverflow, Field: "/name"}}}},
973 {`{"type":"notarealtype","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrInsufficient, Field: "/name"}}}},
974 {`{"type":"public"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/name"}}}},
975 {`{"type":"public","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrOverflow, Field: "/name"}}}},
976 {`{"type":"public","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInsufficient, Field: "/name"}}}},
977 {`{"name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}}}},
978 {`{"type":"notarealtype","name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}}}},
979 {`{"type":"public","name":"My Client"}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}}},
980 {`{"type":"public","name":"My Client", "endpoints": ["https://test.secondbit.org/", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://test.secondbit.org/"}, {URI: "https://paddy.io"}}}},
981 {`{"type":"public","name":"My Client", "endpoints": [":/not a url", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://paddy.io"}}, Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/endpoints/0"}}}},
982 {`{"type":"public","name":"My Client", "endpoints": [":/not a url", "/relative/uri", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://paddy.io"}}, Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/endpoints/0"}, {Slug: requestErrInvalidValue, Field: "/endpoints/1"}}}},
983 }
984 for pos, test := range tests {
985 t.Logf("Test #%d: `%s`", pos, test.request)
986 w = httptest.NewRecorder()
987 body := bytes.NewBufferString(test.request)
988 r.Body = ioutil.NopCloser(body)
989 CreateClientHandler(w, r, c)
990 if w.Code != test.code {
991 t.Errorf("Expected response code to be %d, got %d", test.code, w.Code)
992 }
993 t.Logf("Response: %s", w.Body.String())
994 var res response
995 err = json.Unmarshal(w.Body.Bytes(), &res)
996 if err != nil {
997 t.Error("Unexpected error unmarshalling response:", err)
998 }
999 fillInServerGenerated(test.resp, res)
1000 success, field, expectation, result := compareResponses(test.resp, res)
1001 if !success {
1002 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result)