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
paddy@6 1 package auth
paddy@0 2
paddy@0 3 import (
paddy@31 4 "errors"
paddy@41 5 "net/url"
paddy@41 6 "time"
paddy@31 7
paddy@41 8 "strings"
paddy@0 9 "secondbit.org/uuid"
paddy@0 10 )
paddy@0 11
paddy@31 12 var (
paddy@31 13 ErrClientNotFound = errors.New("Client not found in ClientStore.")
paddy@31 14 ErrClientAlreadyExists = errors.New("Client already exists in ClientStore.")
paddy@41 15
paddy@41 16 ErrClientNameTooShort = errors.New("Client name must be at least 2 characters.")
paddy@41 17 ErrClientNameTooLong = errors.New("Client name must be at most 32 characters.")
paddy@41 18 ErrClientLogoTooShort = errors.New("Client logo URL must be at least 12 characters.")
paddy@41 19 ErrClientLogoTooLong = errors.New("Client logo must be at most 1024 characters.")
paddy@41 20 ErrClientWebsiteTooShort = errors.New("Client website URL must be at least 12 characters.")
paddy@41 21 ErrClientWebsiteTooLong = errors.New("Client website must be at most 1024 characters.")
paddy@31 22 )
paddy@31 23
paddy@25 24 // Client represents a client that grants access
paddy@25 25 // to the auth server, exchanging grants for tokens,
paddy@25 26 // and tokens for access.
paddy@0 27 type Client struct {
paddy@41 28 ID uuid.ID
paddy@41 29 Secret string
paddy@41 30 OwnerID uuid.ID
paddy@41 31 Name string
paddy@41 32 Logo string
paddy@41 33 Website string
paddy@41 34 Type string
paddy@0 35 }
paddy@0 36
paddy@39 37 func (c *Client) ApplyChange(change ClientChange) {
paddy@39 38 if change.Secret != nil {
paddy@39 39 c.Secret = *change.Secret
paddy@39 40 }
paddy@39 41 if change.OwnerID != nil {
paddy@39 42 c.OwnerID = change.OwnerID
paddy@39 43 }
paddy@39 44 if change.Name != nil {
paddy@39 45 c.Name = *change.Name
paddy@39 46 }
paddy@39 47 if change.Logo != nil {
paddy@39 48 c.Logo = *change.Logo
paddy@39 49 }
paddy@39 50 if change.Website != nil {
paddy@39 51 c.Website = *change.Website
paddy@39 52 }
paddy@39 53 }
paddy@39 54
paddy@31 55 type ClientChange struct {
paddy@41 56 Secret *string
paddy@41 57 OwnerID uuid.ID
paddy@41 58 Name *string
paddy@41 59 Logo *string
paddy@41 60 Website *string
paddy@31 61 }
paddy@31 62
paddy@39 63 func (c ClientChange) Validate() error {
paddy@41 64 if c.Name != nil && len(*c.Name) < 2 {
paddy@41 65 return ErrClientNameTooShort
paddy@41 66 }
paddy@41 67 if c.Name != nil && len(*c.Name) > 32 {
paddy@41 68 return ErrClientNameTooLong
paddy@41 69 }
paddy@41 70 if c.Logo != nil && len(*c.Logo) > 1024 {
paddy@41 71 return ErrClientLogoTooLong
paddy@41 72 }
paddy@41 73 if c.Logo != nil && len(*c.Logo) > 0 && len(*c.Logo) < 12 {
paddy@41 74 return ErrClientLogoTooShort
paddy@41 75 }
paddy@41 76 if c.Website != nil && len(*c.Website) > 140 {
paddy@41 77 return ErrClientWebsiteTooLong
paddy@41 78 }
paddy@41 79 if c.Website != nil && len(*c.Website) > 0 && len(*c.Website) < 12 {
paddy@41 80 return ErrClientWebsiteTooShort
paddy@41 81 }
paddy@39 82 return nil
paddy@39 83 }
paddy@39 84
paddy@41 85 type Endpoint struct {
paddy@41 86 ID uuid.ID
paddy@41 87 ClientID uuid.ID
paddy@41 88 URI url.URL
paddy@41 89 Added time.Time
paddy@41 90 }
paddy@41 91
paddy@41 92 type sortedEndpoints []Endpoint
paddy@41 93
paddy@41 94 func (s sortedEndpoints) Len() int {
paddy@41 95 return len(s)
paddy@41 96 }
paddy@41 97
paddy@41 98 func (s sortedEndpoints) Less(i, j int) bool {
paddy@41 99 return s[i].Added.Before(s[j].Added)
paddy@41 100 }
paddy@41 101
paddy@41 102 func (s sortedEndpoints) Swap(i, j int) {
paddy@41 103 s[i], s[j] = s[j], s[i]
paddy@41 104 }
paddy@41 105
paddy@25 106 // ClientStore abstracts the storage interface for
paddy@25 107 // storing and retrieving Clients.
paddy@25 108 type ClientStore interface {
paddy@25 109 GetClient(id uuid.ID) (Client, error)
paddy@25 110 SaveClient(client Client) error
paddy@31 111 UpdateClient(id uuid.ID, change ClientChange) error
paddy@25 112 DeleteClient(id uuid.ID) error
paddy@31 113 ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
paddy@41 114
paddy@41 115 AddEndpoint(client uuid.ID, endpoint Endpoint) error
paddy@41 116 RemoveEndpoint(client, endpoint uuid.ID) error
paddy@41 117 CheckEndpoint(client uuid.ID, endpoint string) (bool, error)
paddy@41 118 ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
paddy@0 119 }
paddy@31 120
paddy@31 121 func (m *Memstore) GetClient(id uuid.ID) (Client, error) {
paddy@31 122 m.clientLock.RLock()
paddy@31 123 defer m.clientLock.RUnlock()
paddy@31 124 c, ok := m.clients[id.String()]
paddy@31 125 if !ok {
paddy@31 126 return Client{}, ErrClientNotFound
paddy@31 127 }
paddy@31 128 return c, nil
paddy@31 129 }
paddy@31 130
paddy@31 131 func (m *Memstore) SaveClient(client Client) error {
paddy@31 132 m.clientLock.Lock()
paddy@31 133 defer m.clientLock.Unlock()
paddy@31 134 if _, ok := m.clients[client.ID.String()]; ok {
paddy@31 135 return ErrClientAlreadyExists
paddy@31 136 }
paddy@31 137 m.clients[client.ID.String()] = client
paddy@31 138 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
paddy@31 139 return nil
paddy@31 140 }
paddy@31 141
paddy@31 142 func (m *Memstore) UpdateClient(id uuid.ID, change ClientChange) error {
paddy@39 143 m.clientLock.Lock()
paddy@39 144 defer m.clientLock.Unlock()
paddy@39 145 c, ok := m.clients[id.String()]
paddy@39 146 if !ok {
paddy@39 147 return ErrClientNotFound
paddy@39 148 }
paddy@39 149 c.ApplyChange(change)
paddy@39 150 m.clients[id.String()] = c
paddy@31 151 return nil
paddy@31 152 }
paddy@31 153
paddy@31 154 func (m *Memstore) DeleteClient(id uuid.ID) error {
paddy@31 155 client, err := m.GetClient(id)
paddy@31 156 if err != nil {
paddy@31 157 return err
paddy@31 158 }
paddy@31 159 m.clientLock.Lock()
paddy@31 160 defer m.clientLock.Unlock()
paddy@31 161 delete(m.clients, id.String())
paddy@31 162 pos := -1
paddy@31 163 for p, item := range m.profileClientLookup[client.OwnerID.String()] {
paddy@31 164 if item.Equal(id) {
paddy@31 165 pos = p
paddy@31 166 break
paddy@31 167 }
paddy@31 168 }
paddy@31 169 if pos >= 0 {
paddy@31 170 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...)
paddy@31 171 }
paddy@31 172 return nil
paddy@31 173 }
paddy@31 174
paddy@31 175 func (m *Memstore) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
paddy@33 176 ids := m.lookupClientsByProfileID(ownerID.String())
paddy@31 177 if len(ids) > num+offset {
paddy@31 178 ids = ids[offset : num+offset]
paddy@31 179 } else if len(ids) > offset {
paddy@31 180 ids = ids[offset:]
paddy@31 181 } else {
paddy@31 182 return []Client{}, nil
paddy@31 183 }
paddy@31 184 clients := []Client{}
paddy@31 185 for _, id := range ids {
paddy@31 186 client, err := m.GetClient(id)
paddy@31 187 if err != nil {
paddy@31 188 return []Client{}, err
paddy@31 189 }
paddy@31 190 clients = append(clients, client)
paddy@31 191 }
paddy@31 192 return clients, nil
paddy@31 193 }
paddy@41 194
paddy@41 195 func (m *Memstore) AddEndpoint(client uuid.ID, endpoint Endpoint) error {
paddy@41 196 m.endpointLock.Lock()
paddy@41 197 defer m.endpointLock.Unlock()
paddy@41 198 m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint)
paddy@41 199 return nil
paddy@41 200 }
paddy@41 201
paddy@41 202 func (m *Memstore) RemoveEndpoint(client, endpoint uuid.ID) error {
paddy@41 203 m.endpointLock.Lock()
paddy@41 204 defer m.endpointLock.Unlock()
paddy@41 205 pos := -1
paddy@41 206 for p, item := range m.endpoints[client.String()] {
paddy@41 207 if item.ID.Equal(endpoint) {
paddy@41 208 pos = p
paddy@41 209 break
paddy@41 210 }
paddy@41 211 }
paddy@41 212 if pos >= 0 {
paddy@41 213 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
paddy@41 214 }
paddy@41 215 return nil
paddy@41 216 }
paddy@41 217
paddy@41 218 func (m *Memstore) CheckEndpoint(client uuid.ID, endpoint string) (bool, error) {
paddy@41 219 m.endpointLock.RLock()
paddy@41 220 defer m.endpointLock.RUnlock()
paddy@41 221 for _, candidate := range m.endpoints[client.String()] {
paddy@41 222 if strings.HasPrefix(endpoint, candidate.URI.String()) {
paddy@41 223 return true, nil
paddy@41 224 }
paddy@41 225 }
paddy@41 226 return false, nil
paddy@41 227 }
paddy@41 228
paddy@41 229 func (m *Memstore) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
paddy@41 230 m.endpointLock.RLock()
paddy@41 231 defer m.endpointLock.RUnlock()
paddy@41 232 return m.endpoints[client.String()], nil
paddy@41 233 }