auth
auth/client.go
Test all possible successful requests, fix query setting in test. Test all the possible successful requests for an authorization code grant. Fix a bug wherein the query string wasn't actually set for the test.
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) (bool, error)
129 ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
130 }
132 func (m *Memstore) GetClient(id uuid.ID) (Client, error) {
133 m.clientLock.RLock()
134 defer m.clientLock.RUnlock()
135 c, ok := m.clients[id.String()]
136 if !ok {
137 return Client{}, ErrClientNotFound
138 }
139 return c, nil
140 }
142 func (m *Memstore) SaveClient(client Client) error {
143 m.clientLock.Lock()
144 defer m.clientLock.Unlock()
145 if _, ok := m.clients[client.ID.String()]; ok {
146 return ErrClientAlreadyExists
147 }
148 m.clients[client.ID.String()] = client
149 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
150 return nil
151 }
153 func (m *Memstore) UpdateClient(id uuid.ID, change ClientChange) error {
154 m.clientLock.Lock()
155 defer m.clientLock.Unlock()
156 c, ok := m.clients[id.String()]
157 if !ok {
158 return ErrClientNotFound
159 }
160 c.ApplyChange(change)
161 m.clients[id.String()] = c
162 return nil
163 }
165 func (m *Memstore) DeleteClient(id uuid.ID) error {
166 client, err := m.GetClient(id)
167 if err != nil {
168 return err
169 }
170 m.clientLock.Lock()
171 defer m.clientLock.Unlock()
172 delete(m.clients, id.String())
173 pos := -1
174 for p, item := range m.profileClientLookup[client.OwnerID.String()] {
175 if item.Equal(id) {
176 pos = p
177 break
178 }
179 }
180 if pos >= 0 {
181 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...)
182 }
183 return nil
184 }
186 func (m *Memstore) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
187 ids := m.lookupClientsByProfileID(ownerID.String())
188 if len(ids) > num+offset {
189 ids = ids[offset : num+offset]
190 } else if len(ids) > offset {
191 ids = ids[offset:]
192 } else {
193 return []Client{}, nil
194 }
195 clients := []Client{}
196 for _, id := range ids {
197 client, err := m.GetClient(id)
198 if err != nil {
199 return []Client{}, err
200 }
201 clients = append(clients, client)
202 }
203 return clients, nil
204 }
206 func (m *Memstore) AddEndpoint(client uuid.ID, endpoint Endpoint) error {
207 m.endpointLock.Lock()
208 defer m.endpointLock.Unlock()
209 m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint)
210 return nil
211 }
213 func (m *Memstore) RemoveEndpoint(client, endpoint uuid.ID) error {
214 m.endpointLock.Lock()
215 defer m.endpointLock.Unlock()
216 pos := -1
217 for p, item := range m.endpoints[client.String()] {
218 if item.ID.Equal(endpoint) {
219 pos = p
220 break
221 }
222 }
223 if pos >= 0 {
224 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
225 }
226 return nil
227 }
229 func (m *Memstore) CheckEndpoint(client uuid.ID, endpoint string) (bool, error) {
230 m.endpointLock.RLock()
231 defer m.endpointLock.RUnlock()
232 for _, candidate := range m.endpoints[client.String()] {
233 if strings.HasPrefix(endpoint, candidate.URI.String()) {
234 return true, nil
235 }
236 }
237 return false, nil
238 }
240 func (m *Memstore) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
241 m.endpointLock.RLock()
242 defer m.endpointLock.RUnlock()
243 return m.endpoints[client.String()], nil
244 }