auth

Paddy 2014-10-22 Parent:73a9f7a6af54 Child:e45bfa2abc00

54:0f80a3e391b8 Go to Latest

auth/client.go

Update CheckEndpoints for strict checking, add CountEndpoints. Create a "strict" mode for CheckEndpoints that will only return true on an exact match, and update the memstore implementation accordingly. Add tests to make sure that the strict mode is adhered to. We need this mode because in certain situations (e.g., the client has more than one endpoint registered), the spec demands a full-string comparison. Add a CountEndpoints method to the ClientStore that will return the number of endpoints registered for a specific client. As we just mentioned, the rules for how a redirect URI is validated depend upon the number of endpoints a client has registered, so we need to be able to get at that number.

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