auth

Paddy 2015-03-07 Parent:f1c8e13e1ce6 Child:93c758f57c69

143:3aeadd2201e9 Go to Latest

auth/client.go

Add GetEndpoint method, add RemoveEndpoint handler. Add a method to our clientStore and Context types for retrieving an Endpoint from the clientStore. We need this to tell if someone is trying to delete an Endpoint that doesn't exist. Add a handler for removing Endpoints from a Client.

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