auth
auth/client.go
Actually validate grant requests. Write the logic to validate grant requests and stub out the rendering/error handling/redirecting locations. Finally, we get to the good stuff: implementing the specification. Write some tests to verify that granting requests works the way we think it does.
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 }