auth
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.
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 +}