auth

Paddy 2015-01-18 Parent:e000b1c24fc0 Child:23c1a07c8a61

124:d14f0a81498c Go to Latest

auth/oauth2_test.go

Fill out token.CreatedFrom. Add a GrantType.AuditString() string method that will return a string for an audit log. Basically, it returns enough information to identify how the token got created. For client credentials, that's just the string "client_credentials". For user credentials, that's just the string "credentials". For auth codes, that's "authcode:", followed by the code used. For refresh tokens, that's "refresh_token:", followed by the refresh token used.

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