auth

Paddy 2014-10-22 Parent:0f80a3e391b8 Child:e45bfa2abc00

56:a5987795707e Go to Latest

auth/client.go

Actually validate grant requests. Write the logic to validate grant requests and stub out the rendering/error handling/redirecting locations. Finally, we get to the good stuff: implementing the specification. Write some tests to verify that granting requests works the way we think it does.

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 }