auth

Paddy 2014-09-27 Parent:022ce4262922 Child:73a9f7a6af54

45:3a6a65ed380c Go to Latest

auth/client.go

Update uuid import path, test for multiple profile updates. Test updating multiple profiles in one request (e.g., when profiles become compromised.) Update the uuid import path to use the new code.secondbit.org/uuid import path.

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