auth
auth/client_test.go
Exchange resource owner credentials for access token. Well, that was easy.
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)
1003 }
1004 }
1005 }