auth

Paddy 2014-10-26 Parent:0f80a3e391b8 Child:b3cd7765a7c8

57:e45bfa2abc00 Go to Latest

auth/client.go

The great documentation and exported interface cleanup. Modify all our *Store interfaces to be unexported, as there's no real good reason they need to be exported, especially as they can be implemented without being exported. The interfaces shouldn't matter to 99% of users of the package, so let's not pollute our package API. Further, all methods of the interfaces are now unexported, for pretty much the same reasoning. Add a doc.go file with documentation explaining the choices the package is making and what it provides. Implement documentation on all our exported types and methods and functions, which makes golint happy. The only remaining golint warning is about NewMemstore, which will stay the way it is. The memstore type is useful outside tests for things like standing up a server quickly when we don't care about the storage, and because the type is unexported, we _need_ a New function to create an instance that can be passed to the Context.

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@57 13 // ErrNoClientStore is returned when a Context tries to act on a clientStore without setting one first.
paddy@57 14 ErrNoClientStore = errors.New("no clientStore was specified for the Context")
paddy@57 15 // ErrClientNotFound is returned when a Client is requested but not found in a clientStore.
paddy@57 16 ErrClientNotFound = errors.New("client not found in clientStore")
paddy@57 17 // ErrClientAlreadyExists is returned when a Client is added to a clientStore, but another Client with
paddy@57 18 // the same ID already exists in the clientStore.
paddy@57 19 ErrClientAlreadyExists = errors.New("client already exists in clientStore")
paddy@41 20
paddy@57 21 // ErrEmptyChange is returned when a Change has all its properties set to nil.
paddy@57 22 ErrEmptyChange = errors.New("change must have at least one property set")
paddy@57 23 // ErrClientNameTooShort is returned when a Client's Name property is too short.
paddy@57 24 ErrClientNameTooShort = errors.New("client name must be at least 2 characters")
paddy@57 25 // ErrClientNameTooLong is returned when a Client's Name property is too long.
paddy@57 26 ErrClientNameTooLong = errors.New("client name must be at most 32 characters")
paddy@57 27 // ErrClientLogoTooLong is returned when a Client's Logo property is too long.
paddy@57 28 ErrClientLogoTooLong = errors.New("client logo must be at most 1024 characters")
paddy@57 29 // ErrClientLogoNotURL is returned when a Client's Logo property is not a valid absolute URL.
paddy@57 30 ErrClientLogoNotURL = errors.New("client logo must be a valid absolute URL")
paddy@57 31 // ErrClientWebsiteTooLong is returned when a Client's Website property is too long.
paddy@49 32 ErrClientWebsiteTooLong = errors.New("client website must be at most 1024 characters")
paddy@57 33 // ErrClientWebsiteNotURL is returned when a Client's Website property is not a valid absolute URL.
paddy@57 34 ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL")
paddy@31 35 )
paddy@31 36
paddy@25 37 // Client represents a client that grants access
paddy@25 38 // to the auth server, exchanging grants for tokens,
paddy@25 39 // and tokens for access.
paddy@0 40 type Client struct {
paddy@41 41 ID uuid.ID
paddy@41 42 Secret string
paddy@41 43 OwnerID uuid.ID
paddy@41 44 Name string
paddy@41 45 Logo string
paddy@41 46 Website string
paddy@41 47 Type string
paddy@0 48 }
paddy@0 49
paddy@57 50 // ApplyChange applies the properties of the passed
paddy@57 51 // ClientChange to the Client object it is called on.
paddy@39 52 func (c *Client) ApplyChange(change ClientChange) {
paddy@39 53 if change.Secret != nil {
paddy@39 54 c.Secret = *change.Secret
paddy@39 55 }
paddy@39 56 if change.OwnerID != nil {
paddy@39 57 c.OwnerID = change.OwnerID
paddy@39 58 }
paddy@39 59 if change.Name != nil {
paddy@39 60 c.Name = *change.Name
paddy@39 61 }
paddy@39 62 if change.Logo != nil {
paddy@39 63 c.Logo = *change.Logo
paddy@39 64 }
paddy@39 65 if change.Website != nil {
paddy@39 66 c.Website = *change.Website
paddy@39 67 }
paddy@39 68 }
paddy@39 69
paddy@57 70 // ClientChange represents a bundle of options for
paddy@57 71 // updating a Client's mutable data.
paddy@31 72 type ClientChange struct {
paddy@41 73 Secret *string
paddy@41 74 OwnerID uuid.ID
paddy@41 75 Name *string
paddy@41 76 Logo *string
paddy@41 77 Website *string
paddy@31 78 }
paddy@31 79
paddy@57 80 // Validate checks the ClientChange it is called on
paddy@57 81 // and asserts its internal validity, or lack thereof.
paddy@39 82 func (c ClientChange) Validate() error {
paddy@42 83 if c.Secret == nil && c.OwnerID == nil && c.Name == nil && c.Logo == nil && c.Website == nil {
paddy@42 84 return ErrEmptyChange
paddy@42 85 }
paddy@41 86 if c.Name != nil && len(*c.Name) < 2 {
paddy@41 87 return ErrClientNameTooShort
paddy@41 88 }
paddy@41 89 if c.Name != nil && len(*c.Name) > 32 {
paddy@41 90 return ErrClientNameTooLong
paddy@41 91 }
paddy@42 92 if c.Logo != nil && *c.Logo != "" {
paddy@42 93 if len(*c.Logo) > 1024 {
paddy@42 94 return ErrClientLogoTooLong
paddy@42 95 }
paddy@42 96 u, err := url.Parse(*c.Logo)
paddy@42 97 if err != nil || !u.IsAbs() {
paddy@42 98 return ErrClientLogoNotURL
paddy@42 99 }
paddy@41 100 }
paddy@42 101 if c.Website != nil && *c.Website != "" {
paddy@42 102 if len(*c.Website) > 140 {
paddy@42 103 return ErrClientWebsiteTooLong
paddy@42 104 }
paddy@42 105 u, err := url.Parse(*c.Website)
paddy@42 106 if err != nil || !u.IsAbs() {
paddy@42 107 return ErrClientWebsiteNotURL
paddy@42 108 }
paddy@41 109 }
paddy@39 110 return nil
paddy@39 111 }
paddy@39 112
paddy@57 113 // Endpoint represents a single URI that a Client
paddy@57 114 // controls. Users will be redirected to these URIs
paddy@57 115 // following successful authorization grants and
paddy@57 116 // exchanges for access tokens.
paddy@41 117 type Endpoint struct {
paddy@41 118 ID uuid.ID
paddy@41 119 ClientID uuid.ID
paddy@41 120 URI url.URL
paddy@41 121 Added time.Time
paddy@41 122 }
paddy@41 123
paddy@41 124 type sortedEndpoints []Endpoint
paddy@41 125
paddy@41 126 func (s sortedEndpoints) Len() int {
paddy@41 127 return len(s)
paddy@41 128 }
paddy@41 129
paddy@41 130 func (s sortedEndpoints) Less(i, j int) bool {
paddy@41 131 return s[i].Added.Before(s[j].Added)
paddy@41 132 }
paddy@41 133
paddy@41 134 func (s sortedEndpoints) Swap(i, j int) {
paddy@41 135 s[i], s[j] = s[j], s[i]
paddy@41 136 }
paddy@41 137
paddy@57 138 type clientStore interface {
paddy@57 139 getClient(id uuid.ID) (Client, error)
paddy@57 140 saveClient(client Client) error
paddy@57 141 updateClient(id uuid.ID, change ClientChange) error
paddy@57 142 deleteClient(id uuid.ID) error
paddy@57 143 listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
paddy@41 144
paddy@57 145 addEndpoint(client uuid.ID, endpoint Endpoint) error
paddy@57 146 removeEndpoint(client, endpoint uuid.ID) error
paddy@57 147 checkEndpoint(client uuid.ID, endpoint string, strict bool) (bool, error)
paddy@57 148 listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
paddy@57 149 countEndpoints(client uuid.ID) (int64, error)
paddy@0 150 }
paddy@31 151
paddy@57 152 func (m *memstore) getClient(id uuid.ID) (Client, error) {
paddy@31 153 m.clientLock.RLock()
paddy@31 154 defer m.clientLock.RUnlock()
paddy@31 155 c, ok := m.clients[id.String()]
paddy@31 156 if !ok {
paddy@31 157 return Client{}, ErrClientNotFound
paddy@31 158 }
paddy@31 159 return c, nil
paddy@31 160 }
paddy@31 161
paddy@57 162 func (m *memstore) saveClient(client Client) error {
paddy@31 163 m.clientLock.Lock()
paddy@31 164 defer m.clientLock.Unlock()
paddy@31 165 if _, ok := m.clients[client.ID.String()]; ok {
paddy@31 166 return ErrClientAlreadyExists
paddy@31 167 }
paddy@31 168 m.clients[client.ID.String()] = client
paddy@31 169 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
paddy@31 170 return nil
paddy@31 171 }
paddy@31 172
paddy@57 173 func (m *memstore) updateClient(id uuid.ID, change ClientChange) error {
paddy@39 174 m.clientLock.Lock()
paddy@39 175 defer m.clientLock.Unlock()
paddy@39 176 c, ok := m.clients[id.String()]
paddy@39 177 if !ok {
paddy@39 178 return ErrClientNotFound
paddy@39 179 }
paddy@39 180 c.ApplyChange(change)
paddy@39 181 m.clients[id.String()] = c
paddy@31 182 return nil
paddy@31 183 }
paddy@31 184
paddy@57 185 func (m *memstore) deleteClient(id uuid.ID) error {
paddy@57 186 client, err := m.getClient(id)
paddy@31 187 if err != nil {
paddy@31 188 return err
paddy@31 189 }
paddy@31 190 m.clientLock.Lock()
paddy@31 191 defer m.clientLock.Unlock()
paddy@31 192 delete(m.clients, id.String())
paddy@31 193 pos := -1
paddy@31 194 for p, item := range m.profileClientLookup[client.OwnerID.String()] {
paddy@31 195 if item.Equal(id) {
paddy@31 196 pos = p
paddy@31 197 break
paddy@31 198 }
paddy@31 199 }
paddy@31 200 if pos >= 0 {
paddy@31 201 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...)
paddy@31 202 }
paddy@31 203 return nil
paddy@31 204 }
paddy@31 205
paddy@57 206 func (m *memstore) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
paddy@33 207 ids := m.lookupClientsByProfileID(ownerID.String())
paddy@31 208 if len(ids) > num+offset {
paddy@31 209 ids = ids[offset : num+offset]
paddy@31 210 } else if len(ids) > offset {
paddy@31 211 ids = ids[offset:]
paddy@31 212 } else {
paddy@31 213 return []Client{}, nil
paddy@31 214 }
paddy@31 215 clients := []Client{}
paddy@31 216 for _, id := range ids {
paddy@57 217 client, err := m.getClient(id)
paddy@31 218 if err != nil {
paddy@31 219 return []Client{}, err
paddy@31 220 }
paddy@31 221 clients = append(clients, client)
paddy@31 222 }
paddy@31 223 return clients, nil
paddy@31 224 }
paddy@41 225
paddy@57 226 func (m *memstore) addEndpoint(client uuid.ID, endpoint Endpoint) error {
paddy@41 227 m.endpointLock.Lock()
paddy@41 228 defer m.endpointLock.Unlock()
paddy@41 229 m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint)
paddy@41 230 return nil
paddy@41 231 }
paddy@41 232
paddy@57 233 func (m *memstore) removeEndpoint(client, endpoint uuid.ID) error {
paddy@41 234 m.endpointLock.Lock()
paddy@41 235 defer m.endpointLock.Unlock()
paddy@41 236 pos := -1
paddy@41 237 for p, item := range m.endpoints[client.String()] {
paddy@41 238 if item.ID.Equal(endpoint) {
paddy@41 239 pos = p
paddy@41 240 break
paddy@41 241 }
paddy@41 242 }
paddy@41 243 if pos >= 0 {
paddy@41 244 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
paddy@41 245 }
paddy@41 246 return nil
paddy@41 247 }
paddy@41 248
paddy@57 249 func (m *memstore) checkEndpoint(client uuid.ID, endpoint string, strict bool) (bool, error) {
paddy@41 250 m.endpointLock.RLock()
paddy@41 251 defer m.endpointLock.RUnlock()
paddy@41 252 for _, candidate := range m.endpoints[client.String()] {
paddy@54 253 if !strict && strings.HasPrefix(endpoint, candidate.URI.String()) {
paddy@54 254 return true, nil
paddy@54 255 } else if strict && endpoint == candidate.URI.String() {
paddy@41 256 return true, nil
paddy@41 257 }
paddy@41 258 }
paddy@41 259 return false, nil
paddy@41 260 }
paddy@41 261
paddy@57 262 func (m *memstore) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
paddy@41 263 m.endpointLock.RLock()
paddy@41 264 defer m.endpointLock.RUnlock()
paddy@41 265 return m.endpoints[client.String()], nil
paddy@41 266 }
paddy@54 267
paddy@57 268 func (m *memstore) countEndpoints(client uuid.ID) (int64, error) {
paddy@54 269 m.endpointLock.RLock()
paddy@54 270 defer m.endpointLock.RUnlock()
paddy@54 271 return int64(len(m.endpoints[client.String()])), nil
paddy@54 272 }