auth

Paddy 2015-03-22 Parent:8267e1c8bcd1 Child:2809016184f6

151:77db7c65216c Go to Latest

auth/authcode_test.go

Implement postgres clientStore. Stop requiring the client ID be passed to clientStore.addEndpoints and context.AddEndpoints. The Endpoints themselves contain the client ID. When using the authd server, set the log flags to include the file path and line number. Add an ErrEndpointAlreadyExists error, to return when creating an endpoint and its ID already exists in the database. Add a Deleted property to Clients and remove the clientStore.deleteClient and context.DeleteClient methods. We're not going to actually remove that data, and we want to be able to restore it, so include it in the ClientChange type and call it using UpdateClient. Create a ClientChange.Empty helper method that will return whether the ClientChange has any changes to perform. Return ErrClientNotFound from clientStore.getClient if the Client's Deleted property is set to true. This also requires us to ignore ErrClientNotFound errors when calling memstore.listClientsByOwner, as they should just be skipped instead of returning an error. Add the postgres type methods needed to implement clientStore. Include postgres as a clientStore if the testing.Short() flag is not set. Generate a new ID for the Client on every run in the tests, now that we can't actually remove it from the database/memstore in code. We really just need a *Store.Reset() function that erases all the data and starts over again, to give the tests a clean execution environment (and so they can clean up after themselves). Add the CREATE TABLE statements for the Clients table and the Endpoints table to sql/postgres_init.sql.

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