auth

Paddy 2015-07-18 Parent:73e12d5a1124 Child:b7e685839a1b

179:7bba108d2d9a Go to Latest

auth/authcode_test.go

Send events when logins are verified. Add an ActionLoginVerified constant to use as the action when a login has been verified. On second thought, this should probably just be "verified", huh? Then we can reuse it across models. Oops. We also added a call to send a login verified event to NSQ when the login is verified.

History
paddy@29 1 package auth
paddy@29 2
paddy@29 3 import (
paddy@111 4 "bytes"
paddy@111 5 "io/ioutil"
paddy@111 6 "net/http"
paddy@111 7 "net/http/httptest"
paddy@111 8 "net/url"
paddy@156 9 "os"
paddy@111 10 "strings"
paddy@29 11 "testing"
paddy@29 12 "time"
paddy@29 13
paddy@107 14 "code.secondbit.org/uuid.hg"
paddy@29 15 )
paddy@29 16
paddy@156 17 func init() {
paddy@156 18 if os.Getenv("PG_TEST_DB") != "" {
paddy@156 19 p, err := NewPostgres(os.Getenv("PG_TEST_DB"))
paddy@156 20 if err != nil {
paddy@156 21 panic(err)
paddy@156 22 }
paddy@156 23 authCodeStores = append(authCodeStores, &p)
paddy@156 24 }
paddy@156 25 }
paddy@156 26
paddy@87 27 var authCodeStores = []authorizationCodeStore{NewMemstore()}
paddy@29 28
paddy@87 29 func compareAuthorizationCodes(authCode1, authCode2 AuthorizationCode) (success bool, field string, authCode1val, authCode2val interface{}) {
paddy@87 30 if authCode1.Code != authCode2.Code {
paddy@87 31 return false, "code", authCode1.Code, authCode2.Code
paddy@34 32 }
paddy@87 33 if !authCode1.Created.Equal(authCode2.Created) {
paddy@87 34 return false, "created", authCode1.Created, authCode2.Created
paddy@34 35 }
paddy@87 36 if authCode1.ExpiresIn != authCode2.ExpiresIn {
paddy@87 37 return false, "expires in", authCode1.ExpiresIn, authCode2.ExpiresIn
paddy@34 38 }
paddy@87 39 if !authCode1.ClientID.Equal(authCode2.ClientID) {
paddy@87 40 return false, "client ID", authCode1.ClientID, authCode2.ClientID
paddy@34 41 }
paddy@135 42 if len(authCode1.Scopes) != len(authCode2.Scopes) {
paddy@135 43 return false, "scopes", authCode1.Scopes, authCode2.Scopes
paddy@135 44 }
paddy@135 45 for pos, scope := range authCode1.Scopes {
paddy@135 46 if scope != authCode2.Scopes[pos] {
paddy@135 47 return false, "scopes", authCode1.Scopes, authCode2.Scopes
paddy@135 48 }
paddy@34 49 }
paddy@87 50 if authCode1.RedirectURI != authCode2.RedirectURI {
paddy@87 51 return false, "redirect URI", authCode1.RedirectURI, authCode2.RedirectURI
paddy@34 52 }
paddy@87 53 if authCode1.State != authCode2.State {
paddy@87 54 return false, "state", authCode1.State, authCode2.State
paddy@34 55 }
paddy@111 56 if !authCode1.ProfileID.Equal(authCode2.ProfileID) {
paddy@111 57 return false, "profile ID", authCode1.ProfileID, authCode2.ProfileID
paddy@111 58 }
paddy@111 59 if authCode1.Used != authCode2.Used {
paddy@111 60 return false, "used", authCode1.Used, authCode2.Used
paddy@111 61 }
paddy@34 62 return true, "", nil, nil
paddy@34 63 }
paddy@34 64
paddy@111 65 func TestAuthorizationCodeStore(t *testing.T) {
paddy@36 66 t.Parallel()
paddy@87 67 authCode := AuthorizationCode{
paddy@29 68 Code: "code",
paddy@149 69 Created: time.Now().Round(time.Millisecond),
paddy@29 70 ExpiresIn: 180,
paddy@29 71 ClientID: uuid.NewID(),
paddy@163 72 Scopes: stringsToScopes([]string{"scope"}),
paddy@29 73 RedirectURI: "redirectURI",
paddy@29 74 State: "state",
paddy@29 75 }
paddy@87 76 for _, store := range authCodeStores {
paddy@116 77 context := Context{authCodes: store}
paddy@116 78 err := context.SaveAuthorizationCode(authCode)
paddy@29 79 if err != nil {
paddy@87 80 t.Errorf("Error saving auth code to %T: %s", store, err)
paddy@34 81 }
paddy@116 82 err = context.SaveAuthorizationCode(authCode)
paddy@87 83 if err != ErrAuthorizationCodeAlreadyExists {
paddy@87 84 t.Errorf("Expected ErrAuthorizationCodeAlreadyExists from %T, got %+v", store, err)
paddy@29 85 }
paddy@116 86 retrieved, err := context.GetAuthorizationCode(authCode.Code)
paddy@29 87 if err != nil {
paddy@87 88 t.Errorf("Error retrieving auth code from %T: %s", store, err)
paddy@29 89 }
paddy@87 90 match, field, expectation, result := compareAuthorizationCodes(authCode, retrieved)
paddy@34 91 if !match {
paddy@87 92 t.Errorf("Expected `%v` in the `%s` field of auth code retrieved from %T, got `%v`", expectation, field, store, result)
paddy@34 93 }
paddy@116 94 err = context.UseAuthorizationCode(authCode.Code)
paddy@111 95 if err != nil {
paddy@111 96 t.Errorf("Error retrieving auth code from %T: %s", store, err)
paddy@111 97 }
paddy@116 98 retrieved, err = context.GetAuthorizationCode(authCode.Code)
paddy@111 99 if err != nil {
paddy@111 100 t.Errorf("Error retrieving auth code from %T: %s", store, err)
paddy@111 101 }
paddy@111 102 authCode.Used = true
paddy@111 103 match, field, expectation, result = compareAuthorizationCodes(authCode, retrieved)
paddy@111 104 if !match {
paddy@111 105 t.Errorf("Expected `%v` in the `%s` field of auth code retrieved from %T, got `%v`", expectation, field, store, result)
paddy@111 106 }
paddy@116 107 err = context.DeleteAuthorizationCode(authCode.Code)
paddy@29 108 if err != nil {
paddy@87 109 t.Errorf("Error removing auth code from %T: %s", store, err)
paddy@29 110 }
paddy@116 111 retrieved, err = context.GetAuthorizationCode(authCode.Code)
paddy@87 112 if err != ErrAuthorizationCodeNotFound {
paddy@87 113 t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v and %+v", store, retrieved, err)
paddy@34 114 }
paddy@116 115 err = context.DeleteAuthorizationCode(authCode.Code)
paddy@87 116 if err != ErrAuthorizationCodeNotFound {
paddy@87 117 t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v", store, err)
paddy@29 118 }
paddy@116 119 err = context.UseAuthorizationCode(authCode.Code)
paddy@111 120 if err != ErrAuthorizationCodeNotFound {
paddy@111 121 t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v", store, err)
paddy@111 122 }
paddy@29 123 }
paddy@29 124 }
paddy@111 125
paddy@111 126 func TestAuthCodeGrantValidate(t *testing.T) {
paddy@111 127 t.Parallel()
paddy@111 128 store := NewMemstore()
paddy@111 129 testContext := Context{
paddy@111 130 clients: store,
paddy@111 131 authCodes: store,
paddy@111 132 profiles: store,
paddy@111 133 tokens: store,
paddy@111 134 sessions: store,
paddy@111 135 }
paddy@111 136 client := Client{
paddy@111 137 ID: uuid.NewID(),
paddy@111 138 Secret: "super secret!",
paddy@111 139 OwnerID: uuid.NewID(),
paddy@111 140 Name: "My test client",
paddy@111 141 Logo: "https://secondbit.org/logo.png",
paddy@111 142 Website: "https://secondbit.org/",
paddy@111 143 Type: "public",
paddy@111 144 }
paddy@111 145 endpoint := Endpoint{
paddy@111 146 ID: uuid.NewID(),
paddy@111 147 ClientID: client.ID,
paddy@116 148 URI: "https://test.secondbit.org/redirect",
paddy@149 149 Added: time.Now().Round(time.Millisecond),
paddy@111 150 }
paddy@116 151 err := testContext.SaveClient(client)
paddy@111 152 if err != nil {
paddy@111 153 t.Fatal("Can't store client:", err)
paddy@111 154 }
paddy@151 155 err = testContext.AddEndpoints([]Endpoint{endpoint})
paddy@111 156 if err != nil {
paddy@111 157 t.Fatal("Can't store endpoint:", err)
paddy@111 158 }
paddy@111 159 code := AuthorizationCode{
paddy@111 160 Code: "myauthcode",
paddy@149 161 Created: time.Now().Round(time.Millisecond),
paddy@111 162 ExpiresIn: 180,
paddy@111 163 ClientID: uuid.NewID(),
paddy@163 164 Scopes: stringsToScopes([]string{"scope"}),
paddy@111 165 RedirectURI: "redirectURI",
paddy@111 166 State: "state",
paddy@111 167 }
paddy@111 168 err = testContext.SaveAuthorizationCode(code)
paddy@111 169 if err != nil {
paddy@111 170 t.Fatal("Can't add auth code:", err)
paddy@111 171 }
paddy@112 172 code2 := code
paddy@112 173 code2.Code = "otherauthcode"
paddy@112 174 code2.ClientID = client.ID
paddy@112 175 err = testContext.SaveAuthorizationCode(code2)
paddy@112 176 if err != nil {
paddy@112 177 t.Fatal("Can't add second auth code:", err)
paddy@112 178 }
paddy@111 179 req, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@111 180 if err != nil {
paddy@111 181 t.Fatal("Can't build request:", err)
paddy@111 182 }
paddy@112 183 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@111 184 w := httptest.NewRecorder()
paddy@111 185 params := url.Values{}
paddy@111 186 body := bytes.NewBufferString(params.Encode())
paddy@111 187 req.Body = ioutil.NopCloser(body)
paddy@111 188 scope, profileID, valid := authCodeGrantValidate(w, req, testContext)
paddy@111 189 if valid {
paddy@112 190 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID)
paddy@111 191 }
paddy@111 192 if w.Code != http.StatusBadRequest {
paddy@111 193 t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
paddy@111 194 }
paddy@112 195 expectedBody := `{"error":"invalid_request"}`
paddy@112 196 if strings.TrimSpace(w.Body.String()) != expectedBody {
paddy@112 197 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
paddy@112 198 }
paddy@112 199
paddy@112 200 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@112 201 if err != nil {
paddy@112 202 t.Fatal("Can't build request:", err)
paddy@112 203 }
paddy@112 204 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@112 205 w = httptest.NewRecorder()
paddy@112 206 params = url.Values{}
paddy@112 207 params.Set("code", "notmycode")
paddy@112 208 body = bytes.NewBufferString(params.Encode())
paddy@112 209 req.Body = ioutil.NopCloser(body)
paddy@112 210 err = req.ParseForm()
paddy@112 211 if err != nil {
paddy@112 212 t.Log(err)
paddy@112 213 }
paddy@112 214 scope, profileID, valid = authCodeGrantValidate(w, req, testContext)
paddy@112 215 if valid {
paddy@112 216 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID)
paddy@112 217 }
paddy@112 218 if w.Code != http.StatusUnauthorized {
paddy@112 219 t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
paddy@112 220 }
paddy@112 221 expectedBody = `{"error":"invalid_client"}`
paddy@112 222 if expectedBody != strings.TrimSpace(w.Body.String()) {
paddy@112 223 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
paddy@112 224 }
paddy@112 225
paddy@112 226 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@112 227 if err != nil {
paddy@112 228 t.Fatal("Can't build request:", err)
paddy@112 229 }
paddy@112 230 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@112 231 req.SetBasicAuth(client.ID.String(), client.Secret)
paddy@112 232 w = httptest.NewRecorder()
paddy@112 233 params = url.Values{}
paddy@112 234 params.Set("code", "notmycode")
paddy@112 235 body = bytes.NewBufferString(params.Encode())
paddy@112 236 req.Body = ioutil.NopCloser(body)
paddy@112 237 err = req.ParseForm()
paddy@112 238 if err != nil {
paddy@112 239 t.Log(err)
paddy@112 240 }
paddy@112 241 scope, profileID, valid = authCodeGrantValidate(w, req, testContext)
paddy@112 242 if valid {
paddy@112 243 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID)
paddy@112 244 }
paddy@112 245 if w.Code != http.StatusBadRequest {
paddy@112 246 t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
paddy@112 247 }
paddy@112 248 expectedBody = `{"error":"invalid_grant"}`
paddy@112 249 if expectedBody != strings.TrimSpace(w.Body.String()) {
paddy@112 250 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
paddy@112 251 }
paddy@112 252
paddy@112 253 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@112 254 if err != nil {
paddy@112 255 t.Fatal("Can't build request:", err)
paddy@112 256 }
paddy@112 257 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@112 258 req.SetBasicAuth(client.ID.String(), client.Secret)
paddy@112 259 w = httptest.NewRecorder()
paddy@112 260 params = url.Values{}
paddy@112 261 params.Set("code", code.Code)
paddy@112 262 params.Set("redirect_uri", "not my redirectURI")
paddy@112 263 body = bytes.NewBufferString(params.Encode())
paddy@112 264 req.Body = ioutil.NopCloser(body)
paddy@112 265 err = req.ParseForm()
paddy@112 266 if err != nil {
paddy@112 267 t.Log(err)
paddy@112 268 }
paddy@112 269 scope, profileID, valid = authCodeGrantValidate(w, req, testContext)
paddy@112 270 if valid {
paddy@112 271 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID)
paddy@112 272 }
paddy@112 273 if w.Code != http.StatusBadRequest {
paddy@112 274 t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
paddy@112 275 }
paddy@112 276 expectedBody = `{"error":"invalid_grant"}`
paddy@112 277 if expectedBody != strings.TrimSpace(w.Body.String()) {
paddy@112 278 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
paddy@112 279 }
paddy@112 280
paddy@112 281 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@112 282 if err != nil {
paddy@112 283 t.Fatal("Can't build request:", err)
paddy@112 284 }
paddy@112 285 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@112 286 req.SetBasicAuth(client.ID.String(), client.Secret)
paddy@112 287 w = httptest.NewRecorder()
paddy@112 288 params = url.Values{}
paddy@112 289 params.Set("code", code.Code)
paddy@112 290 params.Set("redirect_uri", code.RedirectURI)
paddy@112 291 body = bytes.NewBufferString(params.Encode())
paddy@112 292 req.Body = ioutil.NopCloser(body)
paddy@112 293 err = req.ParseForm()
paddy@112 294 if err != nil {
paddy@112 295 t.Log(err)
paddy@112 296 }
paddy@112 297 scope, profileID, valid = authCodeGrantValidate(w, req, testContext)
paddy@112 298 if valid {
paddy@112 299 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID)
paddy@112 300 }
paddy@112 301 if w.Code != http.StatusBadRequest {
paddy@112 302 t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
paddy@112 303 }
paddy@112 304 expectedBody = `{"error":"invalid_grant"}`
paddy@112 305 if expectedBody != strings.TrimSpace(w.Body.String()) {
paddy@112 306 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String()))
paddy@112 307 }
paddy@112 308
paddy@112 309 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@112 310 if err != nil {
paddy@112 311 t.Fatal("Can't build request:", err)
paddy@112 312 }
paddy@112 313 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@112 314 req.SetBasicAuth(client.ID.String(), client.Secret)
paddy@112 315 w = httptest.NewRecorder()
paddy@112 316 params = url.Values{}
paddy@112 317 params.Set("code", code2.Code)
paddy@112 318 params.Set("redirect_uri", code2.RedirectURI)
paddy@112 319 body = bytes.NewBufferString(params.Encode())
paddy@112 320 req.Body = ioutil.NopCloser(body)
paddy@112 321 err = req.ParseForm()
paddy@112 322 if err != nil {
paddy@112 323 t.Log(err)
paddy@112 324 }
paddy@112 325 scope, profileID, valid = authCodeGrantValidate(w, req, testContext)
paddy@112 326 if !valid {
paddy@112 327 t.Fatalf("Expected valid auth code, was not valid.")
paddy@111 328 }
paddy@111 329 }
paddy@112 330
paddy@112 331 func TestAuthCodeGrantInvalidate(t *testing.T) {
paddy@112 332 t.Parallel()
paddy@112 333 store := NewMemstore()
paddy@112 334 testContext := Context{
paddy@112 335 clients: store,
paddy@112 336 authCodes: store,
paddy@112 337 profiles: store,
paddy@112 338 tokens: store,
paddy@112 339 sessions: store,
paddy@112 340 }
paddy@112 341 code := AuthorizationCode{
paddy@112 342 Code: "myauthcode",
paddy@149 343 Created: time.Now().Round(time.Millisecond),
paddy@112 344 ExpiresIn: 180,
paddy@112 345 ClientID: uuid.NewID(),
paddy@163 346 Scopes: stringsToScopes([]string{"scope"}),
paddy@112 347 RedirectURI: "redirectURI",
paddy@112 348 State: "state",
paddy@112 349 }
paddy@112 350 err := testContext.SaveAuthorizationCode(code)
paddy@112 351 if err != nil {
paddy@112 352 t.Fatal("Can't add auth code:", err)
paddy@112 353 }
paddy@112 354 req, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@112 355 if err != nil {
paddy@112 356 t.Fatal("Can't build request:", err)
paddy@112 357 }
paddy@112 358 err = authCodeGrantInvalidate(req, testContext)
paddy@112 359 if err != ErrAuthorizationCodeNotFound {
paddy@112 360 t.Errorf("Expected `%s`, got `%+v`", ErrAuthorizationCodeNotFound, err)
paddy@112 361 }
paddy@112 362 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@112 363 if err != nil {
paddy@112 364 t.Fatal("Can't build request:", err)
paddy@112 365 }
paddy@112 366 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@112 367 params := url.Values{}
paddy@112 368 params.Set("code", "notmycode")
paddy@112 369 body := bytes.NewBufferString(params.Encode())
paddy@112 370 req.Body = ioutil.NopCloser(body)
paddy@112 371 err = authCodeGrantInvalidate(req, testContext)
paddy@112 372 if err != ErrAuthorizationCodeNotFound {
paddy@112 373 t.Errorf("Expected `%s`, got `%+v`", ErrAuthorizationCodeNotFound, err)
paddy@112 374 }
paddy@112 375 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@112 376 if err != nil {
paddy@112 377 t.Fatal("Can't build request:", err)
paddy@112 378 }
paddy@112 379 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@112 380 params.Set("code", code.Code)
paddy@112 381 body = bytes.NewBufferString(params.Encode())
paddy@112 382 req.Body = ioutil.NopCloser(body)
paddy@112 383 err = authCodeGrantInvalidate(req, testContext)
paddy@112 384 if err != nil {
paddy@112 385 t.Error("Error invalidating auth code:", err)
paddy@112 386 }
paddy@112 387 authCode, err := testContext.GetAuthorizationCode(code.Code)
paddy@112 388 if err != nil {
paddy@112 389 t.Error("Error retrieving auth code:", err)
paddy@112 390 }
paddy@112 391 if !authCode.Used {
paddy@112 392 t.Error("Expected auth code to be used, was not.")
paddy@112 393 }
paddy@112 394 }