auth
auth/client.go
Turn AddEndpoint into AddEndpoints. Because one is a special case of many, it makes sense to be able to add multiple endpoints in a single call to the database. So we've converted the AddEndpoint method into an AddEndpoints method and updated our tests appropriately. We also filled in the errors when creating a client through the API, and moved things around to optimize for the maximum number of errors returned in a single call.
1.1 --- a/client.go Sat Jan 10 04:16:07 2015 -0500 1.2 +++ b/client.go Wed Jan 14 00:23:30 2015 -0500 1.3 @@ -8,6 +8,7 @@ 1.4 "github.com/gorilla/mux" 1.5 "net/http" 1.6 "net/url" 1.7 + "strconv" 1.8 "time" 1.9 1.10 "code.secondbit.org/uuid.hg" 1.11 @@ -38,6 +39,11 @@ 1.12 ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL") 1.13 ) 1.14 1.15 +const ( 1.16 + clientTypePublic = "public" 1.17 + clientTypeConfidential = "confidential" 1.18 +) 1.19 + 1.20 // Client represents a client that grants access 1.21 // to the auth server, exchanging grants for tokens, 1.22 // and tokens for access. 1.23 @@ -190,7 +196,7 @@ 1.24 deleteClient(id uuid.ID) error 1.25 listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) 1.26 1.27 - addEndpoint(client uuid.ID, endpoint Endpoint) error 1.28 + addEndpoints(client uuid.ID, endpoint []Endpoint) error 1.29 removeEndpoint(client, endpoint uuid.ID) error 1.30 checkEndpoint(client uuid.ID, endpoint string) (bool, error) 1.31 listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) 1.32 @@ -271,10 +277,10 @@ 1.33 return clients, nil 1.34 } 1.35 1.36 -func (m *memstore) addEndpoint(client uuid.ID, endpoint Endpoint) error { 1.37 +func (m *memstore) addEndpoints(client uuid.ID, endpoints []Endpoint) error { 1.38 m.endpointLock.Lock() 1.39 defer m.endpointLock.Unlock() 1.40 - m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint) 1.41 + m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoints...) 1.42 return nil 1.43 } 1.44 1.45 @@ -330,14 +336,17 @@ 1.46 } 1.47 1.48 func CreateClientHandler(w http.ResponseWriter, r *http.Request, c Context) { 1.49 + errors := []requestError{} 1.50 username, password, ok := r.BasicAuth() 1.51 if !ok { 1.52 - // TODO(paddy): return error 1.53 + errors = append(errors, requestError{Slug: requestErrAccessDenied}) 1.54 + encode(w, r, http.StatusUnauthorized, response{Errors: errors}) 1.55 return 1.56 } 1.57 profile, err := authenticate(username, password, c) 1.58 if err != nil { 1.59 - // TODO(paddy): return error 1.60 + errors = append(errors, requestError{Slug: requestErrAccessDenied}) 1.61 + encode(w, r, http.StatusUnauthorized, response{Errors: errors}) 1.62 return 1.63 } 1.64 var req newClientReq 1.65 @@ -347,31 +356,43 @@ 1.66 encode(w, r, http.StatusBadRequest, invalidFormatResponse) 1.67 return 1.68 } 1.69 - secret := make([]byte, 32) 1.70 - _, err = rand.Read(secret) 1.71 - if err != nil { 1.72 - // TODO(paddy): return error 1.73 + if req.Type != clientTypePublic && req.Type != clientTypeConfidential { 1.74 + errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/type"}) 1.75 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.76 return 1.77 } 1.78 client := Client{ 1.79 ID: uuid.NewID(), 1.80 - Secret: hex.EncodeToString(secret), 1.81 OwnerID: profile.ID, 1.82 Name: req.Name, 1.83 Logo: req.Logo, 1.84 Website: req.Website, 1.85 Type: req.Type, 1.86 } 1.87 + if client.Type == clientTypePublic { 1.88 + secret := make([]byte, 32) 1.89 + _, err = rand.Read(secret) 1.90 + if err != nil { 1.91 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.92 + return 1.93 + } 1.94 + client.Secret = hex.EncodeToString(secret) 1.95 + } 1.96 err = c.SaveClient(client) 1.97 if err != nil { 1.98 - // TODO(paddy): return error 1.99 + if err == ErrClientAlreadyExists { 1.100 + errors = append(errors, requestError{Slug: requestErrConflict, Field: "/id"}) 1.101 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.102 + return 1.103 + } 1.104 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.105 return 1.106 } 1.107 endpoints := []Endpoint{} 1.108 - for _, u := range req.Endpoints { 1.109 + for pos, u := range req.Endpoints { 1.110 uri, err := url.Parse(u) 1.111 if err != nil { 1.112 - // TODO(paddy): add error to response 1.113 + errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)}) 1.114 continue 1.115 } 1.116 endpoint := Endpoint{ 1.117 @@ -380,13 +401,14 @@ 1.118 URI: *uri, 1.119 Added: time.Now(), 1.120 } 1.121 - err = c.AddEndpoint(client.ID, endpoint) 1.122 - if err != nil { 1.123 - // TODO(paddy): return error 1.124 - return 1.125 - } 1.126 endpoints = append(endpoints, endpoint) 1.127 } 1.128 + err = c.AddEndpoints(client.ID, endpoints) 1.129 + if err != nil { 1.130 + errors = append(errors, requestError{Slug: requestErrActOfGod}) 1.131 + encode(w, r, http.StatusInternalServerError, response{Errors: errors, Clients: []Client{client}}) 1.132 + return 1.133 + } 1.134 resp := response{ 1.135 Clients: []Client{client}, 1.136 Endpoints: endpoints,