auth

Paddy 2015-01-14 Parent:5bd46746b809 Child:e000b1c24fc0

115:fa8ee6a4507c Go to Latest

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.

History
     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,