auth

Paddy 2015-01-18 Parent:0a1e16b9c141 Child:dcd2125c4f57

124:d14f0a81498c Go to Latest

auth/token.go

Fill out token.CreatedFrom. Add a GrantType.AuditString() string method that will return a string for an audit log. Basically, it returns enough information to identify how the token got created. For client credentials, that's just the string "client_credentials". For user credentials, that's just the string "credentials". For auth codes, that's "authcode:", followed by the code used. For refresh tokens, that's "refresh_token:", followed by the refresh token used.

History
1 package auth
3 import (
4 "encoding/json"
5 "errors"
6 "log"
7 "net/http"
8 "time"
10 "code.secondbit.org/uuid.hg"
11 )
13 const (
14 defaultTokenExpiration = 3600 // one hour
15 defaultRefreshTokenExpiration = 86400 // one day
16 )
18 func init() {
19 RegisterGrantType("refresh_token", GrantType{
20 Validate: refreshTokenValidate,
21 Invalidate: refreshTokenInvalidate,
22 IssuesRefresh: true,
23 ReturnToken: RenderJSONToken,
24 AuditString: refreshTokenAuditString,
25 })
26 }
28 var (
29 // ErrNoTokenStore is returned when a Context tries to act on a tokenStore without setting one first.
30 ErrNoTokenStore = errors.New("no tokenStore was specified for the Context")
31 // ErrTokenNotFound is returned when a Token is requested but not found in a tokenStore.
32 ErrTokenNotFound = errors.New("token not found in tokenStore")
33 // ErrTokenAlreadyExists is returned when a Token is added to a tokenStore, but another Token with
34 // the same AccessToken property already exists in the tokenStore.
35 ErrTokenAlreadyExists = errors.New("token already exists in tokenStore")
36 )
38 // Token represents an access and/or refresh token that the Client can use to access user data
39 // or obtain a new access token.
40 type Token struct {
41 AccessToken string
42 RefreshToken string
43 Created time.Time
44 CreatedFrom string
45 ExpiresIn int32
46 RefreshExpiresIn int32
47 TokenType string
48 Scope string
49 ProfileID uuid.ID
50 ClientID uuid.ID
51 Revoked bool
52 RefreshRevoked bool
53 }
55 type tokenStore interface {
56 getToken(token string, refresh bool) (Token, error)
57 saveToken(token Token) error
58 removeToken(token string) error
59 revokeToken(token string, refresh bool) error
60 getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
61 }
63 func (m *memstore) getToken(token string, refresh bool) (Token, error) {
64 if refresh {
65 t, err := m.lookupTokenByRefresh(token)
66 if err != nil {
67 return Token{}, err
68 }
69 token = t
70 }
71 m.tokenLock.RLock()
72 defer m.tokenLock.RUnlock()
73 result, ok := m.tokens[token]
74 if !ok {
75 return Token{}, ErrTokenNotFound
76 }
77 return result, nil
78 }
80 func (m *memstore) saveToken(token Token) error {
81 m.tokenLock.Lock()
82 defer m.tokenLock.Unlock()
83 _, ok := m.tokens[token.AccessToken]
84 if ok {
85 return ErrTokenAlreadyExists
86 }
87 m.tokens[token.AccessToken] = token
88 if token.RefreshToken != "" {
89 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
90 }
91 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
92 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
93 } else {
94 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
95 }
96 return nil
97 }
99 func (m *memstore) removeToken(token string) error {
100 m.tokenLock.Lock()
101 defer m.tokenLock.Unlock()
102 t, ok := m.tokens[token]
103 if !ok {
104 return ErrTokenNotFound
105 }
106 delete(m.tokens, token)
107 if t.RefreshToken != "" {
108 delete(m.refreshTokenLookup, t.RefreshToken)
109 }
110 pos := -1
111 for p, item := range m.profileTokenLookup[t.ProfileID.String()] {
112 if item == token {
113 pos = p
114 break
115 }
116 }
117 if pos >= 0 {
118 m.profileTokenLookup[t.ProfileID.String()] = append(m.profileTokenLookup[t.ProfileID.String()][:pos], m.profileTokenLookup[t.ProfileID.String()][pos+1:]...)
119 }
120 return nil
121 }
123 func (m *memstore) revokeToken(token string, refresh bool) error {
124 if refresh {
125 t, err := m.lookupTokenByRefresh(token)
126 if err != nil {
127 return err
128 }
129 token = t
130 }
131 m.tokenLock.Lock()
132 defer m.tokenLock.Unlock()
133 t, ok := m.tokens[token]
134 if !ok {
135 return ErrTokenNotFound
136 }
137 if refresh {
138 t.RefreshRevoked = true
139 } else {
140 t.Revoked = true
141 }
142 m.tokens[token] = t
143 return nil
144 }
146 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
147 ids, err := m.lookupTokensByProfileID(profileID.String())
148 if err != nil {
149 return []Token{}, err
150 }
151 if len(ids) > num+offset {
152 ids = ids[offset : num+offset]
153 } else if len(ids) > offset {
154 ids = ids[offset:]
155 } else {
156 return []Token{}, nil
157 }
158 tokens := []Token{}
159 for _, id := range ids {
160 token, err := m.getToken(id, false)
161 if err != nil {
162 return []Token{}, err
163 }
164 tokens = append(tokens, token)
165 }
166 return tokens, nil
167 }
169 func refreshTokenValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) {
170 enc := json.NewEncoder(w)
171 refresh := r.PostFormValue("refresh_token")
172 if refresh == "" {
173 w.WriteHeader(http.StatusBadRequest)
174 renderJSONError(enc, "invalid_request")
175 return
176 }
177 token, err := context.GetToken(refresh, true)
178 if err != nil {
179 if err == ErrTokenNotFound {
180 w.WriteHeader(http.StatusBadRequest)
181 renderJSONError(enc, "invalid_grant")
182 return
183 }
184 log.Println("Error exchanging refresh token:", err)
185 w.WriteHeader(http.StatusInternalServerError)
186 renderJSONError(enc, "server_error")
187 return
188 }
189 clientID, _, ok := getClientAuth(w, r, true)
190 if !ok {
191 return
192 }
193 if !token.ClientID.Equal(clientID) {
194 w.WriteHeader(http.StatusBadRequest)
195 renderJSONError(enc, "invalid_grant")
196 return
197 }
198 if token.RefreshRevoked {
199 w.WriteHeader(http.StatusBadRequest)
200 renderJSONError(enc, "invalid_grant")
201 return
202 }
203 expires := token.Created.Add(time.Duration(token.RefreshExpiresIn) * time.Second)
204 if expires.Before(time.Now()) {
205 w.WriteHeader(http.StatusBadRequest)
206 renderJSONError(enc, "invalid_grant")
207 return
208 }
209 return token.Scope, token.ProfileID, true
210 }
212 func refreshTokenInvalidate(r *http.Request, context Context) error {
213 refresh := r.PostFormValue("refresh_token")
214 if refresh == "" {
215 return ErrTokenNotFound
216 }
217 return context.RevokeToken(refresh, true)
218 }
220 func refreshTokenAuditString(r *http.Request) string {
221 return "refresh_token:" + r.PostFormValue("refresh_token")
222 }