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