auth

Paddy 2014-07-18 Child:7b9e0fc20256

0:7a6f64db7246 Go to Latest

auth/access.go

Start rewriting the repo. This code originally was a carbon copy of https://github.com/RangelReale/osin, but I am methodically stripping out the extensible nature of it for a simpler interface, while simultaneously bringing the style into line with the Ducky style.

History
1 package oauth2
3 import (
4 "net/http"
5 "time"
7 "secondbit.org/uuid"
8 )
10 // GrantType is the type for OAuth param `grant_type`
11 type GrantType string
13 const (
14 AuthorizationCodeGrant GrantType = "authorization_code"
15 RefreshTokenGrant = "refresh_token"
16 PasswordGrant = "password"
17 ClientCredentialsGrant = "client_credentials"
18 AssertionGrant = "assertion"
19 ImplicitGrant = "__implicit"
20 )
22 // AccessRequest is a request for access tokens
23 type AccessRequest struct {
24 Code string
25 Client Client
26 AuthorizeData AuthorizeData
27 AccessData AccessData
28 RedirectURI string
29 Scope string
30 Username string
31 Password string
32 AssertionType string
33 Assertion string
35 // Token expiration in seconds. Change if different from default
36 Expiration int32
38 // Set if a refresh token should be generated
39 GenerateRefresh bool
40 }
42 // AccessData represents an access grant (tokens, expiration, client, etc)
43 type AccessData struct {
44 // Client information
45 Client Client
47 // Authorize data, for authorization code
48 AuthorizeData *AuthorizeData
50 // Previous access data, for refresh token
51 AccessData *AccessData
53 // Access token
54 AccessToken string
56 // Refresh Token. Can be blank
57 RefreshToken string
59 // Token expiration in seconds
60 ExpiresIn int32
62 // Requested scope
63 Scope string
65 // Redirect URI from request
66 RedirectURI string
68 // Date created
69 CreatedAt time.Time
70 }
72 // IsExpired returns true if access expired
73 func (d *AccessData) IsExpired() bool {
74 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now())
75 }
77 // ExpireAt returns the expiration date
78 func (d *AccessData) ExpireAt() time.Time {
79 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
80 }
82 // AccessTokenGen generates access tokens
83 type AccessTokenGen interface {
84 GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error)
85 }
87 // HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests.
88 func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
89 // Only allow GET or POST
90 if r.Method != "POST" {
91 if r.Method == "GET" && !ctx.Config.AllowGetAccessRequest {
92 // TODO: return error
93 return
94 }
95 }
97 err := r.ParseForm()
98 if err != nil {
99 // TODO: return error
100 return
101 }
103 grantType := GrantType(r.Form.Get("grant_type"))
104 if ctx.Config.AllowedAccessTypes.Exists(grantType) {
105 switch grantType {
106 case AuthorizationCodeGrant:
107 handleAuthorizationCodeRequest(w, r, ctx)
108 case RefreshTokenGrant:
109 handleRefreshTokenRequest(w, r, ctx)
110 case PasswordGrant:
111 handlePasswordRequest(w, r, ctx)
112 case ClientCredentialsGrant:
113 handleClientCredentialsRequest(w, r, ctx)
114 case AssertionGrant:
115 handleAssertionRequest(w, r, ctx)
116 default:
117 // TODO: return error
118 return
119 }
120 }
121 }
123 func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
124 // get client authentication
125 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
126 if err != nil {
127 // TODO: return error
128 return
129 }
131 // generate access token
132 ret := AccessRequest{
133 Code: r.Form.Get("code"),
134 RedirectURI: r.Form.Get("redirect_uri"),
135 GenerateRefresh: true,
136 Expiration: ctx.Config.AccessExpiration,
137 }
139 // "code" is required
140 if ret.Code == "" {
141 // TODO: return error
142 return
143 }
145 // must have a valid client
146 ret.Client, err = getClient(auth, ctx)
147 if err != nil {
148 // TODO: return error
149 return
150 }
152 // must be a valid authorization code
153 ret.AuthorizeData, err = loadAuthorize(ret.Code, ctx)
154 if err != nil {
155 // TODO: return error
156 return
157 }
158 if ret.AuthorizeData.Client.RedirectURI == "" {
159 // TODO: return error
160 return
161 }
162 if ret.AuthorizeData.IsExpired() {
163 return // TODO: return error
164 }
166 // code must be from the client
167 if !ret.AuthorizeData.Client.ID.Equal(ret.Client.ID) {
168 // TODO: return error
169 return
170 }
172 // check redirect uri
173 if ret.RedirectURI == "" {
174 ret.RedirectURI = ret.Client.RedirectURI
175 }
176 if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil {
177 // TODO: return error
178 return
179 }
180 if ret.AuthorizeData.RedirectURI != ret.RedirectURI {
181 // TODO: return error
182 return
183 }
185 // set rest of data
186 ret.Scope = ret.AuthorizeData.Scope
187 // TODO: write ret
188 }
190 func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
191 // get client authentication
192 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
193 if err != nil {
194 // TODO: return error
195 return
196 }
198 // generate access token
199 ret := AccessRequest{
200 Code: r.Form.Get("refresh_token"),
201 Scope: r.Form.Get("scope"),
202 GenerateRefresh: true,
203 Expiration: ctx.Config.AccessExpiration,
204 }
206 // "refresh_token" is required
207 if ret.Code == "" {
208 // TODO: return error
209 return
210 }
212 // must have a valid client
213 ret.Client, err = getClient(auth, ctx)
214 if err != nil {
215 // TODO: return error
216 return
217 }
219 // must be a valid refresh code
220 ret.AccessData, err = loadRefresh(ret.Code, ctx)
221 if err != nil {
222 // TODO: return error
223 return
224 }
225 if ret.AccessData.Client.RedirectURI == "" {
226 // TODO: return error
227 return
228 }
230 // client must be the same as the previous token
231 if !ret.AccessData.Client.ID.Equal(ret.Client.ID) {
232 // TODO: return error
233 return
235 }
237 // set rest of data
238 ret.RedirectURI = ret.AccessData.RedirectURI
239 if ret.Scope == "" {
240 ret.Scope = ret.AccessData.Scope
241 }
243 // TODO: write ret
244 }
246 func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
247 // get client authentication
248 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
249 if err != nil {
250 // TODO: return error
251 return
252 }
254 // generate access token
255 ret := AccessRequest{
256 Username: r.Form.Get("username"),
257 Password: r.Form.Get("password"),
258 Scope: r.Form.Get("scope"),
259 GenerateRefresh: true,
260 Expiration: ctx.Config.AccessExpiration,
261 }
263 // "username" and "password" is required
264 if ret.Username == "" || ret.Password == "" {
265 // TODO: return error
266 return
267 }
269 // must have a valid client
270 ret.Client, err = getClient(auth, ctx)
271 if err != nil {
272 // TODO: return error
273 return
274 }
276 // set redirect uri
277 ret.RedirectURI = ret.Client.RedirectURI
279 // TODO: write ret
280 }
282 func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
283 // get client authentication
284 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
285 if err != nil {
286 // TODO: return error
287 return
288 }
290 // generate access token
291 ret := AccessRequest{
292 Scope: r.Form.Get("scope"),
293 GenerateRefresh: true,
294 Expiration: ctx.Config.AccessExpiration,
295 }
297 // must have a valid client
298 ret.Client, err = getClient(auth, ctx)
299 if err != nil {
300 // TODO: return error
301 return
302 }
304 // set redirect uri
305 ret.RedirectURI = ret.Client.RedirectURI
307 // TODO: write ret
308 }
310 func handleAssertionRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
311 // get client authentication
312 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams)
313 if err != nil {
314 // TODO: return error
315 return
316 }
318 // generate access token
319 ret := &AccessRequest{
320 Scope: r.Form.Get("scope"),
321 AssertionType: r.Form.Get("assertion_type"),
322 Assertion: r.Form.Get("assertion"),
323 GenerateRefresh: false, // assertion should NOT generate a refresh token, per the RFC
324 Expiration: ctx.Config.AccessExpiration,
325 }
327 // "assertion_type" and "assertion" is required
328 if ret.AssertionType == "" || ret.Assertion == "" {
329 // TODO: return error
330 return
331 }
333 // must have a valid client
334 ret.Client, err = getClient(auth, ctx)
335 if err != nil {
336 //TODO: return error
337 return
338 }
340 // set redirect uri
341 ret.RedirectURI = ret.Client.RedirectURI
343 // TODO: write ret
344 }
346 func FinishAccessRequest(w http.ResponseWriter, r *http.Request, ar AccessRequest, ctx Context) {
347 // TODO: check if authorized?
348 redirectURI := r.Form.Get("redirect_uri")
349 // Get redirect uri from AccessRequest if it's there (e.g., refresh token request)
350 if ar.RedirectURI != "" {
351 redirectURI = ar.RedirectURI
352 }
353 ret := AccessData{
354 Client: ar.Client,
355 AuthorizeData: &ar.AuthorizeData,
356 AccessData: &ar.AccessData,
357 RedirectURI: redirectURI,
358 CreatedAt: time.Now(),
359 ExpiresIn: ar.Expiration,
360 Scope: ar.Scope,
361 }
363 var err error
365 // generate access token
366 ret.AccessToken = newToken()
367 if ar.GenerateRefresh {
368 ret.RefreshToken = newToken()
369 }
371 // save access token
372 err = saveAccess(ret, ctx)
373 if err != nil {
374 // TODO: return error
375 return
376 }
378 // remove authorization token
379 if ret.AuthorizeData != nil {
380 err = removeAuthorize(ret.AuthorizeData.Code, ctx)
381 if err != nil {
382 // TODO: log error
383 }
384 }
386 // remove previous access token
387 if ret.AccessData != nil {
388 if ret.AccessData.RefreshToken != "" {
389 err = removeRefresh(ret.AccessData.RefreshToken, ctx)
390 if err != nil {
391 // TODO: log error
392 }
393 }
394 err = removeAccess(ret.AccessData.AccessToken, ctx)
395 if err != nil {
396 // TODO: log error
397 }
398 }
400 // output data
401 //w.Output["access_token"] = ret.AccessToken
402 //w.Output["token_type"] = ctx.Config.TokenType
403 //w.Output["expires_in"] = ret.ExpiresIn
404 //if ret.RefreshToken != "" {
405 // w.Output["refresh_token"] = ret.RefreshToken
406 //}
407 //if ar.Scope != "" {
408 // w.Output["scope"] = ar.Scope
409 //}
410 // TODO: write ret
411 }
413 // Helper Functions
415 // getClient looks up and authenticates the basic auth using the given
416 // storage. Sets an error on the response if auth fails or a server error occurs.
417 func getClient(auth BasicAuth, ctx Context) (Client, error) {
418 id, err := uuid.Parse(auth.Username)
419 if err != nil {
420 return Client{}, err
421 }
422 client, err := GetClient(id, ctx)
423 if err != nil {
424 // TODO: abstract out errors
425 return Client{}, err
426 }
427 if client.Secret != auth.Password {
428 // TODO: return E_UNAUTHORIZED_CLIENT error
429 return Client{}, nil
430 }
431 if client.RedirectURI == "" {
432 // TODO: return E_UNAUTHORIZED_CLIENT error
433 return Client{}, nil
434 }
435 return client, nil
436 }
438 func loadRefresh(code string, ctx Context) (AccessData, error) {
439 return AccessData{}, nil
440 }
442 func loadAccess(code string, ctx Context) (AccessData, error) {
443 return AccessData{}, nil
444 }
446 func saveAccess(data AccessData, ctx Context) error {
447 return nil
448 }
450 func removeAccess(token string, ctx Context) error {
451 return nil
452 }
454 func removeRefresh(token string, ctx Context) error {
455 return nil
456 }