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