auth

Paddy 2014-09-18 Parent:113ccb15b919 Child:3a6a65ed380c

42:022ce4262922 Go to Latest

auth/client.go

Make sure client URLs are actually URLs. When updating a client website or logo, make sure that URL is actually a URL. Instead of returning an error for too short input, just return an error if the input isn't a URL.

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