auth

Paddy 2014-10-15 Parent:73a9f7a6af54 Child:0f80a3e391b8

52:aff6863e3cb3 Go to Latest

auth/client.go

Move HTTP tests to http_test.go, rename the GetGrant test. Rename the GetGrant test to something that makes it more obvious, and indicate that we're only testing for successful responses in this function (e.g., responses that should be successfully handled). Another function will deal with failure modes. Move the function to a new http_test.go file. The model files are shouldn't have information about how the models are being represented.

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@41 128 CheckEndpoint(client uuid.ID, endpoint string) (bool, error)
paddy@41 129 ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
paddy@0 130 }
paddy@31 131
paddy@31 132 func (m *Memstore) GetClient(id uuid.ID) (Client, error) {
paddy@31 133 m.clientLock.RLock()
paddy@31 134 defer m.clientLock.RUnlock()
paddy@31 135 c, ok := m.clients[id.String()]
paddy@31 136 if !ok {
paddy@31 137 return Client{}, ErrClientNotFound
paddy@31 138 }
paddy@31 139 return c, nil
paddy@31 140 }
paddy@31 141
paddy@31 142 func (m *Memstore) SaveClient(client Client) error {
paddy@31 143 m.clientLock.Lock()
paddy@31 144 defer m.clientLock.Unlock()
paddy@31 145 if _, ok := m.clients[client.ID.String()]; ok {
paddy@31 146 return ErrClientAlreadyExists
paddy@31 147 }
paddy@31 148 m.clients[client.ID.String()] = client
paddy@31 149 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
paddy@31 150 return nil
paddy@31 151 }
paddy@31 152
paddy@31 153 func (m *Memstore) UpdateClient(id uuid.ID, change ClientChange) error {
paddy@39 154 m.clientLock.Lock()
paddy@39 155 defer m.clientLock.Unlock()
paddy@39 156 c, ok := m.clients[id.String()]
paddy@39 157 if !ok {
paddy@39 158 return ErrClientNotFound
paddy@39 159 }
paddy@39 160 c.ApplyChange(change)
paddy@39 161 m.clients[id.String()] = c
paddy@31 162 return nil
paddy@31 163 }
paddy@31 164
paddy@31 165 func (m *Memstore) DeleteClient(id uuid.ID) error {
paddy@31 166 client, err := m.GetClient(id)
paddy@31 167 if err != nil {
paddy@31 168 return err
paddy@31 169 }
paddy@31 170 m.clientLock.Lock()
paddy@31 171 defer m.clientLock.Unlock()
paddy@31 172 delete(m.clients, id.String())
paddy@31 173 pos := -1
paddy@31 174 for p, item := range m.profileClientLookup[client.OwnerID.String()] {
paddy@31 175 if item.Equal(id) {
paddy@31 176 pos = p
paddy@31 177 break
paddy@31 178 }
paddy@31 179 }
paddy@31 180 if pos >= 0 {
paddy@31 181 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...)
paddy@31 182 }
paddy@31 183 return nil
paddy@31 184 }
paddy@31 185
paddy@31 186 func (m *Memstore) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
paddy@33 187 ids := m.lookupClientsByProfileID(ownerID.String())
paddy@31 188 if len(ids) > num+offset {
paddy@31 189 ids = ids[offset : num+offset]
paddy@31 190 } else if len(ids) > offset {
paddy@31 191 ids = ids[offset:]
paddy@31 192 } else {
paddy@31 193 return []Client{}, nil
paddy@31 194 }
paddy@31 195 clients := []Client{}
paddy@31 196 for _, id := range ids {
paddy@31 197 client, err := m.GetClient(id)
paddy@31 198 if err != nil {
paddy@31 199 return []Client{}, err
paddy@31 200 }
paddy@31 201 clients = append(clients, client)
paddy@31 202 }
paddy@31 203 return clients, nil
paddy@31 204 }
paddy@41 205
paddy@41 206 func (m *Memstore) AddEndpoint(client uuid.ID, endpoint Endpoint) error {
paddy@41 207 m.endpointLock.Lock()
paddy@41 208 defer m.endpointLock.Unlock()
paddy@41 209 m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint)
paddy@41 210 return nil
paddy@41 211 }
paddy@41 212
paddy@41 213 func (m *Memstore) RemoveEndpoint(client, endpoint uuid.ID) error {
paddy@41 214 m.endpointLock.Lock()
paddy@41 215 defer m.endpointLock.Unlock()
paddy@41 216 pos := -1
paddy@41 217 for p, item := range m.endpoints[client.String()] {
paddy@41 218 if item.ID.Equal(endpoint) {
paddy@41 219 pos = p
paddy@41 220 break
paddy@41 221 }
paddy@41 222 }
paddy@41 223 if pos >= 0 {
paddy@41 224 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
paddy@41 225 }
paddy@41 226 return nil
paddy@41 227 }
paddy@41 228
paddy@41 229 func (m *Memstore) CheckEndpoint(client uuid.ID, endpoint string) (bool, error) {
paddy@41 230 m.endpointLock.RLock()
paddy@41 231 defer m.endpointLock.RUnlock()
paddy@41 232 for _, candidate := range m.endpoints[client.String()] {
paddy@41 233 if strings.HasPrefix(endpoint, candidate.URI.String()) {
paddy@41 234 return true, nil
paddy@41 235 }
paddy@41 236 }
paddy@41 237 return false, nil
paddy@41 238 }
paddy@41 239
paddy@41 240 func (m *Memstore) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
paddy@41 241 m.endpointLock.RLock()
paddy@41 242 defer m.endpointLock.RUnlock()
paddy@41 243 return m.endpoints[client.String()], nil
paddy@41 244 }