auth

Paddy 2014-08-16 Parent:0ccace901036

22:e6a44cfda658 Go to Latest

auth/authorize.go

Redirect unauthenticated users to the login page. Redirect unauthenticated users to the login page, and encode the current URL in the redirect_to param so that the user returns to the OAuth2 flow after they log in.

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@22 147 http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound)
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@22 201 http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound)
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 }