auth

Paddy 2014-09-18 Parent:690561c6619a Child:022ce4262922

41:113ccb15b919 Go to Latest

auth/client.go

Added validation for clients, split endpoints out. Split endpoints out into their own type and added associated methods to the ClientStores, so now each client can have more than one redirect endpoint. Added unit testing for endpoint methods. Added validation code to validate client changes.

History
     1.1 --- a/client.go	Thu Sep 18 19:34:18 2014 -0400
     1.2 +++ b/client.go	Thu Sep 18 22:13:22 2014 -0400
     1.3 @@ -2,35 +2,42 @@
     1.4  
     1.5  import (
     1.6  	"errors"
     1.7 +	"net/url"
     1.8 +	"time"
     1.9  
    1.10 +	"strings"
    1.11  	"secondbit.org/uuid"
    1.12  )
    1.13  
    1.14  var (
    1.15  	ErrClientNotFound      = errors.New("Client not found in ClientStore.")
    1.16  	ErrClientAlreadyExists = errors.New("Client already exists in ClientStore.")
    1.17 +
    1.18 +	ErrClientNameTooShort    = errors.New("Client name must be at least 2 characters.")
    1.19 +	ErrClientNameTooLong     = errors.New("Client name must be at most 32 characters.")
    1.20 +	ErrClientLogoTooShort    = errors.New("Client logo URL must be at least 12 characters.")
    1.21 +	ErrClientLogoTooLong     = errors.New("Client logo must be at most 1024 characters.")
    1.22 +	ErrClientWebsiteTooShort = errors.New("Client website URL must be at least 12 characters.")
    1.23 +	ErrClientWebsiteTooLong  = errors.New("Client website must be at most 1024 characters.")
    1.24  )
    1.25  
    1.26  // Client represents a client that grants access
    1.27  // to the auth server, exchanging grants for tokens,
    1.28  // and tokens for access.
    1.29  type Client struct {
    1.30 -	ID          uuid.ID
    1.31 -	Secret      string
    1.32 -	RedirectURI string
    1.33 -	OwnerID     uuid.ID
    1.34 -	Name        string
    1.35 -	Logo        string
    1.36 -	Website     string
    1.37 +	ID      uuid.ID
    1.38 +	Secret  string
    1.39 +	OwnerID uuid.ID
    1.40 +	Name    string
    1.41 +	Logo    string
    1.42 +	Website string
    1.43 +	Type    string
    1.44  }
    1.45  
    1.46  func (c *Client) ApplyChange(change ClientChange) {
    1.47  	if change.Secret != nil {
    1.48  		c.Secret = *change.Secret
    1.49  	}
    1.50 -	if change.RedirectURI != nil {
    1.51 -		c.RedirectURI = *change.RedirectURI
    1.52 -	}
    1.53  	if change.OwnerID != nil {
    1.54  		c.OwnerID = change.OwnerID
    1.55  	}
    1.56 @@ -46,19 +53,56 @@
    1.57  }
    1.58  
    1.59  type ClientChange struct {
    1.60 -	Secret      *string
    1.61 -	RedirectURI *string
    1.62 -	OwnerID     uuid.ID
    1.63 -	Name        *string
    1.64 -	Logo        *string
    1.65 -	Website     *string
    1.66 +	Secret  *string
    1.67 +	OwnerID uuid.ID
    1.68 +	Name    *string
    1.69 +	Logo    *string
    1.70 +	Website *string
    1.71  }
    1.72  
    1.73  func (c ClientChange) Validate() error {
    1.74 -	// TODO: validate client changes
    1.75 +	if c.Name != nil && len(*c.Name) < 2 {
    1.76 +		return ErrClientNameTooShort
    1.77 +	}
    1.78 +	if c.Name != nil && len(*c.Name) > 32 {
    1.79 +		return ErrClientNameTooLong
    1.80 +	}
    1.81 +	if c.Logo != nil && len(*c.Logo) > 1024 {
    1.82 +		return ErrClientLogoTooLong
    1.83 +	}
    1.84 +	if c.Logo != nil && len(*c.Logo) > 0 && len(*c.Logo) < 12 {
    1.85 +		return ErrClientLogoTooShort
    1.86 +	}
    1.87 +	if c.Website != nil && len(*c.Website) > 140 {
    1.88 +		return ErrClientWebsiteTooLong
    1.89 +	}
    1.90 +	if c.Website != nil && len(*c.Website) > 0 && len(*c.Website) < 12 {
    1.91 +		return ErrClientWebsiteTooShort
    1.92 +	}
    1.93  	return nil
    1.94  }
    1.95  
    1.96 +type Endpoint struct {
    1.97 +	ID       uuid.ID
    1.98 +	ClientID uuid.ID
    1.99 +	URI      url.URL
   1.100 +	Added    time.Time
   1.101 +}
   1.102 +
   1.103 +type sortedEndpoints []Endpoint
   1.104 +
   1.105 +func (s sortedEndpoints) Len() int {
   1.106 +	return len(s)
   1.107 +}
   1.108 +
   1.109 +func (s sortedEndpoints) Less(i, j int) bool {
   1.110 +	return s[i].Added.Before(s[j].Added)
   1.111 +}
   1.112 +
   1.113 +func (s sortedEndpoints) Swap(i, j int) {
   1.114 +	s[i], s[j] = s[j], s[i]
   1.115 +}
   1.116 +
   1.117  // ClientStore abstracts the storage interface for
   1.118  // storing and retrieving Clients.
   1.119  type ClientStore interface {
   1.120 @@ -67,6 +111,11 @@
   1.121  	UpdateClient(id uuid.ID, change ClientChange) error
   1.122  	DeleteClient(id uuid.ID) error
   1.123  	ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
   1.124 +
   1.125 +	AddEndpoint(client uuid.ID, endpoint Endpoint) error
   1.126 +	RemoveEndpoint(client, endpoint uuid.ID) error
   1.127 +	CheckEndpoint(client uuid.ID, endpoint string) (bool, error)
   1.128 +	ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
   1.129  }
   1.130  
   1.131  func (m *Memstore) GetClient(id uuid.ID) (Client, error) {
   1.132 @@ -142,3 +191,43 @@
   1.133  	}
   1.134  	return clients, nil
   1.135  }
   1.136 +
   1.137 +func (m *Memstore) AddEndpoint(client uuid.ID, endpoint Endpoint) error {
   1.138 +	m.endpointLock.Lock()
   1.139 +	defer m.endpointLock.Unlock()
   1.140 +	m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint)
   1.141 +	return nil
   1.142 +}
   1.143 +
   1.144 +func (m *Memstore) RemoveEndpoint(client, endpoint uuid.ID) error {
   1.145 +	m.endpointLock.Lock()
   1.146 +	defer m.endpointLock.Unlock()
   1.147 +	pos := -1
   1.148 +	for p, item := range m.endpoints[client.String()] {
   1.149 +		if item.ID.Equal(endpoint) {
   1.150 +			pos = p
   1.151 +			break
   1.152 +		}
   1.153 +	}
   1.154 +	if pos >= 0 {
   1.155 +		m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
   1.156 +	}
   1.157 +	return nil
   1.158 +}
   1.159 +
   1.160 +func (m *Memstore) CheckEndpoint(client uuid.ID, endpoint string) (bool, error) {
   1.161 +	m.endpointLock.RLock()
   1.162 +	defer m.endpointLock.RUnlock()
   1.163 +	for _, candidate := range m.endpoints[client.String()] {
   1.164 +		if strings.HasPrefix(endpoint, candidate.URI.String()) {
   1.165 +			return true, nil
   1.166 +		}
   1.167 +	}
   1.168 +	return false, nil
   1.169 +}
   1.170 +
   1.171 +func (m *Memstore) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
   1.172 +	m.endpointLock.RLock()
   1.173 +	defer m.endpointLock.RUnlock()
   1.174 +	return m.endpoints[client.String()], nil
   1.175 +}