auth

Paddy 2014-08-01 Parent:7a6f64db7246 Child:65c49af1ed3f

1:7b9e0fc20256 Go to Latest

auth/access.go

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.

History
     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 -}