auth
auth/oauth2.go
Attach our Scope type to AuthCodes and Tokens. When obtaining an AuthorizationCode or Token, attach a slice of strings, each one a Scope ID, instead of just attaching the encoded string the user passes in. This will allow us to change our Scope encoding down the line, and is more conceptually faithful. Also, if an authorization request is made with an invalid scope, return the invalid_scope error.
1.1 --- a/oauth2.go Fri Feb 20 22:34:43 2015 -0500 1.2 +++ b/oauth2.go Tue Mar 03 22:18:28 2015 -0500 1.3 @@ -8,6 +8,7 @@ 1.4 "net/http" 1.5 "net/url" 1.6 "strconv" 1.7 + "strings" 1.8 "sync" 1.9 "time" 1.10 1.11 @@ -64,7 +65,7 @@ 1.12 // The ReturnToken will be called when a token is created and needs to be returned to the client. If it returns true, the token 1.13 // was successfully returned and the Invalidate function will be called asynchronously. 1.14 type GrantType struct { 1.15 - Validate func(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) 1.16 + Validate func(w http.ResponseWriter, r *http.Request, context Context) (scopes []string, profileID uuid.ID, valid bool) 1.17 Invalidate func(r *http.Request, context Context) error 1.18 ReturnToken func(w http.ResponseWriter, r *http.Request, token Token, context Context) bool 1.19 AuditString func(r *http.Request) string 1.20 @@ -264,7 +265,6 @@ 1.21 }) 1.22 return 1.23 } 1.24 - scope := r.URL.Query().Get("scope") 1.25 state := r.URL.Query().Get("state") 1.26 responseType := r.URL.Query().Get("response_type") 1.27 q := redirectURL.Query() 1.28 @@ -275,6 +275,21 @@ 1.29 http.Redirect(w, r, redirectURL.String(), http.StatusFound) 1.30 return 1.31 } 1.32 + scopeParams := strings.Split(r.URL.Query().Get("scope"), " ") 1.33 + scopes, err := context.GetScopes(scopeParams) 1.34 + if err != nil { 1.35 + if _, ok := err.(ErrScopeNotFound); ok { 1.36 + q.Add("error", "invalid_scope") 1.37 + redirectURL.RawQuery = q.Encode() 1.38 + http.Redirect(w, r, redirectURL.String(), http.StatusFound) 1.39 + return 1.40 + } 1.41 + log.Println("Error retrieving scopes:", err) 1.42 + q.Add("error", "server_error") 1.43 + redirectURL.RawQuery = q.Encode() 1.44 + http.Redirect(w, r, redirectURL.String(), http.StatusFound) 1.45 + return 1.46 + } 1.47 if r.Method == "POST" { 1.48 if checkCSRF(r, session) != nil { 1.49 log.Println("CSRF attempt detected.") 1.50 @@ -294,7 +309,7 @@ 1.51 Created: time.Now(), 1.52 ExpiresIn: defaultAuthorizationCodeExpiration, 1.53 ClientID: clientID, 1.54 - Scope: scope, 1.55 + Scopes: scopeParams, 1.56 RedirectURI: r.URL.Query().Get("redirect_uri"), 1.57 State: state, 1.58 ProfileID: session.ProfileID, 1.59 @@ -313,7 +328,7 @@ 1.60 CreatedFrom: "implicit", 1.61 ExpiresIn: defaultTokenExpiration, 1.62 TokenType: "bearer", 1.63 - Scope: scope, 1.64 + Scopes: scopeParams, 1.65 ProfileID: session.ProfileID, 1.66 ClientID: clientID, 1.67 } 1.68 @@ -327,7 +342,7 @@ 1.69 q.Add("access_token", token.AccessToken) 1.70 q.Add("token_type", token.TokenType) 1.71 q.Add("expires_in", strconv.FormatInt(int64(token.ExpiresIn), 10)) 1.72 - q.Add("scope", token.Scope) 1.73 + q.Add("scope", strings.Join(token.Scopes, " ")) 1.74 q.Add("state", state) // we wiped out the old values, so we need to set the state again 1.75 fragment = true 1.76 } 1.77 @@ -356,7 +371,7 @@ 1.78 context.Render(w, getAuthorizationCodeTemplateName, map[string]interface{}{ 1.79 "client": client, 1.80 "redirectURL": redirectURL, 1.81 - "scope": scope, 1.82 + "scopes": scopes, 1.83 "profile": profile, 1.84 "csrftoken": session.CSRFToken, 1.85 }) 1.86 @@ -377,7 +392,7 @@ 1.87 if !success { 1.88 return 1.89 } 1.90 - scope, profileID, valid := gt.Validate(w, r, context) 1.91 + scopes, profileID, valid := gt.Validate(w, r, context) 1.92 if !valid { 1.93 return 1.94 } 1.95 @@ -392,7 +407,7 @@ 1.96 CreatedFrom: gt.AuditString(r), 1.97 ExpiresIn: defaultTokenExpiration, 1.98 TokenType: "bearer", 1.99 - Scope: scope, 1.100 + Scopes: scopes, 1.101 ProfileID: profileID, 1.102 ClientID: clientID, 1.103 }