auth

Paddy 2015-01-19 Parent:dcd2125c4f57 Child:b714af0578dc

126:34de07217709 Go to Latest

auth/token.go

Test around client types and secrets. Implement a test that the CreateClient handler will correctly create a confidential client and issue a secret for it. Also, just generally test that clients that are confidential are issued secrets and clients that are public are not.

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