auth
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.
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 -}