auth

Paddy 2015-04-11 Parent:77db7c65216c Child:73e12d5a1124

162:6f473576c6ae Go to Latest

auth/client.go

Clean up sessions and tokens after Profile is deleted. Add a terminateSessionsByProfile method to our sessionStore to mark Sessions associated with a Profile as inactive. Implement memstore and postgres implementations of the terminateSessionsByProfile method. Add a TerminateSessionsByProfile wrapper method to Context. Add a revokeTokensByProfileID method to our tokenStore to mark Tokens associated with a Profile as revoked. Implement memstore and postgres implementation of the revokeTokensByProfileID method. Add a RevokeTokensByProfileID wrapper method to Context. Call our RevokeTokensByProfileID and TerminateSessionsByProfile methods after a Profile is deleted, to clean up the Tokens and Sessions associated with it.

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)
272 addEndpoints(endpoint []Endpoint) error
273 removeEndpoint(client, endpoint uuid.ID) error
274 getEndpoint(client, endpoint uuid.ID) (Endpoint, error)
275 checkEndpoint(client uuid.ID, endpoint string) (bool, error)
276 listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
277 countEndpoints(client uuid.ID) (int64, error)
278 }
280 func (m *memstore) getClient(id uuid.ID) (Client, error) {
281 m.clientLock.RLock()
282 defer m.clientLock.RUnlock()
283 c, ok := m.clients[id.String()]
284 if !ok || c.Deleted {
285 return Client{}, ErrClientNotFound
286 }
287 return c, nil
288 }
290 func (m *memstore) saveClient(client Client) error {
291 m.clientLock.Lock()
292 defer m.clientLock.Unlock()
293 if _, ok := m.clients[client.ID.String()]; ok {
294 return ErrClientAlreadyExists
295 }
296 m.clients[client.ID.String()] = client
297 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
298 return nil
299 }
301 func (m *memstore) updateClient(id uuid.ID, change ClientChange) error {
302 m.clientLock.Lock()
303 defer m.clientLock.Unlock()
304 c, ok := m.clients[id.String()]
305 if !ok {
306 return ErrClientNotFound
307 }
308 c.ApplyChange(change)
309 m.clients[id.String()] = c
310 return nil
311 }
313 func (m *memstore) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
314 ids := m.lookupClientsByProfileID(ownerID.String())
315 if len(ids) > num+offset {
316 ids = ids[offset : num+offset]
317 } else if len(ids) > offset {
318 ids = ids[offset:]
319 } else {
320 return []Client{}, nil
321 }
322 clients := []Client{}
323 for _, id := range ids {
324 client, err := m.getClient(id)
325 if err != nil {
326 if err == ErrClientNotFound {
327 continue
328 }
329 return []Client{}, err
330 }
331 clients = append(clients, client)
332 }
333 return clients, nil
334 }
336 func (m *memstore) addEndpoints(endpoints []Endpoint) error {
337 m.endpointLock.Lock()
338 defer m.endpointLock.Unlock()
339 clients := map[string][]Endpoint{}
340 for _, endpoint := range endpoints {
341 clients[endpoint.ClientID.String()] = append(clients[endpoint.ClientID.String()], endpoint)
342 }
343 for client, e := range clients {
344 m.endpoints[client] = append(m.endpoints[client], e...)
345 }
346 return nil
347 }
349 func (m *memstore) getEndpoint(client, endpoint uuid.ID) (Endpoint, error) {
350 m.endpointLock.Lock()
351 defer m.endpointLock.Unlock()
352 for _, item := range m.endpoints[client.String()] {
353 if item.ID.Equal(endpoint) {
354 return item, nil
355 }
356 }
357 return Endpoint{}, ErrEndpointNotFound
358 }
360 func (m *memstore) removeEndpoint(client, endpoint uuid.ID) error {
361 m.endpointLock.Lock()
362 defer m.endpointLock.Unlock()
363 pos := -1
364 for p, item := range m.endpoints[client.String()] {
365 if item.ID.Equal(endpoint) {
366 pos = p
367 break
368 }
369 }
370 if pos >= 0 {
371 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
372 }
373 return nil
374 }
376 func (m *memstore) checkEndpoint(client uuid.ID, endpoint string) (bool, error) {
377 m.endpointLock.RLock()
378 defer m.endpointLock.RUnlock()
379 for _, candidate := range m.endpoints[client.String()] {
380 if endpoint == candidate.NormalizedURI {
381 return true, nil
382 }
383 }
384 return false, nil
385 }
387 func (m *memstore) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
388 m.endpointLock.RLock()
389 defer m.endpointLock.RUnlock()
390 return m.endpoints[client.String()], nil
391 }
393 func (m *memstore) countEndpoints(client uuid.ID) (int64, error) {
394 m.endpointLock.RLock()
395 defer m.endpointLock.RUnlock()
396 return int64(len(m.endpoints[client.String()])), nil
397 }
399 type newClientReq struct {
400 Name string `json:"name"`
401 Logo string `json:"logo"`
402 Website string `json:"website"`
403 Type string `json:"type"`
404 Endpoints []string `json:"endpoints"`
405 }
407 func RegisterClientHandlers(r *mux.Router, context Context) {
408 r.Handle("/clients", wrap(context, CreateClientHandler)).Methods("POST")
409 r.Handle("/clients", wrap(context, ListClientsHandler)).Methods("GET")
410 r.Handle("/clients/{id}", wrap(context, GetClientHandler)).Methods("GET")
411 r.Handle("/clients/{id}", wrap(context, UpdateClientHandler)).Methods("PATCH")
412 r.Handle("/clients/{id}", wrap(context, RemoveClientHandler)).Methods("DELETE")
413 r.Handle("/clients/{id}/endpoints", wrap(context, AddEndpointsHandler)).Methods("POST")
414 r.Handle("/clients/{client_id}/endpoints/{id}", wrap(context, RemoveEndpointHandler)).Methods("DELETE")
415 r.Handle("/clients/{id}/endpoints", wrap(context, ListEndpointsHandler)).Methods("GET")
416 }
418 func CreateClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
419 errors := []requestError{}
420 username, password, ok := r.BasicAuth()
421 if !ok {
422 errors = append(errors, requestError{Slug: requestErrAccessDenied})
423 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
424 return
425 }
426 profile, err := authenticate(username, password, c)
427 if err != nil {
428 if isAuthError(err) {
429 errors = append(errors, requestError{Slug: requestErrAccessDenied})
430 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
431 } else {
432 log.Printf("Error authenticating: %#+v\n", err)
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 log.Printf("Error generating secret: %#+v\n", err)
474 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
475 return
476 }
477 client.Secret = hex.EncodeToString(secret)
478 }
479 err = c.SaveClient(client)
480 if err != nil {
481 if err == ErrClientAlreadyExists {
482 errors = append(errors, requestError{Slug: requestErrConflict, Field: "/id"})
483 encode(w, r, http.StatusBadRequest, response{Errors: errors})
484 return
485 }
486 log.Printf("Error saving client: %#+v\n", err)
487 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
488 return
489 }
490 endpoints := []Endpoint{}
491 for pos, u := range req.Endpoints {
492 uri, err := url.Parse(u)
493 if err != nil {
494 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)})
495 continue
496 }
497 if !uri.IsAbs() {
498 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/endpoints/" + strconv.Itoa(pos)})
499 continue
500 }
501 endpoint := Endpoint{
502 ID: uuid.NewID(),
503 ClientID: client.ID,
504 URI: uri.String(),
505 Added: time.Now(),
506 }
507 endpoints = append(endpoints, endpoint)
508 }
509 err = c.AddEndpoints(endpoints)
510 if err != nil {
511 log.Printf("Error adding endpoints: %#+v\n", err)
512 errors = append(errors, requestError{Slug: requestErrActOfGod})
513 encode(w, r, http.StatusInternalServerError, response{Errors: errors, Clients: []Client{client}})
514 return
515 }
516 resp := response{
517 Clients: []Client{client},
518 Endpoints: endpoints,
519 Errors: errors,
520 }
521 encode(w, r, http.StatusCreated, resp)
522 }
524 func GetClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
525 errors := []requestError{}
526 vars := mux.Vars(r)
527 if vars["id"] == "" {
528 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
529 encode(w, r, http.StatusBadRequest, response{Errors: errors})
530 return
531 }
532 id, err := uuid.Parse(vars["id"])
533 if err != nil {
534 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
535 encode(w, r, http.StatusBadRequest, response{Errors: errors})
536 return
537 }
538 client, err := c.GetClient(id)
539 if err != nil {
540 if err == ErrClientNotFound {
541 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
542 encode(w, r, http.StatusNotFound, response{Errors: errors})
543 return
544 }
545 errors = append(errors, requestError{Slug: requestErrActOfGod})
546 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
547 return
548 }
549 username, password, ok := r.BasicAuth()
550 if !ok {
551 client.Secret = ""
552 } else {
553 profile, err := authenticate(username, password, c)
554 if err != nil {
555 if isAuthError(err) {
556 errors = append(errors, requestError{Slug: requestErrAccessDenied})
557 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
558 } else {
559 errors = append(errors, requestError{Slug: requestErrActOfGod})
560 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
561 }
562 return
563 }
564 if !client.OwnerID.Equal(profile.ID) {
565 client.Secret = ""
566 }
567 }
568 resp := response{
569 Clients: []Client{client},
570 Errors: errors,
571 }
572 encode(w, r, http.StatusOK, resp)
573 }
575 func ListClientsHandler(w http.ResponseWriter, r *http.Request, c Context) {
576 errors := []requestError{}
577 var err error
578 // BUG(paddy): If ids are provided in query params, retrieve only those clients
579 num := defaultClientResponseSize
580 offset := 0
581 ownerIDStr := r.URL.Query().Get("owner_id")
582 numStr := r.URL.Query().Get("num")
583 offsetStr := r.URL.Query().Get("offset")
584 if numStr != "" {
585 num, err = strconv.Atoi(numStr)
586 if err != nil {
587 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "num"})
588 }
589 if num > maxClientResponseSize {
590 errors = append(errors, requestError{Slug: requestErrOverflow, Param: "num"})
591 }
592 }
593 if offsetStr != "" {
594 offset, err = strconv.Atoi(offsetStr)
595 if err != nil {
596 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "offset"})
597 }
598 }
599 if ownerIDStr == "" {
600 errors = append(errors, requestError{Slug: requestErrMissing, Param: "owner_id"})
601 }
602 if len(errors) > 0 {
603 encode(w, r, http.StatusBadRequest, response{Errors: errors})
604 return
605 }
606 ownerID, err := uuid.Parse(ownerIDStr)
607 if err != nil {
608 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "owner_id"})
609 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
610 return
611 }
612 clients, err := c.ListClientsByOwner(ownerID, num, offset)
613 if err != nil {
614 errors = append(errors, requestError{Slug: requestErrActOfGod})
615 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
616 return
617 }
618 username, password, ok := r.BasicAuth()
619 if !ok {
620 for pos, client := range clients {
621 client.Secret = ""
622 clients[pos] = client
623 }
624 } else {
625 profile, err := authenticate(username, password, c)
626 if err != nil {
627 if isAuthError(err) {
628 errors = append(errors, requestError{Slug: requestErrAccessDenied})
629 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
630 } else {
631 errors = append(errors, requestError{Slug: requestErrActOfGod})
632 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
633 }
634 return
635 }
636 for pos, client := range clients {
637 if !client.OwnerID.Equal(profile.ID) {
638 client.Secret = ""
639 clients[pos] = client
640 }
641 }
642 }
643 resp := response{
644 Clients: clients,
645 Errors: errors,
646 }
647 encode(w, r, http.StatusOK, resp)
648 }
650 func UpdateClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
651 errors := []requestError{}
652 vars := mux.Vars(r)
653 if _, ok := vars["id"]; !ok {
654 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
655 encode(w, r, http.StatusBadRequest, response{Errors: errors})
656 return
657 }
658 id, err := uuid.Parse(vars["id"])
659 if err != nil {
660 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
661 }
662 username, password, ok := r.BasicAuth()
663 if !ok {
664 errors = append(errors, requestError{Slug: requestErrAccessDenied})
665 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
666 return
667 }
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 var change ClientChange
680 err = decode(r, &change)
681 if err != nil {
682 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/"})
683 encode(w, r, http.StatusBadRequest, response{Errors: errors})
684 return
685 }
686 errs := change.Validate()
687 for _, err := range errs {
688 switch err {
689 case ErrEmptyChange:
690 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/"})
691 case ErrClientNameTooShort:
692 errors = append(errors, requestError{Slug: requestErrInsufficient, Field: "/name"})
693 case ErrClientNameTooLong:
694 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/name"})
695 case ErrClientLogoTooLong:
696 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/logo"})
697 case ErrClientLogoNotURL:
698 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/logo"})
699 case ErrClientWebsiteTooLong:
700 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/website"})
701 case ErrClientWebsiteNotURL:
702 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/website"})
703 default:
704 log.Println("Unrecognised error from client change validation:", err)
705 }
706 }
707 if len(errors) > 0 {
708 encode(w, r, http.StatusBadRequest, response{Errors: errors})
709 return
710 }
711 client, err := c.GetClient(id)
712 if err == ErrClientNotFound {
713 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
714 encode(w, r, http.StatusNotFound, response{Errors: errors})
715 return
716 } else if err != nil {
717 log.Println("Error retrieving client:", err)
718 errors = append(errors, requestError{Slug: requestErrActOfGod})
719 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
720 return
721 }
722 if !client.OwnerID.Equal(profile.ID) {
723 errors = append(errors, requestError{Slug: requestErrAccessDenied})
724 encode(w, r, http.StatusForbidden, response{Errors: errors})
725 return
726 }
727 if change.Secret != nil && client.Type == clientTypeConfidential {
728 secret := make([]byte, 32)
729 _, err = rand.Read(secret)
730 if err != nil {
731 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
732 return
733 }
734 newSecret := hex.EncodeToString(secret)
735 change.Secret = &newSecret
736 }
737 err = c.UpdateClient(id, change)
738 if err != nil {
739 log.Println("Error updating client:", err)
740 errors = append(errors, requestError{Slug: requestErrActOfGod})
741 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
742 return
743 }
744 client.ApplyChange(change)
745 encode(w, r, http.StatusOK, response{Clients: []Client{client}, Errors: errors})
746 return
747 }
749 func RemoveClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
750 errors := []requestError{}
751 vars := mux.Vars(r)
752 if _, ok := vars["id"]; !ok {
753 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
754 encode(w, r, http.StatusNotFound, response{Errors: errors})
755 return
756 }
757 id, err := uuid.Parse(vars["id"])
758 if err != nil {
759 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
760 }
761 username, password, ok := r.BasicAuth()
762 if !ok {
763 errors = append(errors, requestError{Slug: requestErrAccessDenied})
764 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
765 return
766 }
767 profile, err := authenticate(username, password, c)
768 if err != nil {
769 if isAuthError(err) {
770 errors = append(errors, requestError{Slug: requestErrAccessDenied})
771 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
772 } else {
773 errors = append(errors, requestError{Slug: requestErrActOfGod})
774 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
775 }
776 return
777 }
778 client, err := c.GetClient(id)
779 if err != nil {
780 if err == ErrClientNotFound {
781 errors = append(errors, requestError{Slug: requestErrNotFound})
782 encode(w, r, http.StatusNotFound, response{Errors: errors})
783 return
784 }
785 log.Println("Error retrieving client:", err)
786 errors = append(errors, requestError{Slug: requestErrActOfGod})
787 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
788 return
789 }
790 if !client.OwnerID.Equal(profile.ID) {
791 errors = append(errors, requestError{Slug: requestErrAccessDenied})
792 encode(w, r, http.StatusForbidden, response{Errors: errors})
793 return
794 }
795 deleted := true
796 change := ClientChange{Deleted: &deleted}
797 err = c.UpdateClient(id, change)
798 if err != nil {
799 if err == ErrClientNotFound {
800 errors = append(errors, requestError{Slug: requestErrNotFound})
801 encode(w, r, http.StatusNotFound, response{Errors: errors})
802 return
803 }
804 log.Println("Error deleting client:", err)
805 errors = append(errors, requestError{Slug: requestErrActOfGod})
806 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
807 return
808 }
809 // BUG(paddy): Client needs to clean up after itself, invalidating tokens, deleting unused grants, deleting endpoints
810 encode(w, r, http.StatusOK, response{Errors: errors})
811 return
812 }
814 func AddEndpointsHandler(w http.ResponseWriter, r *http.Request, c Context) {
815 type addEndpointReq struct {
816 Endpoints []string `json:"endpoints"`
817 }
818 errors := []requestError{}
819 vars := mux.Vars(r)
820 if vars["id"] == "" {
821 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
822 encode(w, r, http.StatusBadRequest, response{Errors: errors})
823 return
824 }
825 id, err := uuid.Parse(vars["id"])
826 if err != nil {
827 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
828 encode(w, r, http.StatusBadRequest, response{Errors: errors})
829 return
830 }
831 username, password, ok := r.BasicAuth()
832 if !ok {
833 errors = append(errors, requestError{Slug: requestErrAccessDenied})
834 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
835 return
836 }
837 profile, err := authenticate(username, password, c)
838 if err != nil {
839 if isAuthError(err) {
840 errors = append(errors, requestError{Slug: requestErrAccessDenied})
841 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
842 } else {
843 errors = append(errors, requestError{Slug: requestErrActOfGod})
844 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
845 }
846 return
847 }
848 client, err := c.GetClient(id)
849 if err != nil {
850 if err == ErrClientNotFound {
851 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
852 encode(w, r, http.StatusBadRequest, response{Errors: errors})
853 return
854 }
855 errors = append(errors, requestError{Slug: requestErrActOfGod})
856 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
857 return
858 }
859 if !client.OwnerID.Equal(profile.ID) {
860 errors = append(errors, requestError{Slug: requestErrAccessDenied})
861 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
862 return
863 }
864 var req addEndpointReq
865 decoder := json.NewDecoder(r.Body)
866 err = decoder.Decode(&req)
867 if err != nil {
868 encode(w, r, http.StatusBadRequest, invalidFormatResponse)
869 return
870 }
871 if len(req.Endpoints) < 1 {
872 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/endpoints"})
873 encode(w, r, http.StatusBadRequest, response{Errors: errors})
874 return
875 }
876 endpoints := []Endpoint{}
877 for pos, u := range req.Endpoints {
878 if parsed, err := url.Parse(u); err != nil {
879 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)})
880 continue
881 } else if !parsed.IsAbs() {
882 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/endpoints" + strconv.Itoa(pos)})
883 continue
884 }
885 e := Endpoint{
886 ID: uuid.NewID(),
887 ClientID: id,
888 URI: u,
889 Added: time.Now(),
890 }
891 endpoints = append(endpoints, e)
892 }
893 if len(errors) > 0 {
894 encode(w, r, http.StatusBadRequest, response{Errors: errors})
895 return
896 }
897 err = c.AddEndpoints(endpoints)
898 if err != nil {
899 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
900 return
901 }
902 resp := response{
903 Errors: errors,
904 Endpoints: endpoints,
905 }
906 encode(w, r, http.StatusCreated, resp)
907 }
909 func ListEndpointsHandler(w http.ResponseWriter, r *http.Request, c Context) {
910 errors := []requestError{}
911 vars := mux.Vars(r)
912 clientID, err := uuid.Parse(vars["id"])
913 if err != nil {
914 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "client_id"})
915 encode(w, r, http.StatusBadRequest, response{Errors: errors})
916 return
917 }
918 num := defaultEndpointResponseSize
919 offset := 0
920 numStr := r.URL.Query().Get("num")
921 offsetStr := r.URL.Query().Get("offset")
922 if numStr != "" {
923 num, err = strconv.Atoi(numStr)
924 if err != nil {
925 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "num"})
926 }
927 if num > maxEndpointResponseSize {
928 errors = append(errors, requestError{Slug: requestErrOverflow, Param: "num"})
929 }
930 }
931 if offsetStr != "" {
932 offset, err = strconv.Atoi(offsetStr)
933 if err != nil {
934 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "offset"})
935 }
936 }
937 if len(errors) > 0 {
938 encode(w, r, http.StatusBadRequest, response{Errors: errors})
939 return
940 }
941 endpoints, err := c.ListEndpoints(clientID, num, offset)
942 if err != nil {
943 errors = append(errors, requestError{Slug: requestErrActOfGod})
944 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
945 return
946 }
947 resp := response{
948 Endpoints: endpoints,
949 Errors: errors,
950 }
951 encode(w, r, http.StatusOK, resp)
952 }
954 func RemoveEndpointHandler(w http.ResponseWriter, r *http.Request, c Context) {
955 errors := []requestError{}
956 vars := mux.Vars(r)
957 if vars["client_id"] == "" {
958 errors = append(errors, requestError{Slug: requestErrMissing, Param: "client_id"})
959 encode(w, r, http.StatusBadRequest, response{Errors: errors})
960 return
961 }
962 clientID, err := uuid.Parse(vars["client_id"])
963 if err != nil {
964 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "client_id"})
965 encode(w, r, http.StatusBadRequest, response{Errors: errors})
966 return
967 }
968 if vars["id"] == "" {
969 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
970 encode(w, r, http.StatusBadRequest, response{Errors: errors})
971 return
972 }
973 id, err := uuid.Parse(vars["id"])
974 if err != nil {
975 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
976 encode(w, r, http.StatusBadRequest, response{Errors: errors})
977 return
978 }
979 username, password, ok := r.BasicAuth()
980 if !ok {
981 errors = append(errors, requestError{Slug: requestErrAccessDenied})
982 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
983 return
984 }
985 profile, err := authenticate(username, password, c)
986 if err != nil {
987 if isAuthError(err) {
988 errors = append(errors, requestError{Slug: requestErrAccessDenied})
989 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
990 } else {
991 errors = append(errors, requestError{Slug: requestErrActOfGod})
992 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
993 }
994 return
995 }
996 client, err := c.GetClient(clientID)
997 if err != nil {
998 if err == ErrClientNotFound {
999 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "client_id"})
1000 encode(w, r, http.StatusBadRequest, response{Errors: errors})
1001 return
1003 errors = append(errors, requestError{Slug: requestErrActOfGod})
1004 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
1005 return
1007 if !client.OwnerID.Equal(profile.ID) {
1008 errors = append(errors, requestError{Slug: requestErrAccessDenied})
1009 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
1010 return
1012 endpoint, err := c.GetEndpoint(clientID, id)
1013 if err != nil {
1014 if err == ErrEndpointNotFound {
1015 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
1016 encode(w, r, http.StatusBadRequest, response{Errors: errors})
1017 return
1019 errors = append(errors, requestError{Slug: requestErrActOfGod})
1020 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
1021 return
1023 err = c.RemoveEndpoint(clientID, id)
1024 if err != nil {
1025 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
1026 return
1028 resp := response{
1029 Errors: errors,
1030 Endpoints: []Endpoint{endpoint},
1032 encode(w, r, http.StatusCreated, resp)
1035 func clientCredentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes []string, profileID uuid.ID, valid bool) {
1036 scopes = strings.Split(r.PostFormValue("scope"), " ")
1037 valid = true
1038 return
1041 func clientCredentialsAuditString(r *http.Request) string {
1042 return "client_credentials"