auth

Paddy 2014-10-22 Parent:73a9f7a6af54 Child:e45bfa2abc00

54:0f80a3e391b8 Go to Latest

auth/client.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.

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