auth

Paddy 2014-09-27 Parent:022ce4262922 Child:73a9f7a6af54

45:3a6a65ed380c Go to Latest

auth/client.go

Update uuid import path, test for multiple profile updates. Test updating multiple profiles in one request (e.g., when profiles become compromised.) Update the uuid import path to use the new code.secondbit.org/uuid import path.

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@45 8 "code.secondbit.org/uuid"
paddy@41 9 "strings"
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 }