auth

Paddy 2015-12-14 Parent:8ecb60d29b0d

181:b7e685839a1b Go to Latest

auth/client.go

Break out scopes and events. This repo has gotten unwieldy, and there are portions of it that need to be imported by a large number of other packages. For example, scopes will be used in almost every API we write. Rather than importing the entirety of this codebase into every API we write, I've opted to move the scope logic out into a scopes package, with a subpackage for the defined types, which is all most projects actually want to import. We also define some event type constants, and importing those shouldn't require a project to import all our dependencies, either. So I made an events subpackage that just holds those constants. This package has become a little bit of a red-headed stepchild and is do for a refactor, but I'm trying to put that off as long as I can. The refactoring of our scopes stuff has left a bug wherein a token can be granted for scopes that don't exist. I'm going to need to revisit that, and also how to limit scopes to only be granted to the users that should be able to request them. But that's a battle for another day.

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/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
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
1011 if vars["id"] == "" {
1012 errors = append(errors, RequestError{Slug: RequestErrMissing, Param: "id"})
1013 encode(w, r, http.StatusBadRequest, Response{Errors: errors})
1014 return
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
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
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})
1037 return
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
1046 errors = append(errors, RequestError{Slug: RequestErrActOfGod})
1047 encode(w, r, http.StatusInternalServerError, Response{Errors: errors})
1048 return
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
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
1062 errors = append(errors, RequestError{Slug: RequestErrActOfGod})
1063 encode(w, r, http.StatusInternalServerError, Response{Errors: errors})
1064 return
1066 err = c.RemoveEndpoint(clientID, id)
1067 if err != nil {
1068 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
1069 return
1071 resp := Response{
1072 Errors: errors,
1073 Endpoints: []Endpoint{endpoint},
1075 encode(w, r, http.StatusCreated, resp)
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
1084 func clientCredentialsAuditString(r *http.Request) string {
1085 return "client_credentials"