auth

Paddy 2014-09-01 Parent:1aa3a85ff853

25:9fc5fd8196b4 Go to Latest

auth/authorize.go.old

Rough out client. Remove our old client implementation, and start exploring a new ClientStore interface for storing and retrieving Client data. Keep track of a website for clients.

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