auth

Paddy 2015-01-04 Parent:c03b5eb3179e Child:fa8ee6a4507c

110:d46d22e5b5d6 Go to Latest

auth/oauth2_test.go

Allow empty "Accept" headers. Some OAuth2 clients don't set Accept headers at all. Detect this, and just return JSON by default.

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