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