auth

Paddy 2014-12-13 Parent:1dc4e152e3b0 Child:c03b5eb3179e

94:9c50b2e2e03b Go to Latest

auth/client.go

Implement invalidating AuthorizationCodes once used. Add a Used property to AuthorizationCodes, which is set to true in the Invalidate function of the AuthorizationCode GrantType. Implement a useAuthorizationCode function for the memstore. Add useAuthorizzationCode to the authorizationCodeStore interface.

History
1 package auth
3 import (
4 "encoding/json"
5 "errors"
6 "net/http"
7 "net/url"
8 "time"
10 "code.secondbit.org/uuid"
11 )
13 var (
14 // ErrNoClientStore is returned when a Context tries to act on a clientStore without setting one first.
15 ErrNoClientStore = errors.New("no clientStore was specified for the Context")
16 // ErrClientNotFound is returned when a Client is requested but not found in a clientStore.
17 ErrClientNotFound = errors.New("client not found in clientStore")
18 // ErrClientAlreadyExists is returned when a Client is added to a clientStore, but another Client with
19 // the same ID already exists in the clientStore.
20 ErrClientAlreadyExists = errors.New("client already exists in clientStore")
22 // ErrEmptyChange is returned when a Change has all its properties set to nil.
23 ErrEmptyChange = errors.New("change must have at least one property set")
24 // ErrClientNameTooShort is returned when a Client's Name property is too short.
25 ErrClientNameTooShort = errors.New("client name must be at least 2 characters")
26 // ErrClientNameTooLong is returned when a Client's Name property is too long.
27 ErrClientNameTooLong = errors.New("client name must be at most 32 characters")
28 // ErrClientLogoTooLong is returned when a Client's Logo property is too long.
29 ErrClientLogoTooLong = errors.New("client logo must be at most 1024 characters")
30 // ErrClientLogoNotURL is returned when a Client's Logo property is not a valid absolute URL.
31 ErrClientLogoNotURL = errors.New("client logo must be a valid absolute URL")
32 // ErrClientWebsiteTooLong is returned when a Client's Website property is too long.
33 ErrClientWebsiteTooLong = errors.New("client website must be at most 1024 characters")
34 // ErrClientWebsiteNotURL is returned when a Client's Website property is not a valid absolute URL.
35 ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL")
36 )
38 // Client represents a client that grants access
39 // to the auth server, exchanging grants for tokens,
40 // and tokens for access.
41 type Client struct {
42 ID uuid.ID
43 Secret string
44 OwnerID uuid.ID
45 Name string
46 Logo string
47 Website string
48 Type string
49 }
51 // ApplyChange applies the properties of the passed
52 // ClientChange to the Client object it is called on.
53 func (c *Client) ApplyChange(change ClientChange) {
54 if change.Secret != nil {
55 c.Secret = *change.Secret
56 }
57 if change.OwnerID != nil {
58 c.OwnerID = change.OwnerID
59 }
60 if change.Name != nil {
61 c.Name = *change.Name
62 }
63 if change.Logo != nil {
64 c.Logo = *change.Logo
65 }
66 if change.Website != nil {
67 c.Website = *change.Website
68 }
69 }
71 // ClientChange represents a bundle of options for
72 // updating a Client's mutable data.
73 type ClientChange struct {
74 Secret *string
75 OwnerID uuid.ID
76 Name *string
77 Logo *string
78 Website *string
79 }
81 // Validate checks the ClientChange it is called on
82 // and asserts its internal validity, or lack thereof.
83 func (c ClientChange) Validate() error {
84 if c.Secret == nil && c.OwnerID == nil && c.Name == nil && c.Logo == nil && c.Website == nil {
85 return ErrEmptyChange
86 }
87 if c.Name != nil && len(*c.Name) < 2 {
88 return ErrClientNameTooShort
89 }
90 if c.Name != nil && len(*c.Name) > 32 {
91 return ErrClientNameTooLong
92 }
93 if c.Logo != nil && *c.Logo != "" {
94 if len(*c.Logo) > 1024 {
95 return ErrClientLogoTooLong
96 }
97 u, err := url.Parse(*c.Logo)
98 if err != nil || !u.IsAbs() {
99 return ErrClientLogoNotURL
100 }
101 }
102 if c.Website != nil && *c.Website != "" {
103 if len(*c.Website) > 140 {
104 return ErrClientWebsiteTooLong
105 }
106 u, err := url.Parse(*c.Website)
107 if err != nil || !u.IsAbs() {
108 return ErrClientWebsiteNotURL
109 }
110 }
111 return nil
112 }
114 func verifyClient(w http.ResponseWriter, r *http.Request, allowPublic bool, context Context) (uuid.ID, bool) {
115 enc := json.NewEncoder(w)
116 clientIDStr, clientSecret, fromAuthHeader := r.BasicAuth()
117 if !fromAuthHeader {
118 if !allowPublic {
119 w.WriteHeader(http.StatusBadRequest)
120 renderJSONError(enc, "unauthorized_client")
121 return nil, false
122 }
123 clientIDStr = r.PostFormValue("client_id")
124 }
125 clientID, err := uuid.Parse(clientIDStr)
126 if err != nil {
127 w.WriteHeader(http.StatusUnauthorized)
128 if fromAuthHeader {
129 w.Header().Set("WWW-Authenticate", "Basic")
130 }
131 renderJSONError(enc, "invalid_client")
132 return nil, false
133 }
134 client, err := context.GetClient(clientID)
135 if err == ErrClientNotFound {
136 w.WriteHeader(http.StatusUnauthorized)
137 if fromAuthHeader {
138 w.Header().Set("WWW-Authenticate", "Basic")
139 }
140 renderJSONError(enc, "invalid_client")
141 return nil, false
142 } else if err != nil {
143 w.WriteHeader(http.StatusInternalServerError)
144 renderJSONError(enc, "server_error")
145 return nil, false
146 }
147 if client.Secret != clientSecret {
148 w.WriteHeader(http.StatusUnauthorized)
149 if fromAuthHeader {
150 w.Header().Set("WWW-Authenticate", "Basic")
151 }
152 renderJSONError(enc, "invalid_client")
153 return nil, false
154 }
155 return clientID, true
156 }
158 // Endpoint represents a single URI that a Client
159 // controls. Users will be redirected to these URIs
160 // following successful authorization grants and
161 // exchanges for access tokens.
162 type Endpoint struct {
163 ID uuid.ID
164 ClientID uuid.ID
165 URI url.URL
166 Added time.Time
167 }
169 type sortedEndpoints []Endpoint
171 func (s sortedEndpoints) Len() int {
172 return len(s)
173 }
175 func (s sortedEndpoints) Less(i, j int) bool {
176 return s[i].Added.Before(s[j].Added)
177 }
179 func (s sortedEndpoints) Swap(i, j int) {
180 s[i], s[j] = s[j], s[i]
181 }
183 type clientStore interface {
184 getClient(id uuid.ID) (Client, error)
185 saveClient(client Client) error
186 updateClient(id uuid.ID, change ClientChange) error
187 deleteClient(id uuid.ID) error
188 listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
190 addEndpoint(client uuid.ID, endpoint Endpoint) error
191 removeEndpoint(client, endpoint uuid.ID) error
192 checkEndpoint(client uuid.ID, endpoint string) (bool, error)
193 listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
194 countEndpoints(client uuid.ID) (int64, error)
195 }
197 func (m *memstore) getClient(id uuid.ID) (Client, error) {
198 m.clientLock.RLock()
199 defer m.clientLock.RUnlock()
200 c, ok := m.clients[id.String()]
201 if !ok {
202 return Client{}, ErrClientNotFound
203 }
204 return c, nil
205 }
207 func (m *memstore) saveClient(client Client) error {
208 m.clientLock.Lock()
209 defer m.clientLock.Unlock()
210 if _, ok := m.clients[client.ID.String()]; ok {
211 return ErrClientAlreadyExists
212 }
213 m.clients[client.ID.String()] = client
214 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
215 return nil
216 }
218 func (m *memstore) updateClient(id uuid.ID, change ClientChange) error {
219 m.clientLock.Lock()
220 defer m.clientLock.Unlock()
221 c, ok := m.clients[id.String()]
222 if !ok {
223 return ErrClientNotFound
224 }
225 c.ApplyChange(change)
226 m.clients[id.String()] = c
227 return nil
228 }
230 func (m *memstore) deleteClient(id uuid.ID) error {
231 client, err := m.getClient(id)
232 if err != nil {
233 return err
234 }
235 m.clientLock.Lock()
236 defer m.clientLock.Unlock()
237 delete(m.clients, id.String())
238 pos := -1
239 for p, item := range m.profileClientLookup[client.OwnerID.String()] {
240 if item.Equal(id) {
241 pos = p
242 break
243 }
244 }
245 if pos >= 0 {
246 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...)
247 }
248 return nil
249 }
251 func (m *memstore) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
252 ids := m.lookupClientsByProfileID(ownerID.String())
253 if len(ids) > num+offset {
254 ids = ids[offset : num+offset]
255 } else if len(ids) > offset {
256 ids = ids[offset:]
257 } else {
258 return []Client{}, nil
259 }
260 clients := []Client{}
261 for _, id := range ids {
262 client, err := m.getClient(id)
263 if err != nil {
264 return []Client{}, err
265 }
266 clients = append(clients, client)
267 }
268 return clients, nil
269 }
271 func (m *memstore) addEndpoint(client uuid.ID, endpoint Endpoint) error {
272 m.endpointLock.Lock()
273 defer m.endpointLock.Unlock()
274 m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint)
275 return nil
276 }
278 func (m *memstore) removeEndpoint(client, endpoint uuid.ID) error {
279 m.endpointLock.Lock()
280 defer m.endpointLock.Unlock()
281 pos := -1
282 for p, item := range m.endpoints[client.String()] {
283 if item.ID.Equal(endpoint) {
284 pos = p
285 break
286 }
287 }
288 if pos >= 0 {
289 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
290 }
291 return nil
292 }
294 func (m *memstore) checkEndpoint(client uuid.ID, endpoint string) (bool, error) {
295 m.endpointLock.RLock()
296 defer m.endpointLock.RUnlock()
297 for _, candidate := range m.endpoints[client.String()] {
298 if endpoint == candidate.URI.String() {
299 return true, nil
300 }
301 }
302 return false, nil
303 }
305 func (m *memstore) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
306 m.endpointLock.RLock()
307 defer m.endpointLock.RUnlock()
308 return m.endpoints[client.String()], nil
309 }
311 func (m *memstore) countEndpoints(client uuid.ID) (int64, error) {
312 m.endpointLock.RLock()
313 defer m.endpointLock.RUnlock()
314 return int64(len(m.endpoints[client.String()])), nil
315 }