auth

Paddy 2014-08-01 Parent:7a6f64db7246 Child:0aa843a306cd

1:7b9e0fc20256 Browse Files

Continue our descent to horribleness. Remove all the nonsense about "extensibility" and "clean separation of concerns", instead hardcoding connections to decisions. Remove all those "test" things that stopped passing.

access.go access_test.go authorize.go authorize_test.go config.go context.go errors.go info.go info_test.go session.go storage.go storage_test.go tokengen.go urivalidate.go urivalidate_test.go util.go util_test.go

     1.1 --- a/access.go	Fri Jul 18 07:13:22 2014 -0400
     1.2 +++ b/access.go	Fri Aug 01 23:08:38 2014 -0400
     1.3 @@ -2,8 +2,10 @@
     1.4  
     1.5  import (
     1.6  	"net/http"
     1.7 +	"net/url"
     1.8  	"time"
     1.9  
    1.10 +	"strconv"
    1.11  	"secondbit.org/uuid"
    1.12  )
    1.13  
    1.14 @@ -15,58 +17,19 @@
    1.15  	RefreshTokenGrant                = "refresh_token"
    1.16  	PasswordGrant                    = "password"
    1.17  	ClientCredentialsGrant           = "client_credentials"
    1.18 -	AssertionGrant                   = "assertion"
    1.19 -	ImplicitGrant                    = "__implicit"
    1.20  )
    1.21  
    1.22 -// AccessRequest is a request for access tokens
    1.23 -type AccessRequest struct {
    1.24 -	Code          string
    1.25 -	Client        Client
    1.26 -	AuthorizeData AuthorizeData
    1.27 -	AccessData    AccessData
    1.28 -	RedirectURI   string
    1.29 -	Scope         string
    1.30 -	Username      string
    1.31 -	Password      string
    1.32 -	AssertionType string
    1.33 -	Assertion     string
    1.34 -
    1.35 -	// Token expiration in seconds. Change if different from default
    1.36 -	Expiration int32
    1.37 -
    1.38 -	// Set if a refresh token should be generated
    1.39 -	GenerateRefresh bool
    1.40 -}
    1.41 -
    1.42  // AccessData represents an access grant (tokens, expiration, client, etc)
    1.43  type AccessData struct {
    1.44 -	// Client information
    1.45 -	Client Client
    1.46 -
    1.47 -	// Authorize data, for authorization code
    1.48 -	AuthorizeData *AuthorizeData
    1.49 -
    1.50 -	// Previous access data, for refresh token
    1.51 -	AccessData *AccessData
    1.52 -
    1.53 -	// Access token
    1.54 -	AccessToken string
    1.55 -
    1.56 -	// Refresh Token. Can be blank
    1.57 -	RefreshToken string
    1.58 -
    1.59 -	// Token expiration in seconds
    1.60 -	ExpiresIn int32
    1.61 -
    1.62 -	// Requested scope
    1.63 -	Scope string
    1.64 -
    1.65 -	// Redirect URI from request
    1.66 -	RedirectURI string
    1.67 -
    1.68 -	// Date created
    1.69 -	CreatedAt time.Time
    1.70 +	PreviousAuthorizeData *AuthorizeData
    1.71 +	PreviousAccessData    *AccessData // previous access data, when refreshing
    1.72 +	AccessToken           string
    1.73 +	RefreshToken          string
    1.74 +	ExpiresIn             int32
    1.75 +	CreatedAt             time.Time
    1.76 +	TokenType             string
    1.77 +	ProfileID             uuid.ID
    1.78 +	AuthRequest
    1.79  }
    1.80  
    1.81  // IsExpired returns true if access expired
    1.82 @@ -79,27 +42,16 @@
    1.83  	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
    1.84  }
    1.85  
    1.86 -// AccessTokenGen generates access tokens
    1.87 -type AccessTokenGen interface {
    1.88 -	GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error)
    1.89 -}
    1.90 -
    1.91  // HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests.
    1.92  func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    1.93  	// Only allow GET or POST
    1.94  	if r.Method != "POST" {
    1.95 -		if r.Method == "GET" && !ctx.Config.AllowGetAccessRequest {
    1.96 +		if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest {
    1.97  			// TODO: return error
    1.98  			return
    1.99  		}
   1.100  	}
   1.101  
   1.102 -	err := r.ParseForm()
   1.103 -	if err != nil {
   1.104 -		// TODO: return error
   1.105 -		return
   1.106 -	}
   1.107 -
   1.108  	grantType := GrantType(r.Form.Get("grant_type"))
   1.109  	if ctx.Config.AllowedAccessTypes.Exists(grantType) {
   1.110  		switch grantType {
   1.111 @@ -111,8 +63,6 @@
   1.112  			handlePasswordRequest(w, r, ctx)
   1.113  		case ClientCredentialsGrant:
   1.114  			handleClientCredentialsRequest(w, r, ctx)
   1.115 -		case AssertionGrant:
   1.116 -			handleAssertionRequest(w, r, ctx)
   1.117  		default:
   1.118  			// TODO: return error
   1.119  			return
   1.120 @@ -128,63 +78,69 @@
   1.121  		return
   1.122  	}
   1.123  
   1.124 -	// generate access token
   1.125 -	ret := AccessRequest{
   1.126 -		Code:            r.Form.Get("code"),
   1.127 -		RedirectURI:     r.Form.Get("redirect_uri"),
   1.128 -		GenerateRefresh: true,
   1.129 -		Expiration:      ctx.Config.AccessExpiration,
   1.130 -	}
   1.131 -
   1.132 +	code := r.Form.Get("code")
   1.133  	// "code" is required
   1.134 -	if ret.Code == "" {
   1.135 +	if code == "" {
   1.136  		// TODO: return error
   1.137  		return
   1.138  	}
   1.139  
   1.140  	// must have a valid client
   1.141 -	ret.Client, err = getClient(auth, ctx)
   1.142 +	client, err := getClient(auth, ctx)
   1.143  	if err != nil {
   1.144  		// TODO: return error
   1.145  		return
   1.146  	}
   1.147  
   1.148  	// must be a valid authorization code
   1.149 -	ret.AuthorizeData, err = loadAuthorize(ret.Code, ctx)
   1.150 +	authData, err := ctx.Tokens.GetAuthorization(code)
   1.151  	if err != nil {
   1.152  		// TODO: return error
   1.153  		return
   1.154  	}
   1.155 -	if ret.AuthorizeData.Client.RedirectURI == "" {
   1.156 +	if authData.Client.RedirectURI == "" {
   1.157  		// TODO: return error
   1.158  		return
   1.159  	}
   1.160 -	if ret.AuthorizeData.IsExpired() {
   1.161 +	if authData.IsExpired() {
   1.162  		return // TODO: return error
   1.163  	}
   1.164  
   1.165  	// code must be from the client
   1.166 -	if !ret.AuthorizeData.Client.ID.Equal(ret.Client.ID) {
   1.167 +	if !authData.Client.ID.Equal(client.ID) {
   1.168  		// TODO: return error
   1.169  		return
   1.170  	}
   1.171  
   1.172  	// check redirect uri
   1.173 -	if ret.RedirectURI == "" {
   1.174 -		ret.RedirectURI = ret.Client.RedirectURI
   1.175 +	redirectURI := r.Form.Get("redirect_uri")
   1.176 +	if redirectURI == "" {
   1.177 +		redirectURI = client.RedirectURI
   1.178  	}
   1.179 -	if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil {
   1.180 +	if err = validateURI(client.RedirectURI, redirectURI); err != nil {
   1.181  		// TODO: return error
   1.182  		return
   1.183  	}
   1.184 -	if ret.AuthorizeData.RedirectURI != ret.RedirectURI {
   1.185 +	if authData.RedirectURI != redirectURI {
   1.186  		// TODO: return error
   1.187  		return
   1.188  	}
   1.189  
   1.190 -	// set rest of data
   1.191 -	ret.Scope = ret.AuthorizeData.Scope
   1.192 -	// TODO: write ret
   1.193 +	data := AccessData{
   1.194 +		AuthRequest: AuthRequest{
   1.195 +			Client:      client,
   1.196 +			RedirectURI: redirectURI,
   1.197 +			Scope:       authData.Scope,
   1.198 +		},
   1.199 +		PreviousAuthorizeData: &authData,
   1.200 +	}
   1.201 +
   1.202 +	err = fillTokens(&data, true, ctx)
   1.203 +	if err != nil {
   1.204 +		// TODO: return error
   1.205 +		return
   1.206 +	}
   1.207 +	// TODO: write data
   1.208  }
   1.209  
   1.210  func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.211 @@ -195,52 +151,62 @@
   1.212  		return
   1.213  	}
   1.214  
   1.215 -	// generate access token
   1.216 -	ret := AccessRequest{
   1.217 -		Code:            r.Form.Get("refresh_token"),
   1.218 -		Scope:           r.Form.Get("scope"),
   1.219 -		GenerateRefresh: true,
   1.220 -		Expiration:      ctx.Config.AccessExpiration,
   1.221 -	}
   1.222 +	code := r.Form.Get("refresh_token")
   1.223  
   1.224  	// "refresh_token" is required
   1.225 -	if ret.Code == "" {
   1.226 +	if code == "" {
   1.227  		// TODO: return error
   1.228  		return
   1.229  	}
   1.230  
   1.231  	// must have a valid client
   1.232 -	ret.Client, err = getClient(auth, ctx)
   1.233 +	client, err := getClient(auth, ctx)
   1.234  	if err != nil {
   1.235  		// TODO: return error
   1.236  		return
   1.237  	}
   1.238  
   1.239  	// must be a valid refresh code
   1.240 -	ret.AccessData, err = loadRefresh(ret.Code, ctx)
   1.241 +	refreshData, err := ctx.Tokens.GetRefresh(code)
   1.242  	if err != nil {
   1.243  		// TODO: return error
   1.244  		return
   1.245  	}
   1.246 -	if ret.AccessData.Client.RedirectURI == "" {
   1.247 +	if refreshData.Client.RedirectURI == "" {
   1.248  		// TODO: return error
   1.249  		return
   1.250  	}
   1.251  
   1.252  	// client must be the same as the previous token
   1.253 -	if !ret.AccessData.Client.ID.Equal(ret.Client.ID) {
   1.254 +	if !refreshData.Client.ID.Equal(client.ID) {
   1.255  		// TODO: return error
   1.256  		return
   1.257 -
   1.258  	}
   1.259  
   1.260  	// set rest of data
   1.261 -	ret.RedirectURI = ret.AccessData.RedirectURI
   1.262 -	if ret.Scope == "" {
   1.263 -		ret.Scope = ret.AccessData.Scope
   1.264 +	redirectURI := r.Form.Get("redirect_uri")
   1.265 +	if redirectURI == "" {
   1.266 +		redirectURI = refreshData.RedirectURI
   1.267 +	}
   1.268 +	scope := r.Form.Get("scope")
   1.269 +	if scope == "" {
   1.270 +		scope = refreshData.Scope
   1.271  	}
   1.272  
   1.273 -	// TODO: write ret
   1.274 +	data := AccessData{
   1.275 +		AuthRequest: AuthRequest{
   1.276 +			Client:      client,
   1.277 +			RedirectURI: redirectURI,
   1.278 +			Scope:       scope,
   1.279 +		},
   1.280 +		PreviousAccessData: &refreshData,
   1.281 +	}
   1.282 +	err = fillTokens(&data, true, ctx)
   1.283 +	if err != nil {
   1.284 +		// TODO: return error
   1.285 +		return
   1.286 +	}
   1.287 +	// TODO: write data
   1.288  }
   1.289  
   1.290  func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.291 @@ -251,32 +217,50 @@
   1.292  		return
   1.293  	}
   1.294  
   1.295 -	// generate access token
   1.296 -	ret := AccessRequest{
   1.297 -		Username:        r.Form.Get("username"),
   1.298 -		Password:        r.Form.Get("password"),
   1.299 -		Scope:           r.Form.Get("scope"),
   1.300 -		GenerateRefresh: true,
   1.301 -		Expiration:      ctx.Config.AccessExpiration,
   1.302 -	}
   1.303 +	username := r.Form.Get("username")
   1.304 +	password := r.Form.Get("password")
   1.305 +	scope := r.Form.Get("scope")
   1.306  
   1.307  	// "username" and "password" is required
   1.308 -	if ret.Username == "" || ret.Password == "" {
   1.309 +	if username == "" || password == "" {
   1.310  		// TODO: return error
   1.311  		return
   1.312  	}
   1.313  
   1.314  	// must have a valid client
   1.315 -	ret.Client, err = getClient(auth, ctx)
   1.316 +	client, err := getClient(auth, ctx)
   1.317  	if err != nil {
   1.318  		// TODO: return error
   1.319  		return
   1.320  	}
   1.321  
   1.322  	// set redirect uri
   1.323 -	ret.RedirectURI = ret.Client.RedirectURI
   1.324 +	redirectURI := r.Form.Get("redirect_uri")
   1.325 +	if redirectURI == "" {
   1.326 +		redirectURI = client.RedirectURI
   1.327 +	}
   1.328  
   1.329 -	// TODO: write ret
   1.330 +	_, err = ctx.Profiles.GetProfile(username, password)
   1.331 +	if err != nil {
   1.332 +		// TODO: return error
   1.333 +		return
   1.334 +	}
   1.335 +
   1.336 +	data := AccessData{
   1.337 +		AuthRequest: AuthRequest{
   1.338 +			Client:      client,
   1.339 +			RedirectURI: redirectURI,
   1.340 +			Scope:       scope,
   1.341 +		},
   1.342 +	}
   1.343 +
   1.344 +	err = fillTokens(&data, true, ctx)
   1.345 +	if err != nil {
   1.346 +		// TODO: return error
   1.347 +		return
   1.348 +	}
   1.349 +
   1.350 +	// TODO: write data
   1.351  }
   1.352  
   1.353  func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.354 @@ -287,130 +271,111 @@
   1.355  		return
   1.356  	}
   1.357  
   1.358 -	// generate access token
   1.359 -	ret := AccessRequest{
   1.360 -		Scope:           r.Form.Get("scope"),
   1.361 -		GenerateRefresh: true,
   1.362 -		Expiration:      ctx.Config.AccessExpiration,
   1.363 -	}
   1.364 +	scope := r.Form.Get("scope")
   1.365  
   1.366  	// must have a valid client
   1.367 -	ret.Client, err = getClient(auth, ctx)
   1.368 +	client, err := getClient(auth, ctx)
   1.369  	if err != nil {
   1.370  		// TODO: return error
   1.371  		return
   1.372  	}
   1.373  
   1.374  	// set redirect uri
   1.375 -	ret.RedirectURI = ret.Client.RedirectURI
   1.376 +	redirectURI := r.Form.Get("redirect_uri")
   1.377 +	if redirectURI == "" {
   1.378 +		redirectURI = client.RedirectURI
   1.379 +	}
   1.380  
   1.381 -	// TODO: write ret
   1.382 -}
   1.383 +	data := AccessData{
   1.384 +		AuthRequest: AuthRequest{
   1.385 +			Client:      client,
   1.386 +			RedirectURI: redirectURI,
   1.387 +			Scope:       scope,
   1.388 +		},
   1.389 +	}
   1.390  
   1.391 -func handleAssertionRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.392 -	// get client authentication
   1.393 -	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
   1.394 +	err = fillTokens(&data, true, ctx)
   1.395  	if err != nil {
   1.396  		// TODO: return error
   1.397  		return
   1.398  	}
   1.399  
   1.400 -	// generate access token
   1.401 -	ret := &AccessRequest{
   1.402 -		Scope:           r.Form.Get("scope"),
   1.403 -		AssertionType:   r.Form.Get("assertion_type"),
   1.404 -		Assertion:       r.Form.Get("assertion"),
   1.405 -		GenerateRefresh: false, // assertion should NOT generate a refresh token, per the RFC
   1.406 -		Expiration:      ctx.Config.AccessExpiration,
   1.407 -	}
   1.408 -
   1.409 -	// "assertion_type" and "assertion" is required
   1.410 -	if ret.AssertionType == "" || ret.Assertion == "" {
   1.411 -		// TODO: return error
   1.412 -		return
   1.413 -	}
   1.414 -
   1.415 -	// must have a valid client
   1.416 -	ret.Client, err = getClient(auth, ctx)
   1.417 -	if err != nil {
   1.418 -		//TODO: return error
   1.419 -		return
   1.420 -	}
   1.421 -
   1.422 -	// set redirect uri
   1.423 -	ret.RedirectURI = ret.Client.RedirectURI
   1.424 -
   1.425 -	// TODO: write ret
   1.426 +	// TODO: write data
   1.427  }
   1.428  
   1.429 -func FinishAccessRequest(w http.ResponseWriter, r *http.Request, ar AccessRequest, ctx Context) {
   1.430 -	// TODO: check if authorized?
   1.431 -	redirectURI := r.Form.Get("redirect_uri")
   1.432 -	// Get redirect uri from AccessRequest if it's there (e.g., refresh token request)
   1.433 -	if ar.RedirectURI != "" {
   1.434 -		redirectURI = ar.RedirectURI
   1.435 -	}
   1.436 -	ret := AccessData{
   1.437 -		Client:        ar.Client,
   1.438 -		AuthorizeData: &ar.AuthorizeData,
   1.439 -		AccessData:    &ar.AccessData,
   1.440 -		RedirectURI:   redirectURI,
   1.441 -		CreatedAt:     time.Now(),
   1.442 -		ExpiresIn:     ar.Expiration,
   1.443 -		Scope:         ar.Scope,
   1.444 -	}
   1.445 -
   1.446 +func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error {
   1.447  	var err error
   1.448  
   1.449  	// generate access token
   1.450 -	ret.AccessToken = newToken()
   1.451 -	if ar.GenerateRefresh {
   1.452 -		ret.RefreshToken = newToken()
   1.453 +	data.AccessToken = newToken()
   1.454 +	if includeRefresh {
   1.455 +		data.RefreshToken = newToken()
   1.456  	}
   1.457  
   1.458  	// save access token
   1.459 -	err = saveAccess(ret, ctx)
   1.460 +	err = ctx.Tokens.SaveAccess(*data)
   1.461  	if err != nil {
   1.462 -		// TODO: return error
   1.463 -		return
   1.464 +		// TODO: abstract out error
   1.465 +		return err
   1.466  	}
   1.467  
   1.468  	// remove authorization token
   1.469 -	if ret.AuthorizeData != nil {
   1.470 -		err = removeAuthorize(ret.AuthorizeData.Code, ctx)
   1.471 +	if data.PreviousAuthorizeData != nil {
   1.472 +		err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code)
   1.473  		if err != nil {
   1.474  			// TODO: log error
   1.475  		}
   1.476  	}
   1.477  
   1.478  	// remove previous access token
   1.479 -	if ret.AccessData != nil {
   1.480 -		if ret.AccessData.RefreshToken != "" {
   1.481 -			err = removeRefresh(ret.AccessData.RefreshToken, ctx)
   1.482 +	if data.PreviousAccessData != nil {
   1.483 +		if data.PreviousAccessData.RefreshToken != "" {
   1.484 +			err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken)
   1.485  			if err != nil {
   1.486  				// TODO: log error
   1.487  			}
   1.488  		}
   1.489 -		err = removeAccess(ret.AccessData.AccessToken, ctx)
   1.490 +		err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken)
   1.491  		if err != nil {
   1.492  			// TODO: log error
   1.493  		}
   1.494  	}
   1.495  
   1.496 -	// output data
   1.497 -	//w.Output["access_token"] = ret.AccessToken
   1.498 -	//w.Output["token_type"] = ctx.Config.TokenType
   1.499 -	//w.Output["expires_in"] = ret.ExpiresIn
   1.500 -	//if ret.RefreshToken != "" {
   1.501 -	//	w.Output["refresh_token"] = ret.RefreshToken
   1.502 -	//}
   1.503 -	//if ar.Scope != "" {
   1.504 -	//	w.Output["scope"] = ar.Scope
   1.505 -	//}
   1.506 -	// TODO: write ret
   1.507 +	data.TokenType = ctx.Config.TokenType
   1.508 +	data.ExpiresIn = ctx.Config.AccessExpiration
   1.509 +	data.CreatedAt = time.Now()
   1.510 +	return nil
   1.511  }
   1.512  
   1.513 -// Helper Functions
   1.514 +func (data AccessData) GetRedirect(fragment bool) (string, error) {
   1.515 +	u, err := url.Parse(data.RedirectURI)
   1.516 +	if err != nil {
   1.517 +		return "", err
   1.518 +	}
   1.519 +
   1.520 +	// add parameters
   1.521 +	q := u.Query()
   1.522 +	q.Set("access_token", data.AccessToken)
   1.523 +	q.Set("token_type", data.TokenType)
   1.524 +	q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10))
   1.525 +	if data.RefreshToken != "" {
   1.526 +		q.Set("refresh_token", data.RefreshToken)
   1.527 +	}
   1.528 +	if data.Scope != "" {
   1.529 +		q.Set("scope", data.Scope)
   1.530 +	}
   1.531 +	if len(data.ProfileID) > 0 {
   1.532 +		q.Set("profile", data.ProfileID.String())
   1.533 +	}
   1.534 +	if fragment {
   1.535 +		u.RawQuery = ""
   1.536 +		u.Fragment = q.Encode()
   1.537 +	} else {
   1.538 +		u.RawQuery = q.Encode()
   1.539 +	}
   1.540 +
   1.541 +	return u.String(), nil
   1.542 +}
   1.543  
   1.544  // getClient looks up and authenticates the basic auth using the given
   1.545  // storage. Sets an error on the response if auth fails or a server error occurs.
   1.546 @@ -419,7 +384,7 @@
   1.547  	if err != nil {
   1.548  		return Client{}, err
   1.549  	}
   1.550 -	client, err := GetClient(id, ctx)
   1.551 +	client, err := ctx.Clients.GetClient(id)
   1.552  	if err != nil {
   1.553  		// TODO: abstract out errors
   1.554  		return Client{}, err
   1.555 @@ -434,23 +399,3 @@
   1.556  	}
   1.557  	return client, nil
   1.558  }
   1.559 -
   1.560 -func loadRefresh(code string, ctx Context) (AccessData, error) {
   1.561 -	return AccessData{}, nil
   1.562 -}
   1.563 -
   1.564 -func loadAccess(code string, ctx Context) (AccessData, error) {
   1.565 -	return AccessData{}, nil
   1.566 -}
   1.567 -
   1.568 -func saveAccess(data AccessData, ctx Context) error {
   1.569 -	return nil
   1.570 -}
   1.571 -
   1.572 -func removeAccess(token string, ctx Context) error {
   1.573 -	return nil
   1.574 -}
   1.575 -
   1.576 -func removeRefresh(token string, ctx Context) error {
   1.577 -	return nil
   1.578 -}
     2.1 --- a/access_test.go	Fri Jul 18 07:13:22 2014 -0400
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,195 +0,0 @@
     2.4 -package oauth2
     2.5 -
     2.6 -import (
     2.7 -	"net/http"
     2.8 -	"net/url"
     2.9 -	"testing"
    2.10 -)
    2.11 -
    2.12 -func TestAccessAuthorizationCode(t *testing.T) {
    2.13 -	sconfig := NewServerConfig()
    2.14 -	sconfig.AllowedAccessTypes = AllowedAccessType{AuthorizationCodeART}
    2.15 -	server := NewServer(sconfig, NewTestingStorage())
    2.16 -	server.AccessTokenGen = &TestingAccessTokenGen{}
    2.17 -	resp := server.NewResponse()
    2.18 -
    2.19 -	req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
    2.20 -	if err != nil {
    2.21 -		t.Fatal(err)
    2.22 -	}
    2.23 -	req.SetBasicAuth("1234", "aabbccdd")
    2.24 -
    2.25 -	req.Form = make(url.Values)
    2.26 -	req.Form.Set("grant_type", string(AuthorizationCodeART))
    2.27 -	req.Form.Set("code", "9999")
    2.28 -	req.Form.Set("state", "a")
    2.29 -	req.PostForm = make(url.Values)
    2.30 -
    2.31 -	if ar := server.HandleAccessRequest(resp, req); ar != nil {
    2.32 -		ar.Authorized = true
    2.33 -		server.FinishAccessRequest(resp, req, ar)
    2.34 -	}
    2.35 -
    2.36 -	//fmt.Printf("%+v", resp)
    2.37 -
    2.38 -	if resp.IsError && resp.InternalError != nil {
    2.39 -		t.Fatalf("Error in response: %s", resp.InternalError)
    2.40 -	}
    2.41 -
    2.42 -	if resp.IsError {
    2.43 -		t.Fatalf("Should not be an error")
    2.44 -	}
    2.45 -
    2.46 -	if resp.Type != DATA {
    2.47 -		t.Fatalf("Response should be data")
    2.48 -	}
    2.49 -
    2.50 -	if d := resp.Output["access_token"]; d != "1" {
    2.51 -		t.Fatalf("Unexpected access token: %s", d)
    2.52 -	}
    2.53 -
    2.54 -	if d := resp.Output["refresh_token"]; d != "r1" {
    2.55 -		t.Fatalf("Unexpected refresh token: %s", d)
    2.56 -	}
    2.57 -}
    2.58 -
    2.59 -func TestAccessRefreshToken(t *testing.T) {
    2.60 -	sconfig := NewServerConfig()
    2.61 -	sconfig.AllowedAccessTypes = AllowedAccessType{REFRESH_TOKEN}
    2.62 -	server := NewServer(sconfig, NewTestingStorage())
    2.63 -	server.AccessTokenGen = &TestingAccessTokenGen{}
    2.64 -	resp := server.NewResponse()
    2.65 -
    2.66 -	req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
    2.67 -	if err != nil {
    2.68 -		t.Fatal(err)
    2.69 -	}
    2.70 -	req.SetBasicAuth("1234", "aabbccdd")
    2.71 -
    2.72 -	req.Form = make(url.Values)
    2.73 -	req.Form.Set("grant_type", string(REFRESH_TOKEN))
    2.74 -	req.Form.Set("refresh_token", "r9999")
    2.75 -	req.Form.Set("state", "a")
    2.76 -	req.PostForm = make(url.Values)
    2.77 -
    2.78 -	if ar := server.HandleAccessRequest(resp, req); ar != nil {
    2.79 -		ar.Authorized = true
    2.80 -		server.FinishAccessRequest(resp, req, ar)
    2.81 -	}
    2.82 -
    2.83 -	//fmt.Printf("%+v", resp)
    2.84 -
    2.85 -	if resp.IsError && resp.InternalError != nil {
    2.86 -		t.Fatalf("Error in response: %s", resp.InternalError)
    2.87 -	}
    2.88 -
    2.89 -	if resp.IsError {
    2.90 -		t.Fatalf("Should not be an error")
    2.91 -	}
    2.92 -
    2.93 -	if resp.Type != DATA {
    2.94 -		t.Fatalf("Response should be data")
    2.95 -	}
    2.96 -
    2.97 -	if d := resp.Output["access_token"]; d != "1" {
    2.98 -		t.Fatalf("Unexpected access token: %s", d)
    2.99 -	}
   2.100 -
   2.101 -	if d := resp.Output["refresh_token"]; d != "r1" {
   2.102 -		t.Fatalf("Unexpected refresh token: %s", d)
   2.103 -	}
   2.104 -}
   2.105 -
   2.106 -func TestAccessPassword(t *testing.T) {
   2.107 -	sconfig := NewServerConfig()
   2.108 -	sconfig.AllowedAccessTypes = AllowedAccessType{PASSWORD}
   2.109 -	server := NewServer(sconfig, NewTestingStorage())
   2.110 -	server.AccessTokenGen = &TestingAccessTokenGen{}
   2.111 -	resp := server.NewResponse()
   2.112 -
   2.113 -	req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
   2.114 -	if err != nil {
   2.115 -		t.Fatal(err)
   2.116 -	}
   2.117 -	req.SetBasicAuth("1234", "aabbccdd")
   2.118 -
   2.119 -	req.Form = make(url.Values)
   2.120 -	req.Form.Set("grant_type", string(PASSWORD))
   2.121 -	req.Form.Set("username", "testing")
   2.122 -	req.Form.Set("password", "testing")
   2.123 -	req.Form.Set("state", "a")
   2.124 -	req.PostForm = make(url.Values)
   2.125 -
   2.126 -	if ar := server.HandleAccessRequest(resp, req); ar != nil {
   2.127 -		ar.Authorized = ar.Username == "testing" && ar.Password == "testing"
   2.128 -		server.FinishAccessRequest(resp, req, ar)
   2.129 -	}
   2.130 -
   2.131 -	//fmt.Printf("%+v", resp)
   2.132 -
   2.133 -	if resp.IsError && resp.InternalError != nil {
   2.134 -		t.Fatalf("Error in response: %s", resp.InternalError)
   2.135 -	}
   2.136 -
   2.137 -	if resp.IsError {
   2.138 -		t.Fatalf("Should not be an error")
   2.139 -	}
   2.140 -
   2.141 -	if resp.Type != DATA {
   2.142 -		t.Fatalf("Response should be data")
   2.143 -	}
   2.144 -
   2.145 -	if d := resp.Output["access_token"]; d != "1" {
   2.146 -		t.Fatalf("Unexpected access token: %s", d)
   2.147 -	}
   2.148 -
   2.149 -	if d := resp.Output["refresh_token"]; d != "r1" {
   2.150 -		t.Fatalf("Unexpected refresh token: %s", d)
   2.151 -	}
   2.152 -}
   2.153 -
   2.154 -func TestAccessClientCredentials(t *testing.T) {
   2.155 -	sconfig := NewServerConfig()
   2.156 -	sconfig.AllowedAccessTypes = AllowedAccessType{CLIENT_CREDENTIALS}
   2.157 -	server := NewServer(sconfig, NewTestingStorage())
   2.158 -	server.AccessTokenGen = &TestingAccessTokenGen{}
   2.159 -	resp := server.NewResponse()
   2.160 -
   2.161 -	req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
   2.162 -	if err != nil {
   2.163 -		t.Fatal(err)
   2.164 -	}
   2.165 -	req.SetBasicAuth("1234", "aabbccdd")
   2.166 -
   2.167 -	req.Form = make(url.Values)
   2.168 -	req.Form.Set("grant_type", string(CLIENT_CREDENTIALS))
   2.169 -	req.Form.Set("state", "a")
   2.170 -	req.PostForm = make(url.Values)
   2.171 -
   2.172 -	if ar := server.HandleAccessRequest(resp, req); ar != nil {
   2.173 -		ar.Authorized = true
   2.174 -		server.FinishAccessRequest(resp, req, ar)
   2.175 -	}
   2.176 -
   2.177 -	//fmt.Printf("%+v", resp)
   2.178 -
   2.179 -	if resp.IsError && resp.InternalError != nil {
   2.180 -		t.Fatalf("Error in response: %s", resp.InternalError)
   2.181 -	}
   2.182 -
   2.183 -	if resp.IsError {
   2.184 -		t.Fatalf("Should not be an error")
   2.185 -	}
   2.186 -
   2.187 -	if resp.Type != DATA {
   2.188 -		t.Fatalf("Response should be data")
   2.189 -	}
   2.190 -
   2.191 -	if d := resp.Output["access_token"]; d != "1" {
   2.192 -		t.Fatalf("Unexpected access token: %s", d)
   2.193 -	}
   2.194 -
   2.195 -	if d := resp.Output["refresh_token"]; d != "r1" {
   2.196 -		t.Fatalf("Unexpected refresh token: %s", d)
   2.197 -	}
   2.198 -}
     3.1 --- a/authorize.go	Fri Jul 18 07:13:22 2014 -0400
     3.2 +++ b/authorize.go	Fri Aug 01 23:08:38 2014 -0400
     3.3 @@ -5,6 +5,7 @@
     3.4  	"net/url"
     3.5  	"time"
     3.6  
     3.7 +	"strings"
     3.8  	"secondbit.org/uuid"
     3.9  )
    3.10  
    3.11 @@ -16,41 +17,27 @@
    3.12  	TokenAuthRT                      = "token"
    3.13  )
    3.14  
    3.15 -// Authorize request information
    3.16 -type AuthorizeRequest struct {
    3.17 -	Type        AuthorizeRequestType
    3.18 +type AuthRequest struct {
    3.19  	Client      Client
    3.20  	Scope       string
    3.21  	RedirectURI string
    3.22  	State       string
    3.23 -
    3.24 -	// Token expiration in seconds. Change if different from default.
    3.25 -	// If type = TokenAuthRT, this expiration will be for the ACCESS token.
    3.26 -	Expiration int32
    3.27  }
    3.28  
    3.29  // Authorization data
    3.30  type AuthorizeData struct {
    3.31 -	// Client information
    3.32 -	Client Client
    3.33 -
    3.34  	// Authorization code
    3.35  	Code string
    3.36  
    3.37  	// Token expiration in seconds
    3.38  	ExpiresIn int32
    3.39  
    3.40 -	// Requested scope
    3.41 -	Scope string
    3.42 -
    3.43 -	// Redirect URI from request
    3.44 -	RedirectURI string
    3.45 -
    3.46 -	// State data from request
    3.47 -	State string
    3.48 -
    3.49  	// Date created
    3.50  	CreatedAt time.Time
    3.51 +
    3.52 +	ProfileID uuid.ID
    3.53 +
    3.54 +	AuthRequest
    3.55  }
    3.56  
    3.57  // IsExpired is true if authorization expired
    3.58 @@ -67,153 +54,217 @@
    3.59  // authorization requests
    3.60  func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    3.61  	r.ParseForm()
    3.62 +	// create the authorization request
    3.63 +	redirectURI := r.Form.Get("redirect_uri")
    3.64 +	var err error
    3.65 +	if redirectURI != "" {
    3.66 +		redirectURI, err = url.QueryUnescape(redirectURI)
    3.67 +		if err != nil {
    3.68 +			ctx.RenderError(w, URIFormatError(redirectURI))
    3.69 +			return
    3.70 +		}
    3.71 +	}
    3.72 +
    3.73 +	state := r.Form.Get("state")
    3.74 +	scope := r.Form.Get("scope")
    3.75 +
    3.76 +	// must have a valid client
    3.77 +	id, err := uuid.Parse(r.Form.Get("client_id"))
    3.78 +	if err != nil {
    3.79 +		ctx.RenderError(w, InvalidClientIDError(r.Form.Get("client_id")))
    3.80 +		return
    3.81 +	}
    3.82 +	client, err := GetClient(id, ctx)
    3.83 +	if err != nil {
    3.84 +		if err == ClientNotFoundError {
    3.85 +			ctx.RenderError(w, ClientNotFoundError)
    3.86 +			return
    3.87 +		}
    3.88 +		if redirectURI == "" {
    3.89 +			ctx.RenderError(w, URIMissingError)
    3.90 +			return
    3.91 +		}
    3.92 +		req := AuthRequest{
    3.93 +			RedirectURI: redirectURI,
    3.94 +			Scope:       scope,
    3.95 +			State:       state,
    3.96 +		}
    3.97 +		redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
    3.98 +		if err != nil {
    3.99 +			ctx.RenderError(w, URIFormatError(redirectURI))
   3.100 +			return
   3.101 +		}
   3.102 +		http.Redirect(w, r, redir, http.StatusFound)
   3.103 +		return
   3.104 +	}
   3.105 +	if client.RedirectURI == "" {
   3.106 +		ctx.RenderError(w, URIMissingError)
   3.107 +		return
   3.108 +	}
   3.109 +
   3.110 +	// check redirect uri
   3.111 +	if redirectURI == "" {
   3.112 +		redirectURI = client.RedirectURI
   3.113 +	}
   3.114 +	if err = validateURI(client.RedirectURI, redirectURI); err != nil {
   3.115 +		ctx.RenderError(w, NewURIMismatchError(client.RedirectURI, redirectURI))
   3.116 +		return
   3.117 +	}
   3.118 +
   3.119 +	req := AuthRequest{
   3.120 +		Client:      client,
   3.121 +		RedirectURI: redirectURI,
   3.122 +		Scope:       scope,
   3.123 +		State:       state,
   3.124 +	}
   3.125  
   3.126  	requestType := AuthorizeRequestType(r.Form.Get("response_type"))
   3.127  	if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) {
   3.128  		switch requestType {
   3.129  		case CodeAuthRT:
   3.130 -			handleCodeRequest(w, r, ctx)
   3.131 +			req.handleCodeRequest(w, r, ctx)
   3.132  			return
   3.133  		case TokenAuthRT:
   3.134 -			handleTokenRequest(w, r, ctx)
   3.135 +			req.handleTokenRequest(w, r, ctx)
   3.136  			return
   3.137  		}
   3.138  	}
   3.139 -	// TODO: return error
   3.140 +	redir, err := req.GetErrorRedirect(ErrorInvalidRequest, "Invalid response type.", ctx.Config.DocumentationDomain)
   3.141 +	if err != nil {
   3.142 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.143 +		return
   3.144 +	}
   3.145 +	http.Redirect(w, r, redir, http.StatusFound)
   3.146  }
   3.147  
   3.148 -func handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   3.149 -	// create the authorization request
   3.150 -	unescapedURI, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
   3.151 -	if err != nil {
   3.152 -		unescapedURI = ""
   3.153 -	}
   3.154 -	ret := &AuthorizeRequest{
   3.155 -		Type:        CodeAuthRT,
   3.156 -		State:       r.Form.Get("state"),
   3.157 -		Scope:       r.Form.Get("scope"),
   3.158 -		RedirectURI: unescapedURI,
   3.159 -		Expiration:  ctx.Config.AuthorizationExpiration,
   3.160 -	}
   3.161 +func (req AuthRequest) handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   3.162  
   3.163 -	// must have a valid client
   3.164 -	id, err := uuid.Parse(r.Form.Get("client_id"))
   3.165 -	if err != nil {
   3.166 -		// TODO: return error
   3.167 +	if r.Method == "GET" {
   3.168 +		ctx.RenderConfirmation(w)
   3.169  		return
   3.170 -	}
   3.171 -	ret.Client, err = GetClient(id, ctx)
   3.172 -	if err != nil {
   3.173 -		// TODO: return error
   3.174 -		return
   3.175 -	}
   3.176 -	if ret.Client.RedirectURI == "" {
   3.177 -		// TODO: return error
   3.178 +	} else if r.Method != "POST" {
   3.179 +		ctx.RenderError(w, InvalidMethodError)
   3.180  		return
   3.181  	}
   3.182  
   3.183 -	// check redirect uri
   3.184 -	if ret.RedirectURI == "" {
   3.185 -		ret.RedirectURI = ret.Client.RedirectURI
   3.186 -	}
   3.187 -	if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil {
   3.188 -		// TODO: return error
   3.189 +	if err := validateSession(r); err == ErrorNotAuthenticated {
   3.190 +		ctx.RenderLogin(w)
   3.191 +		return
   3.192 +	} else if err != nil {
   3.193 +		ctx.RenderError(w, err)
   3.194  		return
   3.195  	}
   3.196  
   3.197 -	// TODO: do redirect with ret data
   3.198 -}
   3.199 -
   3.200 -func handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   3.201 -	// create the authorization request
   3.202 -	unescapedURI, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
   3.203 -	if err != nil {
   3.204 -		unescapedURI = ""
   3.205 -	}
   3.206 -	ret := &AuthorizeRequest{
   3.207 -		Type:        TokenAuthRT,
   3.208 -		State:       r.Form.Get("state"),
   3.209 -		Scope:       r.Form.Get("scope"),
   3.210 -		RedirectURI: unescapedURI,
   3.211 -		// this type will generate a token directly, use access token expiration instead.
   3.212 -		Expiration: ctx.Config.AccessExpiration,
   3.213 -	}
   3.214 -
   3.215 -	// must have a valid client
   3.216 -	id, err := uuid.Parse(r.Form.Get("client_id"))
   3.217 -	if err != nil {
   3.218 -		// TODO: return error
   3.219 -		return
   3.220 -	}
   3.221 -	ret.Client, err = GetClient(id, ctx)
   3.222 -	if err != nil {
   3.223 -		// TODO: return error
   3.224 -		return
   3.225 -	}
   3.226 -	if ret.Client.RedirectURI == "" {
   3.227 -		// TODO: return error
   3.228 +	if r.FormValue("approved") != "true" {
   3.229 +		redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
   3.230 +		if err != nil {
   3.231 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.232 +			return
   3.233 +		}
   3.234 +		http.Redirect(w, r, redir, http.StatusFound)
   3.235  		return
   3.236  	}
   3.237  
   3.238 -	// check redirect uri
   3.239 -	if ret.RedirectURI == "" {
   3.240 -		ret.RedirectURI = ret.Client.RedirectURI
   3.241 -	}
   3.242 -	if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil {
   3.243 -		// TODO: return error
   3.244 +	data := AuthorizeData{AuthRequest: req}
   3.245 +
   3.246 +	data.ExpiresIn = ctx.Config.AuthorizationExpiration
   3.247 +	data.Code = newToken()
   3.248 +	data.CreatedAt = time.Now()
   3.249 +
   3.250 +	err := ctx.Tokens.SaveAuthorization(data)
   3.251 +	if err != nil {
   3.252 +		redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   3.253 +		if err != nil {
   3.254 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.255 +			return
   3.256 +		}
   3.257 +		http.Redirect(w, r, redir, http.StatusFound)
   3.258 +		return
   3.259  	}
   3.260  
   3.261 -	// TODO: redirect with ret information
   3.262 +	redir, err := data.GetRedirect()
   3.263 +	if err != nil {
   3.264 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.265 +		return
   3.266 +	}
   3.267 +	http.Redirect(w, r, redir, http.StatusFound)
   3.268  }
   3.269  
   3.270 -func FinishAuthorizeRequest(w http.ResponseWriter, r *http.Request, ar *AuthorizeRequest, ctx Context) {
   3.271 -	// TODO: check if authorized?
   3.272 -	if ar.Type == TokenAuthRT {
   3.273 -		// TODO: w.SetRedirectFragment(true) was called...
   3.274 +func (req AuthRequest) handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   3.275  
   3.276 -		// generate token directly
   3.277 -		ret := AccessRequest{
   3.278 -			Code:            "",
   3.279 -			Client:          ar.Client,
   3.280 -			RedirectURI:     ar.RedirectURI,
   3.281 -			Scope:           ar.Scope,
   3.282 -			GenerateRefresh: false, // per the RFC, should NOT generate a refresh token in this case
   3.283 -			Expiration:      ar.Expiration,
   3.284 -		}
   3.285 -		// TODO: ret.type was implicit
   3.286 -		// TODO: ret.Authorized was true
   3.287 -		FinishAccessRequest(w, r, ret, ctx)
   3.288 -	} else {
   3.289 -		// generate authorization token
   3.290 -		ret := AuthorizeData{
   3.291 -			Client:      ar.Client,
   3.292 -			CreatedAt:   time.Now(),
   3.293 -			ExpiresIn:   ar.Expiration,
   3.294 -			RedirectURI: ar.RedirectURI,
   3.295 -			State:       ar.State,
   3.296 -			Scope:       ar.Scope,
   3.297 -			Code:        newToken(),
   3.298 -		}
   3.299 +	if r.Method == "GET" {
   3.300 +		ctx.RenderConfirmation(w)
   3.301 +		return
   3.302 +	} else if r.Method != "POST" {
   3.303 +		ctx.RenderError(w, InvalidMethodError)
   3.304 +		return
   3.305 +	}
   3.306  
   3.307 -		// save authorization token
   3.308 -		err := saveAuthorize(ret, ctx)
   3.309 +	if err := validateSession(r); err == ErrorNotAuthenticated {
   3.310 +		ctx.RenderLogin(w)
   3.311 +		return
   3.312 +	} else if err != nil {
   3.313 +		ctx.RenderError(w, err)
   3.314 +		return
   3.315 +	}
   3.316 +
   3.317 +	if r.FormValue("approved") != "true" {
   3.318 +		redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
   3.319  		if err != nil {
   3.320 -			// TODO: return error
   3.321 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.322  			return
   3.323  		}
   3.324 +		http.Redirect(w, r, redir, http.StatusFound)
   3.325 +		return
   3.326 +	}
   3.327  
   3.328 -		// TODO: redirect with ret.Code and ret.State
   3.329 +	data := AccessData{AuthRequest: req}
   3.330 +
   3.331 +	err := fillTokens(&data, false, ctx)
   3.332 +	if err != nil {
   3.333 +		ctx.RenderError(w, InternalServerError)
   3.334 +		return
   3.335  	}
   3.336 +
   3.337 +	redir, err := data.GetRedirect(true)
   3.338 +	if err != nil {
   3.339 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.340 +		return
   3.341 +	}
   3.342 +	http.Redirect(w, r, redir, http.StatusFound)
   3.343  }
   3.344  
   3.345 -func loadAuthorize(code string, ctx Context) (AuthorizeData, error) {
   3.346 -	return AuthorizeData{}, nil
   3.347 +func (data AuthorizeData) GetRedirect() (string, error) {
   3.348 +	u, err := url.Parse(data.RedirectURI)
   3.349 +	if err != nil {
   3.350 +		return "", err
   3.351 +	}
   3.352 +
   3.353 +	// add parameters
   3.354 +	q := u.Query()
   3.355 +	q.Set("code", data.Code)
   3.356 +	q.Set("state", data.State)
   3.357 +	u.RawQuery = q.Encode()
   3.358 +
   3.359 +	return u.String(), nil
   3.360  }
   3.361  
   3.362 -func saveAuthorize(ret AuthorizeData, ctx Context) error {
   3.363 -	return nil
   3.364 +func (req AuthRequest) GetErrorRedirect(code, description, uriBase string) (string, error) {
   3.365 +	u, err := url.Parse(req.RedirectURI)
   3.366 +	if err != nil {
   3.367 +		return "", err
   3.368 +	}
   3.369 +
   3.370 +	// add parameters
   3.371 +	q := u.Query()
   3.372 +	q.Set("error", code)
   3.373 +	q.Set("error_description", description)
   3.374 +	q.Set("error_uri", strings.Join([]string{
   3.375 +		strings.TrimRight(uriBase, "/"),
   3.376 +		strings.TrimLeft(code, "/"),
   3.377 +	}, "/"))
   3.378 +	q.Set("state", req.State)
   3.379 +	u.RawQuery = q.Encode()
   3.380 +
   3.381 +	return u.String(), nil
   3.382  }
   3.383 -
   3.384 -func removeAuthorize(code string, ctx Context) error {
   3.385 -	return nil
   3.386 -}
     4.1 --- a/authorize_test.go	Fri Jul 18 07:13:22 2014 -0400
     4.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 @@ -1,88 +0,0 @@
     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 TestAuthorizeCode(t *testing.T) {
    4.13 -	sconfig := NewServerConfig()
    4.14 -	sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{CODE}
    4.15 -	server := NewServer(sconfig, NewTestingStorage())
    4.16 -	server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{}
    4.17 -	resp := server.NewResponse()
    4.18 -
    4.19 -	req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
    4.20 -	if err != nil {
    4.21 -		t.Fatal(err)
    4.22 -	}
    4.23 -	req.Form = make(url.Values)
    4.24 -	req.Form.Set("response_type", string(CODE))
    4.25 -	req.Form.Set("client_id", "1234")
    4.26 -	req.Form.Set("state", "a")
    4.27 -
    4.28 -	if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
    4.29 -		ar.Authorized = true
    4.30 -		server.FinishAuthorizeRequest(resp, req, ar)
    4.31 -	}
    4.32 -
    4.33 -	//fmt.Printf("%+v", resp)
    4.34 -
    4.35 -	if resp.IsError && resp.InternalError != nil {
    4.36 -		t.Fatalf("Error in response: %s", resp.InternalError)
    4.37 -	}
    4.38 -
    4.39 -	if resp.IsError {
    4.40 -		t.Fatalf("Should not be an error")
    4.41 -	}
    4.42 -
    4.43 -	if resp.Type != REDIRECT {
    4.44 -		t.Fatalf("Response should be a redirect")
    4.45 -	}
    4.46 -
    4.47 -	if d := resp.Output["code"]; d != "1" {
    4.48 -		t.Fatalf("Unexpected authorization code: %s", d)
    4.49 -	}
    4.50 -}
    4.51 -
    4.52 -func TestAuthorizeToken(t *testing.T) {
    4.53 -	sconfig := NewServerConfig()
    4.54 -	sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{TOKEN}
    4.55 -	server := NewServer(sconfig, NewTestingStorage())
    4.56 -	server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{}
    4.57 -	server.AccessTokenGen = &TestingAccessTokenGen{}
    4.58 -	resp := server.NewResponse()
    4.59 -
    4.60 -	req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
    4.61 -	if err != nil {
    4.62 -		t.Fatal(err)
    4.63 -	}
    4.64 -	req.Form = make(url.Values)
    4.65 -	req.Form.Set("response_type", string(TOKEN))
    4.66 -	req.Form.Set("client_id", "1234")
    4.67 -	req.Form.Set("state", "a")
    4.68 -
    4.69 -	if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
    4.70 -		ar.Authorized = true
    4.71 -		server.FinishAuthorizeRequest(resp, req, ar)
    4.72 -	}
    4.73 -
    4.74 -	//fmt.Printf("%+v", resp)
    4.75 -
    4.76 -	if resp.IsError && resp.InternalError != nil {
    4.77 -		t.Fatalf("Error in response: %s", resp.InternalError)
    4.78 -	}
    4.79 -
    4.80 -	if resp.IsError {
    4.81 -		t.Fatalf("Should not be an error")
    4.82 -	}
    4.83 -
    4.84 -	if resp.Type != REDIRECT || !resp.RedirectInFragment {
    4.85 -		t.Fatalf("Response should be a redirect with fragment")
    4.86 -	}
    4.87 -
    4.88 -	if d := resp.Output["access_token"]; d != "1" {
    4.89 -		t.Fatalf("Unexpected access token: %s", d)
    4.90 -	}
    4.91 -}
     5.1 --- a/config.go	Fri Jul 18 07:13:22 2014 -0400
     5.2 +++ b/config.go	Fri Aug 01 23:08:38 2014 -0400
     5.3 @@ -53,6 +53,9 @@
     5.4  
     5.5  	// If true allows access request using GET, else only POST - default false
     5.6  	AllowGetAccessRequest bool
     5.7 +
     5.8 +	// The base path of documentation
     5.9 +	DocumentationDomain string
    5.10  }
    5.11  
    5.12  // NewServerConfig returns a new ServerConfig with default configuration
     6.1 --- a/context.go	Fri Jul 18 07:13:22 2014 -0400
     6.2 +++ b/context.go	Fri Aug 01 23:08:38 2014 -0400
     6.3 @@ -1,5 +1,19 @@
     6.4  package oauth2
     6.5  
     6.6 +import "io"
     6.7 +
     6.8  type Context struct {
     6.9 -	Config ServerConfig
    6.10 +	Config   ServerConfig
    6.11 +	Clients  ClientStore
    6.12 +	Tokens   TokenStore
    6.13 +	Profiles ProfileStore
    6.14  }
    6.15 +
    6.16 +func (c Context) RenderError(w io.Writer, err error) {
    6.17 +}
    6.18 +
    6.19 +func (c Context) RenderConfirmation(w io.Writer) {
    6.20 +}
    6.21 +
    6.22 +func (c Context) RenderLogin(w io.Writer) {
    6.23 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/errors.go	Fri Aug 01 23:08:38 2014 -0400
     7.3 @@ -0,0 +1,45 @@
     7.4 +package oauth2
     7.5 +
     7.6 +import "errors"
     7.7 +
     7.8 +const (
     7.9 +	ErrorServerError    = "server_error"
    7.10 +	ErrorInvalidRequest = "invalid_request"
    7.11 +	ErrorAccessDenied   = "access_denied"
    7.12 +)
    7.13 +
    7.14 +var (
    7.15 +	ClientNotFoundError   = errors.New("Client not found.")
    7.16 +	URIMissingError       = errors.New("Redirect URI missing.")
    7.17 +	InvalidMethodError    = errors.New("Invalid request method.")
    7.18 +	InternalServerError   = errors.New("Internal server error.")
    7.19 +	ErrorNotAuthenticated = errors.New("Not authenticated.")
    7.20 +)
    7.21 +
    7.22 +type URIFormatError string
    7.23 +
    7.24 +func (err URIFormatError) Error() string {
    7.25 +	return "Invalid URI format: " + string(err)
    7.26 +}
    7.27 +
    7.28 +type InvalidClientIDError string
    7.29 +
    7.30 +func (err InvalidClientIDError) Error() string {
    7.31 +	return "Invalid client ID: " + string(err)
    7.32 +}
    7.33 +
    7.34 +type URIMismatchError struct {
    7.35 +	uri      string
    7.36 +	mismatch string
    7.37 +}
    7.38 +
    7.39 +func (err URIMismatchError) Error() string {
    7.40 +	return "Supplied redirect URI " + err.mismatch + " does not match the redirect in the database (" + err.uri + ")"
    7.41 +}
    7.42 +
    7.43 +func NewURIMismatchError(uri, mismatch string) error {
    7.44 +	return URIMismatchError{
    7.45 +		uri:      uri,
    7.46 +		mismatch: mismatch,
    7.47 +	}
    7.48 +}
     8.1 --- a/info.go	Fri Jul 18 07:13:22 2014 -0400
     8.2 +++ b/info.go	Fri Aug 01 23:08:38 2014 -0400
     8.3 @@ -1,59 +1,37 @@
     8.4  package oauth2
     8.5  
     8.6 -import "net/http"
     8.7 -
     8.8 -// InfoRequest is a request for information about some AccessData
     8.9 -type InfoRequest struct {
    8.10 -	Code       string     // Code to look up
    8.11 -	AccessData AccessData // AccessData associated with Code
    8.12 -}
    8.13 +import (
    8.14 +	"net/http"
    8.15 +	"time"
    8.16 +)
    8.17  
    8.18  // HandleInfoRequest is an http.HandlerFunc for server information
    8.19  // NOT an RFC specification.
    8.20  func HandleInfoRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    8.21  	r.ParseForm()
    8.22  
    8.23 -	// generate info request
    8.24 -	ret := InfoRequest{
    8.25 -		Code: r.Form.Get("code"),
    8.26 -	}
    8.27 +	code := r.Form.Get("code")
    8.28  
    8.29 -	if ret.Code == "" {
    8.30 +	if code == "" {
    8.31  		// TODO: return error
    8.32  		return
    8.33  	}
    8.34  
    8.35 -	var err error
    8.36 -
    8.37  	// load access data
    8.38 -	ret.AccessData, err = loadAccess(ret.Code, ctx)
    8.39 +	accessData, err := ctx.Tokens.GetAccess(code)
    8.40  	if err != nil {
    8.41  		// TODO: return error
    8.42  		return
    8.43  	}
    8.44 -	if ret.AccessData.Client.RedirectURI == "" {
    8.45 +	if accessData.Client.RedirectURI == "" {
    8.46  		// TODO: return error
    8.47  		return
    8.48  	}
    8.49 -	if ret.AccessData.IsExpired() {
    8.50 +	if accessData.IsExpired() {
    8.51  		// TODO: return error
    8.52  		return
    8.53  	}
    8.54 -	// TODO: write ret
    8.55 +
    8.56 +	accessData.ExpiresIn = int32(accessData.CreatedAt.Add(time.Duration(accessData.ExpiresIn)*time.Second).Sub(time.Now()) / time.Second)
    8.57 +	// TODO: write accessData
    8.58  }
    8.59 -
    8.60 -// FinishInfoRequest finalizes the request handled by HandleInfoRequest
    8.61 -func FinishInfoRequest(w http.ResponseWriter, r *http.Request, ir *InfoRequest, ctx Context) {
    8.62 -	// output data
    8.63 -	//w.Output["client_id"] = ir.AccessData.Client.Id
    8.64 -	//w.Output["access_token"] = ir.AccessData.AccessToken
    8.65 -	//w.Output["token_type"] = s.Config.TokenType
    8.66 -	//w.Output["expires_in"] = ir.AccessData.CreatedAt.Add(time.Duration(ir.AccessData.ExpiresIn)*time.Second).Sub(time.Now()) / time.Second
    8.67 -	//if ir.AccessData.RefreshToken != "" {
    8.68 -	//	w.Output["refresh_token"] = ir.AccessData.RefreshToken
    8.69 -	//}
    8.70 -	//if ir.AccessData.Scope != "" {
    8.71 -	//	w.Output["scope"] = ir.AccessData.Scope
    8.72 -	//}
    8.73 -	// TODO: write output
    8.74 -}
     9.1 --- a/info_test.go	Fri Jul 18 07:13:22 2014 -0400
     9.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.3 @@ -1,42 +0,0 @@
     9.4 -package oauth2
     9.5 -
     9.6 -import (
     9.7 -	"net/http"
     9.8 -	"net/url"
     9.9 -	"testing"
    9.10 -)
    9.11 -
    9.12 -func TestInfo(t *testing.T) {
    9.13 -	sconfig := NewServerConfig()
    9.14 -	server := NewServer(sconfig, NewTestingStorage())
    9.15 -	resp := server.NewResponse()
    9.16 -
    9.17 -	req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
    9.18 -	if err != nil {
    9.19 -		t.Fatal(err)
    9.20 -	}
    9.21 -	req.Form = make(url.Values)
    9.22 -	req.Form.Set("code", "9999")
    9.23 -
    9.24 -	if ar := server.HandleInfoRequest(resp, req); ar != nil {
    9.25 -		server.FinishInfoRequest(resp, req, ar)
    9.26 -	}
    9.27 -
    9.28 -	//fmt.Printf("%+v", resp)
    9.29 -
    9.30 -	if resp.IsError && resp.InternalError != nil {
    9.31 -		t.Fatalf("Error in response: %s", resp.InternalError)
    9.32 -	}
    9.33 -
    9.34 -	if resp.IsError {
    9.35 -		t.Fatalf("Should not be an error")
    9.36 -	}
    9.37 -
    9.38 -	if resp.Type != DATA {
    9.39 -		t.Fatalf("Response should be data")
    9.40 -	}
    9.41 -
    9.42 -	if d := resp.Output["access_token"]; d != "9999" {
    9.43 -		t.Fatalf("Unexpected authorization code: %s", d)
    9.44 -	}
    9.45 -}
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/session.go	Fri Aug 01 23:08:38 2014 -0400
    10.3 @@ -0,0 +1,7 @@
    10.4 +package oauth2
    10.5 +
    10.6 +import "net/http"
    10.7 +
    10.8 +func validateSession(r *http.Request) error {
    10.9 +	return nil
   10.10 +}
    11.1 --- a/storage.go	Fri Jul 18 07:13:22 2014 -0400
    11.2 +++ b/storage.go	Fri Aug 01 23:08:38 2014 -0400
    11.3 @@ -1,39 +1,28 @@
    11.4  package oauth2
    11.5  
    11.6 -// Storage interface
    11.7 -type Storage interface {
    11.8 +import "secondbit.org/uuid"
    11.9  
   11.10 -	// GetClient loads the client by id (client_id)
   11.11 -	GetClient(id string) (*Client, error)
   11.12 +type ClientStore interface {
   11.13 +	GetClient(id uuid.ID) (Client, error)
   11.14 +	CreateClient(name, logo, redirectURI string, owner uuid.ID) (Client, error)
   11.15 +	UpdateClient(client *Client, name, logo, redirectURI *string) error
   11.16 +	RemoveClient(id uuid.ID, ctx Context) error
   11.17 +	ListClients(id uuid.ID, page, num int, ctx Context) ([]Client, error)
   11.18 +}
   11.19  
   11.20 -	// SaveAuthorize saves authorize data.
   11.21 -	SaveAuthorize(*AuthorizeData) error
   11.22 +type TokenStore interface {
   11.23 +	SaveAuthorization(AuthorizeData) error
   11.24 +	GetAuthorization(code string) (AuthorizeData, error)
   11.25 +	RemoveAuthorization(code string) error
   11.26  
   11.27 -	// LoadAuthorize looks up AuthorizeData by a code.
   11.28 -	// Client information MUST be loaded together.
   11.29 -	// Optionally can return error if expired.
   11.30 -	LoadAuthorize(code string) (*AuthorizeData, error)
   11.31 -
   11.32 -	// RemoveAuthorize revokes or deletes the authorization code.
   11.33 -	RemoveAuthorize(code string) error
   11.34 -
   11.35 -	// SaveAccess writes AccessData.
   11.36 -	// If RefreshToken is not blank, it must save in a way that can be loaded using LoadRefresh.
   11.37 -	SaveAccess(*AccessData) error
   11.38 -
   11.39 -	// LoadAccess retrieves access data by token. Client information MUST be loaded together.
   11.40 -	// AuthorizeData and AccessData DON'T NEED to be loaded if not easily available.
   11.41 -	// Optionally can return error if expired.
   11.42 -	LoadAccess(token string) (*AccessData, error)
   11.43 -
   11.44 -	// RemoveAccess revokes or deletes an AccessData.
   11.45 +	SaveAccess(AccessData) error
   11.46 +	GetAccess(token string) (AccessData, error)
   11.47  	RemoveAccess(token string) error
   11.48  
   11.49 -	// LoadRefresh retrieves refresh AccessData. Client information MUST be loaded together.
   11.50 -	// AuthorizeData and AccessData DON'T NEED to be loaded if not easily available.
   11.51 -	// Optionally can return error if expired.
   11.52 -	LoadRefresh(token string) (*AccessData, error)
   11.53 -
   11.54 -	// RemoveRefresh revokes or deletes refresh AccessData.
   11.55 +	GetRefresh(token string) (AccessData, error)
   11.56  	RemoveRefresh(token string) error
   11.57  }
   11.58 +
   11.59 +type ProfileStore interface {
   11.60 +	GetProfile(username, password string) (uuid.ID, error)
   11.61 +}
    12.1 --- a/storage_test.go	Fri Jul 18 07:13:22 2014 -0400
    12.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.3 @@ -1,147 +0,0 @@
    12.4 -package oauth2
    12.5 -
    12.6 -import (
    12.7 -	"errors"
    12.8 -	"strconv"
    12.9 -	"time"
   12.10 -)
   12.11 -
   12.12 -type TestingStorage struct {
   12.13 -	clients   map[string]*Client
   12.14 -	authorize map[string]*AuthorizeData
   12.15 -	access    map[string]*AccessData
   12.16 -	refresh   map[string]string
   12.17 -}
   12.18 -
   12.19 -func NewTestingStorage() *TestingStorage {
   12.20 -	r := &TestingStorage{
   12.21 -		clients:   make(map[string]*Client),
   12.22 -		authorize: make(map[string]*AuthorizeData),
   12.23 -		access:    make(map[string]*AccessData),
   12.24 -		refresh:   make(map[string]string),
   12.25 -	}
   12.26 -
   12.27 -	r.clients["1234"] = &Client{
   12.28 -		Id:          "1234",
   12.29 -		Secret:      "aabbccdd",
   12.30 -		RedirectUri: "http://localhost:14000/appauth",
   12.31 -	}
   12.32 -
   12.33 -	r.authorize["9999"] = &AuthorizeData{
   12.34 -		Client:      r.clients["1234"],
   12.35 -		Code:        "9999",
   12.36 -		ExpiresIn:   3600,
   12.37 -		CreatedAt:   time.Now(),
   12.38 -		RedirectUri: "http://localhost:14000/appauth",
   12.39 -	}
   12.40 -
   12.41 -	r.access["9999"] = &AccessData{
   12.42 -		Client:        r.clients["1234"],
   12.43 -		AuthorizeData: r.authorize["9999"],
   12.44 -		AccessToken:   "9999",
   12.45 -		ExpiresIn:     3600,
   12.46 -		CreatedAt:     time.Now(),
   12.47 -	}
   12.48 -
   12.49 -	r.access["r9999"] = &AccessData{
   12.50 -		Client:        r.clients["1234"],
   12.51 -		AuthorizeData: r.authorize["9999"],
   12.52 -		AccessData:    r.access["9999"],
   12.53 -		AccessToken:   "9999",
   12.54 -		RefreshToken:  "r9999",
   12.55 -		ExpiresIn:     3600,
   12.56 -		CreatedAt:     time.Now(),
   12.57 -	}
   12.58 -
   12.59 -	r.refresh["r9999"] = "9999"
   12.60 -
   12.61 -	return r
   12.62 -}
   12.63 -
   12.64 -func (s *TestingStorage) GetClient(id string) (*Client, error) {
   12.65 -	if c, ok := s.clients[id]; ok {
   12.66 -		return c, nil
   12.67 -	}
   12.68 -	return nil, errors.New("Client not found")
   12.69 -}
   12.70 -
   12.71 -func (s *TestingStorage) SetClient(id string, client *Client) error {
   12.72 -	s.clients[id] = client
   12.73 -	return nil
   12.74 -}
   12.75 -
   12.76 -func (s *TestingStorage) SaveAuthorize(data *AuthorizeData) error {
   12.77 -	s.authorize[data.Code] = data
   12.78 -	return nil
   12.79 -}
   12.80 -
   12.81 -func (s *TestingStorage) LoadAuthorize(code string) (*AuthorizeData, error) {
   12.82 -	if d, ok := s.authorize[code]; ok {
   12.83 -		return d, nil
   12.84 -	}
   12.85 -	return nil, errors.New("Authorize not found")
   12.86 -}
   12.87 -
   12.88 -func (s *TestingStorage) RemoveAuthorize(code string) error {
   12.89 -	delete(s.authorize, code)
   12.90 -	return nil
   12.91 -}
   12.92 -
   12.93 -func (s *TestingStorage) SaveAccess(data *AccessData) error {
   12.94 -	s.access[data.AccessToken] = data
   12.95 -	if data.RefreshToken != "" {
   12.96 -		s.refresh[data.RefreshToken] = data.AccessToken
   12.97 -	}
   12.98 -	return nil
   12.99 -}
  12.100 -
  12.101 -func (s *TestingStorage) LoadAccess(code string) (*AccessData, error) {
  12.102 -	if d, ok := s.access[code]; ok {
  12.103 -		return d, nil
  12.104 -	}
  12.105 -	return nil, errors.New("Access not found")
  12.106 -}
  12.107 -
  12.108 -func (s *TestingStorage) RemoveAccess(code string) error {
  12.109 -	delete(s.access, code)
  12.110 -	return nil
  12.111 -}
  12.112 -
  12.113 -func (s *TestingStorage) LoadRefresh(code string) (*AccessData, error) {
  12.114 -	if d, ok := s.refresh[code]; ok {
  12.115 -		return s.LoadAccess(d)
  12.116 -	}
  12.117 -	return nil, errors.New("Refresh not found")
  12.118 -}
  12.119 -
  12.120 -func (s *TestingStorage) RemoveRefresh(code string) error {
  12.121 -	delete(s.refresh, code)
  12.122 -	return nil
  12.123 -}
  12.124 -
  12.125 -// Predictable testing token generation
  12.126 -
  12.127 -type TestingAuthorizeTokenGen struct {
  12.128 -	counter int64
  12.129 -}
  12.130 -
  12.131 -func (a *TestingAuthorizeTokenGen) GenerateAuthorizeToken(data *AuthorizeData) (ret string, err error) {
  12.132 -	a.counter++
  12.133 -	return strconv.FormatInt(a.counter, 10), nil
  12.134 -}
  12.135 -
  12.136 -type TestingAccessTokenGen struct {
  12.137 -	acounter int64
  12.138 -	rcounter int64
  12.139 -}
  12.140 -
  12.141 -func (a *TestingAccessTokenGen) GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error) {
  12.142 -	a.acounter++
  12.143 -	accesstoken = strconv.FormatInt(a.acounter, 10)
  12.144 -
  12.145 -	if generaterefresh {
  12.146 -		a.rcounter++
  12.147 -		refreshtoken = "r" + strconv.FormatInt(a.rcounter, 10)
  12.148 -	}
  12.149 -	return
  12.150 -}
    13.1 --- a/tokengen.go	Fri Jul 18 07:13:22 2014 -0400
    13.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.3 @@ -1,11 +0,0 @@
    13.4 -package oauth2
    13.5 -
    13.6 -import (
    13.7 -	"encoding/base64"
    13.8 -
    13.9 -	"code.google.com/p/go-uuid/uuid"
   13.10 -)
   13.11 -
   13.12 -func newToken() string {
   13.13 -	return base64.StdEncoding.EncodeToString([]byte(uuid.New()))
   13.14 -}
    14.1 --- a/urivalidate.go	Fri Jul 18 07:13:22 2014 -0400
    14.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.3 @@ -1,39 +0,0 @@
    14.4 -package oauth2
    14.5 -
    14.6 -import (
    14.7 -	"errors"
    14.8 -	"fmt"
    14.9 -	"net/url"
   14.10 -	"strings"
   14.11 -)
   14.12 -
   14.13 -// ValidateURI validates that redirectURI is contained in baseURI
   14.14 -func ValidateURI(baseURI string, redirectURI string) error {
   14.15 -	if baseURI == "" || redirectURI == "" {
   14.16 -		return errors.New("urls cannot be blank.")
   14.17 -	}
   14.18 -
   14.19 -	// parse base url
   14.20 -	base, err := url.Parse(baseURI)
   14.21 -	if err != nil {
   14.22 -		return err
   14.23 -	}
   14.24 -
   14.25 -	// parse passed url
   14.26 -	redirect, err := url.Parse(redirectURI)
   14.27 -	if err != nil {
   14.28 -		return err
   14.29 -	}
   14.30 -
   14.31 -	// must not have fragment
   14.32 -	if base.Fragment != "" || redirect.Fragment != "" {
   14.33 -		return errors.New("url must not include fragment.")
   14.34 -	}
   14.35 -
   14.36 -	// check if urls match
   14.37 -	if base.Scheme == redirect.Scheme && base.Host == redirect.Host && len(redirect.Path) >= len(base.Path) && strings.HasPrefix(redirect.Path, base.Path) {
   14.38 -		return nil
   14.39 -	}
   14.40 -
   14.41 -	return errors.New(fmt.Sprintf("urls don't validate: %s / %s\n", baseURI, redirectURI))
   14.42 -}
    15.1 --- a/urivalidate_test.go	Fri Jul 18 07:13:22 2014 -0400
    15.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.3 @@ -1,27 +0,0 @@
    15.4 -package oauth2
    15.5 -
    15.6 -import (
    15.7 -	"testing"
    15.8 -)
    15.9 -
   15.10 -func TestURIValidate(t *testing.T) {
   15.11 -	// V1
   15.12 -	if err := ValidateUri("http://localhost:14000/appauth", "http://localhost:14000/appauth"); err != nil {
   15.13 -		t.Errorf("V1: %s", err)
   15.14 -	}
   15.15 -
   15.16 -	// V2
   15.17 -	if err := ValidateUri("http://localhost:14000/appauth", "http://localhost:14000/app"); err == nil {
   15.18 -		t.Error("V2 should have failed")
   15.19 -	}
   15.20 -
   15.21 -	// V3
   15.22 -	if err := ValidateUri("http://www.google.com/myapp", "http://www.google.com/myapp/interface/implementation"); err != nil {
   15.23 -		t.Errorf("V3: %s", err)
   15.24 -	}
   15.25 -
   15.26 -	// V4
   15.27 -	if err := ValidateUri("http://www.google.com/myapp", "http://www2.google.com/myapp"); err == nil {
   15.28 -		t.Error("V4 should have failed")
   15.29 -	}
   15.30 -}
    16.1 --- a/util.go	Fri Jul 18 07:13:22 2014 -0400
    16.2 +++ b/util.go	Fri Aug 01 23:08:38 2014 -0400
    16.3 @@ -3,8 +3,12 @@
    16.4  import (
    16.5  	"encoding/base64"
    16.6  	"errors"
    16.7 +	"fmt"
    16.8  	"net/http"
    16.9 +	"net/url"
   16.10  	"strings"
   16.11 +
   16.12 +	"code.google.com/p/go-uuid/uuid"
   16.13  )
   16.14  
   16.15  var (
   16.16 @@ -61,3 +65,38 @@
   16.17  
   16.18  	return CheckBasicAuth(r)
   16.19  }
   16.20 +
   16.21 +func newToken() string {
   16.22 +	return base64.StdEncoding.EncodeToString([]byte(uuid.New()))
   16.23 +}
   16.24 +
   16.25 +// validateURI validates that redirectURI is contained in baseURI
   16.26 +func validateURI(baseURI string, redirectURI string) error {
   16.27 +	if baseURI == "" || redirectURI == "" {
   16.28 +		return errors.New("urls cannot be blank.")
   16.29 +	}
   16.30 +
   16.31 +	// parse base url
   16.32 +	base, err := url.Parse(baseURI)
   16.33 +	if err != nil {
   16.34 +		return err
   16.35 +	}
   16.36 +
   16.37 +	// parse passed url
   16.38 +	redirect, err := url.Parse(redirectURI)
   16.39 +	if err != nil {
   16.40 +		return err
   16.41 +	}
   16.42 +
   16.43 +	// must not have fragment
   16.44 +	if base.Fragment != "" || redirect.Fragment != "" {
   16.45 +		return errors.New("url must not include fragment.")
   16.46 +	}
   16.47 +
   16.48 +	// check if urls match
   16.49 +	if base.Scheme == redirect.Scheme && base.Host == redirect.Host && len(redirect.Path) >= len(base.Path) && strings.HasPrefix(redirect.Path, base.Path) {
   16.50 +		return nil
   16.51 +	}
   16.52 +
   16.53 +	return errors.New(fmt.Sprintf("urls don't validate: %s / %s\n", baseURI, redirectURI))
   16.54 +}
    17.1 --- a/util_test.go	Fri Jul 18 07:13:22 2014 -0400
    17.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.3 @@ -1,96 +0,0 @@
    17.4 -package oauth2
    17.5 -
    17.6 -import (
    17.7 -	"net/http"
    17.8 -	"net/url"
    17.9 -	"testing"
   17.10 -)
   17.11 -
   17.12 -const (
   17.13 -	badAuthValue  = "Digest XHHHHHHH"
   17.14 -	goodAuthValue = "Basic dGVzdDp0ZXN0"
   17.15 -)
   17.16 -
   17.17 -func TestBasicAuth(t *testing.T) {
   17.18 -	r := &http.Request{Header: make(http.Header)}
   17.19 -
   17.20 -	// Without any header
   17.21 -	if b, err := CheckBasicAuth(r); b != nil || err != nil {
   17.22 -		t.Errorf("Validated basic auth without header")
   17.23 -	}
   17.24 -
   17.25 -	// with invalid header
   17.26 -	r.Header.Set("Authorization", badAuthValue)
   17.27 -	b, err := CheckBasicAuth(r)
   17.28 -	if b != nil || err == nil {
   17.29 -		t.Errorf("Validated invalid auth")
   17.30 -		return
   17.31 -	}
   17.32 -
   17.33 -	// with valid header
   17.34 -	r.Header.Set("Authorization", goodAuthValue)
   17.35 -	b, err = CheckBasicAuth(r)
   17.36 -	if b == nil || err != nil {
   17.37 -		t.Errorf("Could not extract basic auth")
   17.38 -		return
   17.39 -	}
   17.40 -
   17.41 -	// check extracted auth data
   17.42 -	if b.Username != "test" || b.Password != "test" {
   17.43 -		t.Errorf("Error decoding basic auth")
   17.44 -	}
   17.45 -}
   17.46 -
   17.47 -func TestGetClientAuth(t *testing.T) {
   17.48 -
   17.49 -	urlWithSecret, _ := url.Parse("http://host.tld/path?client_id=xxx&client_secret=yyy")
   17.50 -	urlWithEmptySecret, _ := url.Parse("http://host.tld/path?client_id=xxx&client_secret=")
   17.51 -	urlNoSecret, _ := url.Parse("http://host.tld/path?client_id=xxx")
   17.52 -
   17.53 -	headerNoAuth := make(http.Header)
   17.54 -	headerBadAuth := make(http.Header)
   17.55 -	headerBadAuth.Set("Authorization", badAuthValue)
   17.56 -	headerOKAuth := make(http.Header)
   17.57 -	headerOKAuth.Set("Authorization", goodAuthValue)
   17.58 -
   17.59 -	var tests = []struct {
   17.60 -		header           http.Header
   17.61 -		url              *url.URL
   17.62 -		allowQueryParams bool
   17.63 -		expectAuth       bool
   17.64 -	}{
   17.65 -		{headerNoAuth, urlWithSecret, true, true},
   17.66 -		{headerNoAuth, urlWithSecret, false, false},
   17.67 -		{headerNoAuth, urlWithEmptySecret, true, true},
   17.68 -		{headerNoAuth, urlWithEmptySecret, false, false},
   17.69 -		{headerNoAuth, urlNoSecret, true, false},
   17.70 -		{headerNoAuth, urlNoSecret, false, false},
   17.71 -
   17.72 -		{headerBadAuth, urlWithSecret, true, true},
   17.73 -		{headerBadAuth, urlWithSecret, false, false},
   17.74 -		{headerBadAuth, urlWithEmptySecret, true, true},
   17.75 -		{headerBadAuth, urlWithEmptySecret, false, false},
   17.76 -		{headerBadAuth, urlNoSecret, true, false},
   17.77 -		{headerBadAuth, urlNoSecret, false, false},
   17.78 -
   17.79 -		{headerOKAuth, urlWithSecret, true, true},
   17.80 -		{headerOKAuth, urlWithSecret, false, true},
   17.81 -		{headerOKAuth, urlWithEmptySecret, true, true},
   17.82 -		{headerOKAuth, urlWithEmptySecret, false, true},
   17.83 -		{headerOKAuth, urlNoSecret, true, true},
   17.84 -		{headerOKAuth, urlNoSecret, false, true},
   17.85 -	}
   17.86 -
   17.87 -	for _, tt := range tests {
   17.88 -		w := new(Response)
   17.89 -		r := &http.Request{Header: tt.header, URL: tt.url}
   17.90 -		r.ParseForm()
   17.91 -		auth := getClientAuth(w, r, tt.allowQueryParams)
   17.92 -		if tt.expectAuth && auth == nil {
   17.93 -			t.Errorf("Auth should not be nil for %v", tt)
   17.94 -		} else if !tt.expectAuth && auth != nil {
   17.95 -			t.Errorf("Auth should be nil for %v", tt)
   17.96 -		}
   17.97 -	}
   17.98 -
   17.99 -}