auth

Paddy 2015-01-14 Parent:da2a0954e8d3 Child:e000b1c24fc0

115:fa8ee6a4507c Go to Latest

auth/authcode_test.go

Turn AddEndpoint into AddEndpoints. Because one is a special case of many, it makes sense to be able to add multiple endpoints in a single call to the database. So we've converted the AddEndpoint method into an AddEndpoints method and updated our tests appropriately. We also filled in the errors when creating a client through the API, and moved things around to optimize for the maximum number of errors returned in a single call.

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