auth

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

180:4b68bac597b7 Go to Latest

auth/authcode_test.go

Update client to detect errors. The client doesn't treat non-200 responses as errors automatically, so we need to detect when the response.Errors property is set, and use that to return an error. To avoid the boilerplate and an extensive error system, I just wrapped them in an httpErrors type that implements the error interface. That way the errors can be returned, and callers can type-cast and interrogate them. I also updated the GetLogin function to return an auth.ErrLoginNotFound error when the httpErrors response indicates that's the reason the request failed.

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 }