auth

Paddy 2014-08-13 Parent:0aa843a306cd Child:3423c552e249

5:7ae3f16002c1 Go to Latest

auth/authorize.go

Handle more errors. Handle client errors. Match redirect handling with the spec.

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@2 150 if err := validateSession(r, ctx); 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@2 203 if err := validateSession(r, ctx); 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 }