auth

Paddy 2015-03-07 Parent:d30a3a12d387 Child:8267e1c8bcd1

141:a8e6122bfc1a Go to Latest

auth/oauth2_test.go

Require authentication to update Clients. Require the Client's owner to supply basic authentication when updating a client.

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