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
paddy@0 1 package oauth2
paddy@0 2
paddy@0 3 import (
paddy@0 4 "net/http"
paddy@0 5 "net/url"
paddy@0 6 "time"
paddy@0 7
paddy@1 8 "strings"
paddy@0 9 "secondbit.org/uuid"
paddy@0 10 )
paddy@0 11
paddy@0 12 // AuthorizeRequestType is the type for OAuth param `response_type`
paddy@0 13 type AuthorizeRequestType string
paddy@0 14
paddy@0 15 const (
paddy@0 16 CodeAuthRT AuthorizeRequestType = "code"
paddy@0 17 TokenAuthRT = "token"
paddy@0 18 )
paddy@0 19
paddy@1 20 type AuthRequest struct {
paddy@0 21 Client Client
paddy@0 22 Scope string
paddy@0 23 RedirectURI string
paddy@0 24 State string
paddy@0 25 }
paddy@0 26
paddy@0 27 // Authorization data
paddy@0 28 type AuthorizeData struct {
paddy@0 29 // Authorization code
paddy@0 30 Code string
paddy@0 31
paddy@0 32 // Token expiration in seconds
paddy@0 33 ExpiresIn int32
paddy@0 34
paddy@0 35 // Date created
paddy@0 36 CreatedAt time.Time
paddy@1 37
paddy@1 38 ProfileID uuid.ID
paddy@1 39
paddy@1 40 AuthRequest
paddy@0 41 }
paddy@0 42
paddy@0 43 // IsExpired is true if authorization expired
paddy@0 44 func (d *AuthorizeData) IsExpired() bool {
paddy@0 45 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now())
paddy@0 46 }
paddy@0 47
paddy@0 48 // ExpireAt returns the expiration date
paddy@0 49 func (d *AuthorizeData) ExpireAt() time.Time {
paddy@0 50 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
paddy@0 51 }
paddy@0 52
paddy@0 53 // HandleAuthorizeRequest is the main http.HandlerFunc for handling
paddy@0 54 // authorization requests
paddy@0 55 func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
paddy@0 56 r.ParseForm()
paddy@1 57 // create the authorization request
paddy@1 58 redirectURI := r.Form.Get("redirect_uri")
paddy@1 59 var err error
paddy@1 60 if redirectURI != "" {
paddy@1 61 redirectURI, err = url.QueryUnescape(redirectURI)
paddy@1 62 if err != nil {
paddy@1 63 ctx.RenderError(w, URIFormatError(redirectURI))
paddy@1 64 return
paddy@1 65 }
paddy@1 66 }
paddy@1 67
paddy@1 68 state := r.Form.Get("state")
paddy@1 69 scope := r.Form.Get("scope")
paddy@1 70
paddy@1 71 // must have a valid client
paddy@1 72 id, err := uuid.Parse(r.Form.Get("client_id"))
paddy@1 73 if err != nil {
paddy@1 74 ctx.RenderError(w, InvalidClientIDError(r.Form.Get("client_id")))
paddy@1 75 return
paddy@1 76 }
paddy@1 77 client, err := GetClient(id, ctx)
paddy@1 78 if err != nil {
paddy@1 79 if err == ClientNotFoundError {
paddy@1 80 ctx.RenderError(w, ClientNotFoundError)
paddy@1 81 return
paddy@1 82 }
paddy@1 83 if redirectURI == "" {
paddy@1 84 ctx.RenderError(w, URIMissingError)
paddy@1 85 return
paddy@1 86 }
paddy@1 87 req := AuthRequest{
paddy@1 88 RedirectURI: redirectURI,
paddy@1 89 Scope: scope,
paddy@1 90 State: state,
paddy@1 91 }
paddy@1 92 redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
paddy@1 93 if err != nil {
paddy@1 94 ctx.RenderError(w, URIFormatError(redirectURI))
paddy@1 95 return
paddy@1 96 }
paddy@1 97 http.Redirect(w, r, redir, http.StatusFound)
paddy@1 98 return
paddy@1 99 }
paddy@1 100 if client.RedirectURI == "" {
paddy@1 101 ctx.RenderError(w, URIMissingError)
paddy@1 102 return
paddy@1 103 }
paddy@1 104
paddy@1 105 // check redirect uri
paddy@1 106 if redirectURI == "" {
paddy@1 107 redirectURI = client.RedirectURI
paddy@1 108 }
paddy@1 109 if err = validateURI(client.RedirectURI, redirectURI); err != nil {
paddy@1 110 ctx.RenderError(w, NewURIMismatchError(client.RedirectURI, redirectURI))
paddy@1 111 return
paddy@1 112 }
paddy@1 113
paddy@1 114 req := AuthRequest{
paddy@1 115 Client: client,
paddy@1 116 RedirectURI: redirectURI,
paddy@1 117 Scope: scope,
paddy@1 118 State: state,
paddy@1 119 }
paddy@0 120
paddy@0 121 requestType := AuthorizeRequestType(r.Form.Get("response_type"))
paddy@0 122 if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) {
paddy@0 123 switch requestType {
paddy@0 124 case CodeAuthRT:
paddy@1 125 req.handleCodeRequest(w, r, ctx)
paddy@0 126 return
paddy@0 127 case TokenAuthRT:
paddy@1 128 req.handleTokenRequest(w, r, ctx)
paddy@0 129 return
paddy@0 130 }
paddy@0 131 }
paddy@1 132 redir, err := req.GetErrorRedirect(ErrorInvalidRequest, "Invalid response type.", ctx.Config.DocumentationDomain)
paddy@1 133 if err != nil {
paddy@1 134 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 135 return
paddy@1 136 }
paddy@1 137 http.Redirect(w, r, redir, http.StatusFound)
paddy@0 138 }
paddy@0 139
paddy@1 140 func (req AuthRequest) handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
paddy@0 141
paddy@1 142 if r.Method == "GET" {
paddy@1 143 ctx.RenderConfirmation(w)
paddy@0 144 return
paddy@1 145 } else if r.Method != "POST" {
paddy@1 146 ctx.RenderError(w, InvalidMethodError)
paddy@0 147 return
paddy@0 148 }
paddy@0 149
paddy@1 150 if err := validateSession(r); err == ErrorNotAuthenticated {
paddy@1 151 ctx.RenderLogin(w)
paddy@1 152 return
paddy@1 153 } else if err != nil {
paddy@1 154 ctx.RenderError(w, err)
paddy@0 155 return
paddy@0 156 }
paddy@0 157
paddy@1 158 if r.FormValue("approved") != "true" {
paddy@1 159 redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
paddy@1 160 if err != nil {
paddy@1 161 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 162 return
paddy@1 163 }
paddy@1 164 http.Redirect(w, r, redir, http.StatusFound)
paddy@0 165 return
paddy@0 166 }
paddy@0 167
paddy@1 168 data := AuthorizeData{AuthRequest: req}
paddy@1 169
paddy@1 170 data.ExpiresIn = ctx.Config.AuthorizationExpiration
paddy@1 171 data.Code = newToken()
paddy@1 172 data.CreatedAt = time.Now()
paddy@1 173
paddy@1 174 err := ctx.Tokens.SaveAuthorization(data)
paddy@1 175 if err != nil {
paddy@1 176 redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
paddy@1 177 if err != nil {
paddy@1 178 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 179 return
paddy@1 180 }
paddy@1 181 http.Redirect(w, r, redir, http.StatusFound)
paddy@1 182 return
paddy@0 183 }
paddy@0 184
paddy@1 185 redir, err := data.GetRedirect()
paddy@1 186 if err != nil {
paddy@1 187 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 188 return
paddy@1 189 }
paddy@1 190 http.Redirect(w, r, redir, http.StatusFound)
paddy@0 191 }
paddy@0 192
paddy@1 193 func (req AuthRequest) handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
paddy@0 194
paddy@1 195 if r.Method == "GET" {
paddy@1 196 ctx.RenderConfirmation(w)
paddy@1 197 return
paddy@1 198 } else if r.Method != "POST" {
paddy@1 199 ctx.RenderError(w, InvalidMethodError)
paddy@1 200 return
paddy@1 201 }
paddy@0 202
paddy@1 203 if err := validateSession(r); err == ErrorNotAuthenticated {
paddy@1 204 ctx.RenderLogin(w)
paddy@1 205 return
paddy@1 206 } else if err != nil {
paddy@1 207 ctx.RenderError(w, err)
paddy@1 208 return
paddy@1 209 }
paddy@1 210
paddy@1 211 if r.FormValue("approved") != "true" {
paddy@1 212 redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
paddy@0 213 if err != nil {
paddy@1 214 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@0 215 return
paddy@0 216 }
paddy@1 217 http.Redirect(w, r, redir, http.StatusFound)
paddy@1 218 return
paddy@1 219 }
paddy@0 220
paddy@1 221 data := AccessData{AuthRequest: req}
paddy@1 222
paddy@1 223 err := fillTokens(&data, false, ctx)
paddy@1 224 if err != nil {
paddy@1 225 ctx.RenderError(w, InternalServerError)
paddy@1 226 return
paddy@0 227 }
paddy@1 228
paddy@1 229 redir, err := data.GetRedirect(true)
paddy@1 230 if err != nil {
paddy@1 231 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 232 return
paddy@1 233 }
paddy@1 234 http.Redirect(w, r, redir, http.StatusFound)
paddy@0 235 }
paddy@0 236
paddy@1 237 func (data AuthorizeData) GetRedirect() (string, error) {
paddy@1 238 u, err := url.Parse(data.RedirectURI)
paddy@1 239 if err != nil {
paddy@1 240 return "", err
paddy@1 241 }
paddy@1 242
paddy@1 243 // add parameters
paddy@1 244 q := u.Query()
paddy@1 245 q.Set("code", data.Code)
paddy@1 246 q.Set("state", data.State)
paddy@1 247 u.RawQuery = q.Encode()
paddy@1 248
paddy@1 249 return u.String(), nil
paddy@0 250 }
paddy@0 251
paddy@1 252 func (req AuthRequest) GetErrorRedirect(code, description, uriBase string) (string, error) {
paddy@1 253 u, err := url.Parse(req.RedirectURI)
paddy@1 254 if err != nil {
paddy@1 255 return "", err
paddy@1 256 }
paddy@1 257
paddy@1 258 // add parameters
paddy@1 259 q := u.Query()
paddy@1 260 q.Set("error", code)
paddy@1 261 q.Set("error_description", description)
paddy@1 262 q.Set("error_uri", strings.Join([]string{
paddy@1 263 strings.TrimRight(uriBase, "/"),
paddy@1 264 strings.TrimLeft(code, "/"),
paddy@1 265 }, "/"))
paddy@1 266 q.Set("state", req.State)
paddy@1 267 u.RawQuery = q.Encode()
paddy@1 268
paddy@1 269 return u.String(), nil
paddy@0 270 }