auth

Paddy 2014-08-01 Parent:7a6f64db7246 Child:65c49af1ed3f

1:7b9e0fc20256 Go to Latest

auth/access.go

Continue our descent to horribleness. Remove all the nonsense about "extensibility" and "clean separation of concerns", instead hardcoding connections to decisions. Remove all those "test" things that stopped passing.

History
1 package oauth2
3 import (
4 "net/http"
5 "net/url"
6 "time"
8 "strconv"
9 "secondbit.org/uuid"
10 )
12 // GrantType is the type for OAuth param `grant_type`
13 type GrantType string
15 const (
16 AuthorizationCodeGrant GrantType = "authorization_code"
17 RefreshTokenGrant = "refresh_token"
18 PasswordGrant = "password"
19 ClientCredentialsGrant = "client_credentials"
20 )
22 // AccessData represents an access grant (tokens, expiration, client, etc)
23 type AccessData struct {
24 PreviousAuthorizeData *AuthorizeData
25 PreviousAccessData *AccessData // previous access data, when refreshing
26 AccessToken string
27 RefreshToken string
28 ExpiresIn int32
29 CreatedAt time.Time
30 TokenType string
31 ProfileID uuid.ID
32 AuthRequest
33 }
35 // IsExpired returns true if access expired
36 func (d *AccessData) IsExpired() bool {
37 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now())
38 }
40 // ExpireAt returns the expiration date
41 func (d *AccessData) ExpireAt() time.Time {
42 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
43 }
45 // HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests.
46 func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
47 // Only allow GET or POST
48 if r.Method != "POST" {
49 if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest {
50 // TODO: return error
51 return
52 }
53 }
55 grantType := GrantType(r.Form.Get("grant_type"))
56 if ctx.Config.AllowedAccessTypes.Exists(grantType) {
57 switch grantType {
58 case AuthorizationCodeGrant:
59 handleAuthorizationCodeRequest(w, r, ctx)
60 case RefreshTokenGrant:
61 handleRefreshTokenRequest(w, r, ctx)
62 case PasswordGrant:
63 handlePasswordRequest(w, r, ctx)
64 case ClientCredentialsGrant:
65 handleClientCredentialsRequest(w, r, ctx)
66 default:
67 // TODO: return error
68 return
69 }
70 }
71 }
73 func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
74 // get client authentication
75 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
76 if err != nil {
77 // TODO: return error
78 return
79 }
81 code := r.Form.Get("code")
82 // "code" is required
83 if code == "" {
84 // TODO: return error
85 return
86 }
88 // must have a valid client
89 client, err := getClient(auth, ctx)
90 if err != nil {
91 // TODO: return error
92 return
93 }
95 // must be a valid authorization code
96 authData, err := ctx.Tokens.GetAuthorization(code)
97 if err != nil {
98 // TODO: return error
99 return
100 }
101 if authData.Client.RedirectURI == "" {
102 // TODO: return error
103 return
104 }
105 if authData.IsExpired() {
106 return // TODO: return error
107 }
109 // code must be from the client
110 if !authData.Client.ID.Equal(client.ID) {
111 // TODO: return error
112 return
113 }
115 // check redirect uri
116 redirectURI := r.Form.Get("redirect_uri")
117 if redirectURI == "" {
118 redirectURI = client.RedirectURI
119 }
120 if err = validateURI(client.RedirectURI, redirectURI); err != nil {
121 // TODO: return error
122 return
123 }
124 if authData.RedirectURI != redirectURI {
125 // TODO: return error
126 return
127 }
129 data := AccessData{
130 AuthRequest: AuthRequest{
131 Client: client,
132 RedirectURI: redirectURI,
133 Scope: authData.Scope,
134 },
135 PreviousAuthorizeData: &authData,
136 }
138 err = fillTokens(&data, true, ctx)
139 if err != nil {
140 // TODO: return error
141 return
142 }
143 // TODO: write data
144 }
146 func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
147 // get client authentication
148 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
149 if err != nil {
150 // TODO: return error
151 return
152 }
154 code := r.Form.Get("refresh_token")
156 // "refresh_token" is required
157 if code == "" {
158 // TODO: return error
159 return
160 }
162 // must have a valid client
163 client, err := getClient(auth, ctx)
164 if err != nil {
165 // TODO: return error
166 return
167 }
169 // must be a valid refresh code
170 refreshData, err := ctx.Tokens.GetRefresh(code)
171 if err != nil {
172 // TODO: return error
173 return
174 }
175 if refreshData.Client.RedirectURI == "" {
176 // TODO: return error
177 return
178 }
180 // client must be the same as the previous token
181 if !refreshData.Client.ID.Equal(client.ID) {
182 // TODO: return error
183 return
184 }
186 // set rest of data
187 redirectURI := r.Form.Get("redirect_uri")
188 if redirectURI == "" {
189 redirectURI = refreshData.RedirectURI
190 }
191 scope := r.Form.Get("scope")
192 if scope == "" {
193 scope = refreshData.Scope
194 }
196 data := AccessData{
197 AuthRequest: AuthRequest{
198 Client: client,
199 RedirectURI: redirectURI,
200 Scope: scope,
201 },
202 PreviousAccessData: &refreshData,
203 }
204 err = fillTokens(&data, true, ctx)
205 if err != nil {
206 // TODO: return error
207 return
208 }
209 // TODO: write data
210 }
212 func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
213 // get client authentication
214 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
215 if err != nil {
216 // TODO: return error
217 return
218 }
220 username := r.Form.Get("username")
221 password := r.Form.Get("password")
222 scope := r.Form.Get("scope")
224 // "username" and "password" is required
225 if username == "" || password == "" {
226 // TODO: return error
227 return
228 }
230 // must have a valid client
231 client, err := getClient(auth, ctx)
232 if err != nil {
233 // TODO: return error
234 return
235 }
237 // set redirect uri
238 redirectURI := r.Form.Get("redirect_uri")
239 if redirectURI == "" {
240 redirectURI = client.RedirectURI
241 }
243 _, err = ctx.Profiles.GetProfile(username, password)
244 if err != nil {
245 // TODO: return error
246 return
247 }
249 data := AccessData{
250 AuthRequest: AuthRequest{
251 Client: client,
252 RedirectURI: redirectURI,
253 Scope: scope,
254 },
255 }
257 err = fillTokens(&data, true, ctx)
258 if err != nil {
259 // TODO: return error
260 return
261 }
263 // TODO: write data
264 }
266 func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
267 // get client authentication
268 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
269 if err != nil {
270 // TODO: return error
271 return
272 }
274 scope := r.Form.Get("scope")
276 // must have a valid client
277 client, err := getClient(auth, ctx)
278 if err != nil {
279 // TODO: return error
280 return
281 }
283 // set redirect uri
284 redirectURI := r.Form.Get("redirect_uri")
285 if redirectURI == "" {
286 redirectURI = client.RedirectURI
287 }
289 data := AccessData{
290 AuthRequest: AuthRequest{
291 Client: client,
292 RedirectURI: redirectURI,
293 Scope: scope,
294 },
295 }
297 err = fillTokens(&data, true, ctx)
298 if err != nil {
299 // TODO: return error
300 return
301 }
303 // TODO: write data
304 }
306 func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error {
307 var err error
309 // generate access token
310 data.AccessToken = newToken()
311 if includeRefresh {
312 data.RefreshToken = newToken()
313 }
315 // save access token
316 err = ctx.Tokens.SaveAccess(*data)
317 if err != nil {
318 // TODO: abstract out error
319 return err
320 }
322 // remove authorization token
323 if data.PreviousAuthorizeData != nil {
324 err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code)
325 if err != nil {
326 // TODO: log error
327 }
328 }
330 // remove previous access token
331 if data.PreviousAccessData != nil {
332 if data.PreviousAccessData.RefreshToken != "" {
333 err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken)
334 if err != nil {
335 // TODO: log error
336 }
337 }
338 err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken)
339 if err != nil {
340 // TODO: log error
341 }
342 }
344 data.TokenType = ctx.Config.TokenType
345 data.ExpiresIn = ctx.Config.AccessExpiration
346 data.CreatedAt = time.Now()
347 return nil
348 }
350 func (data AccessData) GetRedirect(fragment bool) (string, error) {
351 u, err := url.Parse(data.RedirectURI)
352 if err != nil {
353 return "", err
354 }
356 // add parameters
357 q := u.Query()
358 q.Set("access_token", data.AccessToken)
359 q.Set("token_type", data.TokenType)
360 q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10))
361 if data.RefreshToken != "" {
362 q.Set("refresh_token", data.RefreshToken)
363 }
364 if data.Scope != "" {
365 q.Set("scope", data.Scope)
366 }
367 if len(data.ProfileID) > 0 {
368 q.Set("profile", data.ProfileID.String())
369 }
370 if fragment {
371 u.RawQuery = ""
372 u.Fragment = q.Encode()
373 } else {
374 u.RawQuery = q.Encode()
375 }
377 return u.String(), nil
378 }
380 // getClient looks up and authenticates the basic auth using the given
381 // storage. Sets an error on the response if auth fails or a server error occurs.
382 func getClient(auth BasicAuth, ctx Context) (Client, error) {
383 id, err := uuid.Parse(auth.Username)
384 if err != nil {
385 return Client{}, err
386 }
387 client, err := ctx.Clients.GetClient(id)
388 if err != nil {
389 // TODO: abstract out errors
390 return Client{}, err
391 }
392 if client.Secret != auth.Password {
393 // TODO: return E_UNAUTHORIZED_CLIENT error
394 return Client{}, nil
395 }
396 if client.RedirectURI == "" {
397 // TODO: return E_UNAUTHORIZED_CLIENT error
398 return Client{}, nil
399 }
400 return client, nil
401 }