auth
auth/client.go
Add Client updating from the API. Add a handler to update Clients using the API. Add a helper that will decode a request for us based on its Content-Type header. Change the ClientChange.Validate function to return as many errors as possible, as opposed to just the first error it encounters. Update the ClientChange.Validate tests to take advantage of the new signature.
1.1 --- a/client.go Wed Jan 28 07:27:32 2015 -0500 1.2 +++ b/client.go Thu Jan 29 20:40:55 2015 -0500 1.3 @@ -111,35 +111,37 @@ 1.4 1.5 // Validate checks the ClientChange it is called on 1.6 // and asserts its internal validity, or lack thereof. 1.7 -func (c ClientChange) Validate() error { 1.8 +func (c ClientChange) Validate() []error { 1.9 + errors := []error{} 1.10 if c.Secret == nil && c.OwnerID == nil && c.Name == nil && c.Logo == nil && c.Website == nil { 1.11 - return ErrEmptyChange 1.12 + errors = append(errors, ErrEmptyChange) 1.13 + return errors 1.14 } 1.15 if c.Name != nil && len(*c.Name) < 2 { 1.16 - return ErrClientNameTooShort 1.17 + errors = append(errors, ErrClientNameTooShort) 1.18 } 1.19 if c.Name != nil && len(*c.Name) > 32 { 1.20 - return ErrClientNameTooLong 1.21 + errors = append(errors, ErrClientNameTooLong) 1.22 } 1.23 if c.Logo != nil && *c.Logo != "" { 1.24 if len(*c.Logo) > 1024 { 1.25 - return ErrClientLogoTooLong 1.26 + errors = append(errors, ErrClientLogoTooLong) 1.27 } 1.28 u, err := url.Parse(*c.Logo) 1.29 if err != nil || !u.IsAbs() { 1.30 - return ErrClientLogoNotURL 1.31 + errors = append(errors, ErrClientLogoNotURL) 1.32 } 1.33 } 1.34 if c.Website != nil && *c.Website != "" { 1.35 if len(*c.Website) > 140 { 1.36 - return ErrClientWebsiteTooLong 1.37 + errors = append(errors, ErrClientWebsiteTooLong) 1.38 } 1.39 u, err := url.Parse(*c.Website) 1.40 if err != nil || !u.IsAbs() { 1.41 - return ErrClientWebsiteNotURL 1.42 + errors = append(errors, ErrClientWebsiteNotURL) 1.43 } 1.44 } 1.45 - return nil 1.46 + return errors 1.47 } 1.48 1.49 func getClientAuth(w http.ResponseWriter, r *http.Request, allowPublic bool) (uuid.ID, string, bool) { 1.50 @@ -390,7 +392,7 @@ 1.51 r.Handle("/clients", wrap(context, CreateClientHandler)).Methods("POST") 1.52 r.Handle("/clients", wrap(context, ListClientsHandler)).Methods("GET") 1.53 r.Handle("/clients/{id}", wrap(context, GetClientHandler)).Methods("GET") 1.54 - // BUG(paddy): We need to implement a handler to update a client. 1.55 + r.Handle("/clients/{id}", wrap(context, UpdateClientHandler)).Methods("PATCH") 1.56 // 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? 1.57 // BUG(paddy): We need to implement a handler to add an endpoint to a client. 1.58 // BUG(paddy): We need to implement a handler to remove an endpoint from a client. 1.59 @@ -583,6 +585,83 @@ 1.60 encode(w, r, http.StatusOK, resp) 1.61 } 1.62 1.63 +func UpdateClientHandler(w http.ResponseWriter, r *http.Request, c Context) { 1.64 + errors := []requestError{} 1.65 + vars := mux.Vars(r) 1.66 + if _, ok := vars["id"]; !ok { 1.67 + errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"}) 1.68 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.69 + return 1.70 + } 1.71 + var change ClientChange 1.72 + err := decode(r, &change) 1.73 + if err != nil { 1.74 + errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/"}) 1.75 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.76 + return 1.77 + } 1.78 + errs := change.Validate() 1.79 + for _, err := range errs { 1.80 + switch err { 1.81 + case ErrEmptyChange: 1.82 + errors = append(errors, requestError{Slug: requestErrMissing, Field: "/"}) 1.83 + case ErrClientNameTooShort: 1.84 + errors = append(errors, requestError{Slug: requestErrInsufficient, Field: "/name"}) 1.85 + case ErrClientNameTooLong: 1.86 + errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/name"}) 1.87 + case ErrClientLogoTooLong: 1.88 + errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/logo"}) 1.89 + case ErrClientLogoNotURL: 1.90 + errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/logo"}) 1.91 + case ErrClientWebsiteTooLong: 1.92 + errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/website"}) 1.93 + case ErrClientWebsiteNotURL: 1.94 + errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/website"}) 1.95 + default: 1.96 + log.Println("Unrecognised error from client change validation:", err) 1.97 + } 1.98 + } 1.99 + id, err := uuid.Parse(vars["id"]) 1.100 + if err != nil { 1.101 + errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"}) 1.102 + } 1.103 + if len(errors) > 0 { 1.104 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.105 + return 1.106 + } 1.107 + client, err := c.GetClient(id) 1.108 + if err == ErrClientNotFound { 1.109 + errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"}) 1.110 + encode(w, r, http.StatusNotFound, response{Errors: errors}) 1.111 + return 1.112 + } else if err != nil { 1.113 + log.Println("Error retrieving client:", err) 1.114 + errors = append(errors, requestError{Slug: requestErrActOfGod}) 1.115 + encode(w, r, http.StatusInternalServerError, response{Errors: errors}) 1.116 + return 1.117 + } 1.118 + if change.Secret != nil && client.Type == clientTypeConfidential { 1.119 + secret := make([]byte, 32) 1.120 + _, err = rand.Read(secret) 1.121 + if err != nil { 1.122 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.123 + return 1.124 + } 1.125 + newSecret := hex.EncodeToString(secret) 1.126 + change.Secret = &newSecret 1.127 + } 1.128 + err = c.UpdateClient(id, change) 1.129 + if err != nil { 1.130 + log.Println("Error updating client:", err) 1.131 + errors = append(errors, requestError{Slug: requestErrActOfGod}) 1.132 + encode(w, r, http.StatusInternalServerError, response{Errors: errors}) 1.133 + return 1.134 + } 1.135 + client.ApplyChange(change) 1.136 + encode(w, r, http.StatusOK, response{Clients: []Client{client}, Errors: errors}) 1.137 + return 1.138 +} 1.139 + 1.140 func clientCredentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) { 1.141 scope = r.PostFormValue("scope") 1.142 valid = true