auth

Paddy 2014-11-20 Parent:eb3f2938a319 Child:11ad5eca2f82

80:dfb10e19de87 Go to Latest

auth/oauth2_test.go

Add tests for redirecting to the login page. Make sure that we're redirecting to the configured login page (or returning an error) as expected when trying to obtain a grant code.

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