auth

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

1:7b9e0fc20256 Go to Latest

auth/authorize.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/authorize.go	Fri Jul 18 07:13:22 2014 -0400
     1.2 +++ b/authorize.go	Fri Aug 01 23:08:38 2014 -0400
     1.3 @@ -5,6 +5,7 @@
     1.4  	"net/url"
     1.5  	"time"
     1.6  
     1.7 +	"strings"
     1.8  	"secondbit.org/uuid"
     1.9  )
    1.10  
    1.11 @@ -16,41 +17,27 @@
    1.12  	TokenAuthRT                      = "token"
    1.13  )
    1.14  
    1.15 -// Authorize request information
    1.16 -type AuthorizeRequest struct {
    1.17 -	Type        AuthorizeRequestType
    1.18 +type AuthRequest struct {
    1.19  	Client      Client
    1.20  	Scope       string
    1.21  	RedirectURI string
    1.22  	State       string
    1.23 -
    1.24 -	// Token expiration in seconds. Change if different from default.
    1.25 -	// If type = TokenAuthRT, this expiration will be for the ACCESS token.
    1.26 -	Expiration int32
    1.27  }
    1.28  
    1.29  // Authorization data
    1.30  type AuthorizeData struct {
    1.31 -	// Client information
    1.32 -	Client Client
    1.33 -
    1.34  	// Authorization code
    1.35  	Code string
    1.36  
    1.37  	// Token expiration in seconds
    1.38  	ExpiresIn int32
    1.39  
    1.40 -	// Requested scope
    1.41 -	Scope string
    1.42 -
    1.43 -	// Redirect URI from request
    1.44 -	RedirectURI string
    1.45 -
    1.46 -	// State data from request
    1.47 -	State string
    1.48 -
    1.49  	// Date created
    1.50  	CreatedAt time.Time
    1.51 +
    1.52 +	ProfileID uuid.ID
    1.53 +
    1.54 +	AuthRequest
    1.55  }
    1.56  
    1.57  // IsExpired is true if authorization expired
    1.58 @@ -67,153 +54,217 @@
    1.59  // authorization requests
    1.60  func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
    1.61  	r.ParseForm()
    1.62 +	// create the authorization request
    1.63 +	redirectURI := r.Form.Get("redirect_uri")
    1.64 +	var err error
    1.65 +	if redirectURI != "" {
    1.66 +		redirectURI, err = url.QueryUnescape(redirectURI)
    1.67 +		if err != nil {
    1.68 +			ctx.RenderError(w, URIFormatError(redirectURI))
    1.69 +			return
    1.70 +		}
    1.71 +	}
    1.72 +
    1.73 +	state := r.Form.Get("state")
    1.74 +	scope := r.Form.Get("scope")
    1.75 +
    1.76 +	// must have a valid client
    1.77 +	id, err := uuid.Parse(r.Form.Get("client_id"))
    1.78 +	if err != nil {
    1.79 +		ctx.RenderError(w, InvalidClientIDError(r.Form.Get("client_id")))
    1.80 +		return
    1.81 +	}
    1.82 +	client, err := GetClient(id, ctx)
    1.83 +	if err != nil {
    1.84 +		if err == ClientNotFoundError {
    1.85 +			ctx.RenderError(w, ClientNotFoundError)
    1.86 +			return
    1.87 +		}
    1.88 +		if redirectURI == "" {
    1.89 +			ctx.RenderError(w, URIMissingError)
    1.90 +			return
    1.91 +		}
    1.92 +		req := AuthRequest{
    1.93 +			RedirectURI: redirectURI,
    1.94 +			Scope:       scope,
    1.95 +			State:       state,
    1.96 +		}
    1.97 +		redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
    1.98 +		if err != nil {
    1.99 +			ctx.RenderError(w, URIFormatError(redirectURI))
   1.100 +			return
   1.101 +		}
   1.102 +		http.Redirect(w, r, redir, http.StatusFound)
   1.103 +		return
   1.104 +	}
   1.105 +	if client.RedirectURI == "" {
   1.106 +		ctx.RenderError(w, URIMissingError)
   1.107 +		return
   1.108 +	}
   1.109 +
   1.110 +	// check redirect uri
   1.111 +	if redirectURI == "" {
   1.112 +		redirectURI = client.RedirectURI
   1.113 +	}
   1.114 +	if err = validateURI(client.RedirectURI, redirectURI); err != nil {
   1.115 +		ctx.RenderError(w, NewURIMismatchError(client.RedirectURI, redirectURI))
   1.116 +		return
   1.117 +	}
   1.118 +
   1.119 +	req := AuthRequest{
   1.120 +		Client:      client,
   1.121 +		RedirectURI: redirectURI,
   1.122 +		Scope:       scope,
   1.123 +		State:       state,
   1.124 +	}
   1.125  
   1.126  	requestType := AuthorizeRequestType(r.Form.Get("response_type"))
   1.127  	if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) {
   1.128  		switch requestType {
   1.129  		case CodeAuthRT:
   1.130 -			handleCodeRequest(w, r, ctx)
   1.131 +			req.handleCodeRequest(w, r, ctx)
   1.132  			return
   1.133  		case TokenAuthRT:
   1.134 -			handleTokenRequest(w, r, ctx)
   1.135 +			req.handleTokenRequest(w, r, ctx)
   1.136  			return
   1.137  		}
   1.138  	}
   1.139 -	// TODO: return error
   1.140 +	redir, err := req.GetErrorRedirect(ErrorInvalidRequest, "Invalid response type.", ctx.Config.DocumentationDomain)
   1.141 +	if err != nil {
   1.142 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   1.143 +		return
   1.144 +	}
   1.145 +	http.Redirect(w, r, redir, http.StatusFound)
   1.146  }
   1.147  
   1.148 -func handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.149 -	// create the authorization request
   1.150 -	unescapedURI, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
   1.151 -	if err != nil {
   1.152 -		unescapedURI = ""
   1.153 -	}
   1.154 -	ret := &AuthorizeRequest{
   1.155 -		Type:        CodeAuthRT,
   1.156 -		State:       r.Form.Get("state"),
   1.157 -		Scope:       r.Form.Get("scope"),
   1.158 -		RedirectURI: unescapedURI,
   1.159 -		Expiration:  ctx.Config.AuthorizationExpiration,
   1.160 -	}
   1.161 +func (req AuthRequest) handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.162  
   1.163 -	// must have a valid client
   1.164 -	id, err := uuid.Parse(r.Form.Get("client_id"))
   1.165 -	if err != nil {
   1.166 -		// TODO: return error
   1.167 +	if r.Method == "GET" {
   1.168 +		ctx.RenderConfirmation(w)
   1.169  		return
   1.170 -	}
   1.171 -	ret.Client, err = GetClient(id, ctx)
   1.172 -	if err != nil {
   1.173 -		// TODO: return error
   1.174 -		return
   1.175 -	}
   1.176 -	if ret.Client.RedirectURI == "" {
   1.177 -		// TODO: return error
   1.178 +	} else if r.Method != "POST" {
   1.179 +		ctx.RenderError(w, InvalidMethodError)
   1.180  		return
   1.181  	}
   1.182  
   1.183 -	// check redirect uri
   1.184 -	if ret.RedirectURI == "" {
   1.185 -		ret.RedirectURI = ret.Client.RedirectURI
   1.186 -	}
   1.187 -	if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil {
   1.188 -		// TODO: return error
   1.189 +	if err := validateSession(r); err == ErrorNotAuthenticated {
   1.190 +		ctx.RenderLogin(w)
   1.191 +		return
   1.192 +	} else if err != nil {
   1.193 +		ctx.RenderError(w, err)
   1.194  		return
   1.195  	}
   1.196  
   1.197 -	// TODO: do redirect with ret data
   1.198 -}
   1.199 -
   1.200 -func handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.201 -	// create the authorization request
   1.202 -	unescapedURI, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
   1.203 -	if err != nil {
   1.204 -		unescapedURI = ""
   1.205 -	}
   1.206 -	ret := &AuthorizeRequest{
   1.207 -		Type:        TokenAuthRT,
   1.208 -		State:       r.Form.Get("state"),
   1.209 -		Scope:       r.Form.Get("scope"),
   1.210 -		RedirectURI: unescapedURI,
   1.211 -		// this type will generate a token directly, use access token expiration instead.
   1.212 -		Expiration: ctx.Config.AccessExpiration,
   1.213 -	}
   1.214 -
   1.215 -	// must have a valid client
   1.216 -	id, err := uuid.Parse(r.Form.Get("client_id"))
   1.217 -	if err != nil {
   1.218 -		// TODO: return error
   1.219 -		return
   1.220 -	}
   1.221 -	ret.Client, err = GetClient(id, ctx)
   1.222 -	if err != nil {
   1.223 -		// TODO: return error
   1.224 -		return
   1.225 -	}
   1.226 -	if ret.Client.RedirectURI == "" {
   1.227 -		// TODO: return error
   1.228 +	if r.FormValue("approved") != "true" {
   1.229 +		redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
   1.230 +		if err != nil {
   1.231 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   1.232 +			return
   1.233 +		}
   1.234 +		http.Redirect(w, r, redir, http.StatusFound)
   1.235  		return
   1.236  	}
   1.237  
   1.238 -	// check redirect uri
   1.239 -	if ret.RedirectURI == "" {
   1.240 -		ret.RedirectURI = ret.Client.RedirectURI
   1.241 -	}
   1.242 -	if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil {
   1.243 -		// TODO: return error
   1.244 +	data := AuthorizeData{AuthRequest: req}
   1.245 +
   1.246 +	data.ExpiresIn = ctx.Config.AuthorizationExpiration
   1.247 +	data.Code = newToken()
   1.248 +	data.CreatedAt = time.Now()
   1.249 +
   1.250 +	err := ctx.Tokens.SaveAuthorization(data)
   1.251 +	if err != nil {
   1.252 +		redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
   1.253 +		if err != nil {
   1.254 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   1.255 +			return
   1.256 +		}
   1.257 +		http.Redirect(w, r, redir, http.StatusFound)
   1.258 +		return
   1.259  	}
   1.260  
   1.261 -	// TODO: redirect with ret information
   1.262 +	redir, err := data.GetRedirect()
   1.263 +	if err != nil {
   1.264 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   1.265 +		return
   1.266 +	}
   1.267 +	http.Redirect(w, r, redir, http.StatusFound)
   1.268  }
   1.269  
   1.270 -func FinishAuthorizeRequest(w http.ResponseWriter, r *http.Request, ar *AuthorizeRequest, ctx Context) {
   1.271 -	// TODO: check if authorized?
   1.272 -	if ar.Type == TokenAuthRT {
   1.273 -		// TODO: w.SetRedirectFragment(true) was called...
   1.274 +func (req AuthRequest) handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
   1.275  
   1.276 -		// generate token directly
   1.277 -		ret := AccessRequest{
   1.278 -			Code:            "",
   1.279 -			Client:          ar.Client,
   1.280 -			RedirectURI:     ar.RedirectURI,
   1.281 -			Scope:           ar.Scope,
   1.282 -			GenerateRefresh: false, // per the RFC, should NOT generate a refresh token in this case
   1.283 -			Expiration:      ar.Expiration,
   1.284 -		}
   1.285 -		// TODO: ret.type was implicit
   1.286 -		// TODO: ret.Authorized was true
   1.287 -		FinishAccessRequest(w, r, ret, ctx)
   1.288 -	} else {
   1.289 -		// generate authorization token
   1.290 -		ret := AuthorizeData{
   1.291 -			Client:      ar.Client,
   1.292 -			CreatedAt:   time.Now(),
   1.293 -			ExpiresIn:   ar.Expiration,
   1.294 -			RedirectURI: ar.RedirectURI,
   1.295 -			State:       ar.State,
   1.296 -			Scope:       ar.Scope,
   1.297 -			Code:        newToken(),
   1.298 -		}
   1.299 +	if r.Method == "GET" {
   1.300 +		ctx.RenderConfirmation(w)
   1.301 +		return
   1.302 +	} else if r.Method != "POST" {
   1.303 +		ctx.RenderError(w, InvalidMethodError)
   1.304 +		return
   1.305 +	}
   1.306  
   1.307 -		// save authorization token
   1.308 -		err := saveAuthorize(ret, ctx)
   1.309 +	if err := validateSession(r); err == ErrorNotAuthenticated {
   1.310 +		ctx.RenderLogin(w)
   1.311 +		return
   1.312 +	} else if err != nil {
   1.313 +		ctx.RenderError(w, err)
   1.314 +		return
   1.315 +	}
   1.316 +
   1.317 +	if r.FormValue("approved") != "true" {
   1.318 +		redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
   1.319  		if err != nil {
   1.320 -			// TODO: return error
   1.321 +			ctx.RenderError(w, URIFormatError(req.RedirectURI))
   1.322  			return
   1.323  		}
   1.324 +		http.Redirect(w, r, redir, http.StatusFound)
   1.325 +		return
   1.326 +	}
   1.327  
   1.328 -		// TODO: redirect with ret.Code and ret.State
   1.329 +	data := AccessData{AuthRequest: req}
   1.330 +
   1.331 +	err := fillTokens(&data, false, ctx)
   1.332 +	if err != nil {
   1.333 +		ctx.RenderError(w, InternalServerError)
   1.334 +		return
   1.335  	}
   1.336 +
   1.337 +	redir, err := data.GetRedirect(true)
   1.338 +	if err != nil {
   1.339 +		ctx.RenderError(w, URIFormatError(req.RedirectURI))
   1.340 +		return
   1.341 +	}
   1.342 +	http.Redirect(w, r, redir, http.StatusFound)
   1.343  }
   1.344  
   1.345 -func loadAuthorize(code string, ctx Context) (AuthorizeData, error) {
   1.346 -	return AuthorizeData{}, nil
   1.347 +func (data AuthorizeData) GetRedirect() (string, error) {
   1.348 +	u, err := url.Parse(data.RedirectURI)
   1.349 +	if err != nil {
   1.350 +		return "", err
   1.351 +	}
   1.352 +
   1.353 +	// add parameters
   1.354 +	q := u.Query()
   1.355 +	q.Set("code", data.Code)
   1.356 +	q.Set("state", data.State)
   1.357 +	u.RawQuery = q.Encode()
   1.358 +
   1.359 +	return u.String(), nil
   1.360  }
   1.361  
   1.362 -func saveAuthorize(ret AuthorizeData, ctx Context) error {
   1.363 -	return nil
   1.364 +func (req AuthRequest) GetErrorRedirect(code, description, uriBase string) (string, error) {
   1.365 +	u, err := url.Parse(req.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("error", code)
   1.373 +	q.Set("error_description", description)
   1.374 +	q.Set("error_uri", strings.Join([]string{
   1.375 +		strings.TrimRight(uriBase, "/"),
   1.376 +		strings.TrimLeft(code, "/"),
   1.377 +	}, "/"))
   1.378 +	q.Set("state", req.State)
   1.379 +	u.RawQuery = q.Encode()
   1.380 +
   1.381 +	return u.String(), nil
   1.382  }
   1.383 -
   1.384 -func removeAuthorize(code string, ctx Context) error {
   1.385 -	return nil
   1.386 -}