auth
2014-07-18
Child:7b9e0fc20256
0:7a6f64db7246 Browse Files
Start rewriting the repo. This code originally was a carbon copy of https://github.com/RangelReale/osin, but I am methodically stripping out the extensible nature of it for a simpler interface, while simultaneously bringing the style into line with the Ducky style.
LICENSE README.md access.go access_test.go authorize.go authorize_test.go client.go config.go context.go info.go info_test.go storage.go storage_test.go tokengen.go urivalidate.go urivalidate_test.go util.go util_test.go
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/LICENSE Fri Jul 18 07:13:22 2014 -0400 1.3 @@ -0,0 +1,11 @@ 1.4 +Copyright (c) 2013, Rangel Reale 1.5 +Copyright (c) 2014, Second Bit, LLC 1.6 + 1.7 +All rights reserved. 1.8 + 1.9 +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1.10 + 1.11 +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 1.12 +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 1.13 +Neither the name of the SIB IT nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 1.14 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/README.md Fri Jul 18 07:13:22 2014 -0400 2.3 @@ -0,0 +1,66 @@ 2.4 +OSIN 2.5 +==== 2.6 + 2.7 +Golang OAuth2 server library 2.8 +---------------------------- 2.9 + 2.10 +OSIN is an OAuth2 server library for the Go language, as specified at 2.11 +http://tools.ietf.org/html/rfc6749 and http://tools.ietf.org/html/draft-ietf-oauth-v2-10. 2.12 + 2.13 +Using it, you can build your own OAuth2 authentication service. 2.14 + 2.15 +The library implements the majority of the specification, like authorization and token endpoints, and authorization code, implicit, resource owner and client credentials grant types. 2.16 + 2.17 +### Dependencies 2.18 + 2.19 +* go-uuid (http://code.google.com/p/go-uuid) 2.20 + 2.21 +### Example Server 2.22 + 2.23 +````go 2.24 +import "github.com/RangelReale/osin" 2.25 + 2.26 +// TestStorage implements the "osin.Storage" interface 2.27 +server := osin.NewServer(osin.NewServerConfig(), &TestStorage{}) 2.28 + 2.29 +// Authorization code endpoint 2.30 +http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) { 2.31 + resp := server.NewResponse() 2.32 + if ar := server.HandleAuthorizeRequest(resp, r); ar != nil { 2.33 + 2.34 + // HANDLE LOGIN PAGE HERE 2.35 + 2.36 + ar.Authorized = true 2.37 + server.FinishAuthorizeRequest(resp, r, ar) 2.38 + } 2.39 + osin.OutputJSON(resp, w, r) 2.40 +}) 2.41 + 2.42 +// Access token endpoint 2.43 +http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { 2.44 + resp := server.NewResponse() 2.45 + if ar := server.HandleAccessRequest(resp, r); ar != nil { 2.46 + ar.Authorized = true 2.47 + server.FinishAccessRequest(resp, r, ar) 2.48 + } 2.49 + osin.OutputJSON(resp, w, r) 2.50 +}) 2.51 + 2.52 +http.ListenAndServe(":14000", nil) 2.53 +```` 2.54 + 2.55 +### Example Access 2.56 + 2.57 +Open in your web browser: 2.58 + 2.59 +```` 2.60 +http://localhost:14000/authorize?response_type=code&client_id=1234&redirect_url=http%3A%2F%2Flocalhost%3A14000%2Fappauth%2Fcode 2.61 +```` 2.62 + 2.63 +### License 2.64 + 2.65 +The code is licensed using "New BSD" license. 2.66 + 2.67 +### Author 2.68 + 2.69 +Rangel Reale
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/access.go Fri Jul 18 07:13:22 2014 -0400 3.3 @@ -0,0 +1,456 @@ 3.4 +package oauth2 3.5 + 3.6 +import ( 3.7 + "net/http" 3.8 + "time" 3.9 + 3.10 + "secondbit.org/uuid" 3.11 +) 3.12 + 3.13 +// GrantType is the type for OAuth param `grant_type` 3.14 +type GrantType string 3.15 + 3.16 +const ( 3.17 + AuthorizationCodeGrant GrantType = "authorization_code" 3.18 + RefreshTokenGrant = "refresh_token" 3.19 + PasswordGrant = "password" 3.20 + ClientCredentialsGrant = "client_credentials" 3.21 + AssertionGrant = "assertion" 3.22 + ImplicitGrant = "__implicit" 3.23 +) 3.24 + 3.25 +// AccessRequest is a request for access tokens 3.26 +type AccessRequest struct { 3.27 + Code string 3.28 + Client Client 3.29 + AuthorizeData AuthorizeData 3.30 + AccessData AccessData 3.31 + RedirectURI string 3.32 + Scope string 3.33 + Username string 3.34 + Password string 3.35 + AssertionType string 3.36 + Assertion string 3.37 + 3.38 + // Token expiration in seconds. Change if different from default 3.39 + Expiration int32 3.40 + 3.41 + // Set if a refresh token should be generated 3.42 + GenerateRefresh bool 3.43 +} 3.44 + 3.45 +// AccessData represents an access grant (tokens, expiration, client, etc) 3.46 +type AccessData struct { 3.47 + // Client information 3.48 + Client Client 3.49 + 3.50 + // Authorize data, for authorization code 3.51 + AuthorizeData *AuthorizeData 3.52 + 3.53 + // Previous access data, for refresh token 3.54 + AccessData *AccessData 3.55 + 3.56 + // Access token 3.57 + AccessToken string 3.58 + 3.59 + // Refresh Token. Can be blank 3.60 + RefreshToken string 3.61 + 3.62 + // Token expiration in seconds 3.63 + ExpiresIn int32 3.64 + 3.65 + // Requested scope 3.66 + Scope string 3.67 + 3.68 + // Redirect URI from request 3.69 + RedirectURI string 3.70 + 3.71 + // Date created 3.72 + CreatedAt time.Time 3.73 +} 3.74 + 3.75 +// IsExpired returns true if access expired 3.76 +func (d *AccessData) IsExpired() bool { 3.77 + return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now()) 3.78 +} 3.79 + 3.80 +// ExpireAt returns the expiration date 3.81 +func (d *AccessData) ExpireAt() time.Time { 3.82 + return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) 3.83 +} 3.84 + 3.85 +// AccessTokenGen generates access tokens 3.86 +type AccessTokenGen interface { 3.87 + GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error) 3.88 +} 3.89 + 3.90 +// HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests. 3.91 +func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.92 + // Only allow GET or POST 3.93 + if r.Method != "POST" { 3.94 + if r.Method == "GET" && !ctx.Config.AllowGetAccessRequest { 3.95 + // TODO: return error 3.96 + return 3.97 + } 3.98 + } 3.99 + 3.100 + err := r.ParseForm() 3.101 + if err != nil { 3.102 + // TODO: return error 3.103 + return 3.104 + } 3.105 + 3.106 + grantType := GrantType(r.Form.Get("grant_type")) 3.107 + if ctx.Config.AllowedAccessTypes.Exists(grantType) { 3.108 + switch grantType { 3.109 + case AuthorizationCodeGrant: 3.110 + handleAuthorizationCodeRequest(w, r, ctx) 3.111 + case RefreshTokenGrant: 3.112 + handleRefreshTokenRequest(w, r, ctx) 3.113 + case PasswordGrant: 3.114 + handlePasswordRequest(w, r, ctx) 3.115 + case ClientCredentialsGrant: 3.116 + handleClientCredentialsRequest(w, r, ctx) 3.117 + case AssertionGrant: 3.118 + handleAssertionRequest(w, r, ctx) 3.119 + default: 3.120 + // TODO: return error 3.121 + return 3.122 + } 3.123 + } 3.124 +} 3.125 + 3.126 +func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.127 + // get client authentication 3.128 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 3.129 + if err != nil { 3.130 + // TODO: return error 3.131 + return 3.132 + } 3.133 + 3.134 + // generate access token 3.135 + ret := AccessRequest{ 3.136 + Code: r.Form.Get("code"), 3.137 + RedirectURI: r.Form.Get("redirect_uri"), 3.138 + GenerateRefresh: true, 3.139 + Expiration: ctx.Config.AccessExpiration, 3.140 + } 3.141 + 3.142 + // "code" is required 3.143 + if ret.Code == "" { 3.144 + // TODO: return error 3.145 + return 3.146 + } 3.147 + 3.148 + // must have a valid client 3.149 + ret.Client, err = getClient(auth, ctx) 3.150 + if err != nil { 3.151 + // TODO: return error 3.152 + return 3.153 + } 3.154 + 3.155 + // must be a valid authorization code 3.156 + ret.AuthorizeData, err = loadAuthorize(ret.Code, ctx) 3.157 + if err != nil { 3.158 + // TODO: return error 3.159 + return 3.160 + } 3.161 + if ret.AuthorizeData.Client.RedirectURI == "" { 3.162 + // TODO: return error 3.163 + return 3.164 + } 3.165 + if ret.AuthorizeData.IsExpired() { 3.166 + return // TODO: return error 3.167 + } 3.168 + 3.169 + // code must be from the client 3.170 + if !ret.AuthorizeData.Client.ID.Equal(ret.Client.ID) { 3.171 + // TODO: return error 3.172 + return 3.173 + } 3.174 + 3.175 + // check redirect uri 3.176 + if ret.RedirectURI == "" { 3.177 + ret.RedirectURI = ret.Client.RedirectURI 3.178 + } 3.179 + if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil { 3.180 + // TODO: return error 3.181 + return 3.182 + } 3.183 + if ret.AuthorizeData.RedirectURI != ret.RedirectURI { 3.184 + // TODO: return error 3.185 + return 3.186 + } 3.187 + 3.188 + // set rest of data 3.189 + ret.Scope = ret.AuthorizeData.Scope 3.190 + // TODO: write ret 3.191 +} 3.192 + 3.193 +func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.194 + // get client authentication 3.195 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 3.196 + if err != nil { 3.197 + // TODO: return error 3.198 + return 3.199 + } 3.200 + 3.201 + // generate access token 3.202 + ret := AccessRequest{ 3.203 + Code: r.Form.Get("refresh_token"), 3.204 + Scope: r.Form.Get("scope"), 3.205 + GenerateRefresh: true, 3.206 + Expiration: ctx.Config.AccessExpiration, 3.207 + } 3.208 + 3.209 + // "refresh_token" is required 3.210 + if ret.Code == "" { 3.211 + // TODO: return error 3.212 + return 3.213 + } 3.214 + 3.215 + // must have a valid client 3.216 + ret.Client, err = getClient(auth, ctx) 3.217 + if err != nil { 3.218 + // TODO: return error 3.219 + return 3.220 + } 3.221 + 3.222 + // must be a valid refresh code 3.223 + ret.AccessData, err = loadRefresh(ret.Code, ctx) 3.224 + if err != nil { 3.225 + // TODO: return error 3.226 + return 3.227 + } 3.228 + if ret.AccessData.Client.RedirectURI == "" { 3.229 + // TODO: return error 3.230 + return 3.231 + } 3.232 + 3.233 + // client must be the same as the previous token 3.234 + if !ret.AccessData.Client.ID.Equal(ret.Client.ID) { 3.235 + // TODO: return error 3.236 + return 3.237 + 3.238 + } 3.239 + 3.240 + // set rest of data 3.241 + ret.RedirectURI = ret.AccessData.RedirectURI 3.242 + if ret.Scope == "" { 3.243 + ret.Scope = ret.AccessData.Scope 3.244 + } 3.245 + 3.246 + // TODO: write ret 3.247 +} 3.248 + 3.249 +func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.250 + // get client authentication 3.251 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 3.252 + if err != nil { 3.253 + // TODO: return error 3.254 + return 3.255 + } 3.256 + 3.257 + // generate access token 3.258 + ret := AccessRequest{ 3.259 + Username: r.Form.Get("username"), 3.260 + Password: r.Form.Get("password"), 3.261 + Scope: r.Form.Get("scope"), 3.262 + GenerateRefresh: true, 3.263 + Expiration: ctx.Config.AccessExpiration, 3.264 + } 3.265 + 3.266 + // "username" and "password" is required 3.267 + if ret.Username == "" || ret.Password == "" { 3.268 + // TODO: return error 3.269 + return 3.270 + } 3.271 + 3.272 + // must have a valid client 3.273 + ret.Client, err = getClient(auth, ctx) 3.274 + if err != nil { 3.275 + // TODO: return error 3.276 + return 3.277 + } 3.278 + 3.279 + // set redirect uri 3.280 + ret.RedirectURI = ret.Client.RedirectURI 3.281 + 3.282 + // TODO: write ret 3.283 +} 3.284 + 3.285 +func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.286 + // get client authentication 3.287 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 3.288 + if err != nil { 3.289 + // TODO: return error 3.290 + return 3.291 + } 3.292 + 3.293 + // generate access token 3.294 + ret := AccessRequest{ 3.295 + Scope: r.Form.Get("scope"), 3.296 + GenerateRefresh: true, 3.297 + Expiration: ctx.Config.AccessExpiration, 3.298 + } 3.299 + 3.300 + // must have a valid client 3.301 + ret.Client, err = getClient(auth, ctx) 3.302 + if err != nil { 3.303 + // TODO: return error 3.304 + return 3.305 + } 3.306 + 3.307 + // set redirect uri 3.308 + ret.RedirectURI = ret.Client.RedirectURI 3.309 + 3.310 + // TODO: write ret 3.311 +} 3.312 + 3.313 +func handleAssertionRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.314 + // get client authentication 3.315 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 3.316 + if err != nil { 3.317 + // TODO: return error 3.318 + return 3.319 + } 3.320 + 3.321 + // generate access token 3.322 + ret := &AccessRequest{ 3.323 + Scope: r.Form.Get("scope"), 3.324 + AssertionType: r.Form.Get("assertion_type"), 3.325 + Assertion: r.Form.Get("assertion"), 3.326 + GenerateRefresh: false, // assertion should NOT generate a refresh token, per the RFC 3.327 + Expiration: ctx.Config.AccessExpiration, 3.328 + } 3.329 + 3.330 + // "assertion_type" and "assertion" is required 3.331 + if ret.AssertionType == "" || ret.Assertion == "" { 3.332 + // TODO: return error 3.333 + return 3.334 + } 3.335 + 3.336 + // must have a valid client 3.337 + ret.Client, err = getClient(auth, ctx) 3.338 + if err != nil { 3.339 + //TODO: return error 3.340 + return 3.341 + } 3.342 + 3.343 + // set redirect uri 3.344 + ret.RedirectURI = ret.Client.RedirectURI 3.345 + 3.346 + // TODO: write ret 3.347 +} 3.348 + 3.349 +func FinishAccessRequest(w http.ResponseWriter, r *http.Request, ar AccessRequest, ctx Context) { 3.350 + // TODO: check if authorized? 3.351 + redirectURI := r.Form.Get("redirect_uri") 3.352 + // Get redirect uri from AccessRequest if it's there (e.g., refresh token request) 3.353 + if ar.RedirectURI != "" { 3.354 + redirectURI = ar.RedirectURI 3.355 + } 3.356 + ret := AccessData{ 3.357 + Client: ar.Client, 3.358 + AuthorizeData: &ar.AuthorizeData, 3.359 + AccessData: &ar.AccessData, 3.360 + RedirectURI: redirectURI, 3.361 + CreatedAt: time.Now(), 3.362 + ExpiresIn: ar.Expiration, 3.363 + Scope: ar.Scope, 3.364 + } 3.365 + 3.366 + var err error 3.367 + 3.368 + // generate access token 3.369 + ret.AccessToken = newToken() 3.370 + if ar.GenerateRefresh { 3.371 + ret.RefreshToken = newToken() 3.372 + } 3.373 + 3.374 + // save access token 3.375 + err = saveAccess(ret, ctx) 3.376 + if err != nil { 3.377 + // TODO: return error 3.378 + return 3.379 + } 3.380 + 3.381 + // remove authorization token 3.382 + if ret.AuthorizeData != nil { 3.383 + err = removeAuthorize(ret.AuthorizeData.Code, ctx) 3.384 + if err != nil { 3.385 + // TODO: log error 3.386 + } 3.387 + } 3.388 + 3.389 + // remove previous access token 3.390 + if ret.AccessData != nil { 3.391 + if ret.AccessData.RefreshToken != "" { 3.392 + err = removeRefresh(ret.AccessData.RefreshToken, ctx) 3.393 + if err != nil { 3.394 + // TODO: log error 3.395 + } 3.396 + } 3.397 + err = removeAccess(ret.AccessData.AccessToken, ctx) 3.398 + if err != nil { 3.399 + // TODO: log error 3.400 + } 3.401 + } 3.402 + 3.403 + // output data 3.404 + //w.Output["access_token"] = ret.AccessToken 3.405 + //w.Output["token_type"] = ctx.Config.TokenType 3.406 + //w.Output["expires_in"] = ret.ExpiresIn 3.407 + //if ret.RefreshToken != "" { 3.408 + // w.Output["refresh_token"] = ret.RefreshToken 3.409 + //} 3.410 + //if ar.Scope != "" { 3.411 + // w.Output["scope"] = ar.Scope 3.412 + //} 3.413 + // TODO: write ret 3.414 +} 3.415 + 3.416 +// Helper Functions 3.417 + 3.418 +// getClient looks up and authenticates the basic auth using the given 3.419 +// storage. Sets an error on the response if auth fails or a server error occurs. 3.420 +func getClient(auth BasicAuth, ctx Context) (Client, error) { 3.421 + id, err := uuid.Parse(auth.Username) 3.422 + if err != nil { 3.423 + return Client{}, err 3.424 + } 3.425 + client, err := GetClient(id, ctx) 3.426 + if err != nil { 3.427 + // TODO: abstract out errors 3.428 + return Client{}, err 3.429 + } 3.430 + if client.Secret != auth.Password { 3.431 + // TODO: return E_UNAUTHORIZED_CLIENT error 3.432 + return Client{}, nil 3.433 + } 3.434 + if client.RedirectURI == "" { 3.435 + // TODO: return E_UNAUTHORIZED_CLIENT error 3.436 + return Client{}, nil 3.437 + } 3.438 + return client, nil 3.439 +} 3.440 + 3.441 +func loadRefresh(code string, ctx Context) (AccessData, error) { 3.442 + return AccessData{}, nil 3.443 +} 3.444 + 3.445 +func loadAccess(code string, ctx Context) (AccessData, error) { 3.446 + return AccessData{}, nil 3.447 +} 3.448 + 3.449 +func saveAccess(data AccessData, ctx Context) error { 3.450 + return nil 3.451 +} 3.452 + 3.453 +func removeAccess(token string, ctx Context) error { 3.454 + return nil 3.455 +} 3.456 + 3.457 +func removeRefresh(token string, ctx Context) error { 3.458 + return nil 3.459 +}
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/access_test.go Fri Jul 18 07:13:22 2014 -0400 4.3 @@ -0,0 +1,195 @@ 4.4 +package oauth2 4.5 + 4.6 +import ( 4.7 + "net/http" 4.8 + "net/url" 4.9 + "testing" 4.10 +) 4.11 + 4.12 +func TestAccessAuthorizationCode(t *testing.T) { 4.13 + sconfig := NewServerConfig() 4.14 + sconfig.AllowedAccessTypes = AllowedAccessType{AuthorizationCodeART} 4.15 + server := NewServer(sconfig, NewTestingStorage()) 4.16 + server.AccessTokenGen = &TestingAccessTokenGen{} 4.17 + resp := server.NewResponse() 4.18 + 4.19 + req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil) 4.20 + if err != nil { 4.21 + t.Fatal(err) 4.22 + } 4.23 + req.SetBasicAuth("1234", "aabbccdd") 4.24 + 4.25 + req.Form = make(url.Values) 4.26 + req.Form.Set("grant_type", string(AuthorizationCodeART)) 4.27 + req.Form.Set("code", "9999") 4.28 + req.Form.Set("state", "a") 4.29 + req.PostForm = make(url.Values) 4.30 + 4.31 + if ar := server.HandleAccessRequest(resp, req); ar != nil { 4.32 + ar.Authorized = true 4.33 + server.FinishAccessRequest(resp, req, ar) 4.34 + } 4.35 + 4.36 + //fmt.Printf("%+v", resp) 4.37 + 4.38 + if resp.IsError && resp.InternalError != nil { 4.39 + t.Fatalf("Error in response: %s", resp.InternalError) 4.40 + } 4.41 + 4.42 + if resp.IsError { 4.43 + t.Fatalf("Should not be an error") 4.44 + } 4.45 + 4.46 + if resp.Type != DATA { 4.47 + t.Fatalf("Response should be data") 4.48 + } 4.49 + 4.50 + if d := resp.Output["access_token"]; d != "1" { 4.51 + t.Fatalf("Unexpected access token: %s", d) 4.52 + } 4.53 + 4.54 + if d := resp.Output["refresh_token"]; d != "r1" { 4.55 + t.Fatalf("Unexpected refresh token: %s", d) 4.56 + } 4.57 +} 4.58 + 4.59 +func TestAccessRefreshToken(t *testing.T) { 4.60 + sconfig := NewServerConfig() 4.61 + sconfig.AllowedAccessTypes = AllowedAccessType{REFRESH_TOKEN} 4.62 + server := NewServer(sconfig, NewTestingStorage()) 4.63 + server.AccessTokenGen = &TestingAccessTokenGen{} 4.64 + resp := server.NewResponse() 4.65 + 4.66 + req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil) 4.67 + if err != nil { 4.68 + t.Fatal(err) 4.69 + } 4.70 + req.SetBasicAuth("1234", "aabbccdd") 4.71 + 4.72 + req.Form = make(url.Values) 4.73 + req.Form.Set("grant_type", string(REFRESH_TOKEN)) 4.74 + req.Form.Set("refresh_token", "r9999") 4.75 + req.Form.Set("state", "a") 4.76 + req.PostForm = make(url.Values) 4.77 + 4.78 + if ar := server.HandleAccessRequest(resp, req); ar != nil { 4.79 + ar.Authorized = true 4.80 + server.FinishAccessRequest(resp, req, ar) 4.81 + } 4.82 + 4.83 + //fmt.Printf("%+v", resp) 4.84 + 4.85 + if resp.IsError && resp.InternalError != nil { 4.86 + t.Fatalf("Error in response: %s", resp.InternalError) 4.87 + } 4.88 + 4.89 + if resp.IsError { 4.90 + t.Fatalf("Should not be an error") 4.91 + } 4.92 + 4.93 + if resp.Type != DATA { 4.94 + t.Fatalf("Response should be data") 4.95 + } 4.96 + 4.97 + if d := resp.Output["access_token"]; d != "1" { 4.98 + t.Fatalf("Unexpected access token: %s", d) 4.99 + } 4.100 + 4.101 + if d := resp.Output["refresh_token"]; d != "r1" { 4.102 + t.Fatalf("Unexpected refresh token: %s", d) 4.103 + } 4.104 +} 4.105 + 4.106 +func TestAccessPassword(t *testing.T) { 4.107 + sconfig := NewServerConfig() 4.108 + sconfig.AllowedAccessTypes = AllowedAccessType{PASSWORD} 4.109 + server := NewServer(sconfig, NewTestingStorage()) 4.110 + server.AccessTokenGen = &TestingAccessTokenGen{} 4.111 + resp := server.NewResponse() 4.112 + 4.113 + req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil) 4.114 + if err != nil { 4.115 + t.Fatal(err) 4.116 + } 4.117 + req.SetBasicAuth("1234", "aabbccdd") 4.118 + 4.119 + req.Form = make(url.Values) 4.120 + req.Form.Set("grant_type", string(PASSWORD)) 4.121 + req.Form.Set("username", "testing") 4.122 + req.Form.Set("password", "testing") 4.123 + req.Form.Set("state", "a") 4.124 + req.PostForm = make(url.Values) 4.125 + 4.126 + if ar := server.HandleAccessRequest(resp, req); ar != nil { 4.127 + ar.Authorized = ar.Username == "testing" && ar.Password == "testing" 4.128 + server.FinishAccessRequest(resp, req, ar) 4.129 + } 4.130 + 4.131 + //fmt.Printf("%+v", resp) 4.132 + 4.133 + if resp.IsError && resp.InternalError != nil { 4.134 + t.Fatalf("Error in response: %s", resp.InternalError) 4.135 + } 4.136 + 4.137 + if resp.IsError { 4.138 + t.Fatalf("Should not be an error") 4.139 + } 4.140 + 4.141 + if resp.Type != DATA { 4.142 + t.Fatalf("Response should be data") 4.143 + } 4.144 + 4.145 + if d := resp.Output["access_token"]; d != "1" { 4.146 + t.Fatalf("Unexpected access token: %s", d) 4.147 + } 4.148 + 4.149 + if d := resp.Output["refresh_token"]; d != "r1" { 4.150 + t.Fatalf("Unexpected refresh token: %s", d) 4.151 + } 4.152 +} 4.153 + 4.154 +func TestAccessClientCredentials(t *testing.T) { 4.155 + sconfig := NewServerConfig() 4.156 + sconfig.AllowedAccessTypes = AllowedAccessType{CLIENT_CREDENTIALS} 4.157 + server := NewServer(sconfig, NewTestingStorage()) 4.158 + server.AccessTokenGen = &TestingAccessTokenGen{} 4.159 + resp := server.NewResponse() 4.160 + 4.161 + req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil) 4.162 + if err != nil { 4.163 + t.Fatal(err) 4.164 + } 4.165 + req.SetBasicAuth("1234", "aabbccdd") 4.166 + 4.167 + req.Form = make(url.Values) 4.168 + req.Form.Set("grant_type", string(CLIENT_CREDENTIALS)) 4.169 + req.Form.Set("state", "a") 4.170 + req.PostForm = make(url.Values) 4.171 + 4.172 + if ar := server.HandleAccessRequest(resp, req); ar != nil { 4.173 + ar.Authorized = true 4.174 + server.FinishAccessRequest(resp, req, ar) 4.175 + } 4.176 + 4.177 + //fmt.Printf("%+v", resp) 4.178 + 4.179 + if resp.IsError && resp.InternalError != nil { 4.180 + t.Fatalf("Error in response: %s", resp.InternalError) 4.181 + } 4.182 + 4.183 + if resp.IsError { 4.184 + t.Fatalf("Should not be an error") 4.185 + } 4.186 + 4.187 + if resp.Type != DATA { 4.188 + t.Fatalf("Response should be data") 4.189 + } 4.190 + 4.191 + if d := resp.Output["access_token"]; d != "1" { 4.192 + t.Fatalf("Unexpected access token: %s", d) 4.193 + } 4.194 + 4.195 + if d := resp.Output["refresh_token"]; d != "r1" { 4.196 + t.Fatalf("Unexpected refresh token: %s", d) 4.197 + } 4.198 +}
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/authorize.go Fri Jul 18 07:13:22 2014 -0400 5.3 @@ -0,0 +1,219 @@ 5.4 +package oauth2 5.5 + 5.6 +import ( 5.7 + "net/http" 5.8 + "net/url" 5.9 + "time" 5.10 + 5.11 + "secondbit.org/uuid" 5.12 +) 5.13 + 5.14 +// AuthorizeRequestType is the type for OAuth param `response_type` 5.15 +type AuthorizeRequestType string 5.16 + 5.17 +const ( 5.18 + CodeAuthRT AuthorizeRequestType = "code" 5.19 + TokenAuthRT = "token" 5.20 +) 5.21 + 5.22 +// Authorize request information 5.23 +type AuthorizeRequest struct { 5.24 + Type AuthorizeRequestType 5.25 + Client Client 5.26 + Scope string 5.27 + RedirectURI string 5.28 + State string 5.29 + 5.30 + // Token expiration in seconds. Change if different from default. 5.31 + // If type = TokenAuthRT, this expiration will be for the ACCESS token. 5.32 + Expiration int32 5.33 +} 5.34 + 5.35 +// Authorization data 5.36 +type AuthorizeData struct { 5.37 + // Client information 5.38 + Client Client 5.39 + 5.40 + // Authorization code 5.41 + Code string 5.42 + 5.43 + // Token expiration in seconds 5.44 + ExpiresIn int32 5.45 + 5.46 + // Requested scope 5.47 + Scope string 5.48 + 5.49 + // Redirect URI from request 5.50 + RedirectURI string 5.51 + 5.52 + // State data from request 5.53 + State string 5.54 + 5.55 + // Date created 5.56 + CreatedAt time.Time 5.57 +} 5.58 + 5.59 +// IsExpired is true if authorization expired 5.60 +func (d *AuthorizeData) IsExpired() bool { 5.61 + return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now()) 5.62 +} 5.63 + 5.64 +// ExpireAt returns the expiration date 5.65 +func (d *AuthorizeData) ExpireAt() time.Time { 5.66 + return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) 5.67 +} 5.68 + 5.69 +// HandleAuthorizeRequest is the main http.HandlerFunc for handling 5.70 +// authorization requests 5.71 +func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 5.72 + r.ParseForm() 5.73 + 5.74 + requestType := AuthorizeRequestType(r.Form.Get("response_type")) 5.75 + if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) { 5.76 + switch requestType { 5.77 + case CodeAuthRT: 5.78 + handleCodeRequest(w, r, ctx) 5.79 + return 5.80 + case TokenAuthRT: 5.81 + handleTokenRequest(w, r, ctx) 5.82 + return 5.83 + } 5.84 + } 5.85 + // TODO: return error 5.86 +} 5.87 + 5.88 +func handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 5.89 + // create the authorization request 5.90 + unescapedURI, err := url.QueryUnescape(r.Form.Get("redirect_uri")) 5.91 + if err != nil { 5.92 + unescapedURI = "" 5.93 + } 5.94 + ret := &AuthorizeRequest{ 5.95 + Type: CodeAuthRT, 5.96 + State: r.Form.Get("state"), 5.97 + Scope: r.Form.Get("scope"), 5.98 + RedirectURI: unescapedURI, 5.99 + Expiration: ctx.Config.AuthorizationExpiration, 5.100 + } 5.101 + 5.102 + // must have a valid client 5.103 + id, err := uuid.Parse(r.Form.Get("client_id")) 5.104 + if err != nil { 5.105 + // TODO: return error 5.106 + return 5.107 + } 5.108 + ret.Client, err = GetClient(id, ctx) 5.109 + if err != nil { 5.110 + // TODO: return error 5.111 + return 5.112 + } 5.113 + if ret.Client.RedirectURI == "" { 5.114 + // TODO: return error 5.115 + return 5.116 + } 5.117 + 5.118 + // check redirect uri 5.119 + if ret.RedirectURI == "" { 5.120 + ret.RedirectURI = ret.Client.RedirectURI 5.121 + } 5.122 + if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil { 5.123 + // TODO: return error 5.124 + return 5.125 + } 5.126 + 5.127 + // TODO: do redirect with ret data 5.128 +} 5.129 + 5.130 +func handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 5.131 + // create the authorization request 5.132 + unescapedURI, err := url.QueryUnescape(r.Form.Get("redirect_uri")) 5.133 + if err != nil { 5.134 + unescapedURI = "" 5.135 + } 5.136 + ret := &AuthorizeRequest{ 5.137 + Type: TokenAuthRT, 5.138 + State: r.Form.Get("state"), 5.139 + Scope: r.Form.Get("scope"), 5.140 + RedirectURI: unescapedURI, 5.141 + // this type will generate a token directly, use access token expiration instead. 5.142 + Expiration: ctx.Config.AccessExpiration, 5.143 + } 5.144 + 5.145 + // must have a valid client 5.146 + id, err := uuid.Parse(r.Form.Get("client_id")) 5.147 + if err != nil { 5.148 + // TODO: return error 5.149 + return 5.150 + } 5.151 + ret.Client, err = GetClient(id, ctx) 5.152 + if err != nil { 5.153 + // TODO: return error 5.154 + return 5.155 + } 5.156 + if ret.Client.RedirectURI == "" { 5.157 + // TODO: return error 5.158 + return 5.159 + } 5.160 + 5.161 + // check redirect uri 5.162 + if ret.RedirectURI == "" { 5.163 + ret.RedirectURI = ret.Client.RedirectURI 5.164 + } 5.165 + if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil { 5.166 + // TODO: return error 5.167 + } 5.168 + 5.169 + // TODO: redirect with ret information 5.170 +} 5.171 + 5.172 +func FinishAuthorizeRequest(w http.ResponseWriter, r *http.Request, ar *AuthorizeRequest, ctx Context) { 5.173 + // TODO: check if authorized? 5.174 + if ar.Type == TokenAuthRT { 5.175 + // TODO: w.SetRedirectFragment(true) was called... 5.176 + 5.177 + // generate token directly 5.178 + ret := AccessRequest{ 5.179 + Code: "", 5.180 + Client: ar.Client, 5.181 + RedirectURI: ar.RedirectURI, 5.182 + Scope: ar.Scope, 5.183 + GenerateRefresh: false, // per the RFC, should NOT generate a refresh token in this case 5.184 + Expiration: ar.Expiration, 5.185 + } 5.186 + // TODO: ret.type was implicit 5.187 + // TODO: ret.Authorized was true 5.188 + FinishAccessRequest(w, r, ret, ctx) 5.189 + } else { 5.190 + // generate authorization token 5.191 + ret := AuthorizeData{ 5.192 + Client: ar.Client, 5.193 + CreatedAt: time.Now(), 5.194 + ExpiresIn: ar.Expiration, 5.195 + RedirectURI: ar.RedirectURI, 5.196 + State: ar.State, 5.197 + Scope: ar.Scope, 5.198 + Code: newToken(), 5.199 + } 5.200 + 5.201 + // save authorization token 5.202 + err := saveAuthorize(ret, ctx) 5.203 + if err != nil { 5.204 + // TODO: return error 5.205 + return 5.206 + } 5.207 + 5.208 + // TODO: redirect with ret.Code and ret.State 5.209 + } 5.210 +} 5.211 + 5.212 +func loadAuthorize(code string, ctx Context) (AuthorizeData, error) { 5.213 + return AuthorizeData{}, nil 5.214 +} 5.215 + 5.216 +func saveAuthorize(ret AuthorizeData, ctx Context) error { 5.217 + return nil 5.218 +} 5.219 + 5.220 +func removeAuthorize(code string, ctx Context) error { 5.221 + return nil 5.222 +}
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 6.2 +++ b/authorize_test.go Fri Jul 18 07:13:22 2014 -0400 6.3 @@ -0,0 +1,88 @@ 6.4 +package oauth2 6.5 + 6.6 +import ( 6.7 + "net/http" 6.8 + "net/url" 6.9 + "testing" 6.10 +) 6.11 + 6.12 +func TestAuthorizeCode(t *testing.T) { 6.13 + sconfig := NewServerConfig() 6.14 + sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{CODE} 6.15 + server := NewServer(sconfig, NewTestingStorage()) 6.16 + server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{} 6.17 + resp := server.NewResponse() 6.18 + 6.19 + req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil) 6.20 + if err != nil { 6.21 + t.Fatal(err) 6.22 + } 6.23 + req.Form = make(url.Values) 6.24 + req.Form.Set("response_type", string(CODE)) 6.25 + req.Form.Set("client_id", "1234") 6.26 + req.Form.Set("state", "a") 6.27 + 6.28 + if ar := server.HandleAuthorizeRequest(resp, req); ar != nil { 6.29 + ar.Authorized = true 6.30 + server.FinishAuthorizeRequest(resp, req, ar) 6.31 + } 6.32 + 6.33 + //fmt.Printf("%+v", resp) 6.34 + 6.35 + if resp.IsError && resp.InternalError != nil { 6.36 + t.Fatalf("Error in response: %s", resp.InternalError) 6.37 + } 6.38 + 6.39 + if resp.IsError { 6.40 + t.Fatalf("Should not be an error") 6.41 + } 6.42 + 6.43 + if resp.Type != REDIRECT { 6.44 + t.Fatalf("Response should be a redirect") 6.45 + } 6.46 + 6.47 + if d := resp.Output["code"]; d != "1" { 6.48 + t.Fatalf("Unexpected authorization code: %s", d) 6.49 + } 6.50 +} 6.51 + 6.52 +func TestAuthorizeToken(t *testing.T) { 6.53 + sconfig := NewServerConfig() 6.54 + sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{TOKEN} 6.55 + server := NewServer(sconfig, NewTestingStorage()) 6.56 + server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{} 6.57 + server.AccessTokenGen = &TestingAccessTokenGen{} 6.58 + resp := server.NewResponse() 6.59 + 6.60 + req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil) 6.61 + if err != nil { 6.62 + t.Fatal(err) 6.63 + } 6.64 + req.Form = make(url.Values) 6.65 + req.Form.Set("response_type", string(TOKEN)) 6.66 + req.Form.Set("client_id", "1234") 6.67 + req.Form.Set("state", "a") 6.68 + 6.69 + if ar := server.HandleAuthorizeRequest(resp, req); ar != nil { 6.70 + ar.Authorized = true 6.71 + server.FinishAuthorizeRequest(resp, req, ar) 6.72 + } 6.73 + 6.74 + //fmt.Printf("%+v", resp) 6.75 + 6.76 + if resp.IsError && resp.InternalError != nil { 6.77 + t.Fatalf("Error in response: %s", resp.InternalError) 6.78 + } 6.79 + 6.80 + if resp.IsError { 6.81 + t.Fatalf("Should not be an error") 6.82 + } 6.83 + 6.84 + if resp.Type != REDIRECT || !resp.RedirectInFragment { 6.85 + t.Fatalf("Response should be a redirect with fragment") 6.86 + } 6.87 + 6.88 + if d := resp.Output["access_token"]; d != "1" { 6.89 + t.Fatalf("Unexpected access token: %s", d) 6.90 + } 6.91 +}
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/client.go Fri Jul 18 07:13:22 2014 -0400 7.3 @@ -0,0 +1,35 @@ 7.4 +package oauth2 7.5 + 7.6 +import ( 7.7 + "secondbit.org/uuid" 7.8 +) 7.9 + 7.10 +// Client information 7.11 +type Client struct { 7.12 + ID uuid.ID 7.13 + Secret string 7.14 + RedirectURI string 7.15 + OwnerID uuid.ID 7.16 + Name string 7.17 + Logo string 7.18 +} 7.19 + 7.20 +func GetClient(id uuid.ID, ctx Context) (Client, error) { 7.21 + return Client{}, nil 7.22 +} 7.23 + 7.24 +func createClient(name, logo, redirectURI string, owner uuid.ID, ctx Context) (Client, error) { 7.25 + return Client{}, nil 7.26 +} 7.27 + 7.28 +func updateClient(client *Client, name, logo, redirectURI *string, ctx Context) error { 7.29 + return nil 7.30 +} 7.31 + 7.32 +func removeClient(id uuid.ID, ctx Context) error { 7.33 + return nil 7.34 +} 7.35 + 7.36 +func listClients(id uuid.ID, page, num int, ctx Context) ([]Client, error) { 7.37 + return []Client{}, nil 7.38 +}
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 8.2 +++ b/config.go Fri Jul 18 07:13:22 2014 -0400 8.3 @@ -0,0 +1,70 @@ 8.4 +package oauth2 8.5 + 8.6 +// AllowedAuthorizeType is a collection of allowed auth request types 8.7 +type AllowedAuthorizeType []AuthorizeRequestType 8.8 + 8.9 +// Exists returns true if the auth type exists in the list 8.10 +func (t AllowedAuthorizeType) Exists(rt AuthorizeRequestType) bool { 8.11 + for _, k := range t { 8.12 + if k == rt { 8.13 + return true 8.14 + } 8.15 + } 8.16 + return false 8.17 +} 8.18 + 8.19 +// AllowedAccessType is a collection of allowed access request types 8.20 +type AllowedAccessType []GrantType 8.21 + 8.22 +// Exists returns true if the access type exists in the list 8.23 +func (t AllowedAccessType) Exists(rt GrantType) bool { 8.24 + for _, k := range t { 8.25 + if k == rt { 8.26 + return true 8.27 + } 8.28 + } 8.29 + return false 8.30 +} 8.31 + 8.32 +// ServerConfig contains server configuration information 8.33 +type ServerConfig struct { 8.34 + // Authorization token expiration in seconds (default 5 minutes) 8.35 + AuthorizationExpiration int32 8.36 + 8.37 + // Access token expiration in seconds (default 1 hour) 8.38 + AccessExpiration int32 8.39 + 8.40 + // Token type to return 8.41 + TokenType string 8.42 + 8.43 + // List of allowed authorize types (only CodeAuthRT by default) 8.44 + AllowedAuthorizeTypes AllowedAuthorizeType 8.45 + 8.46 + // List of allowed access types (only AUTHORIZATION_CodeAuthRT by default) 8.47 + AllowedAccessTypes AllowedAccessType 8.48 + 8.49 + // HTTP status code to return for errors - default 200 8.50 + // Only used if response was created from server 8.51 + ErrorStatusCode int 8.52 + 8.53 + // If true allows client secret also in params, else only in 8.54 + // Authorization header - default false 8.55 + AllowClientSecretInParams bool 8.56 + 8.57 + // If true allows access request using GET, else only POST - default false 8.58 + AllowGetAccessRequest bool 8.59 +} 8.60 + 8.61 +// NewServerConfig returns a new ServerConfig with default configuration 8.62 +func NewServerConfig() ServerConfig { 8.63 + return ServerConfig{ 8.64 + AuthorizationExpiration: 250, 8.65 + AccessExpiration: 3600, 8.66 + TokenType: "bearer", 8.67 + AllowedAuthorizeTypes: AllowedAuthorizeType{CodeAuthRT}, 8.68 + AllowedAccessTypes: AllowedAccessType{AuthorizationCodeGrant}, 8.69 + ErrorStatusCode: 200, 8.70 + AllowClientSecretInParams: false, 8.71 + AllowGetAccessRequest: false, 8.72 + } 8.73 +}
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 9.2 +++ b/context.go Fri Jul 18 07:13:22 2014 -0400 9.3 @@ -0,0 +1,5 @@ 9.4 +package oauth2 9.5 + 9.6 +type Context struct { 9.7 + Config ServerConfig 9.8 +}
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 10.2 +++ b/info.go Fri Jul 18 07:13:22 2014 -0400 10.3 @@ -0,0 +1,59 @@ 10.4 +package oauth2 10.5 + 10.6 +import "net/http" 10.7 + 10.8 +// InfoRequest is a request for information about some AccessData 10.9 +type InfoRequest struct { 10.10 + Code string // Code to look up 10.11 + AccessData AccessData // AccessData associated with Code 10.12 +} 10.13 + 10.14 +// HandleInfoRequest is an http.HandlerFunc for server information 10.15 +// NOT an RFC specification. 10.16 +func HandleInfoRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 10.17 + r.ParseForm() 10.18 + 10.19 + // generate info request 10.20 + ret := InfoRequest{ 10.21 + Code: r.Form.Get("code"), 10.22 + } 10.23 + 10.24 + if ret.Code == "" { 10.25 + // TODO: return error 10.26 + return 10.27 + } 10.28 + 10.29 + var err error 10.30 + 10.31 + // load access data 10.32 + ret.AccessData, err = loadAccess(ret.Code, ctx) 10.33 + if err != nil { 10.34 + // TODO: return error 10.35 + return 10.36 + } 10.37 + if ret.AccessData.Client.RedirectURI == "" { 10.38 + // TODO: return error 10.39 + return 10.40 + } 10.41 + if ret.AccessData.IsExpired() { 10.42 + // TODO: return error 10.43 + return 10.44 + } 10.45 + // TODO: write ret 10.46 +} 10.47 + 10.48 +// FinishInfoRequest finalizes the request handled by HandleInfoRequest 10.49 +func FinishInfoRequest(w http.ResponseWriter, r *http.Request, ir *InfoRequest, ctx Context) { 10.50 + // output data 10.51 + //w.Output["client_id"] = ir.AccessData.Client.Id 10.52 + //w.Output["access_token"] = ir.AccessData.AccessToken 10.53 + //w.Output["token_type"] = s.Config.TokenType 10.54 + //w.Output["expires_in"] = ir.AccessData.CreatedAt.Add(time.Duration(ir.AccessData.ExpiresIn)*time.Second).Sub(time.Now()) / time.Second 10.55 + //if ir.AccessData.RefreshToken != "" { 10.56 + // w.Output["refresh_token"] = ir.AccessData.RefreshToken 10.57 + //} 10.58 + //if ir.AccessData.Scope != "" { 10.59 + // w.Output["scope"] = ir.AccessData.Scope 10.60 + //} 10.61 + // TODO: write output 10.62 +}
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 11.2 +++ b/info_test.go Fri Jul 18 07:13:22 2014 -0400 11.3 @@ -0,0 +1,42 @@ 11.4 +package oauth2 11.5 + 11.6 +import ( 11.7 + "net/http" 11.8 + "net/url" 11.9 + "testing" 11.10 +) 11.11 + 11.12 +func TestInfo(t *testing.T) { 11.13 + sconfig := NewServerConfig() 11.14 + server := NewServer(sconfig, NewTestingStorage()) 11.15 + resp := server.NewResponse() 11.16 + 11.17 + req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil) 11.18 + if err != nil { 11.19 + t.Fatal(err) 11.20 + } 11.21 + req.Form = make(url.Values) 11.22 + req.Form.Set("code", "9999") 11.23 + 11.24 + if ar := server.HandleInfoRequest(resp, req); ar != nil { 11.25 + server.FinishInfoRequest(resp, req, ar) 11.26 + } 11.27 + 11.28 + //fmt.Printf("%+v", resp) 11.29 + 11.30 + if resp.IsError && resp.InternalError != nil { 11.31 + t.Fatalf("Error in response: %s", resp.InternalError) 11.32 + } 11.33 + 11.34 + if resp.IsError { 11.35 + t.Fatalf("Should not be an error") 11.36 + } 11.37 + 11.38 + if resp.Type != DATA { 11.39 + t.Fatalf("Response should be data") 11.40 + } 11.41 + 11.42 + if d := resp.Output["access_token"]; d != "9999" { 11.43 + t.Fatalf("Unexpected authorization code: %s", d) 11.44 + } 11.45 +}
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 12.2 +++ b/storage.go Fri Jul 18 07:13:22 2014 -0400 12.3 @@ -0,0 +1,39 @@ 12.4 +package oauth2 12.5 + 12.6 +// Storage interface 12.7 +type Storage interface { 12.8 + 12.9 + // GetClient loads the client by id (client_id) 12.10 + GetClient(id string) (*Client, error) 12.11 + 12.12 + // SaveAuthorize saves authorize data. 12.13 + SaveAuthorize(*AuthorizeData) error 12.14 + 12.15 + // LoadAuthorize looks up AuthorizeData by a code. 12.16 + // Client information MUST be loaded together. 12.17 + // Optionally can return error if expired. 12.18 + LoadAuthorize(code string) (*AuthorizeData, error) 12.19 + 12.20 + // RemoveAuthorize revokes or deletes the authorization code. 12.21 + RemoveAuthorize(code string) error 12.22 + 12.23 + // SaveAccess writes AccessData. 12.24 + // If RefreshToken is not blank, it must save in a way that can be loaded using LoadRefresh. 12.25 + SaveAccess(*AccessData) error 12.26 + 12.27 + // LoadAccess retrieves access data by token. Client information MUST be loaded together. 12.28 + // AuthorizeData and AccessData DON'T NEED to be loaded if not easily available. 12.29 + // Optionally can return error if expired. 12.30 + LoadAccess(token string) (*AccessData, error) 12.31 + 12.32 + // RemoveAccess revokes or deletes an AccessData. 12.33 + RemoveAccess(token string) error 12.34 + 12.35 + // LoadRefresh retrieves refresh AccessData. Client information MUST be loaded together. 12.36 + // AuthorizeData and AccessData DON'T NEED to be loaded if not easily available. 12.37 + // Optionally can return error if expired. 12.38 + LoadRefresh(token string) (*AccessData, error) 12.39 + 12.40 + // RemoveRefresh revokes or deletes refresh AccessData. 12.41 + RemoveRefresh(token string) error 12.42 +}
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 13.2 +++ b/storage_test.go Fri Jul 18 07:13:22 2014 -0400 13.3 @@ -0,0 +1,147 @@ 13.4 +package oauth2 13.5 + 13.6 +import ( 13.7 + "errors" 13.8 + "strconv" 13.9 + "time" 13.10 +) 13.11 + 13.12 +type TestingStorage struct { 13.13 + clients map[string]*Client 13.14 + authorize map[string]*AuthorizeData 13.15 + access map[string]*AccessData 13.16 + refresh map[string]string 13.17 +} 13.18 + 13.19 +func NewTestingStorage() *TestingStorage { 13.20 + r := &TestingStorage{ 13.21 + clients: make(map[string]*Client), 13.22 + authorize: make(map[string]*AuthorizeData), 13.23 + access: make(map[string]*AccessData), 13.24 + refresh: make(map[string]string), 13.25 + } 13.26 + 13.27 + r.clients["1234"] = &Client{ 13.28 + Id: "1234", 13.29 + Secret: "aabbccdd", 13.30 + RedirectUri: "http://localhost:14000/appauth", 13.31 + } 13.32 + 13.33 + r.authorize["9999"] = &AuthorizeData{ 13.34 + Client: r.clients["1234"], 13.35 + Code: "9999", 13.36 + ExpiresIn: 3600, 13.37 + CreatedAt: time.Now(), 13.38 + RedirectUri: "http://localhost:14000/appauth", 13.39 + } 13.40 + 13.41 + r.access["9999"] = &AccessData{ 13.42 + Client: r.clients["1234"], 13.43 + AuthorizeData: r.authorize["9999"], 13.44 + AccessToken: "9999", 13.45 + ExpiresIn: 3600, 13.46 + CreatedAt: time.Now(), 13.47 + } 13.48 + 13.49 + r.access["r9999"] = &AccessData{ 13.50 + Client: r.clients["1234"], 13.51 + AuthorizeData: r.authorize["9999"], 13.52 + AccessData: r.access["9999"], 13.53 + AccessToken: "9999", 13.54 + RefreshToken: "r9999", 13.55 + ExpiresIn: 3600, 13.56 + CreatedAt: time.Now(), 13.57 + } 13.58 + 13.59 + r.refresh["r9999"] = "9999" 13.60 + 13.61 + return r 13.62 +} 13.63 + 13.64 +func (s *TestingStorage) GetClient(id string) (*Client, error) { 13.65 + if c, ok := s.clients[id]; ok { 13.66 + return c, nil 13.67 + } 13.68 + return nil, errors.New("Client not found") 13.69 +} 13.70 + 13.71 +func (s *TestingStorage) SetClient(id string, client *Client) error { 13.72 + s.clients[id] = client 13.73 + return nil 13.74 +} 13.75 + 13.76 +func (s *TestingStorage) SaveAuthorize(data *AuthorizeData) error { 13.77 + s.authorize[data.Code] = data 13.78 + return nil 13.79 +} 13.80 + 13.81 +func (s *TestingStorage) LoadAuthorize(code string) (*AuthorizeData, error) { 13.82 + if d, ok := s.authorize[code]; ok { 13.83 + return d, nil 13.84 + } 13.85 + return nil, errors.New("Authorize not found") 13.86 +} 13.87 + 13.88 +func (s *TestingStorage) RemoveAuthorize(code string) error { 13.89 + delete(s.authorize, code) 13.90 + return nil 13.91 +} 13.92 + 13.93 +func (s *TestingStorage) SaveAccess(data *AccessData) error { 13.94 + s.access[data.AccessToken] = data 13.95 + if data.RefreshToken != "" { 13.96 + s.refresh[data.RefreshToken] = data.AccessToken 13.97 + } 13.98 + return nil 13.99 +} 13.100 + 13.101 +func (s *TestingStorage) LoadAccess(code string) (*AccessData, error) { 13.102 + if d, ok := s.access[code]; ok { 13.103 + return d, nil 13.104 + } 13.105 + return nil, errors.New("Access not found") 13.106 +} 13.107 + 13.108 +func (s *TestingStorage) RemoveAccess(code string) error { 13.109 + delete(s.access, code) 13.110 + return nil 13.111 +} 13.112 + 13.113 +func (s *TestingStorage) LoadRefresh(code string) (*AccessData, error) { 13.114 + if d, ok := s.refresh[code]; ok { 13.115 + return s.LoadAccess(d) 13.116 + } 13.117 + return nil, errors.New("Refresh not found") 13.118 +} 13.119 + 13.120 +func (s *TestingStorage) RemoveRefresh(code string) error { 13.121 + delete(s.refresh, code) 13.122 + return nil 13.123 +} 13.124 + 13.125 +// Predictable testing token generation 13.126 + 13.127 +type TestingAuthorizeTokenGen struct { 13.128 + counter int64 13.129 +} 13.130 + 13.131 +func (a *TestingAuthorizeTokenGen) GenerateAuthorizeToken(data *AuthorizeData) (ret string, err error) { 13.132 + a.counter++ 13.133 + return strconv.FormatInt(a.counter, 10), nil 13.134 +} 13.135 + 13.136 +type TestingAccessTokenGen struct { 13.137 + acounter int64 13.138 + rcounter int64 13.139 +} 13.140 + 13.141 +func (a *TestingAccessTokenGen) GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error) { 13.142 + a.acounter++ 13.143 + accesstoken = strconv.FormatInt(a.acounter, 10) 13.144 + 13.145 + if generaterefresh { 13.146 + a.rcounter++ 13.147 + refreshtoken = "r" + strconv.FormatInt(a.rcounter, 10) 13.148 + } 13.149 + return 13.150 +}
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 14.2 +++ b/tokengen.go Fri Jul 18 07:13:22 2014 -0400 14.3 @@ -0,0 +1,11 @@ 14.4 +package oauth2 14.5 + 14.6 +import ( 14.7 + "encoding/base64" 14.8 + 14.9 + "code.google.com/p/go-uuid/uuid" 14.10 +) 14.11 + 14.12 +func newToken() string { 14.13 + return base64.StdEncoding.EncodeToString([]byte(uuid.New())) 14.14 +}
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 15.2 +++ b/urivalidate.go Fri Jul 18 07:13:22 2014 -0400 15.3 @@ -0,0 +1,39 @@ 15.4 +package oauth2 15.5 + 15.6 +import ( 15.7 + "errors" 15.8 + "fmt" 15.9 + "net/url" 15.10 + "strings" 15.11 +) 15.12 + 15.13 +// ValidateURI validates that redirectURI is contained in baseURI 15.14 +func ValidateURI(baseURI string, redirectURI string) error { 15.15 + if baseURI == "" || redirectURI == "" { 15.16 + return errors.New("urls cannot be blank.") 15.17 + } 15.18 + 15.19 + // parse base url 15.20 + base, err := url.Parse(baseURI) 15.21 + if err != nil { 15.22 + return err 15.23 + } 15.24 + 15.25 + // parse passed url 15.26 + redirect, err := url.Parse(redirectURI) 15.27 + if err != nil { 15.28 + return err 15.29 + } 15.30 + 15.31 + // must not have fragment 15.32 + if base.Fragment != "" || redirect.Fragment != "" { 15.33 + return errors.New("url must not include fragment.") 15.34 + } 15.35 + 15.36 + // check if urls match 15.37 + if base.Scheme == redirect.Scheme && base.Host == redirect.Host && len(redirect.Path) >= len(base.Path) && strings.HasPrefix(redirect.Path, base.Path) { 15.38 + return nil 15.39 + } 15.40 + 15.41 + return errors.New(fmt.Sprintf("urls don't validate: %s / %s\n", baseURI, redirectURI)) 15.42 +}
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 16.2 +++ b/urivalidate_test.go Fri Jul 18 07:13:22 2014 -0400 16.3 @@ -0,0 +1,27 @@ 16.4 +package oauth2 16.5 + 16.6 +import ( 16.7 + "testing" 16.8 +) 16.9 + 16.10 +func TestURIValidate(t *testing.T) { 16.11 + // V1 16.12 + if err := ValidateUri("http://localhost:14000/appauth", "http://localhost:14000/appauth"); err != nil { 16.13 + t.Errorf("V1: %s", err) 16.14 + } 16.15 + 16.16 + // V2 16.17 + if err := ValidateUri("http://localhost:14000/appauth", "http://localhost:14000/app"); err == nil { 16.18 + t.Error("V2 should have failed") 16.19 + } 16.20 + 16.21 + // V3 16.22 + if err := ValidateUri("http://www.google.com/myapp", "http://www.google.com/myapp/interface/implementation"); err != nil { 16.23 + t.Errorf("V3: %s", err) 16.24 + } 16.25 + 16.26 + // V4 16.27 + if err := ValidateUri("http://www.google.com/myapp", "http://www2.google.com/myapp"); err == nil { 16.28 + t.Error("V4 should have failed") 16.29 + } 16.30 +}
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 17.2 +++ b/util.go Fri Jul 18 07:13:22 2014 -0400 17.3 @@ -0,0 +1,63 @@ 17.4 +package oauth2 17.5 + 17.6 +import ( 17.7 + "encoding/base64" 17.8 + "errors" 17.9 + "net/http" 17.10 + "strings" 17.11 +) 17.12 + 17.13 +var ( 17.14 + BasicAuthNotSetError = errors.New("Authorization header not set.") 17.15 + InvalidBasicAuthTypeError = errors.New("Invalid basic auth type.") 17.16 + InvalidBasicAuthMessage = errors.New("Invalid basic auth format.") 17.17 +) 17.18 + 17.19 +// Parse basic authentication header 17.20 +type BasicAuth struct { 17.21 + Username string 17.22 + Password string 17.23 +} 17.24 + 17.25 +// Return authorization header data 17.26 +func CheckBasicAuth(r *http.Request) (BasicAuth, error) { 17.27 + if r.Header.Get("Authorization") == "" { 17.28 + return BasicAuth{}, BasicAuthNotSetError 17.29 + } 17.30 + 17.31 + s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) 17.32 + if len(s) != 2 || s[0] != "Basic" { 17.33 + return BasicAuth{}, InvalidBasicAuthTypeError 17.34 + } 17.35 + 17.36 + b, err := base64.StdEncoding.DecodeString(s[1]) 17.37 + if err != nil { 17.38 + return BasicAuth{}, err 17.39 + } 17.40 + pair := strings.SplitN(string(b), ":", 2) 17.41 + if len(pair) != 2 { 17.42 + return BasicAuth{}, InvalidBasicAuthMessage 17.43 + } 17.44 + 17.45 + return BasicAuth{Username: pair[0], Password: pair[1]}, nil 17.46 +} 17.47 + 17.48 +// getClientAuth checks client basic authentication in params if allowed, 17.49 +// otherwise gets it from the header. 17.50 +func getClientAuth(r *http.Request, allowQueryParams bool) (BasicAuth, error) { 17.51 + 17.52 + if allowQueryParams { 17.53 + // Allow for auth without password 17.54 + if _, hasSecret := r.Form["client_secret"]; hasSecret { 17.55 + auth := BasicAuth{ 17.56 + Username: r.Form.Get("client_id"), 17.57 + Password: r.Form.Get("client_secret"), 17.58 + } 17.59 + if auth.Username != "" { 17.60 + return auth, nil 17.61 + } 17.62 + } 17.63 + } 17.64 + 17.65 + return CheckBasicAuth(r) 17.66 +}
18.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 18.2 +++ b/util_test.go Fri Jul 18 07:13:22 2014 -0400 18.3 @@ -0,0 +1,96 @@ 18.4 +package oauth2 18.5 + 18.6 +import ( 18.7 + "net/http" 18.8 + "net/url" 18.9 + "testing" 18.10 +) 18.11 + 18.12 +const ( 18.13 + badAuthValue = "Digest XHHHHHHH" 18.14 + goodAuthValue = "Basic dGVzdDp0ZXN0" 18.15 +) 18.16 + 18.17 +func TestBasicAuth(t *testing.T) { 18.18 + r := &http.Request{Header: make(http.Header)} 18.19 + 18.20 + // Without any header 18.21 + if b, err := CheckBasicAuth(r); b != nil || err != nil { 18.22 + t.Errorf("Validated basic auth without header") 18.23 + } 18.24 + 18.25 + // with invalid header 18.26 + r.Header.Set("Authorization", badAuthValue) 18.27 + b, err := CheckBasicAuth(r) 18.28 + if b != nil || err == nil { 18.29 + t.Errorf("Validated invalid auth") 18.30 + return 18.31 + } 18.32 + 18.33 + // with valid header 18.34 + r.Header.Set("Authorization", goodAuthValue) 18.35 + b, err = CheckBasicAuth(r) 18.36 + if b == nil || err != nil { 18.37 + t.Errorf("Could not extract basic auth") 18.38 + return 18.39 + } 18.40 + 18.41 + // check extracted auth data 18.42 + if b.Username != "test" || b.Password != "test" { 18.43 + t.Errorf("Error decoding basic auth") 18.44 + } 18.45 +} 18.46 + 18.47 +func TestGetClientAuth(t *testing.T) { 18.48 + 18.49 + urlWithSecret, _ := url.Parse("http://host.tld/path?client_id=xxx&client_secret=yyy") 18.50 + urlWithEmptySecret, _ := url.Parse("http://host.tld/path?client_id=xxx&client_secret=") 18.51 + urlNoSecret, _ := url.Parse("http://host.tld/path?client_id=xxx") 18.52 + 18.53 + headerNoAuth := make(http.Header) 18.54 + headerBadAuth := make(http.Header) 18.55 + headerBadAuth.Set("Authorization", badAuthValue) 18.56 + headerOKAuth := make(http.Header) 18.57 + headerOKAuth.Set("Authorization", goodAuthValue) 18.58 + 18.59 + var tests = []struct { 18.60 + header http.Header 18.61 + url *url.URL 18.62 + allowQueryParams bool 18.63 + expectAuth bool 18.64 + }{ 18.65 + {headerNoAuth, urlWithSecret, true, true}, 18.66 + {headerNoAuth, urlWithSecret, false, false}, 18.67 + {headerNoAuth, urlWithEmptySecret, true, true}, 18.68 + {headerNoAuth, urlWithEmptySecret, false, false}, 18.69 + {headerNoAuth, urlNoSecret, true, false}, 18.70 + {headerNoAuth, urlNoSecret, false, false}, 18.71 + 18.72 + {headerBadAuth, urlWithSecret, true, true}, 18.73 + {headerBadAuth, urlWithSecret, false, false}, 18.74 + {headerBadAuth, urlWithEmptySecret, true, true}, 18.75 + {headerBadAuth, urlWithEmptySecret, false, false}, 18.76 + {headerBadAuth, urlNoSecret, true, false}, 18.77 + {headerBadAuth, urlNoSecret, false, false}, 18.78 + 18.79 + {headerOKAuth, urlWithSecret, true, true}, 18.80 + {headerOKAuth, urlWithSecret, false, true}, 18.81 + {headerOKAuth, urlWithEmptySecret, true, true}, 18.82 + {headerOKAuth, urlWithEmptySecret, false, true}, 18.83 + {headerOKAuth, urlNoSecret, true, true}, 18.84 + {headerOKAuth, urlNoSecret, false, true}, 18.85 + } 18.86 + 18.87 + for _, tt := range tests { 18.88 + w := new(Response) 18.89 + r := &http.Request{Header: tt.header, URL: tt.url} 18.90 + r.ParseForm() 18.91 + auth := getClientAuth(w, r, tt.allowQueryParams) 18.92 + if tt.expectAuth && auth == nil { 18.93 + t.Errorf("Auth should not be nil for %v", tt) 18.94 + } else if !tt.expectAuth && auth != nil { 18.95 + t.Errorf("Auth should be nil for %v", tt) 18.96 + } 18.97 + } 18.98 + 18.99 +}