auth

Paddy 2015-01-24 Parent:23c1a07c8a61 Child:163ce22fa4c9

130:6c755b23ec80 Go to Latest

auth/oauth2_test.go

Change normalization flags to a constant. Let's use a constant so we can ensure we're using the same flags everywhere. Otherwise, we can get weird data corruption because we use the wrong flags.

History
1 package auth
3 import (
4 "bytes"
5 "encoding/json"
6 "html/template"
7 "io/ioutil"
8 "net/http"
9 "net/http/httptest"
10 "net/url"
11 "testing"
12 "time"
14 "code.secondbit.org/uuid.hg"
15 )
17 const (
18 scopeSet = 1 << iota
19 stateSet
20 uriSet
21 )
23 func stripParam(param string, u *url.URL) {
24 q := u.Query()
25 q.Del(param)
26 u.RawQuery = q.Encode()
27 }
29 func TestGetAuthorizationCodeCodeSuccess(t *testing.T) {
30 t.Parallel()
31 store := NewMemstore()
32 testContext := Context{
33 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ if not .error }}Get auth grant{{ else }}{{ .error }}{{ end }}")),
34 clients: store,
35 authCodes: store,
36 profiles: store,
37 tokens: store,
38 sessions: store,
39 }
40 client := Client{
41 ID: uuid.NewID(),
42 Secret: "super secret!",
43 OwnerID: uuid.NewID(),
44 Name: "My test client",
45 Logo: "https://secondbit.org/logo.png",
46 Website: "https://secondbit.org",
47 Type: "public",
48 }
49 endpoint := Endpoint{
50 ID: uuid.NewID(),
51 ClientID: client.ID,
52 URI: "https://test.secondbit.org/redirect",
53 Added: time.Now(),
54 }
55 err := testContext.SaveClient(client)
56 if err != nil {
57 t.Fatal("Can't store client:", err)
58 }
59 err = testContext.AddEndpoints(client.ID, []Endpoint{endpoint})
60 if err != nil {
61 t.Fatal("Can't store endpoint:", err)
62 }
63 profile := Profile{
64 ID: uuid.NewID(),
65 }
66 err = testContext.SaveProfile(profile)
67 if err != nil {
68 t.Fatal("Can't store profile:", err)
69 }
70 session := Session{
71 ID: "testsession",
72 Active: true,
73 ProfileID: profile.ID,
74 }
75 err = testContext.CreateSession(session)
76 if err != nil {
77 t.Fatal("Can't store session:", err)
78 }
79 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
80 if err != nil {
81 t.Fatal("Can't build request:", err)
82 }
83 cookie := &http.Cookie{
84 Name: authCookieName,
85 Value: session.ID,
86 }
87 req.AddCookie(cookie)
88 for i := 0; i < 1<<3; i++ {
89 w := httptest.NewRecorder()
90 params := url.Values{}
91 // see OAuth 2.0 spec, section 4.1.1
92 params.Set("response_type", "code")
93 params.Set("client_id", client.ID.String())
94 if i&uriSet != 0 {
95 params.Set("redirect_uri", endpoint.URI)
96 }
97 if i&scopeSet != 0 {
98 params.Set("scope", "testscope")
99 }
100 if i&stateSet != 0 {
101 params.Set("state", "my super secure state string")
102 }
103 req.URL.RawQuery = params.Encode()
104 req.Method = "GET"
105 req.Body = nil
106 req.Header.Del("Content-Type")
107 GetAuthorizationCodeHandler(w, req, testContext)
108 if w.Code != http.StatusOK {
109 t.Log("Response body:", w.Body.String())
110 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusOK, w.Code, req.URL.String())
111 }
112 if w.Body.String() != "Get auth grant" {
113 t.Errorf("Expected body to be `%s`, got `%s` for %s", "Get auth grant", w.Body.String(), req.URL.String())
114 }
115 req.Method = "POST"
116 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
117 w = httptest.NewRecorder()
118 data := url.Values{}
119 data.Set("grant", "approved")
120 body := bytes.NewBufferString(data.Encode())
121 req.Body = ioutil.NopCloser(body)
122 GetAuthorizationCodeHandler(w, req, testContext)
123 if w.Code != http.StatusFound {
124 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusFound, w.Code, req.URL.String())
125 }
126 redirectedTo := w.Header().Get("Location")
127 red, err := url.Parse(redirectedTo)
128 if err != nil {
129 t.Fatalf(`Being redirected to a non-URL "%s" threw error "%s" for "%s"\n`, redirectedTo, err, req.URL.String())
130 }
131 t.Log("Redirected to", redirectedTo)
132 if red.Query().Get("code") == "" {
133 t.Fatalf(`Expected code param in redirect URL to be set, but it wasn't for %s`, req.URL.String())
134 }
135 if _, err := testContext.GetAuthorizationCode(red.Query().Get("code")); err != nil {
136 t.Fatalf(`Unexpected error "%s: retrieving the grant "%s" supplied in the redirect URL for %s`, err, red.Query().Get("code"), req.URL.String())
137 }
138 err = testContext.DeleteAuthorizationCode(red.Query().Get("code"))
139 if err != nil {
140 t.Logf(`Unexpected error "%s" deleting grant "%s" for %s`, err, red.Query().Get("code"), req.URL.String())
141 }
142 stripParam("code", red)
143 if red.Query().Get("state") != params.Get("state") {
144 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s" for %s`, params.Get("state"), red.Query().Get("state"), req.URL.String())
145 }
146 stripParam("state", red)
147 if red.String() != endpoint.URI {
148 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
149 }
150 }
151 }
153 func TestGetAuthorizationCodeCodeInvalidClient(t *testing.T) {
154 t.Parallel()
155 store := NewMemstore()
156 testContext := Context{
157 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .error }}")),
158 clients: store,
159 authCodes: store,
160 profiles: store,
161 tokens: store,
162 sessions: store,
163 }
164 client := Client{
165 ID: uuid.NewID(),
166 Secret: "super secret!",
167 OwnerID: uuid.NewID(),
168 Name: "My test client",
169 Type: "public",
170 }
171 err := testContext.SaveClient(client)
172 if err != nil {
173 t.Fatal("Can't store client:", err)
174 }
175 session := Session{
176 ID: "testsession",
177 Active: true,
178 }
179 err = testContext.CreateSession(session)
180 if err != nil {
181 t.Fatal("Can't store session:", err)
182 }
183 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
184 if err != nil {
185 t.Fatal("Can't build request:", err)
186 }
187 w := httptest.NewRecorder()
188 params := url.Values{}
189 params.Set("response_type", "code")
190 params.Set("redirect_uri", "https://test.secondbit.org/")
191 req.URL.RawQuery = params.Encode()
192 cookie := &http.Cookie{
193 Name: authCookieName,
194 Value: session.ID,
195 }
196 req.AddCookie(cookie)
197 GetAuthorizationCodeHandler(w, req, testContext)
198 if w.Code != http.StatusBadRequest {
199 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
200 }
201 if w.Body.String() != "Client ID must be specified in the request." {
202 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "Client ID must be specified in the request.", w.Body.String())
203 }
204 w = httptest.NewRecorder()
205 params.Set("client_id", "Not an ID")
206 req.URL.RawQuery = params.Encode()
207 GetAuthorizationCodeHandler(w, req, testContext)
208 if w.Code != http.StatusBadRequest {
209 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
210 }
211 if w.Body.String() != "client_id is not a valid Client ID." {
212 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "client_id is not a valid Client ID.", w.Body.String())
213 }
214 w = httptest.NewRecorder()
215 params.Set("client_id", uuid.NewID().String())
216 req.URL.RawQuery = params.Encode()
217 GetAuthorizationCodeHandler(w, req, testContext)
218 if w.Code != http.StatusBadRequest {
219 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
220 }
221 if w.Body.String() != "The specified Client couldn&rsquo;t be found." {
222 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The specified Client couldn&rsquo;t be found.", w.Body.String())
223 }
224 }
226 func TestGetAuthorizationCodeCodeInvalidURI(t *testing.T) {
227 t.Parallel()
228 store := NewMemstore()
229 testContext := Context{
230 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .error }}")),
231 clients: store,
232 authCodes: store,
233 profiles: store,
234 tokens: store,
235 sessions: store,
236 }
237 client := Client{
238 ID: uuid.NewID(),
239 Secret: "super secret!",
240 OwnerID: uuid.NewID(),
241 Name: "My test client",
242 Type: "public",
243 }
244 err := testContext.SaveClient(client)
245 if err != nil {
246 t.Fatal("Can't store client:", err)
247 }
248 session := Session{
249 ID: "testsession",
250 Active: true,
251 }
252 err = testContext.CreateSession(session)
253 if err != nil {
254 t.Fatal("Can't store session:", err)
255 }
256 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
257 if err != nil {
258 t.Fatal("Can't build request:", err)
259 }
260 cookie := &http.Cookie{
261 Name: authCookieName,
262 Value: session.ID,
263 }
264 req.AddCookie(cookie)
265 w := httptest.NewRecorder()
266 params := url.Values{}
267 params.Set("response_type", "code")
268 params.Set("client_id", client.ID.String())
269 req.URL.RawQuery = params.Encode()
270 GetAuthorizationCodeHandler(w, req, testContext)
271 if w.Code != http.StatusBadRequest {
272 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
273 }
274 if w.Body.String() != "The redirect_uri specified is not valid." {
275 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
276 }
277 endpoint := Endpoint{
278 ID: uuid.NewID(),
279 ClientID: client.ID,
280 URI: "https://test.secondbit.org/redirect",
281 Added: time.Now(),
282 }
283 err = testContext.AddEndpoints(client.ID, []Endpoint{endpoint})
284 if err != nil {
285 t.Fatal("Can't store endpoint:", err)
286 }
287 w = httptest.NewRecorder()
288 params.Set("redirect_uri", "https://test.secondbit.org/wrong")
289 req.URL.RawQuery = params.Encode()
290 GetAuthorizationCodeHandler(w, req, testContext)
291 if w.Code != http.StatusBadRequest {
292 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
293 }
294 if w.Body.String() != "The redirect_uri specified is not valid." {
295 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
296 }
297 endpoint2 := Endpoint{
298 ID: uuid.NewID(),
299 ClientID: client.ID,
300 URI: "https://test.secondbit.org/redirect",
301 Added: time.Now(),
302 }
303 err = testContext.AddEndpoints(client.ID, []Endpoint{endpoint2})
304 if err != nil {
305 t.Fatal("Can't store endpoint:", err)
306 }
307 w = httptest.NewRecorder()
308 params.Set("redirect_uri", "")
309 req.URL.RawQuery = params.Encode()
310 GetAuthorizationCodeHandler(w, req, testContext)
311 if w.Code != http.StatusBadRequest {
312 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
313 }
314 if w.Body.String() != "The redirect_uri specified is not valid." {
315 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
316 }
317 w = httptest.NewRecorder()
318 params.Set("redirect_uri", "://not a URL")
319 req.URL.RawQuery = params.Encode()
320 GetAuthorizationCodeHandler(w, req, testContext)
321 if w.Code != http.StatusBadRequest {
322 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
323 }
324 if w.Body.String() != "The redirect_uri specified is not valid." {
325 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
326 }
327 // BUG(paddy): Need to test that setting redirect_uri to a non-URL redirect_uri returns the correct error.
328 }
330 func TestGetAuthorizationCodeCodeInvalidResponseType(t *testing.T) {
331 t.Parallel()
332 store := NewMemstore()
333 testContext := Context{
334 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .error }}")),
335 clients: store,
336 authCodes: store,
337 profiles: store,
338 tokens: store,
339 sessions: store,
340 }
341 client := Client{
342 ID: uuid.NewID(),
343 Secret: "super secret!",
344 OwnerID: uuid.NewID(),
345 Name: "My test client",
346 Logo: "https://secondbit.org/logo.png",
347 Website: "https://secondbit.org",
348 Type: "public",
349 }
350 endpoint := Endpoint{
351 ID: uuid.NewID(),
352 ClientID: client.ID,
353 URI: "https://test.secondbit.org/redirect",
354 Added: time.Now(),
355 }
356 err := testContext.SaveClient(client)
357 if err != nil {
358 t.Fatal("Can't store client:", err)
359 }
360 err = testContext.AddEndpoints(client.ID, []Endpoint{endpoint})
361 if err != nil {
362 t.Fatal("Can't store endpoint:", err)
363 }
364 session := Session{
365 ID: "testsession",
366 Active: true,
367 }
368 err = testContext.CreateSession(session)
369 if err != nil {
370 t.Fatal("Can't store session:", err)
371 }
372 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
373 if err != nil {
374 t.Fatal("Can't build request:", err)
375 }
376 cookie := &http.Cookie{
377 Name: authCookieName,
378 Value: session.ID,
379 }
380 req.AddCookie(cookie)
381 params := url.Values{}
382 params.Set("response_type", "totally not code")
383 params.Set("client_id", client.ID.String())
384 params.Set("redirect_uri", endpoint.URI)
385 params.Set("scope", "testscope")
386 params.Set("state", "my super secure state string")
387 req.URL.RawQuery = params.Encode()
388 w := httptest.NewRecorder()
389 GetAuthorizationCodeHandler(w, req, testContext)
390 if w.Code != http.StatusFound {
391 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
392 }
393 redirectedTo := w.Header().Get("Location")
394 red, err := url.Parse(redirectedTo)
395 if err != nil {
396 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
397 }
398 if red.Query().Get("error") != "invalid_request" {
399 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error"))
400 }
401 stripParam("error", red)
402 if red.Query().Get("state") != params.Get("state") {
403 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
404 }
405 stripParam("state", red)
406 if red.String() != endpoint.URI {
407 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
408 }
409 stripParam("response_type", req.URL)
410 w = httptest.NewRecorder()
411 GetAuthorizationCodeHandler(w, req, testContext)
412 if w.Code != http.StatusFound {
413 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
414 }
415 redirectedTo = w.Header().Get("Location")
416 red, err = url.Parse(redirectedTo)
417 if err != nil {
418 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
419 }
420 if red.Query().Get("error") != "invalid_request" {
421 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error"))
422 }
423 stripParam("error", red)
424 if red.Query().Get("state") != params.Get("state") {
425 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
426 }
427 stripParam("state", red)
428 if red.String() != endpoint.URI {
429 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
430 }
431 }
433 func TestGetAuthorizationCodeCodeDenied(t *testing.T) {
434 t.Parallel()
435 store := NewMemstore()
436 testContext := Context{
437 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .error }}")),
438 clients: store,
439 authCodes: store,
440 profiles: store,
441 tokens: store,
442 sessions: store,
443 }
444 client := Client{
445 ID: uuid.NewID(),
446 Secret: "super secret!",
447 OwnerID: uuid.NewID(),
448 Name: "My test client",
449 Logo: "https://secondbit.org/logo.png",
450 Website: "https://secondbit.org",
451 Type: "public",
452 }
453 endpoint := Endpoint{
454 ID: uuid.NewID(),
455 ClientID: client.ID,
456 URI: "https://test.secondbit.org/redirect",
457 Added: time.Now(),
458 }
459 err := testContext.SaveClient(client)
460 if err != nil {
461 t.Fatal("Can't store client:", err)
462 }
463 err = testContext.AddEndpoints(client.ID, []Endpoint{endpoint})
464 if err != nil {
465 t.Fatal("Can't store endpoint:", err)
466 }
467 session := Session{
468 ID: "testsession",
469 Active: true,
470 }
471 err = testContext.CreateSession(session)
472 if err != nil {
473 t.Fatal("Can't store session:", err)
474 }
475 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
476 if err != nil {
477 t.Fatal("Can't build request:", err)
478 }
479 cookie := &http.Cookie{
480 Name: authCookieName,
481 Value: session.ID,
482 }
483 req.AddCookie(cookie)
484 params := url.Values{}
485 params.Set("response_type", "code")
486 params.Set("client_id", client.ID.String())
487 params.Set("redirect_uri", endpoint.URI)
488 params.Set("scope", "testscope")
489 params.Set("state", "my super secure state string")
490 data := url.Values{}
491 data.Set("grant", "denied")
492 req.URL.RawQuery = params.Encode()
493 req.Body = ioutil.NopCloser(bytes.NewBufferString(data.Encode()))
494 req.Method = "POST"
495 w := httptest.NewRecorder()
496 GetAuthorizationCodeHandler(w, req, testContext)
497 if w.Code != http.StatusFound {
498 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
499 }
500 redirectedTo := w.Header().Get("Location")
501 red, err := url.Parse(redirectedTo)
502 if err != nil {
503 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
504 }
505 if red.Query().Get("error") != "access_denied" {
506 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "access_denied", red.Query().Get("error"))
507 }
508 stripParam("error", red)
509 if red.Query().Get("state") != params.Get("state") {
510 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
511 }
512 stripParam("state", red)
513 if red.String() != endpoint.URI {
514 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
515 }
516 }
518 func TestGetAuthorizationCodeCodeLoginRedirect(t *testing.T) {
519 t.Parallel()
520 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
521 if err != nil {
522 t.Fatal("Can't build request:", err)
523 }
524 w := httptest.NewRecorder()
525 GetAuthorizationCodeHandler(w, req, Context{template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .internal_error }}"))})
526 if w.Code != http.StatusInternalServerError {
527 t.Errorf("Expected status code to be %d, got %d", http.StatusInternalServerError, w.Code)
528 }
529 expectation := "Missing login URL."
530 if w.Body.String() != expectation {
531 t.Errorf(`Expected body to be "%s", got "%s"`, expectation, w.Body.String())
532 }
533 uri, err := url.Parse("https://client.secondbit.org/")
534 if err != nil {
535 t.Error("Unexpected error", err)
536 }
537 testContext := Context{
538 loginURI: uri,
539 }
540 w = httptest.NewRecorder()
541 GetAuthorizationCodeHandler(w, req, testContext)
542 if w.Code != http.StatusFound {
543 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
544 }
545 redirectedTo := w.Header().Get("Location")
546 expectation = "https://client.secondbit.org/?from=https%3A%2F%2Ftest.auth.secondbit.org%2Foauth2%2Fgrant"
547 if redirectedTo != expectation {
548 t.Errorf("Expected to be redirected to '%s', was redirected to '%s'", expectation, redirectedTo)
549 }
550 }
552 // BUG(paddy): Need to test for implicit grant flow
554 func TestCheckCookie(t *testing.T) {
555 t.Parallel()
556 req, err := http.NewRequest("GET", "https://auth.secondbit.org", nil)
557 if err != nil {
558 t.Error("Unexpected error creating base request:", err)
559 }
560 store := NewMemstore()
561 testContext := Context{
562 sessions: store,
563 }
564 session, err := checkCookie(req, testContext)
565 if err != ErrNoSession {
566 t.Errorf("Expected ErrNoSession, got %s", err)
567 }
568 session = Session{
569 ID: "testsession",
570 Active: true,
571 }
572 err = testContext.CreateSession(session)
573 if err != nil {
574 t.Error("Unexpected error persisting session:", err)
575 }
576 invalidSession := Session{
577 ID: "testsession2",
578 Active: false,
579 }
580 err = testContext.CreateSession(invalidSession)
581 if err != nil {
582 t.Error("Unexpected error persisting session:", err)
583 }
584 result, err := checkCookie(req, testContext)
585 if err != ErrNoSession {
586 t.Errorf("Expected ErrNoSession, got %s", err)
587 }
588 req.AddCookie(&http.Cookie{
589 Name: "wrongcookie",
590 Value: "wrong value",
591 })
592 result, err = checkCookie(req, testContext)
593 if err != ErrNoSession {
594 t.Error("Expected ErrNoSession, got", err)
595 }
596 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
597 if err != nil {
598 t.Error("Unexpected error creating base request:", err)
599 }
600 req.AddCookie(&http.Cookie{
601 Name: "Stillwrongcookie",
602 Value: session.ID,
603 })
604 result, err = checkCookie(req, testContext)
605 if err != ErrNoSession {
606 t.Error("Expected ErrNoSession, got", err)
607 }
608 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
609 if err != nil {
610 t.Error("Unexpected error creating base request:", err)
611 }
612 req.AddCookie(&http.Cookie{
613 Name: authCookieName,
614 Value: "wrong value",
615 })
616 result, err = checkCookie(req, testContext)
617 if err != ErrInvalidSession {
618 t.Error("Expected ErrInvalidSession, got", err)
619 }
620 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
621 if err != nil {
622 t.Error("Unexpected error creating base request:", err)
623 }
624 req.AddCookie(&http.Cookie{
625 Name: authCookieName,
626 Value: invalidSession.ID,
627 })
628 result, err = checkCookie(req, testContext)
629 if err != ErrInvalidSession {
630 t.Error("Expected ErrInvalidSession, got", err)
631 }
632 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
633 if err != nil {
634 t.Error("Unexpected error creating base request:", err)
635 }
636 req.AddCookie(&http.Cookie{
637 Name: authCookieName,
638 Value: session.ID,
639 })
640 result, err = checkCookie(req, testContext)
641 if err != nil {
642 t.Error("Unexpected error:", err)
643 }
644 success, field, expectation, outcome := compareSessions(session, result)
645 if !success {
646 t.Errorf(`Expected field %s to be %v, but got %v`, field, expectation, outcome)
647 }
648 }
650 func TestBuildLoginRedirect(t *testing.T) {
651 t.Parallel()
652 req, err := http.NewRequest("GET", "https://client.secondbit.org/my/awesome/path?has=query&params=to&screw=this&all=up", nil)
653 if err != nil {
654 t.Error("Unexpected error creating base request:", err)
655 }
656 result := buildLoginRedirect(req, Context{})
657 if result != "" {
658 t.Error("Expected empty string as the result, got", result)
659 }
660 uri, err := url.Parse("https://auth.secondbit.org/login?query=string&other=param")
661 if err != nil {
662 t.Error("Unexpected error parsing URL:", err)
663 }
664 c := Context{loginURI: uri}
665 result = buildLoginRedirect(req, c)
666 expectation := "https://auth.secondbit.org/login?from=https%3A%2F%2Fclient.secondbit.org%2Fmy%2Fawesome%2Fpath%3Fhas%3Dquery%26params%3Dto%26screw%3Dthis%26all%3Dup&other=param&query=string"
667 if result != expectation {
668 t.Errorf(`Expected result string to be "%s", was "%s"`, expectation, result)
669 }
670 }
672 func TestAuthenticateHelper(t *testing.T) {
673 t.Parallel()
674 store := NewMemstore()
675 context := Context{
676 profiles: store,
677 }
678 profile := Profile{
679 ID: uuid.NewID(),
680 Name: "Test User",
681 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0",
682 Iterations: 1,
683 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b",
684 PassphraseScheme: 1,
685 Compromised: false,
686 LockedUntil: time.Time{},
687 PassphraseReset: "",
688 PassphraseResetCreated: time.Time{},
689 Created: time.Now(),
690 LastSeen: time.Time{},
691 }
692 login := Login{
693 Type: "email",
694 Value: "test@example.com",
695 ProfileID: profile.ID,
696 Created: time.Now(),
697 LastUsed: time.Time{},
698 }
699 err := context.SaveProfile(profile)
700 if err != nil {
701 t.Error("Error saving profile:", err)
702 }
703 err = context.AddLogin(login)
704 if err != nil {
705 t.Error("Error adding login:", err)
706 }
707 response, err := authenticate("test@example.com", "mysecurepassphrase", context)
708 if err != nil {
709 t.Error("Unexpected error:", err)
710 }
711 success, field, expectation, result := compareProfiles(profile, response)
712 if !success {
713 t.Errorf(`Expected field %s to be "%v", got "%v"`, field, expectation, result)
714 }
715 response, err = authenticate("test2@example.com", "mysecurepassphrase", context)
716 if err != ErrIncorrectAuth {
717 t.Error("Expected ErrIncorrectAuth, got", err)
718 }
719 response, err = authenticate("test@example.com", "not the right password", context)
720 if err != ErrIncorrectAuth {
721 t.Error("Expected ErrIncorrectAuth, got", err)
722 }
723 scheme := 1000
724 phrase := "doesn't really matter, the scheme doesn't exist"
725 change := ProfileChange{
726 PassphraseScheme: &scheme,
727 Passphrase: &phrase,
728 }
729 err = context.UpdateProfile(profile.ID, change)
730 if err != nil {
731 t.Error("Unexpected error:", err)
732 }
733 response, err = authenticate("test@example.com", "not the right password", context)
734 if err != ErrInvalidPassphraseScheme {
735 t.Error("Expected ErrIncorrectAuth, got", err)
736 }
737 }
739 func TestGetTokenHandler(t *testing.T) {
740 t.Parallel()
741 store := NewMemstore()
742 context := Context{
743 clients: store,
744 authCodes: store,
745 tokens: store,
746 }
747 client := Client{
748 ID: uuid.NewID(),
749 Secret: "sometimes I feel like I don't know what I'm doing",
750 OwnerID: uuid.NewID(),
751 Name: "A Super Awesome Client!",
752 Logo: "https://logos.secondbit.org/client.png",
753 Website: "https://client.secondbit.org/",
754 Type: "confidential",
755 }
756 authCode := AuthorizationCode{
757 Code: "testcode",
758 Created: time.Now(),
759 ExpiresIn: 600,
760 ClientID: client.ID,
761 Scope: "testscope",
762 RedirectURI: "https://client.secondbit.org/",
763 State: "teststate",
764 ProfileID: uuid.NewID(),
765 }
766 err := context.SaveAuthorizationCode(authCode)
767 if err != nil {
768 t.Error("Error saving auth code:", err)
769 }
770 err = context.SaveClient(client)
771 if err != nil {
772 t.Error("Error saving client:", err)
773 }
774 // BUG(paddy): We're only testing that GetTokenHandler returns the right values when we have the right input. But what about when we have the wrong input? We should test for invalid client errors and invalid grant errors to make sure they're triggered.
775 data := url.Values{}
776 data.Set("grant_type", "authorization_code")
777 data.Set("code", authCode.Code)
778 data.Set("redirect_uri", authCode.RedirectURI)
779 body := bytes.NewBufferString(data.Encode())
780 req, err := http.NewRequest("POST", "https://auth.secondbit.org/", ioutil.NopCloser(body))
781 if err != nil {
782 t.Error("Error constructing request:", err)
783 }
784 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
785 req.SetBasicAuth(client.ID.String(), client.Secret)
786 w := httptest.NewRecorder()
787 GetTokenHandler(w, req, context)
788 resp := tokenResponse{}
789 err = json.Unmarshal(w.Body.Bytes(), &resp)
790 if err != nil {
791 t.Error("Error unmarshalling response:", err)
792 t.Error("Response:", w.Body.String())
793 }
794 if resp.AccessToken == "" {
795 t.Error("Got blank access token back")
796 }
797 if resp.RefreshToken == "" {
798 t.Error("Got blank refresh token back")
799 }
800 if resp.TokenType == "" {
801 t.Error("Got blank token type back")
802 }
803 if resp.ExpiresIn == 0 {
804 t.Error("Got blank expires in back")
805 }
806 tokens, err := context.GetTokensByProfileID(authCode.ProfileID, 1, 0)
807 if err != nil {
808 t.Error("Error retrieving token:", err)
809 }
810 if len(tokens) != 1 {
811 t.Fatalf("Expected %d tokens, got %d", 1, len(tokens))
812 }
813 if tokens[0].AccessToken != resp.AccessToken {
814 t.Errorf(`Expected access token to be "%s", got "%s"`, tokens[0].AccessToken, resp.AccessToken)
815 }
816 if tokens[0].RefreshToken != resp.RefreshToken {
817 t.Errorf(`Expected refresh token to be "%s", got "%s"`, tokens[0].RefreshToken, resp.RefreshToken)
818 }
819 if tokens[0].ExpiresIn != resp.ExpiresIn {
820 t.Errorf(`Expected expires in to be %d, got %d`, tokens[0].ExpiresIn, resp.ExpiresIn)
821 }
822 if tokens[0].TokenType != resp.TokenType {
823 t.Errorf(`Expected token type to be %s, got %s`, tokens[0].TokenType, resp.TokenType)
824 }
825 // BUG(paddy): We need to test for the refresh_token grant type, too.
826 // BUG(paddy): We need to test for the password grant type, too.
827 // BUG(paddy): We need to test for the client_credentials grant type, too.
828 }