auth

Paddy 2014-09-01 Parent:e6a44cfda658 Child:c5c4a991bc35

23:1aa3a85ff853 Browse Files

Deprecate old implementations. Let's remove all of the osin stuff altogether, in favour of a more testable, unit-based approach. Leave all the old files around, for easy reference, but add the .old suffix so the go tools don't pick them up.

access.go access.go.old authorize.go authorize.go.old config.go config.go.old context.go context.go.old errors.go errors.go.old session.go session.go.old util.go util.go.old

     1.1 --- a/access.go	Sat Aug 16 20:49:19 2014 -0400
     1.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.3 @@ -1,415 +0,0 @@
     1.4 -package auth
     1.5 -
     1.6 -import (
     1.7 -	"net/http"
     1.8 -	"net/url"
     1.9 -	"time"
    1.10 -
    1.11 -	"strconv"
    1.12 -	"secondbit.org/uuid"
    1.13 -)
    1.14 -
    1.15 -// GrantType is the type for OAuth param `grant_type`
    1.16 -type GrantType string
    1.17 -
    1.18 -const (
    1.19 -	AuthorizationCodeGrant GrantType = "authorization_code"
    1.20 -	RefreshTokenGrant                = "refresh_token"
    1.21 -	PasswordGrant                    = "password"
    1.22 -	ClientCredentialsGrant           = "client_credentials"
    1.23 -)
    1.24 -
    1.25 -// AccessData represents an access grant (tokens, expiration, client, etc)
    1.26 -type AccessData struct {
    1.27 -	PreviousAuthorizeData *AuthorizeData `json:"-"`
    1.28 -	PreviousAccessData    *AccessData    `json:"-"` // previous access data, when refreshing
    1.29 -	AccessToken           string         `json:"access_token"`
    1.30 -	RefreshToken          string         `json:"refresh_token,omitempty"`
    1.31 -	ExpiresIn             int32          `json:"expires_in"`
    1.32 -	CreatedAt             time.Time      `json:"-"`
    1.33 -	TokenType             string         `json:"token_type"`
    1.34 -	Scope                 string         `json:"scope,omitempty"`
    1.35 -	ProfileID             uuid.ID        `json:"-"`
    1.36 -	AuthRequest           `json:"-"`
    1.37 -}
    1.38 -
    1.39 -// IsExpired returns true if access expired
    1.40 -func (d *AccessData) IsExpired() bool {
    1.41 -	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now())
    1.42 -}
    1.43 -
    1.44 -// ExpireAt returns the expiration date
    1.45 -func (d *AccessData) ExpireAt() time.Time {
    1.46 -	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
    1.47 -}
    1.48 -
    1.49 -// HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests.
    1.50 -func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    1.51 -	// Only allow GET or POST
    1.52 -	if r.Method != "POST" {
    1.53 -		if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest {
    1.54 -			ctx.RenderJSONError(w, ErrorInvalidRequest, "Invalid request method.", ctx.Config.DocumentationDomain)
    1.55 -			return
    1.56 -		}
    1.57 -	}
    1.58 -
    1.59 -	grantType := GrantType(r.Form.Get("grant_type"))
    1.60 -	if ctx.Config.AllowedAccessTypes.Exists(grantType) {
    1.61 -		switch grantType {
    1.62 -		case AuthorizationCodeGrant:
    1.63 -			handleAuthorizationCodeRequest(w, r, ctx)
    1.64 -		case RefreshTokenGrant:
    1.65 -			handleRefreshTokenRequest(w, r, ctx)
    1.66 -		case PasswordGrant:
    1.67 -			handlePasswordRequest(w, r, ctx)
    1.68 -		case ClientCredentialsGrant:
    1.69 -			handleClientCredentialsRequest(w, r, ctx)
    1.70 -		default:
    1.71 -			ctx.RenderJSONError(w, ErrorUnsupportedGrantType, "Unsupported grant type.", ctx.Config.DocumentationDomain)
    1.72 -			return
    1.73 -		}
    1.74 -	}
    1.75 -}
    1.76 -
    1.77 -func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    1.78 -	// get client authentication
    1.79 -	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
    1.80 -	if err != nil {
    1.81 -		ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
    1.82 -		return
    1.83 -	}
    1.84 -
    1.85 -	code := r.Form.Get("code")
    1.86 -	// "code" is required
    1.87 -	if code == "" {
    1.88 -		ctx.RenderJSONError(w, ErrorInvalidRequest, "Code must be supplied.", ctx.Config.DocumentationDomain)
    1.89 -		return
    1.90 -	}
    1.91 -
    1.92 -	// must have a valid client
    1.93 -	client, err := getClient(auth, ctx)
    1.94 -	if err != nil {
    1.95 -		if err == ClientNotFoundError || err == InvalidClientError {
    1.96 -			ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
    1.97 -			return
    1.98 -		}
    1.99 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.100 -		return
   1.101 -	}
   1.102 -
   1.103 -	// must be a valid authorization code
   1.104 -	authData, err := ctx.Tokens.GetAuthorization(code)
   1.105 -	if err != nil {
   1.106 -		if err == AuthorizationNotFoundError {
   1.107 -			ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid authorization.", ctx.Config.DocumentationDomain)
   1.108 -			return
   1.109 -		}
   1.110 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.111 -		return
   1.112 -	}
   1.113 -	if authData.RedirectURI == "" {
   1.114 -		ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid redirect on grant.", ctx.Config.DocumentationDomain)
   1.115 -		return
   1.116 -	}
   1.117 -	if authData.IsExpired() {
   1.118 -		ctx.RenderJSONError(w, ErrorInvalidGrant, "Authorization is expired.", ctx.Config.DocumentationDomain)
   1.119 -		return
   1.120 -	}
   1.121 -
   1.122 -	// code must be from the client
   1.123 -	if !authData.Client.ID.Equal(client.ID) {
   1.124 -		ctx.RenderJSONError(w, ErrorInvalidGrant, "Grant issued to another client.", ctx.Config.DocumentationDomain)
   1.125 -		return
   1.126 -	}
   1.127 -
   1.128 -	// check redirect uri
   1.129 -	redirectURI := r.Form.Get("redirect_uri")
   1.130 -	if redirectURI == "" {
   1.131 -		redirectURI = client.RedirectURI
   1.132 -	}
   1.133 -	if err = validateURI(client.RedirectURI, redirectURI); err != nil {
   1.134 -		ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match client.", ctx.Config.DocumentationDomain)
   1.135 -		return
   1.136 -	}
   1.137 -	if authData.RedirectURI != redirectURI {
   1.138 -		ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match auth redirect.", ctx.Config.DocumentationDomain)
   1.139 -		return
   1.140 -	}
   1.141 -
   1.142 -	data := AccessData{
   1.143 -		AuthRequest: AuthRequest{
   1.144 -			Client:      client,
   1.145 -			RedirectURI: redirectURI,
   1.146 -			Scope:       authData.Scope,
   1.147 -		},
   1.148 -		Scope: authData.Scope,
   1.149 -		PreviousAuthorizeData: &authData,
   1.150 -	}
   1.151 -
   1.152 -	err = fillTokens(&data, true, ctx)
   1.153 -	if err != nil {
   1.154 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.155 -		return
   1.156 -	}
   1.157 -	ctx.RenderJSONToken(w, data)
   1.158 -}
   1.159 -
   1.160 -func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.161 -	// get client authentication
   1.162 -	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
   1.163 -
   1.164 -	if err != nil {
   1.165 -		ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   1.166 -		return
   1.167 -	}
   1.168 -
   1.169 -	code := r.Form.Get("refresh_token")
   1.170 -
   1.171 -	// "refresh_token" is required
   1.172 -	if code == "" {
   1.173 -		ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing refresh token.", ctx.Config.DocumentationDomain)
   1.174 -		return
   1.175 -	}
   1.176 -
   1.177 -	// must have a valid client
   1.178 -	client, err := getClient(auth, ctx)
   1.179 -	if err != nil {
   1.180 -		if err == ClientNotFoundError || err == InvalidClientError {
   1.181 -			ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   1.182 -			return
   1.183 -		}
   1.184 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.185 -		return
   1.186 -	}
   1.187 -
   1.188 -	// must be a valid refresh code
   1.189 -	refreshData, err := ctx.Tokens.GetRefresh(code)
   1.190 -	if err != nil {
   1.191 -		if err == TokenNotFoundError {
   1.192 -			ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token not valid.", ctx.Config.DocumentationDomain)
   1.193 -			return
   1.194 -		}
   1.195 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.196 -		return
   1.197 -	}
   1.198 -
   1.199 -	// client must be the same as the previous token
   1.200 -	if !refreshData.Client.ID.Equal(client.ID) {
   1.201 -		ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token issued to another client.", ctx.Config.DocumentationDomain)
   1.202 -		return
   1.203 -	}
   1.204 -
   1.205 -	scope := r.Form.Get("scope")
   1.206 -	if scope == "" {
   1.207 -		scope = refreshData.Scope
   1.208 -	}
   1.209 -
   1.210 -	data := AccessData{
   1.211 -		AuthRequest: AuthRequest{
   1.212 -			Client: client,
   1.213 -			Scope:  scope,
   1.214 -		},
   1.215 -		Scope:              scope,
   1.216 -		PreviousAccessData: &refreshData,
   1.217 -	}
   1.218 -	err = fillTokens(&data, true, ctx)
   1.219 -	if err != nil {
   1.220 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.221 -		return
   1.222 -	}
   1.223 -	ctx.RenderJSONToken(w, data)
   1.224 -}
   1.225 -
   1.226 -func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.227 -	// get client authentication
   1.228 -	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
   1.229 -	if err != nil {
   1.230 -		ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   1.231 -		return
   1.232 -	}
   1.233 -
   1.234 -	username := r.Form.Get("username")
   1.235 -	password := r.Form.Get("password")
   1.236 -	scope := r.Form.Get("scope")
   1.237 -
   1.238 -	// "username" and "password" is required
   1.239 -	if username == "" || password == "" {
   1.240 -		ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing credentials.", ctx.Config.DocumentationDomain)
   1.241 -		return
   1.242 -	}
   1.243 -
   1.244 -	// must have a valid client
   1.245 -	client, err := getClient(auth, ctx)
   1.246 -	if err != nil {
   1.247 -		if err == ClientNotFoundError || err == InvalidClientError {
   1.248 -			ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   1.249 -			return
   1.250 -		}
   1.251 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.252 -		return
   1.253 -	}
   1.254 -
   1.255 -	_, err = ctx.Profiles.GetProfile(username, password)
   1.256 -	if err != nil {
   1.257 -		if err == ErrProfileNotFound {
   1.258 -			ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid credentials.", ctx.Config.DocumentationDomain)
   1.259 -			return
   1.260 -		}
   1.261 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.262 -		return
   1.263 -	}
   1.264 -
   1.265 -	data := AccessData{
   1.266 -		AuthRequest: AuthRequest{
   1.267 -			Client: client,
   1.268 -			Scope:  scope,
   1.269 -		},
   1.270 -		Scope: scope,
   1.271 -	}
   1.272 -
   1.273 -	err = fillTokens(&data, true, ctx)
   1.274 -	if err != nil {
   1.275 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.276 -		return
   1.277 -	}
   1.278 -	ctx.RenderJSONToken(w, data)
   1.279 -}
   1.280 -
   1.281 -func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.282 -	// get client authentication
   1.283 -	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
   1.284 -	if err != nil {
   1.285 -		ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   1.286 -		return
   1.287 -	}
   1.288 -
   1.289 -	scope := r.Form.Get("scope")
   1.290 -
   1.291 -	// must have a valid client
   1.292 -	client, err := getClient(auth, ctx)
   1.293 -	if err != nil {
   1.294 -		if err == ClientNotFoundError || err == InvalidClientError {
   1.295 -			ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   1.296 -			return
   1.297 -		}
   1.298 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.299 -		return
   1.300 -	}
   1.301 -
   1.302 -	data := AccessData{
   1.303 -		AuthRequest: AuthRequest{
   1.304 -			Client: client,
   1.305 -			Scope:  scope,
   1.306 -		},
   1.307 -		Scope: scope,
   1.308 -	}
   1.309 -
   1.310 -	err = fillTokens(&data, true, ctx)
   1.311 -	if err != nil {
   1.312 -		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.313 -		return
   1.314 -	}
   1.315 -	ctx.RenderJSONToken(w, data)
   1.316 -}
   1.317 -
   1.318 -func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error {
   1.319 -	var err error
   1.320 -
   1.321 -	// generate access token
   1.322 -	data.AccessToken = newToken()
   1.323 -	if includeRefresh {
   1.324 -		data.RefreshToken = newToken()
   1.325 -	}
   1.326 -
   1.327 -	// save access token
   1.328 -	err = ctx.Tokens.SaveAccess(*data)
   1.329 -	if err != nil {
   1.330 -		if ctx.Log != nil {
   1.331 -			ctx.Log.Printf("Error writing access token: %s\n", err)
   1.332 -		}
   1.333 -		return InternalServerError
   1.334 -	}
   1.335 -
   1.336 -	// remove authorization token
   1.337 -	if data.PreviousAuthorizeData != nil {
   1.338 -		err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code)
   1.339 -		if err != nil && ctx.Log != nil {
   1.340 -			ctx.Log.Printf("Error removing previous auth data (%s): %s\n", data.PreviousAuthorizeData.Code, err)
   1.341 -		}
   1.342 -	}
   1.343 -
   1.344 -	// remove previous access token
   1.345 -	if data.PreviousAccessData != nil {
   1.346 -		if data.PreviousAccessData.RefreshToken != "" {
   1.347 -			err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken)
   1.348 -			if err != nil && ctx.Log != nil {
   1.349 -				ctx.Log.Printf("Error removing previous refresh token (%s): %s\n", data.PreviousAccessData.RefreshToken, err)
   1.350 -			}
   1.351 -		}
   1.352 -		err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken)
   1.353 -		if err != nil && ctx.Log != nil {
   1.354 -			ctx.Log.Printf("Error removing previous access token (%s): %s\n", data.PreviousAccessData.AccessToken, err)
   1.355 -		}
   1.356 -	}
   1.357 -
   1.358 -	data.TokenType = ctx.Config.TokenType
   1.359 -	data.ExpiresIn = ctx.Config.AccessExpiration
   1.360 -	data.CreatedAt = time.Now()
   1.361 -	return nil
   1.362 -}
   1.363 -
   1.364 -func (data AccessData) GetRedirect(fragment bool) (string, error) {
   1.365 -	u, err := url.Parse(data.RedirectURI)
   1.366 -	if err != nil {
   1.367 -		return "", err
   1.368 -	}
   1.369 -
   1.370 -	// add parameters
   1.371 -	q := u.Query()
   1.372 -	q.Set("access_token", data.AccessToken)
   1.373 -	q.Set("token_type", data.TokenType)
   1.374 -	q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10))
   1.375 -	if data.RefreshToken != "" {
   1.376 -		q.Set("refresh_token", data.RefreshToken)
   1.377 -	}
   1.378 -	if data.Scope != "" {
   1.379 -		q.Set("scope", data.Scope)
   1.380 -	}
   1.381 -	if len(data.ProfileID) > 0 {
   1.382 -		q.Set("profile", data.ProfileID.String())
   1.383 -	}
   1.384 -	if fragment {
   1.385 -		u.RawQuery = ""
   1.386 -		u.Fragment = q.Encode()
   1.387 -	} else {
   1.388 -		u.RawQuery = q.Encode()
   1.389 -	}
   1.390 -
   1.391 -	return u.String(), nil
   1.392 -}
   1.393 -
   1.394 -// getClient looks up and authenticates the basic auth using the given
   1.395 -// storage. Sets an error on the response if auth fails or a server error occurs.
   1.396 -func getClient(auth BasicAuth, ctx Context) (Client, error) {
   1.397 -	id, err := uuid.Parse(auth.Username)
   1.398 -	if err != nil {
   1.399 -		return Client{}, err
   1.400 -	}
   1.401 -	client, err := ctx.Clients.GetClient(id)
   1.402 -	if err != nil {
   1.403 -		if err == ClientNotFoundError {
   1.404 -			return Client{}, err
   1.405 -		}
   1.406 -		if ctx.Log != nil {
   1.407 -			ctx.Log.Printf("Error retrieving client %s: %s", id, err)
   1.408 -		}
   1.409 -		return Client{}, InternalServerError
   1.410 -	}
   1.411 -	if client.Secret != auth.Password {
   1.412 -		return Client{}, InvalidClientError
   1.413 -	}
   1.414 -	if client.RedirectURI == "" {
   1.415 -		return Client{}, InvalidClientError
   1.416 -	}
   1.417 -	return client, nil
   1.418 -}
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/access.go.old	Mon Sep 01 09:13:52 2014 -0400
     2.3 @@ -0,0 +1,415 @@
     2.4 +package auth
     2.5 +
     2.6 +import (
     2.7 +	"net/http"
     2.8 +	"net/url"
     2.9 +	"time"
    2.10 +
    2.11 +	"strconv"
    2.12 +	"secondbit.org/uuid"
    2.13 +)
    2.14 +
    2.15 +// GrantType is the type for OAuth param `grant_type`
    2.16 +type GrantType string
    2.17 +
    2.18 +const (
    2.19 +	AuthorizationCodeGrant GrantType = "authorization_code"
    2.20 +	RefreshTokenGrant                = "refresh_token"
    2.21 +	PasswordGrant                    = "password"
    2.22 +	ClientCredentialsGrant           = "client_credentials"
    2.23 +)
    2.24 +
    2.25 +// AccessData represents an access grant (tokens, expiration, client, etc)
    2.26 +type AccessData struct {
    2.27 +	PreviousAuthorizeData *AuthorizeData `json:"-"`
    2.28 +	PreviousAccessData    *AccessData    `json:"-"` // previous access data, when refreshing
    2.29 +	AccessToken           string         `json:"access_token"`
    2.30 +	RefreshToken          string         `json:"refresh_token,omitempty"`
    2.31 +	ExpiresIn             int32          `json:"expires_in"`
    2.32 +	CreatedAt             time.Time      `json:"-"`
    2.33 +	TokenType             string         `json:"token_type"`
    2.34 +	Scope                 string         `json:"scope,omitempty"`
    2.35 +	ProfileID             uuid.ID        `json:"-"`
    2.36 +	AuthRequest           `json:"-"`
    2.37 +}
    2.38 +
    2.39 +// IsExpired returns true if access expired
    2.40 +func (d *AccessData) IsExpired() bool {
    2.41 +	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now())
    2.42 +}
    2.43 +
    2.44 +// ExpireAt returns the expiration date
    2.45 +func (d *AccessData) ExpireAt() time.Time {
    2.46 +	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
    2.47 +}
    2.48 +
    2.49 +// HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests.
    2.50 +func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    2.51 +	// Only allow GET or POST
    2.52 +	if r.Method != "POST" {
    2.53 +		if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest {
    2.54 +			ctx.RenderJSONError(w, ErrorInvalidRequest, "Invalid request method.", ctx.Config.DocumentationDomain)
    2.55 +			return
    2.56 +		}
    2.57 +	}
    2.58 +
    2.59 +	grantType := GrantType(r.Form.Get("grant_type"))
    2.60 +	if ctx.Config.AllowedAccessTypes.Exists(grantType) {
    2.61 +		switch grantType {
    2.62 +		case AuthorizationCodeGrant:
    2.63 +			handleAuthorizationCodeRequest(w, r, ctx)
    2.64 +		case RefreshTokenGrant:
    2.65 +			handleRefreshTokenRequest(w, r, ctx)
    2.66 +		case PasswordGrant:
    2.67 +			handlePasswordRequest(w, r, ctx)
    2.68 +		case ClientCredentialsGrant:
    2.69 +			handleClientCredentialsRequest(w, r, ctx)
    2.70 +		default:
    2.71 +			ctx.RenderJSONError(w, ErrorUnsupportedGrantType, "Unsupported grant type.", ctx.Config.DocumentationDomain)
    2.72 +			return
    2.73 +		}
    2.74 +	}
    2.75 +}
    2.76 +
    2.77 +func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    2.78 +	// get client authentication
    2.79 +	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
    2.80 +	if err != nil {
    2.81 +		ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
    2.82 +		return
    2.83 +	}
    2.84 +
    2.85 +	code := r.Form.Get("code")
    2.86 +	// "code" is required
    2.87 +	if code == "" {
    2.88 +		ctx.RenderJSONError(w, ErrorInvalidRequest, "Code must be supplied.", ctx.Config.DocumentationDomain)
    2.89 +		return
    2.90 +	}
    2.91 +
    2.92 +	// must have a valid client
    2.93 +	client, err := getClient(auth, ctx)
    2.94 +	if err != nil {
    2.95 +		if err == ClientNotFoundError || err == InvalidClientError {
    2.96 +			ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
    2.97 +			return
    2.98 +		}
    2.99 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.100 +		return
   2.101 +	}
   2.102 +
   2.103 +	// must be a valid authorization code
   2.104 +	authData, err := ctx.Tokens.GetAuthorization(code)
   2.105 +	if err != nil {
   2.106 +		if err == AuthorizationNotFoundError {
   2.107 +			ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid authorization.", ctx.Config.DocumentationDomain)
   2.108 +			return
   2.109 +		}
   2.110 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.111 +		return
   2.112 +	}
   2.113 +	if authData.RedirectURI == "" {
   2.114 +		ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid redirect on grant.", ctx.Config.DocumentationDomain)
   2.115 +		return
   2.116 +	}
   2.117 +	if authData.IsExpired() {
   2.118 +		ctx.RenderJSONError(w, ErrorInvalidGrant, "Authorization is expired.", ctx.Config.DocumentationDomain)
   2.119 +		return
   2.120 +	}
   2.121 +
   2.122 +	// code must be from the client
   2.123 +	if !authData.Client.ID.Equal(client.ID) {
   2.124 +		ctx.RenderJSONError(w, ErrorInvalidGrant, "Grant issued to another client.", ctx.Config.DocumentationDomain)
   2.125 +		return
   2.126 +	}
   2.127 +
   2.128 +	// check redirect uri
   2.129 +	redirectURI := r.Form.Get("redirect_uri")
   2.130 +	if redirectURI == "" {
   2.131 +		redirectURI = client.RedirectURI
   2.132 +	}
   2.133 +	if err = validateURI(client.RedirectURI, redirectURI); err != nil {
   2.134 +		ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match client.", ctx.Config.DocumentationDomain)
   2.135 +		return
   2.136 +	}
   2.137 +	if authData.RedirectURI != redirectURI {
   2.138 +		ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match auth redirect.", ctx.Config.DocumentationDomain)
   2.139 +		return
   2.140 +	}
   2.141 +
   2.142 +	data := AccessData{
   2.143 +		AuthRequest: AuthRequest{
   2.144 +			Client:      client,
   2.145 +			RedirectURI: redirectURI,
   2.146 +			Scope:       authData.Scope,
   2.147 +		},
   2.148 +		Scope: authData.Scope,
   2.149 +		PreviousAuthorizeData: &authData,
   2.150 +	}
   2.151 +
   2.152 +	err = fillTokens(&data, true, ctx)
   2.153 +	if err != nil {
   2.154 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.155 +		return
   2.156 +	}
   2.157 +	ctx.RenderJSONToken(w, data)
   2.158 +}
   2.159 +
   2.160 +func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   2.161 +	// get client authentication
   2.162 +	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
   2.163 +
   2.164 +	if err != nil {
   2.165 +		ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   2.166 +		return
   2.167 +	}
   2.168 +
   2.169 +	code := r.Form.Get("refresh_token")
   2.170 +
   2.171 +	// "refresh_token" is required
   2.172 +	if code == "" {
   2.173 +		ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing refresh token.", ctx.Config.DocumentationDomain)
   2.174 +		return
   2.175 +	}
   2.176 +
   2.177 +	// must have a valid client
   2.178 +	client, err := getClient(auth, ctx)
   2.179 +	if err != nil {
   2.180 +		if err == ClientNotFoundError || err == InvalidClientError {
   2.181 +			ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   2.182 +			return
   2.183 +		}
   2.184 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.185 +		return
   2.186 +	}
   2.187 +
   2.188 +	// must be a valid refresh code
   2.189 +	refreshData, err := ctx.Tokens.GetRefresh(code)
   2.190 +	if err != nil {
   2.191 +		if err == TokenNotFoundError {
   2.192 +			ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token not valid.", ctx.Config.DocumentationDomain)
   2.193 +			return
   2.194 +		}
   2.195 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.196 +		return
   2.197 +	}
   2.198 +
   2.199 +	// client must be the same as the previous token
   2.200 +	if !refreshData.Client.ID.Equal(client.ID) {
   2.201 +		ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token issued to another client.", ctx.Config.DocumentationDomain)
   2.202 +		return
   2.203 +	}
   2.204 +
   2.205 +	scope := r.Form.Get("scope")
   2.206 +	if scope == "" {
   2.207 +		scope = refreshData.Scope
   2.208 +	}
   2.209 +
   2.210 +	data := AccessData{
   2.211 +		AuthRequest: AuthRequest{
   2.212 +			Client: client,
   2.213 +			Scope:  scope,
   2.214 +		},
   2.215 +		Scope:              scope,
   2.216 +		PreviousAccessData: &refreshData,
   2.217 +	}
   2.218 +	err = fillTokens(&data, true, ctx)
   2.219 +	if err != nil {
   2.220 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.221 +		return
   2.222 +	}
   2.223 +	ctx.RenderJSONToken(w, data)
   2.224 +}
   2.225 +
   2.226 +func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   2.227 +	// get client authentication
   2.228 +	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
   2.229 +	if err != nil {
   2.230 +		ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   2.231 +		return
   2.232 +	}
   2.233 +
   2.234 +	username := r.Form.Get("username")
   2.235 +	password := r.Form.Get("password")
   2.236 +	scope := r.Form.Get("scope")
   2.237 +
   2.238 +	// "username" and "password" is required
   2.239 +	if username == "" || password == "" {
   2.240 +		ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing credentials.", ctx.Config.DocumentationDomain)
   2.241 +		return
   2.242 +	}
   2.243 +
   2.244 +	// must have a valid client
   2.245 +	client, err := getClient(auth, ctx)
   2.246 +	if err != nil {
   2.247 +		if err == ClientNotFoundError || err == InvalidClientError {
   2.248 +			ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   2.249 +			return
   2.250 +		}
   2.251 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.252 +		return
   2.253 +	}
   2.254 +
   2.255 +	_, err = ctx.Profiles.GetProfile(username, password)
   2.256 +	if err != nil {
   2.257 +		if err == ErrProfileNotFound {
   2.258 +			ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid credentials.", ctx.Config.DocumentationDomain)
   2.259 +			return
   2.260 +		}
   2.261 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.262 +		return
   2.263 +	}
   2.264 +
   2.265 +	data := AccessData{
   2.266 +		AuthRequest: AuthRequest{
   2.267 +			Client: client,
   2.268 +			Scope:  scope,
   2.269 +		},
   2.270 +		Scope: scope,
   2.271 +	}
   2.272 +
   2.273 +	err = fillTokens(&data, true, ctx)
   2.274 +	if err != nil {
   2.275 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.276 +		return
   2.277 +	}
   2.278 +	ctx.RenderJSONToken(w, data)
   2.279 +}
   2.280 +
   2.281 +func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   2.282 +	// get client authentication
   2.283 +	auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
   2.284 +	if err != nil {
   2.285 +		ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   2.286 +		return
   2.287 +	}
   2.288 +
   2.289 +	scope := r.Form.Get("scope")
   2.290 +
   2.291 +	// must have a valid client
   2.292 +	client, err := getClient(auth, ctx)
   2.293 +	if err != nil {
   2.294 +		if err == ClientNotFoundError || err == InvalidClientError {
   2.295 +			ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain)
   2.296 +			return
   2.297 +		}
   2.298 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.299 +		return
   2.300 +	}
   2.301 +
   2.302 +	data := AccessData{
   2.303 +		AuthRequest: AuthRequest{
   2.304 +			Client: client,
   2.305 +			Scope:  scope,
   2.306 +		},
   2.307 +		Scope: scope,
   2.308 +	}
   2.309 +
   2.310 +	err = fillTokens(&data, true, ctx)
   2.311 +	if err != nil {
   2.312 +		ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   2.313 +		return
   2.314 +	}
   2.315 +	ctx.RenderJSONToken(w, data)
   2.316 +}
   2.317 +
   2.318 +func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error {
   2.319 +	var err error
   2.320 +
   2.321 +	// generate access token
   2.322 +	data.AccessToken = newToken()
   2.323 +	if includeRefresh {
   2.324 +		data.RefreshToken = newToken()
   2.325 +	}
   2.326 +
   2.327 +	// save access token
   2.328 +	err = ctx.Tokens.SaveAccess(*data)
   2.329 +	if err != nil {
   2.330 +		if ctx.Log != nil {
   2.331 +			ctx.Log.Printf("Error writing access token: %s\n", err)
   2.332 +		}
   2.333 +		return InternalServerError
   2.334 +	}
   2.335 +
   2.336 +	// remove authorization token
   2.337 +	if data.PreviousAuthorizeData != nil {
   2.338 +		err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code)
   2.339 +		if err != nil && ctx.Log != nil {
   2.340 +			ctx.Log.Printf("Error removing previous auth data (%s): %s\n", data.PreviousAuthorizeData.Code, err)
   2.341 +		}
   2.342 +	}
   2.343 +
   2.344 +	// remove previous access token
   2.345 +	if data.PreviousAccessData != nil {
   2.346 +		if data.PreviousAccessData.RefreshToken != "" {
   2.347 +			err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken)
   2.348 +			if err != nil && ctx.Log != nil {
   2.349 +				ctx.Log.Printf("Error removing previous refresh token (%s): %s\n", data.PreviousAccessData.RefreshToken, err)
   2.350 +			}
   2.351 +		}
   2.352 +		err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken)
   2.353 +		if err != nil && ctx.Log != nil {
   2.354 +			ctx.Log.Printf("Error removing previous access token (%s): %s\n", data.PreviousAccessData.AccessToken, err)
   2.355 +		}
   2.356 +	}
   2.357 +
   2.358 +	data.TokenType = ctx.Config.TokenType
   2.359 +	data.ExpiresIn = ctx.Config.AccessExpiration
   2.360 +	data.CreatedAt = time.Now()
   2.361 +	return nil
   2.362 +}
   2.363 +
   2.364 +func (data AccessData) GetRedirect(fragment bool) (string, error) {
   2.365 +	u, err := url.Parse(data.RedirectURI)
   2.366 +	if err != nil {
   2.367 +		return "", err
   2.368 +	}
   2.369 +
   2.370 +	// add parameters
   2.371 +	q := u.Query()
   2.372 +	q.Set("access_token", data.AccessToken)
   2.373 +	q.Set("token_type", data.TokenType)
   2.374 +	q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10))
   2.375 +	if data.RefreshToken != "" {
   2.376 +		q.Set("refresh_token", data.RefreshToken)
   2.377 +	}
   2.378 +	if data.Scope != "" {
   2.379 +		q.Set("scope", data.Scope)
   2.380 +	}
   2.381 +	if len(data.ProfileID) > 0 {
   2.382 +		q.Set("profile", data.ProfileID.String())
   2.383 +	}
   2.384 +	if fragment {
   2.385 +		u.RawQuery = ""
   2.386 +		u.Fragment = q.Encode()
   2.387 +	} else {
   2.388 +		u.RawQuery = q.Encode()
   2.389 +	}
   2.390 +
   2.391 +	return u.String(), nil
   2.392 +}
   2.393 +
   2.394 +// getClient looks up and authenticates the basic auth using the given
   2.395 +// storage. Sets an error on the response if auth fails or a server error occurs.
   2.396 +func getClient(auth BasicAuth, ctx Context) (Client, error) {
   2.397 +	id, err := uuid.Parse(auth.Username)
   2.398 +	if err != nil {
   2.399 +		return Client{}, err
   2.400 +	}
   2.401 +	client, err := ctx.Clients.GetClient(id)
   2.402 +	if err != nil {
   2.403 +		if err == ClientNotFoundError {
   2.404 +			return Client{}, err
   2.405 +		}
   2.406 +		if ctx.Log != nil {
   2.407 +			ctx.Log.Printf("Error retrieving client %s: %s", id, err)
   2.408 +		}
   2.409 +		return Client{}, InternalServerError
   2.410 +	}
   2.411 +	if client.Secret != auth.Password {
   2.412 +		return Client{}, InvalidClientError
   2.413 +	}
   2.414 +	if client.RedirectURI == "" {
   2.415 +		return Client{}, InvalidClientError
   2.416 +	}
   2.417 +	return client, nil
   2.418 +}
     3.1 --- a/authorize.go	Sat Aug 16 20:49:19 2014 -0400
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,272 +0,0 @@
     3.4 -package auth
     3.5 -
     3.6 -import (
     3.7 -	"net/http"
     3.8 -	"net/url"
     3.9 -	"time"
    3.10 -
    3.11 -	"strings"
    3.12 -	"secondbit.org/uuid"
    3.13 -)
    3.14 -
    3.15 -// AuthorizeRequestType is the type for OAuth param `response_type`
    3.16 -type AuthorizeRequestType string
    3.17 -
    3.18 -const (
    3.19 -	CodeAuthRT  AuthorizeRequestType = "code"
    3.20 -	TokenAuthRT                      = "token"
    3.21 -)
    3.22 -
    3.23 -type AuthRequest struct {
    3.24 -	Client      Client
    3.25 -	Scope       string
    3.26 -	RedirectURI string
    3.27 -	State       string
    3.28 -}
    3.29 -
    3.30 -// Authorization data
    3.31 -type AuthorizeData struct {
    3.32 -	// Authorization code
    3.33 -	Code string
    3.34 -
    3.35 -	// Token expiration in seconds
    3.36 -	ExpiresIn int32
    3.37 -
    3.38 -	// Date created
    3.39 -	CreatedAt time.Time
    3.40 -
    3.41 -	ProfileID uuid.ID
    3.42 -
    3.43 -	AuthRequest
    3.44 -}
    3.45 -
    3.46 -// IsExpired is true if authorization expired
    3.47 -func (d *AuthorizeData) IsExpired() bool {
    3.48 -	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now())
    3.49 -}
    3.50 -
    3.51 -// ExpireAt returns the expiration date
    3.52 -func (d *AuthorizeData) ExpireAt() time.Time {
    3.53 -	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
    3.54 -}
    3.55 -
    3.56 -// HandleAuthorizeRequest is the main http.HandlerFunc for handling
    3.57 -// authorization requests
    3.58 -func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    3.59 -	r.ParseForm()
    3.60 -	// create the authorization request
    3.61 -	redirectURI := r.Form.Get("redirect_uri")
    3.62 -	var err error
    3.63 -	if redirectURI != "" {
    3.64 -		redirectURI, err = url.QueryUnescape(redirectURI)
    3.65 -		if err != nil {
    3.66 -			ctx.RenderError(w, URIFormatError(redirectURI))
    3.67 -			return
    3.68 -		}
    3.69 -	}
    3.70 -
    3.71 -	state := r.Form.Get("state")
    3.72 -	scope := r.Form.Get("scope")
    3.73 -
    3.74 -	// must have a valid client
    3.75 -	id, err := uuid.Parse(r.Form.Get("client_id"))
    3.76 -	if err != nil {
    3.77 -		ctx.RenderError(w, InvalidClientIDError(r.Form.Get("client_id")))
    3.78 -		return
    3.79 -	}
    3.80 -	client, err := GetClient(id, ctx)
    3.81 -	if err != nil {
    3.82 -		if err == ClientNotFoundError {
    3.83 -			ctx.RenderError(w, ClientNotFoundError)
    3.84 -			return
    3.85 -		}
    3.86 -		if redirectURI == "" {
    3.87 -			ctx.RenderError(w, URIMissingError)
    3.88 -			return
    3.89 -		}
    3.90 -		req := AuthRequest{
    3.91 -			RedirectURI: redirectURI,
    3.92 -			Scope:       scope,
    3.93 -			State:       state,
    3.94 -		}
    3.95 -		redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
    3.96 -		if err != nil {
    3.97 -			ctx.RenderError(w, URIFormatError(redirectURI))
    3.98 -			return
    3.99 -		}
   3.100 -		http.Redirect(w, r, redir, http.StatusFound)
   3.101 -		return
   3.102 -	}
   3.103 -	if client.RedirectURI == "" {
   3.104 -		ctx.RenderError(w, URIMissingError)
   3.105 -		return
   3.106 -	}
   3.107 -
   3.108 -	// check redirect uri
   3.109 -	if redirectURI == "" {
   3.110 -		redirectURI = client.RedirectURI
   3.111 -	}
   3.112 -	if err = validateURI(client.RedirectURI, redirectURI); err != nil {
   3.113 -		ctx.RenderError(w, NewURIMismatchError(client.RedirectURI, redirectURI))
   3.114 -		return
   3.115 -	}
   3.116 -
   3.117 -	req := AuthRequest{
   3.118 -		Client:      client,
   3.119 -		RedirectURI: redirectURI,
   3.120 -		Scope:       scope,
   3.121 -		State:       state,
   3.122 -	}
   3.123 -
   3.124 -	requestType := AuthorizeRequestType(r.Form.Get("response_type"))
   3.125 -	if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) {
   3.126 -		switch requestType {
   3.127 -		case CodeAuthRT:
   3.128 -			req.handleCodeRequest(w, r, ctx)
   3.129 -			return
   3.130 -		case TokenAuthRT:
   3.131 -			req.handleTokenRequest(w, r, ctx)
   3.132 -			return
   3.133 -		}
   3.134 -	}
   3.135 -	redir, err := req.GetErrorRedirect(ErrorInvalidRequest, "Invalid response type.", ctx.Config.DocumentationDomain)
   3.136 -	if err != nil {
   3.137 -		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.138 -		return
   3.139 -	}
   3.140 -	http.Redirect(w, r, redir, http.StatusFound)
   3.141 -}
   3.142 -
   3.143 -func (req AuthRequest) handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   3.144 -	if r.Method != "GET" && r.Method != "POST" {
   3.145 -		ctx.RenderError(w, InvalidMethodError)
   3.146 -		return
   3.147 -	}
   3.148 -
   3.149 -	if err := validateSession(r, ctx); err == ErrorNotAuthenticated {
   3.150 -		http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound)
   3.151 -		return
   3.152 -	} else if err != nil {
   3.153 -		ctx.RenderError(w, err)
   3.154 -		return
   3.155 -	}
   3.156 -
   3.157 -	if r.Method == "GET" {
   3.158 -		ctx.RenderConfirmation(w, r, req)
   3.159 -		return
   3.160 -	}
   3.161 -
   3.162 -	if r.FormValue("approved") != "true" {
   3.163 -		redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
   3.164 -		if err != nil {
   3.165 -			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.166 -			return
   3.167 -		}
   3.168 -		http.Redirect(w, r, redir, http.StatusFound)
   3.169 -		return
   3.170 -	}
   3.171 -
   3.172 -	data := AuthorizeData{AuthRequest: req}
   3.173 -
   3.174 -	data.ExpiresIn = ctx.Config.AuthorizationExpiration
   3.175 -	data.Code = newToken()
   3.176 -	data.CreatedAt = time.Now()
   3.177 -
   3.178 -	err := ctx.Tokens.SaveAuthorization(data)
   3.179 -	if err != nil {
   3.180 -		redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   3.181 -		if err != nil {
   3.182 -			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.183 -			return
   3.184 -		}
   3.185 -		http.Redirect(w, r, redir, http.StatusFound)
   3.186 -		return
   3.187 -	}
   3.188 -
   3.189 -	redir, err := data.GetRedirect()
   3.190 -	if err != nil {
   3.191 -		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.192 -		return
   3.193 -	}
   3.194 -	http.Redirect(w, r, redir, http.StatusFound)
   3.195 -}
   3.196 -
   3.197 -func (req AuthRequest) handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   3.198 -	if r.Method != "GET" && r.Method != "POST" {
   3.199 -		ctx.RenderError(w, InvalidMethodError)
   3.200 -		return
   3.201 -	}
   3.202 -
   3.203 -	if err := validateSession(r, ctx); err == ErrorNotAuthenticated {
   3.204 -		http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound)
   3.205 -		return
   3.206 -	} else if err != nil {
   3.207 -		ctx.RenderError(w, err)
   3.208 -		return
   3.209 -	}
   3.210 -
   3.211 -	if r.Method == "GET" {
   3.212 -		ctx.RenderConfirmation(w, r, req)
   3.213 -		return
   3.214 -	}
   3.215 -
   3.216 -	if r.FormValue("approved") != "true" {
   3.217 -		redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
   3.218 -		if err != nil {
   3.219 -			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.220 -			return
   3.221 -		}
   3.222 -		http.Redirect(w, r, redir, http.StatusFound)
   3.223 -		return
   3.224 -	}
   3.225 -
   3.226 -	data := AccessData{AuthRequest: req}
   3.227 -
   3.228 -	err := fillTokens(&data, false, ctx)
   3.229 -	if err != nil {
   3.230 -		ctx.RenderError(w, InternalServerError)
   3.231 -		return
   3.232 -	}
   3.233 -
   3.234 -	redir, err := data.GetRedirect(true)
   3.235 -	if err != nil {
   3.236 -		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   3.237 -		return
   3.238 -	}
   3.239 -	http.Redirect(w, r, redir, http.StatusFound)
   3.240 -}
   3.241 -
   3.242 -func (data AuthorizeData) GetRedirect() (string, error) {
   3.243 -	u, err := url.Parse(data.RedirectURI)
   3.244 -	if err != nil {
   3.245 -		return "", err
   3.246 -	}
   3.247 -
   3.248 -	// add parameters
   3.249 -	q := u.Query()
   3.250 -	q.Set("code", data.Code)
   3.251 -	q.Set("state", data.State)
   3.252 -	u.RawQuery = q.Encode()
   3.253 -
   3.254 -	return u.String(), nil
   3.255 -}
   3.256 -
   3.257 -func (req AuthRequest) GetErrorRedirect(code, description, uriBase string) (string, error) {
   3.258 -	u, err := url.Parse(req.RedirectURI)
   3.259 -	if err != nil {
   3.260 -		return "", err
   3.261 -	}
   3.262 -
   3.263 -	// add parameters
   3.264 -	q := u.Query()
   3.265 -	q.Set("error", code)
   3.266 -	q.Set("error_description", description)
   3.267 -	q.Set("error_uri", strings.Join([]string{
   3.268 -		strings.TrimRight(uriBase, "/"),
   3.269 -		strings.TrimLeft(code, "/"),
   3.270 -	}, "/"))
   3.271 -	q.Set("state", req.State)
   3.272 -	u.RawQuery = q.Encode()
   3.273 -
   3.274 -	return u.String(), nil
   3.275 -}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/authorize.go.old	Mon Sep 01 09:13:52 2014 -0400
     4.3 @@ -0,0 +1,272 @@
     4.4 +package auth
     4.5 +
     4.6 +import (
     4.7 +	"net/http"
     4.8 +	"net/url"
     4.9 +	"time"
    4.10 +
    4.11 +	"strings"
    4.12 +	"secondbit.org/uuid"
    4.13 +)
    4.14 +
    4.15 +// AuthorizeRequestType is the type for OAuth param `response_type`
    4.16 +type AuthorizeRequestType string
    4.17 +
    4.18 +const (
    4.19 +	CodeAuthRT  AuthorizeRequestType = "code"
    4.20 +	TokenAuthRT                      = "token"
    4.21 +)
    4.22 +
    4.23 +type AuthRequest struct {
    4.24 +	Client      Client
    4.25 +	Scope       string
    4.26 +	RedirectURI string
    4.27 +	State       string
    4.28 +}
    4.29 +
    4.30 +// Authorization data
    4.31 +type AuthorizeData struct {
    4.32 +	// Authorization code
    4.33 +	Code string
    4.34 +
    4.35 +	// Token expiration in seconds
    4.36 +	ExpiresIn int32
    4.37 +
    4.38 +	// Date created
    4.39 +	CreatedAt time.Time
    4.40 +
    4.41 +	ProfileID uuid.ID
    4.42 +
    4.43 +	AuthRequest
    4.44 +}
    4.45 +
    4.46 +// IsExpired is true if authorization expired
    4.47 +func (d *AuthorizeData) IsExpired() bool {
    4.48 +	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now())
    4.49 +}
    4.50 +
    4.51 +// ExpireAt returns the expiration date
    4.52 +func (d *AuthorizeData) ExpireAt() time.Time {
    4.53 +	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
    4.54 +}
    4.55 +
    4.56 +// HandleAuthorizeRequest is the main http.HandlerFunc for handling
    4.57 +// authorization requests
    4.58 +func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    4.59 +	r.ParseForm()
    4.60 +	// create the authorization request
    4.61 +	redirectURI := r.Form.Get("redirect_uri")
    4.62 +	var err error
    4.63 +	if redirectURI != "" {
    4.64 +		redirectURI, err = url.QueryUnescape(redirectURI)
    4.65 +		if err != nil {
    4.66 +			ctx.RenderError(w, URIFormatError(redirectURI))
    4.67 +			return
    4.68 +		}
    4.69 +	}
    4.70 +
    4.71 +	state := r.Form.Get("state")
    4.72 +	scope := r.Form.Get("scope")
    4.73 +
    4.74 +	// must have a valid client
    4.75 +	id, err := uuid.Parse(r.Form.Get("client_id"))
    4.76 +	if err != nil {
    4.77 +		ctx.RenderError(w, InvalidClientIDError(r.Form.Get("client_id")))
    4.78 +		return
    4.79 +	}
    4.80 +	client, err := GetClient(id, ctx)
    4.81 +	if err != nil {
    4.82 +		if err == ClientNotFoundError {
    4.83 +			ctx.RenderError(w, ClientNotFoundError)
    4.84 +			return
    4.85 +		}
    4.86 +		if redirectURI == "" {
    4.87 +			ctx.RenderError(w, URIMissingError)
    4.88 +			return
    4.89 +		}
    4.90 +		req := AuthRequest{
    4.91 +			RedirectURI: redirectURI,
    4.92 +			Scope:       scope,
    4.93 +			State:       state,
    4.94 +		}
    4.95 +		redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
    4.96 +		if err != nil {
    4.97 +			ctx.RenderError(w, URIFormatError(redirectURI))
    4.98 +			return
    4.99 +		}
   4.100 +		http.Redirect(w, r, redir, http.StatusFound)
   4.101 +		return
   4.102 +	}
   4.103 +	if client.RedirectURI == "" {
   4.104 +		ctx.RenderError(w, URIMissingError)
   4.105 +		return
   4.106 +	}
   4.107 +
   4.108 +	// check redirect uri
   4.109 +	if redirectURI == "" {
   4.110 +		redirectURI = client.RedirectURI
   4.111 +	}
   4.112 +	if err = validateURI(client.RedirectURI, redirectURI); err != nil {
   4.113 +		ctx.RenderError(w, NewURIMismatchError(client.RedirectURI, redirectURI))
   4.114 +		return
   4.115 +	}
   4.116 +
   4.117 +	req := AuthRequest{
   4.118 +		Client:      client,
   4.119 +		RedirectURI: redirectURI,
   4.120 +		Scope:       scope,
   4.121 +		State:       state,
   4.122 +	}
   4.123 +
   4.124 +	requestType := AuthorizeRequestType(r.Form.Get("response_type"))
   4.125 +	if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) {
   4.126 +		switch requestType {
   4.127 +		case CodeAuthRT:
   4.128 +			req.handleCodeRequest(w, r, ctx)
   4.129 +			return
   4.130 +		case TokenAuthRT:
   4.131 +			req.handleTokenRequest(w, r, ctx)
   4.132 +			return
   4.133 +		}
   4.134 +	}
   4.135 +	redir, err := req.GetErrorRedirect(ErrorInvalidRequest, "Invalid response type.", ctx.Config.DocumentationDomain)
   4.136 +	if err != nil {
   4.137 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   4.138 +		return
   4.139 +	}
   4.140 +	http.Redirect(w, r, redir, http.StatusFound)
   4.141 +}
   4.142 +
   4.143 +func (req AuthRequest) handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   4.144 +	if r.Method != "GET" && r.Method != "POST" {
   4.145 +		ctx.RenderError(w, InvalidMethodError)
   4.146 +		return
   4.147 +	}
   4.148 +
   4.149 +	if err := validateSession(r, ctx); err == ErrorNotAuthenticated {
   4.150 +		http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound)
   4.151 +		return
   4.152 +	} else if err != nil {
   4.153 +		ctx.RenderError(w, err)
   4.154 +		return
   4.155 +	}
   4.156 +
   4.157 +	if r.Method == "GET" {
   4.158 +		ctx.RenderConfirmation(w, r, req)
   4.159 +		return
   4.160 +	}
   4.161 +
   4.162 +	if r.FormValue("approved") != "true" {
   4.163 +		redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
   4.164 +		if err != nil {
   4.165 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   4.166 +			return
   4.167 +		}
   4.168 +		http.Redirect(w, r, redir, http.StatusFound)
   4.169 +		return
   4.170 +	}
   4.171 +
   4.172 +	data := AuthorizeData{AuthRequest: req}
   4.173 +
   4.174 +	data.ExpiresIn = ctx.Config.AuthorizationExpiration
   4.175 +	data.Code = newToken()
   4.176 +	data.CreatedAt = time.Now()
   4.177 +
   4.178 +	err := ctx.Tokens.SaveAuthorization(data)
   4.179 +	if err != nil {
   4.180 +		redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   4.181 +		if err != nil {
   4.182 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   4.183 +			return
   4.184 +		}
   4.185 +		http.Redirect(w, r, redir, http.StatusFound)
   4.186 +		return
   4.187 +	}
   4.188 +
   4.189 +	redir, err := data.GetRedirect()
   4.190 +	if err != nil {
   4.191 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   4.192 +		return
   4.193 +	}
   4.194 +	http.Redirect(w, r, redir, http.StatusFound)
   4.195 +}
   4.196 +
   4.197 +func (req AuthRequest) handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   4.198 +	if r.Method != "GET" && r.Method != "POST" {
   4.199 +		ctx.RenderError(w, InvalidMethodError)
   4.200 +		return
   4.201 +	}
   4.202 +
   4.203 +	if err := validateSession(r, ctx); err == ErrorNotAuthenticated {
   4.204 +		http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound)
   4.205 +		return
   4.206 +	} else if err != nil {
   4.207 +		ctx.RenderError(w, err)
   4.208 +		return
   4.209 +	}
   4.210 +
   4.211 +	if r.Method == "GET" {
   4.212 +		ctx.RenderConfirmation(w, r, req)
   4.213 +		return
   4.214 +	}
   4.215 +
   4.216 +	if r.FormValue("approved") != "true" {
   4.217 +		redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
   4.218 +		if err != nil {
   4.219 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   4.220 +			return
   4.221 +		}
   4.222 +		http.Redirect(w, r, redir, http.StatusFound)
   4.223 +		return
   4.224 +	}
   4.225 +
   4.226 +	data := AccessData{AuthRequest: req}
   4.227 +
   4.228 +	err := fillTokens(&data, false, ctx)
   4.229 +	if err != nil {
   4.230 +		ctx.RenderError(w, InternalServerError)
   4.231 +		return
   4.232 +	}
   4.233 +
   4.234 +	redir, err := data.GetRedirect(true)
   4.235 +	if err != nil {
   4.236 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   4.237 +		return
   4.238 +	}
   4.239 +	http.Redirect(w, r, redir, http.StatusFound)
   4.240 +}
   4.241 +
   4.242 +func (data AuthorizeData) GetRedirect() (string, error) {
   4.243 +	u, err := url.Parse(data.RedirectURI)
   4.244 +	if err != nil {
   4.245 +		return "", err
   4.246 +	}
   4.247 +
   4.248 +	// add parameters
   4.249 +	q := u.Query()
   4.250 +	q.Set("code", data.Code)
   4.251 +	q.Set("state", data.State)
   4.252 +	u.RawQuery = q.Encode()
   4.253 +
   4.254 +	return u.String(), nil
   4.255 +}
   4.256 +
   4.257 +func (req AuthRequest) GetErrorRedirect(code, description, uriBase string) (string, error) {
   4.258 +	u, err := url.Parse(req.RedirectURI)
   4.259 +	if err != nil {
   4.260 +		return "", err
   4.261 +	}
   4.262 +
   4.263 +	// add parameters
   4.264 +	q := u.Query()
   4.265 +	q.Set("error", code)
   4.266 +	q.Set("error_description", description)
   4.267 +	q.Set("error_uri", strings.Join([]string{
   4.268 +		strings.TrimRight(uriBase, "/"),
   4.269 +		strings.TrimLeft(code, "/"),
   4.270 +	}, "/"))
   4.271 +	q.Set("state", req.State)
   4.272 +	u.RawQuery = q.Encode()
   4.273 +
   4.274 +	return u.String(), nil
   4.275 +}
     5.1 --- a/config.go	Sat Aug 16 20:49:19 2014 -0400
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,79 +0,0 @@
     5.4 -package auth
     5.5 -
     5.6 -import "time"
     5.7 -
     5.8 -// AllowedAuthorizeType is a collection of allowed auth request types
     5.9 -type AllowedAuthorizeType []AuthorizeRequestType
    5.10 -
    5.11 -// Exists returns true if the auth type exists in the list
    5.12 -func (t AllowedAuthorizeType) Exists(rt AuthorizeRequestType) bool {
    5.13 -	for _, k := range t {
    5.14 -		if k == rt {
    5.15 -			return true
    5.16 -		}
    5.17 -	}
    5.18 -	return false
    5.19 -}
    5.20 -
    5.21 -// AllowedAccessType is a collection of allowed access request types
    5.22 -type AllowedAccessType []GrantType
    5.23 -
    5.24 -// Exists returns true if the access type exists in the list
    5.25 -func (t AllowedAccessType) Exists(rt GrantType) bool {
    5.26 -	for _, k := range t {
    5.27 -		if k == rt {
    5.28 -			return true
    5.29 -		}
    5.30 -	}
    5.31 -	return false
    5.32 -}
    5.33 -
    5.34 -// ServerConfig contains server configuration information
    5.35 -type ServerConfig struct {
    5.36 -	// Authorization token expiration in seconds (default 5 minutes)
    5.37 -	AuthorizationExpiration int32
    5.38 -
    5.39 -	// Access token expiration in seconds (default 1 hour)
    5.40 -	AccessExpiration int32
    5.41 -
    5.42 -	// Token type to return
    5.43 -	TokenType string
    5.44 -
    5.45 -	// List of allowed authorize types (only CodeAuthRT by default)
    5.46 -	AllowedAuthorizeTypes AllowedAuthorizeType
    5.47 -
    5.48 -	// List of allowed access types (only AUTHORIZATION_CodeAuthRT by default)
    5.49 -	AllowedAccessTypes AllowedAccessType
    5.50 -
    5.51 -	// HTTP status code to return for errors - default 200
    5.52 -	// Only used if response was created from server
    5.53 -	ErrorStatusCode int
    5.54 -
    5.55 -	// If true allows client secret also in params, else only in
    5.56 -	// Authorization header - default false
    5.57 -	AllowClientSecretInParams bool
    5.58 -
    5.59 -	// If true allows access request using GET, else only POST - default false
    5.60 -	AllowGetAccessRequest bool
    5.61 -
    5.62 -	// The base path of documentation
    5.63 -	DocumentationDomain string
    5.64 -
    5.65 -	SessionLength       time.Duration
    5.66 -	RequestIPHeader     string
    5.67 -	LoginRedirectDomain string
    5.68 -}
    5.69 -
    5.70 -// NewServerConfig returns a new ServerConfig with default configuration
    5.71 -func NewServerConfig() ServerConfig {
    5.72 -	return ServerConfig{
    5.73 -		AuthorizationExpiration:   250,
    5.74 -		AccessExpiration:          3600,
    5.75 -		TokenType:                 "bearer",
    5.76 -		AllowedAuthorizeTypes:     AllowedAuthorizeType{CodeAuthRT},
    5.77 -		AllowedAccessTypes:        AllowedAccessType{AuthorizationCodeGrant},
    5.78 -		ErrorStatusCode:           200,
    5.79 -		AllowClientSecretInParams: false,
    5.80 -		AllowGetAccessRequest:     false,
    5.81 -	}
    5.82 -}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/config.go.old	Mon Sep 01 09:13:52 2014 -0400
     6.3 @@ -0,0 +1,79 @@
     6.4 +package auth
     6.5 +
     6.6 +import "time"
     6.7 +
     6.8 +// AllowedAuthorizeType is a collection of allowed auth request types
     6.9 +type AllowedAuthorizeType []AuthorizeRequestType
    6.10 +
    6.11 +// Exists returns true if the auth type exists in the list
    6.12 +func (t AllowedAuthorizeType) Exists(rt AuthorizeRequestType) bool {
    6.13 +	for _, k := range t {
    6.14 +		if k == rt {
    6.15 +			return true
    6.16 +		}
    6.17 +	}
    6.18 +	return false
    6.19 +}
    6.20 +
    6.21 +// AllowedAccessType is a collection of allowed access request types
    6.22 +type AllowedAccessType []GrantType
    6.23 +
    6.24 +// Exists returns true if the access type exists in the list
    6.25 +func (t AllowedAccessType) Exists(rt GrantType) bool {
    6.26 +	for _, k := range t {
    6.27 +		if k == rt {
    6.28 +			return true
    6.29 +		}
    6.30 +	}
    6.31 +	return false
    6.32 +}
    6.33 +
    6.34 +// ServerConfig contains server configuration information
    6.35 +type ServerConfig struct {
    6.36 +	// Authorization token expiration in seconds (default 5 minutes)
    6.37 +	AuthorizationExpiration int32
    6.38 +
    6.39 +	// Access token expiration in seconds (default 1 hour)
    6.40 +	AccessExpiration int32
    6.41 +
    6.42 +	// Token type to return
    6.43 +	TokenType string
    6.44 +
    6.45 +	// List of allowed authorize types (only CodeAuthRT by default)
    6.46 +	AllowedAuthorizeTypes AllowedAuthorizeType
    6.47 +
    6.48 +	// List of allowed access types (only AUTHORIZATION_CodeAuthRT by default)
    6.49 +	AllowedAccessTypes AllowedAccessType
    6.50 +
    6.51 +	// HTTP status code to return for errors - default 200
    6.52 +	// Only used if response was created from server
    6.53 +	ErrorStatusCode int
    6.54 +
    6.55 +	// If true allows client secret also in params, else only in
    6.56 +	// Authorization header - default false
    6.57 +	AllowClientSecretInParams bool
    6.58 +
    6.59 +	// If true allows access request using GET, else only POST - default false
    6.60 +	AllowGetAccessRequest bool
    6.61 +
    6.62 +	// The base path of documentation
    6.63 +	DocumentationDomain string
    6.64 +
    6.65 +	SessionLength       time.Duration
    6.66 +	RequestIPHeader     string
    6.67 +	LoginRedirectDomain string
    6.68 +}
    6.69 +
    6.70 +// NewServerConfig returns a new ServerConfig with default configuration
    6.71 +func NewServerConfig() ServerConfig {
    6.72 +	return ServerConfig{
    6.73 +		AuthorizationExpiration:   250,
    6.74 +		AccessExpiration:          3600,
    6.75 +		TokenType:                 "bearer",
    6.76 +		AllowedAuthorizeTypes:     AllowedAuthorizeType{CodeAuthRT},
    6.77 +		AllowedAccessTypes:        AllowedAccessType{AuthorizationCodeGrant},
    6.78 +		ErrorStatusCode:           200,
    6.79 +		AllowClientSecretInParams: false,
    6.80 +		AllowGetAccessRequest:     false,
    6.81 +	}
    6.82 +}
     7.1 --- a/context.go	Sat Aug 16 20:49:19 2014 -0400
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,108 +0,0 @@
     7.4 -package auth
     7.5 -
     7.6 -import (
     7.7 -	"encoding/json"
     7.8 -	"html/template"
     7.9 -	"io"
    7.10 -	"log"
    7.11 -	"net/http"
    7.12 -
    7.13 -	"github.com/justinas/nosurf"
    7.14 -)
    7.15 -
    7.16 -type Context struct {
    7.17 -	Config    ServerConfig
    7.18 -	Clients   ClientStore
    7.19 -	Tokens    TokenStore
    7.20 -	Profiles  ProfileStore
    7.21 -	Sessions  SessionStore
    7.22 -	Log       *log.Logger
    7.23 -	Templates Templates
    7.24 -}
    7.25 -
    7.26 -type Templates struct {
    7.27 -	Error        *template.Template
    7.28 -	Confirmation *template.Template
    7.29 -	Login        *template.Template
    7.30 -}
    7.31 -
    7.32 -type jsonError struct {
    7.33 -	Error       string `json:"error,omitempty"`
    7.34 -	Description string `json:"error_description,omitempty"`
    7.35 -	URI         string `json:"error_uri,omitempty"`
    7.36 -	State       string `json:"state,omitempty"`
    7.37 -}
    7.38 -
    7.39 -func (c Context) RenderError(w io.Writer, err error) {
    7.40 -	if c.Templates.Error == nil {
    7.41 -		log.Println("Error template is nil, can't render error.")
    7.42 -		return
    7.43 -	}
    7.44 -	renderErr := c.Templates.Error.Execute(w, map[string]interface{}{
    7.45 -		"err": err,
    7.46 -	})
    7.47 -	if renderErr != nil {
    7.48 -		log.Printf("Error executing error template (oh, the irony): %s\n", renderErr)
    7.49 -		return
    7.50 -	}
    7.51 -}
    7.52 -
    7.53 -func (c Context) RenderJSONError(w io.Writer, code, description, baseURI string) {
    7.54 -	d, err := json.Marshal(jsonError{
    7.55 -		Error:       code,
    7.56 -		Description: description,
    7.57 -		URI:         baseURI,
    7.58 -	})
    7.59 -	if err != nil {
    7.60 -		log.Printf("Error marshalling json error (oh, the irony): %s\n", err)
    7.61 -		return
    7.62 -	}
    7.63 -	_, err = w.Write(d)
    7.64 -	if err != nil {
    7.65 -		log.Printf("Error writing json error: %s\n", err)
    7.66 -		return
    7.67 -	}
    7.68 -}
    7.69 -
    7.70 -func (c Context) RenderConfirmation(w io.Writer, r *http.Request, req AuthRequest) {
    7.71 -	if c.Templates.Confirmation == nil {
    7.72 -		log.Println("Confirmation template is nil, can't render confirmation.")
    7.73 -		return
    7.74 -	}
    7.75 -	err := c.Templates.Confirmation.Execute(w, map[string]interface{}{
    7.76 -		"scope":      req.Scope,
    7.77 -		"client":     req.Client,
    7.78 -		"csrf_token": nosurf.Token(r),
    7.79 -	})
    7.80 -	if err != nil {
    7.81 -		log.Printf("Error executing confirmation template: %s\n", err)
    7.82 -		return
    7.83 -	}
    7.84 -}
    7.85 -
    7.86 -func (c Context) RenderLogin(w io.Writer, r *http.Request) {
    7.87 -	if c.Templates.Login == nil {
    7.88 -		log.Println("Login template is nil, can't render confirmation.")
    7.89 -		return
    7.90 -	}
    7.91 -	err := c.Templates.Login.Execute(w, map[string]interface{}{
    7.92 -		"csrf_token": nosurf.Token(r),
    7.93 -	})
    7.94 -	if err != nil {
    7.95 -		log.Printf("Error executing login template: %s\n", err)
    7.96 -		return
    7.97 -	}
    7.98 -}
    7.99 -
   7.100 -func (c Context) RenderJSONToken(w io.Writer, data AccessData) {
   7.101 -	d, err := json.Marshal(data)
   7.102 -	if err != nil {
   7.103 -		log.Printf("Error marshalling json token: %s\n", err)
   7.104 -		return
   7.105 -	}
   7.106 -	_, err = w.Write(d)
   7.107 -	if err != nil {
   7.108 -		log.Printf("Error writing json token: %s\n", err)
   7.109 -		return
   7.110 -	}
   7.111 -}
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/context.go.old	Mon Sep 01 09:13:52 2014 -0400
     8.3 @@ -0,0 +1,108 @@
     8.4 +package auth
     8.5 +
     8.6 +import (
     8.7 +	"encoding/json"
     8.8 +	"html/template"
     8.9 +	"io"
    8.10 +	"log"
    8.11 +	"net/http"
    8.12 +
    8.13 +	"github.com/justinas/nosurf"
    8.14 +)
    8.15 +
    8.16 +type Context struct {
    8.17 +	Config    ServerConfig
    8.18 +	Clients   ClientStore
    8.19 +	Tokens    TokenStore
    8.20 +	Profiles  ProfileStore
    8.21 +	Sessions  SessionStore
    8.22 +	Log       *log.Logger
    8.23 +	Templates Templates
    8.24 +}
    8.25 +
    8.26 +type Templates struct {
    8.27 +	Error        *template.Template
    8.28 +	Confirmation *template.Template
    8.29 +	Login        *template.Template
    8.30 +}
    8.31 +
    8.32 +type jsonError struct {
    8.33 +	Error       string `json:"error,omitempty"`
    8.34 +	Description string `json:"error_description,omitempty"`
    8.35 +	URI         string `json:"error_uri,omitempty"`
    8.36 +	State       string `json:"state,omitempty"`
    8.37 +}
    8.38 +
    8.39 +func (c Context) RenderError(w io.Writer, err error) {
    8.40 +	if c.Templates.Error == nil {
    8.41 +		log.Println("Error template is nil, can't render error.")
    8.42 +		return
    8.43 +	}
    8.44 +	renderErr := c.Templates.Error.Execute(w, map[string]interface{}{
    8.45 +		"err": err,
    8.46 +	})
    8.47 +	if renderErr != nil {
    8.48 +		log.Printf("Error executing error template (oh, the irony): %s\n", renderErr)
    8.49 +		return
    8.50 +	}
    8.51 +}
    8.52 +
    8.53 +func (c Context) RenderJSONError(w io.Writer, code, description, baseURI string) {
    8.54 +	d, err := json.Marshal(jsonError{
    8.55 +		Error:       code,
    8.56 +		Description: description,
    8.57 +		URI:         baseURI,
    8.58 +	})
    8.59 +	if err != nil {
    8.60 +		log.Printf("Error marshalling json error (oh, the irony): %s\n", err)
    8.61 +		return
    8.62 +	}
    8.63 +	_, err = w.Write(d)
    8.64 +	if err != nil {
    8.65 +		log.Printf("Error writing json error: %s\n", err)
    8.66 +		return
    8.67 +	}
    8.68 +}
    8.69 +
    8.70 +func (c Context) RenderConfirmation(w io.Writer, r *http.Request, req AuthRequest) {
    8.71 +	if c.Templates.Confirmation == nil {
    8.72 +		log.Println("Confirmation template is nil, can't render confirmation.")
    8.73 +		return
    8.74 +	}
    8.75 +	err := c.Templates.Confirmation.Execute(w, map[string]interface{}{
    8.76 +		"scope":      req.Scope,
    8.77 +		"client":     req.Client,
    8.78 +		"csrf_token": nosurf.Token(r),
    8.79 +	})
    8.80 +	if err != nil {
    8.81 +		log.Printf("Error executing confirmation template: %s\n", err)
    8.82 +		return
    8.83 +	}
    8.84 +}
    8.85 +
    8.86 +func (c Context) RenderLogin(w io.Writer, r *http.Request) {
    8.87 +	if c.Templates.Login == nil {
    8.88 +		log.Println("Login template is nil, can't render confirmation.")
    8.89 +		return
    8.90 +	}
    8.91 +	err := c.Templates.Login.Execute(w, map[string]interface{}{
    8.92 +		"csrf_token": nosurf.Token(r),
    8.93 +	})
    8.94 +	if err != nil {
    8.95 +		log.Printf("Error executing login template: %s\n", err)
    8.96 +		return
    8.97 +	}
    8.98 +}
    8.99 +
   8.100 +func (c Context) RenderJSONToken(w io.Writer, data AccessData) {
   8.101 +	d, err := json.Marshal(data)
   8.102 +	if err != nil {
   8.103 +		log.Printf("Error marshalling json token: %s\n", err)
   8.104 +		return
   8.105 +	}
   8.106 +	_, err = w.Write(d)
   8.107 +	if err != nil {
   8.108 +		log.Printf("Error writing json token: %s\n", err)
   8.109 +		return
   8.110 +	}
   8.111 +}
     9.1 --- a/errors.go	Sat Aug 16 20:49:19 2014 -0400
     9.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.3 @@ -1,55 +0,0 @@
     9.4 -package auth
     9.5 -
     9.6 -import "errors"
     9.7 -
     9.8 -const (
     9.9 -	ErrorServerError          = "server_error"
    9.10 -	ErrorInvalidRequest       = "invalid_request"
    9.11 -	ErrorAccessDenied         = "access_denied"
    9.12 -	ErrorInvalidClient        = "invalid_client"
    9.13 -	ErrorInvalidGrant         = "invalid_grant"
    9.14 -	ErrorUnauthorizedClient   = "unauthorized_client"
    9.15 -	ErrorUnsupportedGrantType = "unsupported_grant_type"
    9.16 -	ErrorInvalidScope         = "invalid_scope"
    9.17 -)
    9.18 -
    9.19 -var (
    9.20 -	ClientNotFoundError        = errors.New("Client not found.")
    9.21 -	URIMissingError            = errors.New("Redirect URI missing.")
    9.22 -	InvalidMethodError         = errors.New("Invalid request method.")
    9.23 -	InternalServerError        = errors.New("Internal server error.")
    9.24 -	ErrorNotAuthenticated      = errors.New("Not authenticated.")
    9.25 -	InvalidClientError         = errors.New("Invalid client.")
    9.26 -	AuthorizationNotFoundError = errors.New("Authorization not found.")
    9.27 -	ErrProfileNotFound         = errors.New("Profile not found.")
    9.28 -	TokenNotFoundError         = errors.New("Token not found.")
    9.29 -	NilClientError             = errors.New("Client was nil.")
    9.30 -)
    9.31 -
    9.32 -type URIFormatError string
    9.33 -
    9.34 -func (err URIFormatError) Error() string {
    9.35 -	return "Invalid URI format: " + string(err)
    9.36 -}
    9.37 -
    9.38 -type InvalidClientIDError string
    9.39 -
    9.40 -func (err InvalidClientIDError) Error() string {
    9.41 -	return "Invalid client ID: " + string(err)
    9.42 -}
    9.43 -
    9.44 -type URIMismatchError struct {
    9.45 -	uri      string
    9.46 -	mismatch string
    9.47 -}
    9.48 -
    9.49 -func (err URIMismatchError) Error() string {
    9.50 -	return "Supplied redirect URI " + err.mismatch + " does not match the redirect in the database (" + err.uri + ")"
    9.51 -}
    9.52 -
    9.53 -func NewURIMismatchError(uri, mismatch string) error {
    9.54 -	return URIMismatchError{
    9.55 -		uri:      uri,
    9.56 -		mismatch: mismatch,
    9.57 -	}
    9.58 -}
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/errors.go.old	Mon Sep 01 09:13:52 2014 -0400
    10.3 @@ -0,0 +1,55 @@
    10.4 +package auth
    10.5 +
    10.6 +import "errors"
    10.7 +
    10.8 +const (
    10.9 +	ErrorServerError          = "server_error"
   10.10 +	ErrorInvalidRequest       = "invalid_request"
   10.11 +	ErrorAccessDenied         = "access_denied"
   10.12 +	ErrorInvalidClient        = "invalid_client"
   10.13 +	ErrorInvalidGrant         = "invalid_grant"
   10.14 +	ErrorUnauthorizedClient   = "unauthorized_client"
   10.15 +	ErrorUnsupportedGrantType = "unsupported_grant_type"
   10.16 +	ErrorInvalidScope         = "invalid_scope"
   10.17 +)
   10.18 +
   10.19 +var (
   10.20 +	ClientNotFoundError        = errors.New("Client not found.")
   10.21 +	URIMissingError            = errors.New("Redirect URI missing.")
   10.22 +	InvalidMethodError         = errors.New("Invalid request method.")
   10.23 +	InternalServerError        = errors.New("Internal server error.")
   10.24 +	ErrorNotAuthenticated      = errors.New("Not authenticated.")
   10.25 +	InvalidClientError         = errors.New("Invalid client.")
   10.26 +	AuthorizationNotFoundError = errors.New("Authorization not found.")
   10.27 +	ErrProfileNotFound         = errors.New("Profile not found.")
   10.28 +	TokenNotFoundError         = errors.New("Token not found.")
   10.29 +	NilClientError             = errors.New("Client was nil.")
   10.30 +)
   10.31 +
   10.32 +type URIFormatError string
   10.33 +
   10.34 +func (err URIFormatError) Error() string {
   10.35 +	return "Invalid URI format: " + string(err)
   10.36 +}
   10.37 +
   10.38 +type InvalidClientIDError string
   10.39 +
   10.40 +func (err InvalidClientIDError) Error() string {
   10.41 +	return "Invalid client ID: " + string(err)
   10.42 +}
   10.43 +
   10.44 +type URIMismatchError struct {
   10.45 +	uri      string
   10.46 +	mismatch string
   10.47 +}
   10.48 +
   10.49 +func (err URIMismatchError) Error() string {
   10.50 +	return "Supplied redirect URI " + err.mismatch + " does not match the redirect in the database (" + err.uri + ")"
   10.51 +}
   10.52 +
   10.53 +func NewURIMismatchError(uri, mismatch string) error {
   10.54 +	return URIMismatchError{
   10.55 +		uri:      uri,
   10.56 +		mismatch: mismatch,
   10.57 +	}
   10.58 +}
    11.1 --- a/session.go	Sat Aug 16 20:49:19 2014 -0400
    11.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.3 @@ -1,94 +0,0 @@
    11.4 -package auth
    11.5 -
    11.6 -import (
    11.7 -	"errors"
    11.8 -	"net/http"
    11.9 -	"net/url"
   11.10 -	"time"
   11.11 -
   11.12 -	"strings"
   11.13 -	"secondbit.org/uuid"
   11.14 -)
   11.15 -
   11.16 -const sessionCookie = "session"
   11.17 -
   11.18 -var (
   11.19 -	ErrSessionNotFound = errors.New("Session not found.")
   11.20 -)
   11.21 -
   11.22 -type Session struct {
   11.23 -	Token   string
   11.24 -	User    uuid.ID
   11.25 -	Expires time.Time
   11.26 -	Created time.Time
   11.27 -	IP      string
   11.28 -}
   11.29 -
   11.30 -func validateSession(r *http.Request, c Context) error {
   11.31 -	cookie, err := r.Cookie(sessionCookie)
   11.32 -	if err == http.ErrNoCookie {
   11.33 -		return ErrSessionNotFound
   11.34 -	}
   11.35 -	_, err = c.Sessions.GetSession(cookie.Value)
   11.36 -	return err
   11.37 -}
   11.38 -
   11.39 -func HandleLoginRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   11.40 -	if r.Method == "GET" {
   11.41 -		ctx.RenderLogin(w, r)
   11.42 -		return
   11.43 -	} else if r.Method != "POST" {
   11.44 -		// TODO: return bad method error
   11.45 -		return
   11.46 -	}
   11.47 -
   11.48 -	if r.FormValue("username") == "" || r.FormValue("password") == "" {
   11.49 -		// TODO: return unauthenticated error
   11.50 -		return
   11.51 -	}
   11.52 -	id, err := ctx.Profiles.GetProfile(r.FormValue("username"), r.FormValue("password"))
   11.53 -	if err != nil {
   11.54 -		if err == ErrProfileNotFound {
   11.55 -			// TODO: return unauthenticated error
   11.56 -			return
   11.57 -		}
   11.58 -		// TODO: return internal server error
   11.59 -		return
   11.60 -	}
   11.61 -	session := Session{
   11.62 -		Token:   newToken(),
   11.63 -		User:    id,
   11.64 -		Expires: time.Now().Add(ctx.Config.SessionLength),
   11.65 -		Created: time.Now(),
   11.66 -		IP:      r.Header.Get(ctx.Config.RequestIPHeader),
   11.67 -	}
   11.68 -	err = ctx.Sessions.SetSession(session)
   11.69 -	if err != nil {
   11.70 -		// TODO: return internal server error
   11.71 -		return
   11.72 -	}
   11.73 -	http.SetCookie(w, &http.Cookie{
   11.74 -		Name:     sessionCookie,
   11.75 -		Value:    session.Token,
   11.76 -		Expires:  session.Expires,
   11.77 -		Secure:   true,
   11.78 -		HttpOnly: true,
   11.79 -	})
   11.80 -
   11.81 -	redirectString := r.URL.Query().Get("redirect_to")
   11.82 -	if redirectString != "" {
   11.83 -		redirectURI, err := url.Parse(redirectString)
   11.84 -		if err != nil {
   11.85 -			// TODO: render a bad request error
   11.86 -			return
   11.87 -		}
   11.88 -		if !strings.HasSuffix("."+ctx.Config.LoginRedirectDomain, redirectURI.Host) && redirectURI.Host != ctx.Config.LoginRedirectDomain {
   11.89 -			// TODO: render a bad request error
   11.90 -			return
   11.91 -		}
   11.92 -	} else {
   11.93 -		redirectString = "https://" + ctx.Config.LoginRedirectDomain
   11.94 -	}
   11.95 -	http.Redirect(w, r, redirectString, http.StatusFound)
   11.96 -	return
   11.97 -}
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/session.go.old	Mon Sep 01 09:13:52 2014 -0400
    12.3 @@ -0,0 +1,101 @@
    12.4 +package auth
    12.5 +
    12.6 +import (
    12.7 +	"errors"
    12.8 +	"net/http"
    12.9 +	"net/url"
   12.10 +	"time"
   12.11 +
   12.12 +	"strings"
   12.13 +	"secondbit.org/uuid"
   12.14 +)
   12.15 +
   12.16 +const sessionCookie = "session"
   12.17 +
   12.18 +var (
   12.19 +	ErrSessionNotFound = errors.New("Session not found.")
   12.20 +)
   12.21 +
   12.22 +type Session struct {
   12.23 +	Token   string
   12.24 +	Profile uuid.ID
   12.25 +	Expires time.Time
   12.26 +	Created time.Time
   12.27 +	IP      string
   12.28 +}
   12.29 +
   12.30 +type SessionStore interface {
   12.31 +	GetSession(token string) (Session, error)
   12.32 +	GetAllSessions(userID uuid.ID, num, page int) ([]Session, error)
   12.33 +	SaveSession(session Session) error
   12.34 +	DeleteSession(sessionID uuid.ID) error
   12.35 +}
   12.36 +
   12.37 +func validateSession(r *http.Request, c Context) error {
   12.38 +	cookie, err := r.Cookie(sessionCookie)
   12.39 +	if err == http.ErrNoCookie {
   12.40 +		return ErrSessionNotFound
   12.41 +	}
   12.42 +	_, err = c.Sessions.GetSession(cookie.Value)
   12.43 +	return err
   12.44 +}
   12.45 +
   12.46 +func HandleLoginRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   12.47 +	if r.Method == "GET" {
   12.48 +		ctx.RenderLogin(w, r)
   12.49 +		return
   12.50 +	} else if r.Method != "POST" {
   12.51 +		// TODO: return bad method error
   12.52 +		return
   12.53 +	}
   12.54 +
   12.55 +	if r.FormValue("username") == "" || r.FormValue("password") == "" {
   12.56 +		// TODO: return unauthenticated error
   12.57 +		return
   12.58 +	}
   12.59 +	id, err := ctx.Profiles.GetProfile(r.FormValue("username"), r.FormValue("password"))
   12.60 +	if err != nil {
   12.61 +		if err == ErrProfileNotFound {
   12.62 +			// TODO: return unauthenticated error
   12.63 +			return
   12.64 +		}
   12.65 +		// TODO: return internal server error
   12.66 +		return
   12.67 +	}
   12.68 +	session := Session{
   12.69 +		Token:   newToken(),
   12.70 +		Profile: id,
   12.71 +		Expires: time.Now().Add(ctx.Config.SessionLength),
   12.72 +		Created: time.Now(),
   12.73 +		IP:      r.Header.Get(ctx.Config.RequestIPHeader),
   12.74 +	}
   12.75 +	err = ctx.Sessions.SetSession(session)
   12.76 +	if err != nil {
   12.77 +		// TODO: return internal server error
   12.78 +		return
   12.79 +	}
   12.80 +	http.SetCookie(w, &http.Cookie{
   12.81 +		Name:     sessionCookie,
   12.82 +		Value:    session.Token,
   12.83 +		Expires:  session.Expires,
   12.84 +		Secure:   true,
   12.85 +		HttpOnly: true,
   12.86 +	})
   12.87 +
   12.88 +	redirectString := r.URL.Query().Get("redirect_to")
   12.89 +	if redirectString != "" {
   12.90 +		redirectURI, err := url.Parse(redirectString)
   12.91 +		if err != nil {
   12.92 +			// TODO: render a bad request error
   12.93 +			return
   12.94 +		}
   12.95 +		if !strings.HasSuffix("."+ctx.Config.LoginRedirectDomain, redirectURI.Host) && redirectURI.Host != ctx.Config.LoginRedirectDomain {
   12.96 +			// TODO: render a bad request error
   12.97 +			return
   12.98 +		}
   12.99 +	} else {
  12.100 +		redirectString = "https://" + ctx.Config.LoginRedirectDomain
  12.101 +	}
  12.102 +	http.Redirect(w, r, redirectString, http.StatusFound)
  12.103 +	return
  12.104 +}
    13.1 --- a/util.go	Sat Aug 16 20:49:19 2014 -0400
    13.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.3 @@ -1,102 +0,0 @@
    13.4 -package auth
    13.5 -
    13.6 -import (
    13.7 -	"encoding/base64"
    13.8 -	"errors"
    13.9 -	"fmt"
   13.10 -	"net/http"
   13.11 -	"net/url"
   13.12 -	"strings"
   13.13 -
   13.14 -	"code.google.com/p/go-uuid/uuid"
   13.15 -)
   13.16 -
   13.17 -var (
   13.18 -	BasicAuthNotSetError      = errors.New("Authorization header not set.")
   13.19 -	InvalidBasicAuthTypeError = errors.New("Invalid basic auth type.")
   13.20 -	InvalidBasicAuthMessage   = errors.New("Invalid basic auth format.")
   13.21 -)
   13.22 -
   13.23 -// Parse basic authentication header
   13.24 -type BasicAuth struct {
   13.25 -	Username string
   13.26 -	Password string
   13.27 -}
   13.28 -
   13.29 -// Return authorization header data
   13.30 -func CheckBasicAuth(r *http.Request) (BasicAuth, error) {
   13.31 -	if r.Header.Get("Authorization") == "" {
   13.32 -		return BasicAuth{}, BasicAuthNotSetError
   13.33 -	}
   13.34 -
   13.35 -	s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
   13.36 -	if len(s) != 2 || s[0] != "Basic" {
   13.37 -		return BasicAuth{}, InvalidBasicAuthTypeError
   13.38 -	}
   13.39 -
   13.40 -	b, err := base64.StdEncoding.DecodeString(s[1])
   13.41 -	if err != nil {
   13.42 -		return BasicAuth{}, err
   13.43 -	}
   13.44 -	pair := strings.SplitN(string(b), ":", 2)
   13.45 -	if len(pair) != 2 {
   13.46 -		return BasicAuth{}, InvalidBasicAuthMessage
   13.47 -	}
   13.48 -
   13.49 -	return BasicAuth{Username: pair[0], Password: pair[1]}, nil
   13.50 -}
   13.51 -
   13.52 -// getClientAuth checks client basic authentication in params if allowed,
   13.53 -// otherwise gets it from the header.
   13.54 -func getClientAuth(r *http.Request, allowQueryParams bool) (BasicAuth, error) {
   13.55 -
   13.56 -	if allowQueryParams {
   13.57 -		// Allow for auth without password
   13.58 -		if _, hasSecret := r.Form["client_secret"]; hasSecret {
   13.59 -			auth := BasicAuth{
   13.60 -				Username: r.Form.Get("client_id"),
   13.61 -				Password: r.Form.Get("client_secret"),
   13.62 -			}
   13.63 -			if auth.Username != "" {
   13.64 -				return auth, nil
   13.65 -			}
   13.66 -		}
   13.67 -	}
   13.68 -
   13.69 -	return CheckBasicAuth(r)
   13.70 -}
   13.71 -
   13.72 -func newToken() string {
   13.73 -	return base64.StdEncoding.EncodeToString([]byte(uuid.New()))
   13.74 -}
   13.75 -
   13.76 -// validateURI validates that redirectURI is contained in baseURI
   13.77 -func validateURI(baseURI string, redirectURI string) error {
   13.78 -	if baseURI == "" || redirectURI == "" {
   13.79 -		return errors.New("urls cannot be blank.")
   13.80 -	}
   13.81 -
   13.82 -	// parse base url
   13.83 -	base, err := url.Parse(baseURI)
   13.84 -	if err != nil {
   13.85 -		return err
   13.86 -	}
   13.87 -
   13.88 -	// parse passed url
   13.89 -	redirect, err := url.Parse(redirectURI)
   13.90 -	if err != nil {
   13.91 -		return err
   13.92 -	}
   13.93 -
   13.94 -	// must not have fragment
   13.95 -	if base.Fragment != "" || redirect.Fragment != "" {
   13.96 -		return errors.New("url must not include fragment.")
   13.97 -	}
   13.98 -
   13.99 -	// check if urls match
  13.100 -	if base.Scheme == redirect.Scheme && base.Host == redirect.Host && len(redirect.Path) >= len(base.Path) && strings.HasPrefix(redirect.Path, base.Path) {
  13.101 -		return nil
  13.102 -	}
  13.103 -
  13.104 -	return errors.New(fmt.Sprintf("urls don't validate: %s / %s\n", baseURI, redirectURI))
  13.105 -}
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/util.go.old	Mon Sep 01 09:13:52 2014 -0400
    14.3 @@ -0,0 +1,102 @@
    14.4 +package auth
    14.5 +
    14.6 +import (
    14.7 +	"encoding/base64"
    14.8 +	"errors"
    14.9 +	"fmt"
   14.10 +	"net/http"
   14.11 +	"net/url"
   14.12 +	"strings"
   14.13 +
   14.14 +	"code.google.com/p/go-uuid/uuid"
   14.15 +)
   14.16 +
   14.17 +var (
   14.18 +	BasicAuthNotSetError      = errors.New("Authorization header not set.")
   14.19 +	InvalidBasicAuthTypeError = errors.New("Invalid basic auth type.")
   14.20 +	InvalidBasicAuthMessage   = errors.New("Invalid basic auth format.")
   14.21 +)
   14.22 +
   14.23 +// Parse basic authentication header
   14.24 +type BasicAuth struct {
   14.25 +	Username string
   14.26 +	Password string
   14.27 +}
   14.28 +
   14.29 +// Return authorization header data
   14.30 +func CheckBasicAuth(r *http.Request) (BasicAuth, error) {
   14.31 +	if r.Header.Get("Authorization") == "" {
   14.32 +		return BasicAuth{}, BasicAuthNotSetError
   14.33 +	}
   14.34 +
   14.35 +	s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
   14.36 +	if len(s) != 2 || s[0] != "Basic" {
   14.37 +		return BasicAuth{}, InvalidBasicAuthTypeError
   14.38 +	}
   14.39 +
   14.40 +	b, err := base64.StdEncoding.DecodeString(s[1])
   14.41 +	if err != nil {
   14.42 +		return BasicAuth{}, err
   14.43 +	}
   14.44 +	pair := strings.SplitN(string(b), ":", 2)
   14.45 +	if len(pair) != 2 {
   14.46 +		return BasicAuth{}, InvalidBasicAuthMessage
   14.47 +	}
   14.48 +
   14.49 +	return BasicAuth{Username: pair[0], Password: pair[1]}, nil
   14.50 +}
   14.51 +
   14.52 +// getClientAuth checks client basic authentication in params if allowed,
   14.53 +// otherwise gets it from the header.
   14.54 +func getClientAuth(r *http.Request, allowQueryParams bool) (BasicAuth, error) {
   14.55 +
   14.56 +	if allowQueryParams {
   14.57 +		// Allow for auth without password
   14.58 +		if _, hasSecret := r.Form["client_secret"]; hasSecret {
   14.59 +			auth := BasicAuth{
   14.60 +				Username: r.Form.Get("client_id"),
   14.61 +				Password: r.Form.Get("client_secret"),
   14.62 +			}
   14.63 +			if auth.Username != "" {
   14.64 +				return auth, nil
   14.65 +			}
   14.66 +		}
   14.67 +	}
   14.68 +
   14.69 +	return CheckBasicAuth(r)
   14.70 +}
   14.71 +
   14.72 +func newToken() string {
   14.73 +	return base64.StdEncoding.EncodeToString([]byte(uuid.New()))
   14.74 +}
   14.75 +
   14.76 +// validateURI validates that redirectURI is contained in baseURI
   14.77 +func validateURI(baseURI string, redirectURI string) error {
   14.78 +	if baseURI == "" || redirectURI == "" {
   14.79 +		return errors.New("urls cannot be blank.")
   14.80 +	}
   14.81 +
   14.82 +	// parse base url
   14.83 +	base, err := url.Parse(baseURI)
   14.84 +	if err != nil {
   14.85 +		return err
   14.86 +	}
   14.87 +
   14.88 +	// parse passed url
   14.89 +	redirect, err := url.Parse(redirectURI)
   14.90 +	if err != nil {
   14.91 +		return err
   14.92 +	}
   14.93 +
   14.94 +	// must not have fragment
   14.95 +	if base.Fragment != "" || redirect.Fragment != "" {
   14.96 +		return errors.New("url must not include fragment.")
   14.97 +	}
   14.98 +
   14.99 +	// check if urls match
  14.100 +	if base.Scheme == redirect.Scheme && base.Host == redirect.Host && len(redirect.Path) >= len(base.Path) && strings.HasPrefix(redirect.Path, base.Path) {
  14.101 +		return nil
  14.102 +	}
  14.103 +
  14.104 +	return errors.New(fmt.Sprintf("urls don't validate: %s / %s\n", baseURI, redirectURI))
  14.105 +}