auth

Paddy 2014-08-16 Parent:1f04b1146cad Child:0ccace901036

19:9fe684b33b3d Go to Latest

auth/authorize.go

Implement session management and move login. Add a session store interface to validate and retrieve data about sessions. Implement session management in endpoints that need session support. Move the login functionality from inlined into the OAuth flow into its own handler, that the OAuth flow will redirect to. The login functionality should take a redirect URL parameter to return to the OAuth flow when login is completed.

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