auth
auth/token.go
Update CheckEndpoints for strict checking, add CountEndpoints. Create a "strict" mode for CheckEndpoints that will only return true on an exact match, and update the memstore implementation accordingly. Add tests to make sure that the strict mode is adhered to. We need this mode because in certain situations (e.g., the client has more than one endpoint registered), the spec demands a full-string comparison. Add a CountEndpoints method to the ClientStore that will return the number of endpoints registered for a specific client. As we just mentioned, the rules for how a redirect URI is validated depend upon the number of endpoints a client has registered, so we need to be able to get at that number.
| paddy@28 | 1 package auth |
| paddy@28 | 2 |
| paddy@28 | 3 import ( |
| paddy@28 | 4 "errors" |
| paddy@28 | 5 "time" |
| paddy@28 | 6 |
| paddy@45 | 7 "code.secondbit.org/uuid" |
| paddy@28 | 8 ) |
| paddy@28 | 9 |
| paddy@28 | 10 var ( |
| paddy@49 | 11 ErrNoTokenStore = errors.New("no TokenStore was specified for the Context") |
| paddy@49 | 12 ErrTokenNotFound = errors.New("token not found in TokenStore") |
| paddy@49 | 13 ErrTokenAlreadyExists = errors.New("token already exists in TokenStore") |
| paddy@28 | 14 ) |
| paddy@28 | 15 |
| paddy@28 | 16 type Token struct { |
| paddy@28 | 17 AccessToken string |
| paddy@28 | 18 RefreshToken string |
| paddy@28 | 19 Created time.Time |
| paddy@28 | 20 ExpiresIn int32 |
| paddy@28 | 21 TokenType string |
| paddy@28 | 22 Scope string |
| paddy@28 | 23 ProfileID uuid.ID |
| paddy@28 | 24 } |
| paddy@28 | 25 |
| paddy@28 | 26 type TokenStore interface { |
| paddy@28 | 27 GetToken(token string, refresh bool) (Token, error) |
| paddy@28 | 28 SaveToken(token Token) error |
| paddy@28 | 29 RemoveToken(token string) error |
| paddy@28 | 30 GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) |
| paddy@28 | 31 } |
| paddy@28 | 32 |
| paddy@28 | 33 func (m *Memstore) GetToken(token string, refresh bool) (Token, error) { |
| paddy@28 | 34 if refresh { |
| paddy@28 | 35 t, err := m.lookupTokenByRefresh(token) |
| paddy@28 | 36 if err != nil { |
| paddy@28 | 37 return Token{}, err |
| paddy@28 | 38 } |
| paddy@28 | 39 token = t |
| paddy@28 | 40 } |
| paddy@28 | 41 m.tokenLock.RLock() |
| paddy@28 | 42 defer m.tokenLock.RUnlock() |
| paddy@28 | 43 result, ok := m.tokens[token] |
| paddy@28 | 44 if !ok { |
| paddy@28 | 45 return Token{}, ErrTokenNotFound |
| paddy@28 | 46 } |
| paddy@28 | 47 return result, nil |
| paddy@28 | 48 } |
| paddy@28 | 49 |
| paddy@28 | 50 func (m *Memstore) SaveToken(token Token) error { |
| paddy@28 | 51 m.tokenLock.Lock() |
| paddy@28 | 52 defer m.tokenLock.Unlock() |
| paddy@28 | 53 _, ok := m.tokens[token.AccessToken] |
| paddy@28 | 54 if ok { |
| paddy@28 | 55 return ErrTokenAlreadyExists |
| paddy@28 | 56 } |
| paddy@28 | 57 m.tokens[token.AccessToken] = token |
| paddy@28 | 58 if token.RefreshToken != "" { |
| paddy@28 | 59 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken |
| paddy@28 | 60 } |
| paddy@28 | 61 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok { |
| paddy@28 | 62 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken) |
| paddy@28 | 63 } else { |
| paddy@28 | 64 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken} |
| paddy@28 | 65 } |
| paddy@28 | 66 return nil |
| paddy@28 | 67 } |
| paddy@28 | 68 |
| paddy@28 | 69 func (m *Memstore) RemoveToken(token string) error { |
| paddy@28 | 70 m.tokenLock.Lock() |
| paddy@28 | 71 defer m.tokenLock.Unlock() |
| paddy@28 | 72 t, ok := m.tokens[token] |
| paddy@28 | 73 if !ok { |
| paddy@28 | 74 return ErrTokenNotFound |
| paddy@28 | 75 } |
| paddy@28 | 76 delete(m.tokens, token) |
| paddy@28 | 77 if t.RefreshToken != "" { |
| paddy@28 | 78 delete(m.refreshTokenLookup, t.RefreshToken) |
| paddy@28 | 79 } |
| paddy@28 | 80 pos := -1 |
| paddy@28 | 81 for p, item := range m.profileTokenLookup[t.ProfileID.String()] { |
| paddy@28 | 82 if item == token { |
| paddy@28 | 83 pos = p |
| paddy@28 | 84 break |
| paddy@28 | 85 } |
| paddy@28 | 86 } |
| paddy@28 | 87 if pos >= 0 { |
| paddy@28 | 88 m.profileTokenLookup[t.ProfileID.String()] = append(m.profileTokenLookup[t.ProfileID.String()][:pos], m.profileTokenLookup[t.ProfileID.String()][pos+1:]...) |
| paddy@28 | 89 } |
| paddy@28 | 90 return nil |
| paddy@28 | 91 } |
| paddy@28 | 92 |
| paddy@28 | 93 func (m *Memstore) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) { |
| paddy@28 | 94 ids, err := m.lookupTokensByProfileID(profileID.String()) |
| paddy@28 | 95 if err != nil { |
| paddy@28 | 96 return []Token{}, err |
| paddy@28 | 97 } |
| paddy@28 | 98 if len(ids) > num+offset { |
| paddy@28 | 99 ids = ids[offset : num+offset] |
| paddy@28 | 100 } else if len(ids) > offset { |
| paddy@28 | 101 ids = ids[offset:] |
| paddy@28 | 102 } else { |
| paddy@28 | 103 return []Token{}, nil |
| paddy@28 | 104 } |
| paddy@28 | 105 tokens := []Token{} |
| paddy@28 | 106 for _, id := range ids { |
| paddy@28 | 107 token, err := m.GetToken(id, false) |
| paddy@28 | 108 if err != nil { |
| paddy@28 | 109 return []Token{}, err |
| paddy@28 | 110 } |
| paddy@28 | 111 tokens = append(tokens, token) |
| paddy@28 | 112 } |
| paddy@28 | 113 return tokens, nil |
| paddy@28 | 114 } |