auth
auth/client.go
Update error strings, add ErrNo*Store errors. Update error strings to consistently begin with a lowercase letter and end without punctuation, as per the Go style guide. See https://code.google.com/p/go-wiki/wiki/CodeReviewComments#Error_Strings For each type of Store, add an ErrNo*Store error (e.g., ErrNoTokenStore) variable, to prepare for our Context type, which will throw these errors when a Store is used without being set.
| paddy@6 | 1 package auth |
| paddy@0 | 2 |
| paddy@0 | 3 import ( |
| paddy@31 | 4 "errors" |
| paddy@41 | 5 "net/url" |
| paddy@49 | 6 "strings" |
| paddy@41 | 7 "time" |
| paddy@31 | 8 |
| paddy@45 | 9 "code.secondbit.org/uuid" |
| paddy@0 | 10 ) |
| paddy@0 | 11 |
| paddy@31 | 12 var ( |
| paddy@49 | 13 ErrNoClientStore = errors.New("no ClientStore was specified for the Context") |
| paddy@49 | 14 ErrClientNotFound = errors.New("client not found in ClientStore") |
| paddy@49 | 15 ErrClientAlreadyExists = errors.New("client already exists in ClientStore") |
| paddy@41 | 16 |
| paddy@49 | 17 ErrEmptyChange = errors.New("change must have at least one change in it") |
| paddy@49 | 18 ErrClientNameTooShort = errors.New("client name must be at least 2 characters") |
| paddy@49 | 19 ErrClientNameTooLong = errors.New("client name must be at most 32 characters") |
| paddy@49 | 20 ErrClientLogoTooLong = errors.New("client logo must be at most 1024 characters") |
| paddy@49 | 21 ErrClientLogoNotURL = errors.New("client logo must be a valid absolute URL") |
| paddy@49 | 22 ErrClientWebsiteTooLong = errors.New("client website must be at most 1024 characters") |
| paddy@49 | 23 ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL") |
| paddy@31 | 24 ) |
| paddy@31 | 25 |
| paddy@25 | 26 // Client represents a client that grants access |
| paddy@25 | 27 // to the auth server, exchanging grants for tokens, |
| paddy@25 | 28 // and tokens for access. |
| paddy@0 | 29 type Client struct { |
| paddy@41 | 30 ID uuid.ID |
| paddy@41 | 31 Secret string |
| paddy@41 | 32 OwnerID uuid.ID |
| paddy@41 | 33 Name string |
| paddy@41 | 34 Logo string |
| paddy@41 | 35 Website string |
| paddy@41 | 36 Type string |
| paddy@0 | 37 } |
| paddy@0 | 38 |
| paddy@39 | 39 func (c *Client) ApplyChange(change ClientChange) { |
| paddy@39 | 40 if change.Secret != nil { |
| paddy@39 | 41 c.Secret = *change.Secret |
| paddy@39 | 42 } |
| paddy@39 | 43 if change.OwnerID != nil { |
| paddy@39 | 44 c.OwnerID = change.OwnerID |
| paddy@39 | 45 } |
| paddy@39 | 46 if change.Name != nil { |
| paddy@39 | 47 c.Name = *change.Name |
| paddy@39 | 48 } |
| paddy@39 | 49 if change.Logo != nil { |
| paddy@39 | 50 c.Logo = *change.Logo |
| paddy@39 | 51 } |
| paddy@39 | 52 if change.Website != nil { |
| paddy@39 | 53 c.Website = *change.Website |
| paddy@39 | 54 } |
| paddy@39 | 55 } |
| paddy@39 | 56 |
| paddy@31 | 57 type ClientChange struct { |
| paddy@41 | 58 Secret *string |
| paddy@41 | 59 OwnerID uuid.ID |
| paddy@41 | 60 Name *string |
| paddy@41 | 61 Logo *string |
| paddy@41 | 62 Website *string |
| paddy@31 | 63 } |
| paddy@31 | 64 |
| paddy@39 | 65 func (c ClientChange) Validate() error { |
| paddy@42 | 66 if c.Secret == nil && c.OwnerID == nil && c.Name == nil && c.Logo == nil && c.Website == nil { |
| paddy@42 | 67 return ErrEmptyChange |
| paddy@42 | 68 } |
| paddy@41 | 69 if c.Name != nil && len(*c.Name) < 2 { |
| paddy@41 | 70 return ErrClientNameTooShort |
| paddy@41 | 71 } |
| paddy@41 | 72 if c.Name != nil && len(*c.Name) > 32 { |
| paddy@41 | 73 return ErrClientNameTooLong |
| paddy@41 | 74 } |
| paddy@42 | 75 if c.Logo != nil && *c.Logo != "" { |
| paddy@42 | 76 if len(*c.Logo) > 1024 { |
| paddy@42 | 77 return ErrClientLogoTooLong |
| paddy@42 | 78 } |
| paddy@42 | 79 u, err := url.Parse(*c.Logo) |
| paddy@42 | 80 if err != nil || !u.IsAbs() { |
| paddy@42 | 81 return ErrClientLogoNotURL |
| paddy@42 | 82 } |
| paddy@41 | 83 } |
| paddy@42 | 84 if c.Website != nil && *c.Website != "" { |
| paddy@42 | 85 if len(*c.Website) > 140 { |
| paddy@42 | 86 return ErrClientWebsiteTooLong |
| paddy@42 | 87 } |
| paddy@42 | 88 u, err := url.Parse(*c.Website) |
| paddy@42 | 89 if err != nil || !u.IsAbs() { |
| paddy@42 | 90 return ErrClientWebsiteNotURL |
| paddy@42 | 91 } |
| paddy@41 | 92 } |
| paddy@39 | 93 return nil |
| paddy@39 | 94 } |
| paddy@39 | 95 |
| paddy@41 | 96 type Endpoint struct { |
| paddy@41 | 97 ID uuid.ID |
| paddy@41 | 98 ClientID uuid.ID |
| paddy@41 | 99 URI url.URL |
| paddy@41 | 100 Added time.Time |
| paddy@41 | 101 } |
| paddy@41 | 102 |
| paddy@41 | 103 type sortedEndpoints []Endpoint |
| paddy@41 | 104 |
| paddy@41 | 105 func (s sortedEndpoints) Len() int { |
| paddy@41 | 106 return len(s) |
| paddy@41 | 107 } |
| paddy@41 | 108 |
| paddy@41 | 109 func (s sortedEndpoints) Less(i, j int) bool { |
| paddy@41 | 110 return s[i].Added.Before(s[j].Added) |
| paddy@41 | 111 } |
| paddy@41 | 112 |
| paddy@41 | 113 func (s sortedEndpoints) Swap(i, j int) { |
| paddy@41 | 114 s[i], s[j] = s[j], s[i] |
| paddy@41 | 115 } |
| paddy@41 | 116 |
| paddy@25 | 117 // ClientStore abstracts the storage interface for |
| paddy@25 | 118 // storing and retrieving Clients. |
| paddy@25 | 119 type ClientStore interface { |
| paddy@25 | 120 GetClient(id uuid.ID) (Client, error) |
| paddy@25 | 121 SaveClient(client Client) error |
| paddy@31 | 122 UpdateClient(id uuid.ID, change ClientChange) error |
| paddy@25 | 123 DeleteClient(id uuid.ID) error |
| paddy@31 | 124 ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) |
| paddy@41 | 125 |
| paddy@41 | 126 AddEndpoint(client uuid.ID, endpoint Endpoint) error |
| paddy@41 | 127 RemoveEndpoint(client, endpoint uuid.ID) error |
| paddy@41 | 128 CheckEndpoint(client uuid.ID, endpoint string) (bool, error) |
| paddy@41 | 129 ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) |
| paddy@0 | 130 } |
| paddy@31 | 131 |
| paddy@31 | 132 func (m *Memstore) GetClient(id uuid.ID) (Client, error) { |
| paddy@31 | 133 m.clientLock.RLock() |
| paddy@31 | 134 defer m.clientLock.RUnlock() |
| paddy@31 | 135 c, ok := m.clients[id.String()] |
| paddy@31 | 136 if !ok { |
| paddy@31 | 137 return Client{}, ErrClientNotFound |
| paddy@31 | 138 } |
| paddy@31 | 139 return c, nil |
| paddy@31 | 140 } |
| paddy@31 | 141 |
| paddy@31 | 142 func (m *Memstore) SaveClient(client Client) error { |
| paddy@31 | 143 m.clientLock.Lock() |
| paddy@31 | 144 defer m.clientLock.Unlock() |
| paddy@31 | 145 if _, ok := m.clients[client.ID.String()]; ok { |
| paddy@31 | 146 return ErrClientAlreadyExists |
| paddy@31 | 147 } |
| paddy@31 | 148 m.clients[client.ID.String()] = client |
| paddy@31 | 149 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID) |
| paddy@31 | 150 return nil |
| paddy@31 | 151 } |
| paddy@31 | 152 |
| paddy@31 | 153 func (m *Memstore) UpdateClient(id uuid.ID, change ClientChange) error { |
| paddy@39 | 154 m.clientLock.Lock() |
| paddy@39 | 155 defer m.clientLock.Unlock() |
| paddy@39 | 156 c, ok := m.clients[id.String()] |
| paddy@39 | 157 if !ok { |
| paddy@39 | 158 return ErrClientNotFound |
| paddy@39 | 159 } |
| paddy@39 | 160 c.ApplyChange(change) |
| paddy@39 | 161 m.clients[id.String()] = c |
| paddy@31 | 162 return nil |
| paddy@31 | 163 } |
| paddy@31 | 164 |
| paddy@31 | 165 func (m *Memstore) DeleteClient(id uuid.ID) error { |
| paddy@31 | 166 client, err := m.GetClient(id) |
| paddy@31 | 167 if err != nil { |
| paddy@31 | 168 return err |
| paddy@31 | 169 } |
| paddy@31 | 170 m.clientLock.Lock() |
| paddy@31 | 171 defer m.clientLock.Unlock() |
| paddy@31 | 172 delete(m.clients, id.String()) |
| paddy@31 | 173 pos := -1 |
| paddy@31 | 174 for p, item := range m.profileClientLookup[client.OwnerID.String()] { |
| paddy@31 | 175 if item.Equal(id) { |
| paddy@31 | 176 pos = p |
| paddy@31 | 177 break |
| paddy@31 | 178 } |
| paddy@31 | 179 } |
| paddy@31 | 180 if pos >= 0 { |
| paddy@31 | 181 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...) |
| paddy@31 | 182 } |
| paddy@31 | 183 return nil |
| paddy@31 | 184 } |
| paddy@31 | 185 |
| paddy@31 | 186 func (m *Memstore) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) { |
| paddy@33 | 187 ids := m.lookupClientsByProfileID(ownerID.String()) |
| paddy@31 | 188 if len(ids) > num+offset { |
| paddy@31 | 189 ids = ids[offset : num+offset] |
| paddy@31 | 190 } else if len(ids) > offset { |
| paddy@31 | 191 ids = ids[offset:] |
| paddy@31 | 192 } else { |
| paddy@31 | 193 return []Client{}, nil |
| paddy@31 | 194 } |
| paddy@31 | 195 clients := []Client{} |
| paddy@31 | 196 for _, id := range ids { |
| paddy@31 | 197 client, err := m.GetClient(id) |
| paddy@31 | 198 if err != nil { |
| paddy@31 | 199 return []Client{}, err |
| paddy@31 | 200 } |
| paddy@31 | 201 clients = append(clients, client) |
| paddy@31 | 202 } |
| paddy@31 | 203 return clients, nil |
| paddy@31 | 204 } |
| paddy@41 | 205 |
| paddy@41 | 206 func (m *Memstore) AddEndpoint(client uuid.ID, endpoint Endpoint) error { |
| paddy@41 | 207 m.endpointLock.Lock() |
| paddy@41 | 208 defer m.endpointLock.Unlock() |
| paddy@41 | 209 m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint) |
| paddy@41 | 210 return nil |
| paddy@41 | 211 } |
| paddy@41 | 212 |
| paddy@41 | 213 func (m *Memstore) RemoveEndpoint(client, endpoint uuid.ID) error { |
| paddy@41 | 214 m.endpointLock.Lock() |
| paddy@41 | 215 defer m.endpointLock.Unlock() |
| paddy@41 | 216 pos := -1 |
| paddy@41 | 217 for p, item := range m.endpoints[client.String()] { |
| paddy@41 | 218 if item.ID.Equal(endpoint) { |
| paddy@41 | 219 pos = p |
| paddy@41 | 220 break |
| paddy@41 | 221 } |
| paddy@41 | 222 } |
| paddy@41 | 223 if pos >= 0 { |
| paddy@41 | 224 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...) |
| paddy@41 | 225 } |
| paddy@41 | 226 return nil |
| paddy@41 | 227 } |
| paddy@41 | 228 |
| paddy@41 | 229 func (m *Memstore) CheckEndpoint(client uuid.ID, endpoint string) (bool, error) { |
| paddy@41 | 230 m.endpointLock.RLock() |
| paddy@41 | 231 defer m.endpointLock.RUnlock() |
| paddy@41 | 232 for _, candidate := range m.endpoints[client.String()] { |
| paddy@41 | 233 if strings.HasPrefix(endpoint, candidate.URI.String()) { |
| paddy@41 | 234 return true, nil |
| paddy@41 | 235 } |
| paddy@41 | 236 } |
| paddy@41 | 237 return false, nil |
| paddy@41 | 238 } |
| paddy@41 | 239 |
| paddy@41 | 240 func (m *Memstore) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) { |
| paddy@41 | 241 m.endpointLock.RLock() |
| paddy@41 | 242 defer m.endpointLock.RUnlock() |
| paddy@41 | 243 return m.endpoints[client.String()], nil |
| paddy@41 | 244 } |