auth

Paddy 2015-12-14 Parent:73e12d5a1124

181:b7e685839a1b Go to Latest

auth/authcode_test.go

Break out scopes and events. This repo has gotten unwieldy, and there are portions of it that need to be imported by a large number of other packages. For example, scopes will be used in almost every API we write. Rather than importing the entirety of this codebase into every API we write, I've opted to move the scope logic out into a scopes package, with a subpackage for the defined types, which is all most projects actually want to import. We also define some event type constants, and importing those shouldn't require a project to import all our dependencies, either. So I made an events subpackage that just holds those constants. This package has become a little bit of a red-headed stepchild and is do for a refactor, but I'm trying to put that off as long as I can. The refactoring of our scopes stuff has left a bug wherein a token can be granted for scopes that don't exist. I'm going to need to revisit that, and also how to limit scopes to only be granted to the users that should be able to request them. But that's a battle for another day.

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