auth

Paddy 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 +}