auth

Paddy 2015-05-12 Parent:cf1aef6eb81f Child:8ecb60d29b0d

167:0ff23f3a4ede Go to Latest

auth/client.go

Implement an endpoint for token information. Implement an endpoint that allows us to look up information on a token. We strip the refresh token before the response is sent to avoid leaking the response token.

History
1 package auth
3 import (
4 "crypto/rand"
5 "encoding/hex"
6 "encoding/json"
7 "errors"
8 "log"
9 "net/http"
10 "net/url"
11 "strconv"
12 "strings"
13 "time"
15 "github.com/PuerkitoBio/purell"
16 "github.com/gorilla/mux"
18 "code.secondbit.org/uuid.hg"
19 )
21 func init() {
22 RegisterGrantType("client_credentials", GrantType{
23 Validate: clientCredentialsValidate,
24 Invalidate: nil,
25 IssuesRefresh: true,
26 ReturnToken: RenderJSONToken,
27 AllowsPublic: false,
28 AuditString: clientCredentialsAuditString,
29 })
30 }
32 var (
33 // ErrNoClientStore is returned when a Context tries to act on a clientStore without setting one first.
34 ErrNoClientStore = errors.New("no clientStore was specified for the Context")
35 // ErrClientNotFound is returned when a Client is requested but not found in a clientStore.
36 ErrClientNotFound = errors.New("client not found in clientStore")
37 // ErrClientAlreadyExists is returned when a Client is added to a clientStore, but another Client with
38 // the same ID already exists in the clientStore.
39 ErrClientAlreadyExists = errors.New("client already exists in clientStore")
40 // ErrEndpointNotFound is returned when an Endpoint is requested but not found in a clientSTore.
41 ErrEndpointNotFound = errors.New("endpoint not found in clientStore")
42 // ErrEndpointAlreadyExists is returned when an Endpoint is added to a clientStore, but another Endpoint
43 // with the same ID already exists in the clientStore.
44 ErrEndpointAlreadyExists = errors.New("endpoint already exists in clientStore")
46 // ErrEmptyChange is returned when a Change has all its properties set to nil.
47 ErrEmptyChange = errors.New("change must have at least one property set")
48 // ErrClientNameTooShort is returned when a Client's Name property is too short.
49 ErrClientNameTooShort = errors.New("client name must be at least 2 characters")
50 // ErrClientNameTooLong is returned when a Client's Name property is too long.
51 ErrClientNameTooLong = errors.New("client name must be at most 32 characters")
52 // ErrClientLogoTooLong is returned when a Client's Logo property is too long.
53 ErrClientLogoTooLong = errors.New("client logo must be at most 1024 characters")
54 // ErrClientLogoNotURL is returned when a Client's Logo property is not a valid absolute URL.
55 ErrClientLogoNotURL = errors.New("client logo must be a valid absolute URL")
56 // ErrClientWebsiteTooLong is returned when a Client's Website property is too long.
57 ErrClientWebsiteTooLong = errors.New("client website must be at most 1024 characters")
58 // ErrClientWebsiteNotURL is returned when a Client's Website property is not a valid absolute URL.
59 ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL")
60 // ErrEndpointURINotURL is returned when an Endpoint's URI property is not a valid absolute URL.
61 ErrEndpointURINotURL = errors.New("endpoint URI must be a valid absolute URL")
62 )
64 const (
65 clientTypePublic = "public"
66 clientTypeConfidential = "confidential"
67 minClientNameLen = 2
68 maxClientNameLen = 24
69 defaultClientResponseSize = 20
70 maxClientResponseSize = 50
71 defaultEndpointResponseSize = 20
72 maxEndpointResponseSize = 50
74 normalizeFlags = purell.FlagsUsuallySafeNonGreedy | purell.FlagSortQuery
75 )
77 // Client represents a client that grants access
78 // to the auth server, exchanging grants for tokens,
79 // and tokens for access.
80 type Client struct {
81 ID uuid.ID `json:"id,omitempty"`
82 Secret string `json:"secret,omitempty"`
83 OwnerID uuid.ID `json:"owner_id,omitempty"`
84 Name string `json:"name,omitempty"`
85 Logo string `json:"logo,omitempty"`
86 Website string `json:"website,omitempty"`
87 Type string `json:"type,omitempty"`
88 Deleted bool `json:"deleted,omitempty"`
89 }
91 // ApplyChange applies the properties of the passed
92 // ClientChange to the Client object it is called on.
93 func (c *Client) ApplyChange(change ClientChange) {
94 if change.Secret != nil {
95 c.Secret = *change.Secret
96 }
97 if change.OwnerID != nil {
98 c.OwnerID = change.OwnerID
99 }
100 if change.Name != nil {
101 c.Name = *change.Name
102 }
103 if change.Logo != nil {
104 c.Logo = *change.Logo
105 }
106 if change.Website != nil {
107 c.Website = *change.Website
108 }
109 if change.Deleted != nil {
110 c.Deleted = *change.Deleted
111 }
112 }
114 // ClientChange represents a bundle of options for
115 // updating a Client's mutable data.
116 type ClientChange struct {
117 Secret *string
118 OwnerID uuid.ID
119 Name *string
120 Logo *string
121 Website *string
122 Deleted *bool
123 }
125 func (c ClientChange) Empty() bool {
126 return c.Secret == nil && c.OwnerID == nil && c.Name == nil && c.Logo == nil && c.Website == nil && c.Deleted == nil
127 }
129 // Validate checks the ClientChange it is called on
130 // and asserts its internal validity, or lack thereof.
131 func (c ClientChange) Validate() []error {
132 errors := []error{}
133 if c.Empty() {
134 errors = append(errors, ErrEmptyChange)
135 return errors
136 }
137 if c.Name != nil && len(*c.Name) < 2 {
138 errors = append(errors, ErrClientNameTooShort)
139 }
140 if c.Name != nil && len(*c.Name) > 32 {
141 errors = append(errors, ErrClientNameTooLong)
142 }
143 if c.Logo != nil && *c.Logo != "" {
144 if len(*c.Logo) > 1024 {
145 errors = append(errors, ErrClientLogoTooLong)
146 }
147 u, err := url.Parse(*c.Logo)
148 if err != nil || !u.IsAbs() {
149 errors = append(errors, ErrClientLogoNotURL)
150 }
151 }
152 if c.Website != nil && *c.Website != "" {
153 if len(*c.Website) > 140 {
154 errors = append(errors, ErrClientWebsiteTooLong)
155 }
156 u, err := url.Parse(*c.Website)
157 if err != nil || !u.IsAbs() {
158 errors = append(errors, ErrClientWebsiteNotURL)
159 }
160 }
161 return errors
162 }
164 func getClientAuth(w http.ResponseWriter, r *http.Request, allowPublic bool) (uuid.ID, string, bool) {
165 enc := json.NewEncoder(w)
166 clientIDStr, clientSecret, fromAuthHeader := r.BasicAuth()
167 if !fromAuthHeader {
168 clientIDStr = r.PostFormValue("client_id")
169 }
170 if clientIDStr == "" {
171 w.WriteHeader(http.StatusUnauthorized)
172 if fromAuthHeader {
173 w.Header().Set("WWW-Authenticate", "Basic")
174 }
175 renderJSONError(enc, "invalid_client")
176 return nil, "", false
177 }
178 if !allowPublic && !fromAuthHeader {
179 w.WriteHeader(http.StatusBadRequest)
180 renderJSONError(enc, "unauthorized_client")
181 return nil, "", false
182 }
183 clientID, err := uuid.Parse(clientIDStr)
184 if err != nil {
185 log.Println("Error decoding client ID:", err)
186 w.WriteHeader(http.StatusUnauthorized)
187 if fromAuthHeader {
188 w.Header().Set("WWW-Authenticate", "Basic")
189 }
190 renderJSONError(enc, "invalid_client")
191 return nil, "", false
192 }
193 return clientID, clientSecret, true
194 }
196 func verifyClient(w http.ResponseWriter, r *http.Request, allowPublic bool, context Context) (uuid.ID, bool) {
197 enc := json.NewEncoder(w)
198 clientID, clientSecret, ok := getClientAuth(w, r, allowPublic)
199 if !ok {
200 return nil, false
201 }
202 _, _, fromAuthHeader := r.BasicAuth()
203 client, err := context.GetClient(clientID)
204 if err == ErrClientNotFound {
205 w.WriteHeader(http.StatusUnauthorized)
206 if fromAuthHeader {
207 w.Header().Set("WWW-Authenticate", "Basic")
208 }
209 renderJSONError(enc, "invalid_client")
210 return nil, false
211 } else if err != nil {
212 w.WriteHeader(http.StatusInternalServerError)
213 renderJSONError(enc, "server_error")
214 return nil, false
215 }
216 if client.Secret != clientSecret { // it's important that any client deemed "public" is not issued a client secret.
217 w.WriteHeader(http.StatusUnauthorized)
218 if fromAuthHeader {
219 w.Header().Set("WWW-Authenticate", "Basic")
220 }
221 renderJSONError(enc, "invalid_client")
222 return nil, false
223 }
224 return clientID, true
225 }
227 // Endpoint represents a single URI that a Client
228 // controls. Users will be redirected to these URIs
229 // following successful authorization grants and
230 // exchanges for access tokens.
231 type Endpoint struct {
232 ID uuid.ID `json:"id,omitempty"`
233 ClientID uuid.ID `json:"client_id,omitempty"`
234 URI string `json:"uri,omitempty"`
235 NormalizedURI string `json:"-"`
236 Added time.Time `json:"added,omitempty"`
237 }
239 func normalizeURIString(in string) (string, error) {
240 n, err := purell.NormalizeURLString(in, normalizeFlags)
241 if err != nil {
242 log.Println(err)
243 return in, ErrEndpointURINotURL
244 }
245 return n, nil
246 }
248 func normalizeURI(in *url.URL) string {
249 return purell.NormalizeURL(in, normalizeFlags)
250 }
252 type sortedEndpoints []Endpoint
254 func (s sortedEndpoints) Len() int {
255 return len(s)
256 }
258 func (s sortedEndpoints) Less(i, j int) bool {
259 return s[i].Added.Before(s[j].Added)
260 }
262 func (s sortedEndpoints) Swap(i, j int) {
263 s[i], s[j] = s[j], s[i]
264 }
266 type clientStore interface {
267 getClient(id uuid.ID) (Client, error)
268 saveClient(client Client) error
269 updateClient(id uuid.ID, change ClientChange) error
270 listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
271 deleteClientsByOwner(ownerID uuid.ID) error
273 addEndpoints(endpoint []Endpoint) error
274 removeEndpoint(client, endpoint uuid.ID) error
275 getEndpoint(client, endpoint uuid.ID) (Endpoint, error)
276 checkEndpoint(client uuid.ID, endpoint string) (bool, error)
277 listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
278 removeEndpointsByClientID(client uuid.ID) error
279 countEndpoints(client uuid.ID) (int64, error)
280 }
282 func (m *memstore) getClient(id uuid.ID) (Client, error) {
283 m.clientLock.RLock()
284 defer m.clientLock.RUnlock()
285 c, ok := m.clients[id.String()]
286 if !ok || c.Deleted {
287 return Client{}, ErrClientNotFound
288 }
289 return c, nil
290 }
292 func (m *memstore) saveClient(client Client) error {
293 m.clientLock.Lock()
294 defer m.clientLock.Unlock()
295 if _, ok := m.clients[client.ID.String()]; ok {
296 return ErrClientAlreadyExists
297 }
298 m.clients[client.ID.String()] = client
299 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
300 return nil
301 }
303 func (m *memstore) updateClient(id uuid.ID, change ClientChange) error {
304 m.clientLock.Lock()
305 defer m.clientLock.Unlock()
306 c, ok := m.clients[id.String()]
307 if !ok {
308 return ErrClientNotFound
309 }
310 c.ApplyChange(change)
311 m.clients[id.String()] = c
312 return nil
313 }
315 func (m *memstore) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
316 ids := m.lookupClientsByProfileID(ownerID.String())
317 if len(ids) > num+offset && num > 0 {
318 ids = ids[offset : num+offset]
319 } else if len(ids) > offset {
320 ids = ids[offset:]
321 } else {
322 return []Client{}, nil
323 }
324 clients := []Client{}
325 for _, id := range ids {
326 client, err := m.getClient(id)
327 if err != nil {
328 if err == ErrClientNotFound {
329 continue
330 }
331 return []Client{}, err
332 }
333 clients = append(clients, client)
334 }
335 return clients, nil
336 }
338 func (m *memstore) deleteClientsByOwner(ownerID uuid.ID) error {
339 ids := m.lookupClientsByProfileID(ownerID.String())
340 m.clientLock.Lock()
341 defer m.clientLock.RUnlock()
342 for _, id := range ids {
343 client, ok := m.clients[id.String()]
344 if !ok {
345 continue
346 }
347 client.Deleted = true
348 m.clients[id.String()] = client
349 }
350 return nil
351 }
353 func (m *memstore) addEndpoints(endpoints []Endpoint) error {
354 m.endpointLock.Lock()
355 defer m.endpointLock.Unlock()
356 clients := map[string][]Endpoint{}
357 for _, endpoint := range endpoints {
358 clients[endpoint.ClientID.String()] = append(clients[endpoint.ClientID.String()], endpoint)
359 }
360 for client, e := range clients {
361 m.endpoints[client] = append(m.endpoints[client], e...)
362 }
363 return nil
364 }
366 func (m *memstore) getEndpoint(client, endpoint uuid.ID) (Endpoint, error) {
367 m.endpointLock.Lock()
368 defer m.endpointLock.Unlock()
369 for _, item := range m.endpoints[client.String()] {
370 if item.ID.Equal(endpoint) {
371 return item, nil
372 }
373 }
374 return Endpoint{}, ErrEndpointNotFound
375 }
377 func (m *memstore) removeEndpoint(client, endpoint uuid.ID) error {
378 m.endpointLock.Lock()
379 defer m.endpointLock.Unlock()
380 pos := -1
381 for p, item := range m.endpoints[client.String()] {
382 if item.ID.Equal(endpoint) {
383 pos = p
384 break
385 }
386 }
387 if pos >= 0 {
388 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
389 }
390 return nil
391 }
393 func (m *memstore) checkEndpoint(client uuid.ID, endpoint string) (bool, error) {
394 m.endpointLock.RLock()
395 defer m.endpointLock.RUnlock()
396 for _, candidate := range m.endpoints[client.String()] {
397 if endpoint == candidate.NormalizedURI {
398 return true, nil
399 }
400 }
401 return false, nil
402 }
404 func (m *memstore) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
405 m.endpointLock.RLock()
406 defer m.endpointLock.RUnlock()
407 return m.endpoints[client.String()], nil
408 }
410 func (m *memstore) removeEndpointsByClientID(client uuid.ID) error {
411 m.endpointLock.Lock()
412 defer m.endpointLock.Unlock()
413 delete(m.endpoints, client.String())
414 return nil
415 }
417 func (m *memstore) countEndpoints(client uuid.ID) (int64, error) {
418 m.endpointLock.RLock()
419 defer m.endpointLock.RUnlock()
420 return int64(len(m.endpoints[client.String()])), nil
421 }
423 func cleanUpAfterClientDeletion(client uuid.ID, context Context) {
424 err := context.RemoveEndpointsByClientID(client)
425 if err != nil {
426 log.Printf("Error removing endpoints from client %s: %+v\n", client, err)
427 }
428 err = context.DeleteAuthorizationCodesByClientID(client)
429 if err != nil {
430 log.Printf("Error removing auth codes belonging to client %s: %+v\n", client, err)
431 }
432 err = context.RevokeTokensByClientID(client)
433 if err != nil {
434 log.Printf("Error revoking tokens belonging to client %s: %+v\n", client, err)
435 }
436 }
438 type newClientReq struct {
439 Name string `json:"name"`
440 Logo string `json:"logo"`
441 Website string `json:"website"`
442 Type string `json:"type"`
443 Endpoints []string `json:"endpoints"`
444 }
446 func RegisterClientHandlers(r *mux.Router, context Context) {
447 r.Handle("/clients", wrap(context, CreateClientHandler)).Methods("POST")
448 r.Handle("/clients", wrap(context, ListClientsHandler)).Methods("GET")
449 r.Handle("/clients/{id}", wrap(context, GetClientHandler)).Methods("GET")
450 r.Handle("/clients/{id}", wrap(context, UpdateClientHandler)).Methods("PATCH")
451 r.Handle("/clients/{id}", wrap(context, RemoveClientHandler)).Methods("DELETE")
452 r.Handle("/clients/{id}/endpoints", wrap(context, AddEndpointsHandler)).Methods("POST")
453 r.Handle("/clients/{client_id}/endpoints/{id}", wrap(context, RemoveEndpointHandler)).Methods("DELETE")
454 r.Handle("/clients/{id}/endpoints", wrap(context, ListEndpointsHandler)).Methods("GET")
455 }
457 func CreateClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
458 errors := []requestError{}
459 username, password, ok := r.BasicAuth()
460 if !ok {
461 errors = append(errors, requestError{Slug: requestErrAccessDenied})
462 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
463 return
464 }
465 profile, err := authenticate(username, password, c)
466 if err != nil {
467 if isAuthError(err) {
468 errors = append(errors, requestError{Slug: requestErrAccessDenied})
469 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
470 } else {
471 log.Printf("Error authenticating: %#+v\n", err)
472 errors = append(errors, requestError{Slug: requestErrActOfGod})
473 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
474 }
475 return
476 }
477 var req newClientReq
478 decoder := json.NewDecoder(r.Body)
479 err = decoder.Decode(&req)
480 if err != nil {
481 encode(w, r, http.StatusBadRequest, invalidFormatResponse)
482 return
483 }
484 if req.Type == "" {
485 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/type"})
486 } else if req.Type != clientTypePublic && req.Type != clientTypeConfidential {
487 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/type"})
488 }
489 if req.Name == "" {
490 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/name"})
491 } else if len(req.Name) < minClientNameLen {
492 errors = append(errors, requestError{Slug: requestErrInsufficient, Field: "/name"})
493 } else if len(req.Name) > maxClientNameLen {
494 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/name"})
495 }
496 if len(errors) > 0 {
497 encode(w, r, http.StatusBadRequest, response{Errors: errors})
498 return
499 }
500 client := Client{
501 ID: uuid.NewID(),
502 OwnerID: profile.ID,
503 Name: req.Name,
504 Logo: req.Logo,
505 Website: req.Website,
506 Type: req.Type,
507 }
508 if client.Type == clientTypeConfidential {
509 secret := make([]byte, 32)
510 _, err = rand.Read(secret)
511 if err != nil {
512 log.Printf("Error generating secret: %#+v\n", err)
513 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
514 return
515 }
516 client.Secret = hex.EncodeToString(secret)
517 }
518 err = c.SaveClient(client)
519 if err != nil {
520 if err == ErrClientAlreadyExists {
521 errors = append(errors, requestError{Slug: requestErrConflict, Field: "/id"})
522 encode(w, r, http.StatusBadRequest, response{Errors: errors})
523 return
524 }
525 log.Printf("Error saving client: %#+v\n", err)
526 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
527 return
528 }
529 endpoints := []Endpoint{}
530 for pos, u := range req.Endpoints {
531 uri, err := url.Parse(u)
532 if err != nil {
533 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)})
534 continue
535 }
536 if !uri.IsAbs() {
537 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/endpoints/" + strconv.Itoa(pos)})
538 continue
539 }
540 endpoint := Endpoint{
541 ID: uuid.NewID(),
542 ClientID: client.ID,
543 URI: uri.String(),
544 Added: time.Now(),
545 }
546 endpoints = append(endpoints, endpoint)
547 }
548 err = c.AddEndpoints(endpoints)
549 if err != nil {
550 log.Printf("Error adding endpoints: %#+v\n", err)
551 errors = append(errors, requestError{Slug: requestErrActOfGod})
552 encode(w, r, http.StatusInternalServerError, response{Errors: errors, Clients: []Client{client}})
553 return
554 }
555 resp := response{
556 Clients: []Client{client},
557 Endpoints: endpoints,
558 Errors: errors,
559 }
560 encode(w, r, http.StatusCreated, resp)
561 }
563 func GetClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
564 errors := []requestError{}
565 vars := mux.Vars(r)
566 if vars["id"] == "" {
567 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
568 encode(w, r, http.StatusBadRequest, response{Errors: errors})
569 return
570 }
571 id, err := uuid.Parse(vars["id"])
572 if err != nil {
573 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
574 encode(w, r, http.StatusBadRequest, response{Errors: errors})
575 return
576 }
577 client, err := c.GetClient(id)
578 if err != nil {
579 if err == ErrClientNotFound {
580 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
581 encode(w, r, http.StatusNotFound, response{Errors: errors})
582 return
583 }
584 errors = append(errors, requestError{Slug: requestErrActOfGod})
585 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
586 return
587 }
588 username, password, ok := r.BasicAuth()
589 if !ok {
590 client.Secret = ""
591 } else {
592 profile, err := authenticate(username, password, c)
593 if err != nil {
594 if isAuthError(err) {
595 errors = append(errors, requestError{Slug: requestErrAccessDenied})
596 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
597 } else {
598 errors = append(errors, requestError{Slug: requestErrActOfGod})
599 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
600 }
601 return
602 }
603 if !client.OwnerID.Equal(profile.ID) {
604 client.Secret = ""
605 }
606 }
607 resp := response{
608 Clients: []Client{client},
609 Errors: errors,
610 }
611 encode(w, r, http.StatusOK, resp)
612 }
614 func ListClientsHandler(w http.ResponseWriter, r *http.Request, c Context) {
615 errors := []requestError{}
616 var err error
617 // BUG(paddy): If ids are provided in query params, retrieve only those clients
618 num := defaultClientResponseSize
619 offset := 0
620 ownerIDStr := r.URL.Query().Get("owner_id")
621 numStr := r.URL.Query().Get("num")
622 offsetStr := r.URL.Query().Get("offset")
623 if numStr != "" {
624 num, err = strconv.Atoi(numStr)
625 if err != nil {
626 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "num"})
627 }
628 if num > maxClientResponseSize {
629 errors = append(errors, requestError{Slug: requestErrOverflow, Param: "num"})
630 }
631 if num < 1 {
632 errors = append(errors, requestError{Slug: requestErrInsufficient, Param: "num"})
633 }
634 }
635 if offsetStr != "" {
636 offset, err = strconv.Atoi(offsetStr)
637 if err != nil {
638 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "offset"})
639 }
640 }
641 if ownerIDStr == "" {
642 errors = append(errors, requestError{Slug: requestErrMissing, Param: "owner_id"})
643 }
644 if len(errors) > 0 {
645 encode(w, r, http.StatusBadRequest, response{Errors: errors})
646 return
647 }
648 ownerID, err := uuid.Parse(ownerIDStr)
649 if err != nil {
650 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "owner_id"})
651 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
652 return
653 }
654 clients, err := c.ListClientsByOwner(ownerID, num, offset)
655 if err != nil {
656 errors = append(errors, requestError{Slug: requestErrActOfGod})
657 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
658 return
659 }
660 username, password, ok := r.BasicAuth()
661 if !ok {
662 for pos, client := range clients {
663 client.Secret = ""
664 clients[pos] = client
665 }
666 } else {
667 profile, err := authenticate(username, password, c)
668 if err != nil {
669 if isAuthError(err) {
670 errors = append(errors, requestError{Slug: requestErrAccessDenied})
671 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
672 } else {
673 errors = append(errors, requestError{Slug: requestErrActOfGod})
674 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
675 }
676 return
677 }
678 for pos, client := range clients {
679 if !client.OwnerID.Equal(profile.ID) {
680 client.Secret = ""
681 clients[pos] = client
682 }
683 }
684 }
685 resp := response{
686 Clients: clients,
687 Errors: errors,
688 }
689 encode(w, r, http.StatusOK, resp)
690 }
692 func UpdateClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
693 errors := []requestError{}
694 vars := mux.Vars(r)
695 if _, ok := vars["id"]; !ok {
696 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
697 encode(w, r, http.StatusBadRequest, response{Errors: errors})
698 return
699 }
700 id, err := uuid.Parse(vars["id"])
701 if err != nil {
702 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
703 }
704 username, password, ok := r.BasicAuth()
705 if !ok {
706 errors = append(errors, requestError{Slug: requestErrAccessDenied})
707 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
708 return
709 }
710 profile, err := authenticate(username, password, c)
711 if err != nil {
712 if isAuthError(err) {
713 errors = append(errors, requestError{Slug: requestErrAccessDenied})
714 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
715 } else {
716 errors = append(errors, requestError{Slug: requestErrActOfGod})
717 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
718 }
719 return
720 }
721 var change ClientChange
722 err = decode(r, &change)
723 if err != nil {
724 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/"})
725 encode(w, r, http.StatusBadRequest, response{Errors: errors})
726 return
727 }
728 errs := change.Validate()
729 for _, err := range errs {
730 switch err {
731 case ErrEmptyChange:
732 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/"})
733 case ErrClientNameTooShort:
734 errors = append(errors, requestError{Slug: requestErrInsufficient, Field: "/name"})
735 case ErrClientNameTooLong:
736 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/name"})
737 case ErrClientLogoTooLong:
738 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/logo"})
739 case ErrClientLogoNotURL:
740 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/logo"})
741 case ErrClientWebsiteTooLong:
742 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/website"})
743 case ErrClientWebsiteNotURL:
744 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/website"})
745 default:
746 log.Println("Unrecognised error from client change validation:", err)
747 }
748 }
749 if len(errors) > 0 {
750 encode(w, r, http.StatusBadRequest, response{Errors: errors})
751 return
752 }
753 client, err := c.GetClient(id)
754 if err == ErrClientNotFound {
755 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
756 encode(w, r, http.StatusNotFound, response{Errors: errors})
757 return
758 } else if err != nil {
759 log.Println("Error retrieving client:", err)
760 errors = append(errors, requestError{Slug: requestErrActOfGod})
761 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
762 return
763 }
764 if !client.OwnerID.Equal(profile.ID) {
765 errors = append(errors, requestError{Slug: requestErrAccessDenied})
766 encode(w, r, http.StatusForbidden, response{Errors: errors})
767 return
768 }
769 if change.Secret != nil && client.Type == clientTypeConfidential {
770 secret := make([]byte, 32)
771 _, err = rand.Read(secret)
772 if err != nil {
773 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
774 return
775 }
776 newSecret := hex.EncodeToString(secret)
777 change.Secret = &newSecret
778 }
779 err = c.UpdateClient(id, change)
780 if err != nil {
781 log.Println("Error updating client:", err)
782 errors = append(errors, requestError{Slug: requestErrActOfGod})
783 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
784 return
785 }
786 client.ApplyChange(change)
787 encode(w, r, http.StatusOK, response{Clients: []Client{client}, Errors: errors})
788 return
789 }
791 func RemoveClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
792 errors := []requestError{}
793 vars := mux.Vars(r)
794 if _, ok := vars["id"]; !ok {
795 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
796 encode(w, r, http.StatusNotFound, response{Errors: errors})
797 return
798 }
799 id, err := uuid.Parse(vars["id"])
800 if err != nil {
801 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
802 }
803 username, password, ok := r.BasicAuth()
804 if !ok {
805 errors = append(errors, requestError{Slug: requestErrAccessDenied})
806 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
807 return
808 }
809 profile, err := authenticate(username, password, c)
810 if err != nil {
811 if isAuthError(err) {
812 errors = append(errors, requestError{Slug: requestErrAccessDenied})
813 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
814 } else {
815 errors = append(errors, requestError{Slug: requestErrActOfGod})
816 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
817 }
818 return
819 }
820 client, err := c.GetClient(id)
821 if err != nil {
822 if err == ErrClientNotFound {
823 errors = append(errors, requestError{Slug: requestErrNotFound})
824 encode(w, r, http.StatusNotFound, response{Errors: errors})
825 return
826 }
827 log.Println("Error retrieving client:", err)
828 errors = append(errors, requestError{Slug: requestErrActOfGod})
829 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
830 return
831 }
832 if !client.OwnerID.Equal(profile.ID) {
833 errors = append(errors, requestError{Slug: requestErrAccessDenied})
834 encode(w, r, http.StatusForbidden, response{Errors: errors})
835 return
836 }
837 deleted := true
838 change := ClientChange{Deleted: &deleted}
839 err = c.UpdateClient(id, change)
840 if err != nil {
841 if err == ErrClientNotFound {
842 errors = append(errors, requestError{Slug: requestErrNotFound})
843 encode(w, r, http.StatusNotFound, response{Errors: errors})
844 return
845 }
846 log.Println("Error deleting client:", err)
847 errors = append(errors, requestError{Slug: requestErrActOfGod})
848 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
849 return
850 }
851 encode(w, r, http.StatusOK, response{Errors: errors})
852 go cleanUpAfterClientDeletion(id, c)
853 return
854 }
856 func AddEndpointsHandler(w http.ResponseWriter, r *http.Request, c Context) {
857 type addEndpointReq struct {
858 Endpoints []string `json:"endpoints"`
859 }
860 errors := []requestError{}
861 vars := mux.Vars(r)
862 if vars["id"] == "" {
863 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
864 encode(w, r, http.StatusBadRequest, response{Errors: errors})
865 return
866 }
867 id, err := uuid.Parse(vars["id"])
868 if err != nil {
869 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
870 encode(w, r, http.StatusBadRequest, response{Errors: errors})
871 return
872 }
873 username, password, ok := r.BasicAuth()
874 if !ok {
875 errors = append(errors, requestError{Slug: requestErrAccessDenied})
876 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
877 return
878 }
879 profile, err := authenticate(username, password, c)
880 if err != nil {
881 if isAuthError(err) {
882 errors = append(errors, requestError{Slug: requestErrAccessDenied})
883 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
884 } else {
885 errors = append(errors, requestError{Slug: requestErrActOfGod})
886 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
887 }
888 return
889 }
890 client, err := c.GetClient(id)
891 if err != nil {
892 if err == ErrClientNotFound {
893 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
894 encode(w, r, http.StatusBadRequest, response{Errors: errors})
895 return
896 }
897 errors = append(errors, requestError{Slug: requestErrActOfGod})
898 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
899 return
900 }
901 if !client.OwnerID.Equal(profile.ID) {
902 errors = append(errors, requestError{Slug: requestErrAccessDenied})
903 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
904 return
905 }
906 var req addEndpointReq
907 decoder := json.NewDecoder(r.Body)
908 err = decoder.Decode(&req)
909 if err != nil {
910 encode(w, r, http.StatusBadRequest, invalidFormatResponse)
911 return
912 }
913 if len(req.Endpoints) < 1 {
914 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/endpoints"})
915 encode(w, r, http.StatusBadRequest, response{Errors: errors})
916 return
917 }
918 endpoints := []Endpoint{}
919 for pos, u := range req.Endpoints {
920 if parsed, err := url.Parse(u); err != nil {
921 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)})
922 continue
923 } else if !parsed.IsAbs() {
924 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/endpoints" + strconv.Itoa(pos)})
925 continue
926 }
927 e := Endpoint{
928 ID: uuid.NewID(),
929 ClientID: id,
930 URI: u,
931 Added: time.Now(),
932 }
933 endpoints = append(endpoints, e)
934 }
935 if len(errors) > 0 {
936 encode(w, r, http.StatusBadRequest, response{Errors: errors})
937 return
938 }
939 err = c.AddEndpoints(endpoints)
940 if err != nil {
941 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
942 return
943 }
944 resp := response{
945 Errors: errors,
946 Endpoints: endpoints,
947 }
948 encode(w, r, http.StatusCreated, resp)
949 }
951 func ListEndpointsHandler(w http.ResponseWriter, r *http.Request, c Context) {
952 errors := []requestError{}
953 vars := mux.Vars(r)
954 clientID, err := uuid.Parse(vars["id"])
955 if err != nil {
956 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "client_id"})
957 encode(w, r, http.StatusBadRequest, response{Errors: errors})
958 return
959 }
960 num := defaultEndpointResponseSize
961 offset := 0
962 numStr := r.URL.Query().Get("num")
963 offsetStr := r.URL.Query().Get("offset")
964 if numStr != "" {
965 num, err = strconv.Atoi(numStr)
966 if err != nil {
967 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "num"})
968 }
969 if num > maxEndpointResponseSize {
970 errors = append(errors, requestError{Slug: requestErrOverflow, Param: "num"})
971 }
972 }
973 if offsetStr != "" {
974 offset, err = strconv.Atoi(offsetStr)
975 if err != nil {
976 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "offset"})
977 }
978 }
979 if len(errors) > 0 {
980 encode(w, r, http.StatusBadRequest, response{Errors: errors})
981 return
982 }
983 endpoints, err := c.ListEndpoints(clientID, num, offset)
984 if err != nil {
985 errors = append(errors, requestError{Slug: requestErrActOfGod})
986 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
987 return
988 }
989 resp := response{
990 Endpoints: endpoints,
991 Errors: errors,
992 }
993 encode(w, r, http.StatusOK, resp)
994 }
996 func RemoveEndpointHandler(w http.ResponseWriter, r *http.Request, c Context) {
997 errors := []requestError{}
998 vars := mux.Vars(r)
999 if vars["client_id"] == "" {
1000 errors = append(errors, requestError{Slug: requestErrMissing, Param: "client_id"})
1001 encode(w, r, http.StatusBadRequest, response{Errors: errors})
1002 return
1004 clientID, err := uuid.Parse(vars["client_id"])
1005 if err != nil {
1006 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "client_id"})
1007 encode(w, r, http.StatusBadRequest, response{Errors: errors})
1008 return
1010 if vars["id"] == "" {
1011 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
1012 encode(w, r, http.StatusBadRequest, response{Errors: errors})
1013 return
1015 id, err := uuid.Parse(vars["id"])
1016 if err != nil {
1017 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
1018 encode(w, r, http.StatusBadRequest, response{Errors: errors})
1019 return
1021 username, password, ok := r.BasicAuth()
1022 if !ok {
1023 errors = append(errors, requestError{Slug: requestErrAccessDenied})
1024 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
1025 return
1027 profile, err := authenticate(username, password, c)
1028 if err != nil {
1029 if isAuthError(err) {
1030 errors = append(errors, requestError{Slug: requestErrAccessDenied})
1031 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
1032 } else {
1033 errors = append(errors, requestError{Slug: requestErrActOfGod})
1034 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
1036 return
1038 client, err := c.GetClient(clientID)
1039 if err != nil {
1040 if err == ErrClientNotFound {
1041 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "client_id"})
1042 encode(w, r, http.StatusBadRequest, response{Errors: errors})
1043 return
1045 errors = append(errors, requestError{Slug: requestErrActOfGod})
1046 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
1047 return
1049 if !client.OwnerID.Equal(profile.ID) {
1050 errors = append(errors, requestError{Slug: requestErrAccessDenied})
1051 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
1052 return
1054 endpoint, err := c.GetEndpoint(clientID, id)
1055 if err != nil {
1056 if err == ErrEndpointNotFound {
1057 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
1058 encode(w, r, http.StatusBadRequest, response{Errors: errors})
1059 return
1061 errors = append(errors, requestError{Slug: requestErrActOfGod})
1062 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
1063 return
1065 err = c.RemoveEndpoint(clientID, id)
1066 if err != nil {
1067 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
1068 return
1070 resp := response{
1071 Errors: errors,
1072 Endpoints: []Endpoint{endpoint},
1074 encode(w, r, http.StatusCreated, resp)
1077 func clientCredentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
1078 scopes = stringsToScopes(strings.Split(r.PostFormValue("scope"), " "))
1079 valid = true
1080 return
1083 func clientCredentialsAuditString(r *http.Request) string {
1084 return "client_credentials"