auth

Paddy 2014-08-16 Parent:0ccace901036 Child:e6a44cfda658

21:51700827b6ee Go to Latest

auth/authorize.go

Redirect after login. After a successful login, redirect based on a query parameter. Only allow redirections to the domain listed in the config and its subdomains. If no redirect is specified, redirect to the root of the domain listed in the config.

History
paddy@6 1 package auth
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@20 141 if r.Method != "GET" && r.Method != "POST" {
paddy@1 142 ctx.RenderError(w, InvalidMethodError)
paddy@0 143 return
paddy@0 144 }
paddy@0 145
paddy@2 146 if err := validateSession(r, ctx); err == ErrorNotAuthenticated {
paddy@19 147 // TODO: redirect to login
paddy@1 148 return
paddy@1 149 } else if err != nil {
paddy@1 150 ctx.RenderError(w, err)
paddy@0 151 return
paddy@0 152 }
paddy@0 153
paddy@20 154 if r.Method == "GET" {
paddy@20 155 ctx.RenderConfirmation(w, r, req)
paddy@20 156 return
paddy@20 157 }
paddy@20 158
paddy@1 159 if r.FormValue("approved") != "true" {
paddy@1 160 redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
paddy@1 161 if err != nil {
paddy@1 162 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 163 return
paddy@1 164 }
paddy@1 165 http.Redirect(w, r, redir, http.StatusFound)
paddy@0 166 return
paddy@0 167 }
paddy@0 168
paddy@1 169 data := AuthorizeData{AuthRequest: req}
paddy@1 170
paddy@1 171 data.ExpiresIn = ctx.Config.AuthorizationExpiration
paddy@1 172 data.Code = newToken()
paddy@1 173 data.CreatedAt = time.Now()
paddy@1 174
paddy@1 175 err := ctx.Tokens.SaveAuthorization(data)
paddy@1 176 if err != nil {
paddy@1 177 redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain)
paddy@1 178 if err != nil {
paddy@1 179 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 180 return
paddy@1 181 }
paddy@1 182 http.Redirect(w, r, redir, http.StatusFound)
paddy@1 183 return
paddy@0 184 }
paddy@0 185
paddy@1 186 redir, err := data.GetRedirect()
paddy@1 187 if err != nil {
paddy@1 188 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 189 return
paddy@1 190 }
paddy@1 191 http.Redirect(w, r, redir, http.StatusFound)
paddy@0 192 }
paddy@0 193
paddy@1 194 func (req AuthRequest) handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
paddy@20 195 if r.Method != "GET" && r.Method != "POST" {
paddy@1 196 ctx.RenderError(w, InvalidMethodError)
paddy@1 197 return
paddy@1 198 }
paddy@0 199
paddy@2 200 if err := validateSession(r, ctx); err == ErrorNotAuthenticated {
paddy@19 201 // TODO: redirect to login
paddy@1 202 return
paddy@1 203 } else if err != nil {
paddy@1 204 ctx.RenderError(w, err)
paddy@1 205 return
paddy@1 206 }
paddy@1 207
paddy@20 208 if r.Method == "GET" {
paddy@20 209 ctx.RenderConfirmation(w, r, req)
paddy@20 210 return
paddy@20 211 }
paddy@20 212
paddy@1 213 if r.FormValue("approved") != "true" {
paddy@1 214 redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain)
paddy@0 215 if err != nil {
paddy@1 216 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@0 217 return
paddy@0 218 }
paddy@1 219 http.Redirect(w, r, redir, http.StatusFound)
paddy@1 220 return
paddy@1 221 }
paddy@0 222
paddy@1 223 data := AccessData{AuthRequest: req}
paddy@1 224
paddy@1 225 err := fillTokens(&data, false, ctx)
paddy@1 226 if err != nil {
paddy@1 227 ctx.RenderError(w, InternalServerError)
paddy@1 228 return
paddy@0 229 }
paddy@1 230
paddy@1 231 redir, err := data.GetRedirect(true)
paddy@1 232 if err != nil {
paddy@1 233 ctx.RenderError(w, URIFormatError(req.RedirectURI))
paddy@1 234 return
paddy@1 235 }
paddy@1 236 http.Redirect(w, r, redir, http.StatusFound)
paddy@0 237 }
paddy@0 238
paddy@1 239 func (data AuthorizeData) GetRedirect() (string, error) {
paddy@1 240 u, err := url.Parse(data.RedirectURI)
paddy@1 241 if err != nil {
paddy@1 242 return "", err
paddy@1 243 }
paddy@1 244
paddy@1 245 // add parameters
paddy@1 246 q := u.Query()
paddy@1 247 q.Set("code", data.Code)
paddy@1 248 q.Set("state", data.State)
paddy@1 249 u.RawQuery = q.Encode()
paddy@1 250
paddy@1 251 return u.String(), nil
paddy@0 252 }
paddy@0 253
paddy@1 254 func (req AuthRequest) GetErrorRedirect(code, description, uriBase string) (string, error) {
paddy@1 255 u, err := url.Parse(req.RedirectURI)
paddy@1 256 if err != nil {
paddy@1 257 return "", err
paddy@1 258 }
paddy@1 259
paddy@1 260 // add parameters
paddy@1 261 q := u.Query()
paddy@1 262 q.Set("error", code)
paddy@1 263 q.Set("error_description", description)
paddy@1 264 q.Set("error_uri", strings.Join([]string{
paddy@1 265 strings.TrimRight(uriBase, "/"),
paddy@1 266 strings.TrimLeft(code, "/"),
paddy@1 267 }, "/"))
paddy@1 268 q.Set("state", req.State)
paddy@1 269 u.RawQuery = q.Encode()
paddy@1 270
paddy@1 271 return u.String(), nil
paddy@0 272 }