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.
9 "code.secondbit.org/uuid"
13 // ErrNoClientStore is returned when a Context tries to act on a clientStore without setting one first.
14 ErrNoClientStore = errors.New("no clientStore was specified for the Context")
15 // ErrClientNotFound is returned when a Client is requested but not found in a clientStore.
16 ErrClientNotFound = errors.New("client not found in clientStore")
17 // ErrClientAlreadyExists is returned when a Client is added to a clientStore, but another Client with
18 // the same ID already exists in the clientStore.
19 ErrClientAlreadyExists = errors.New("client already exists in clientStore")
21 // ErrEmptyChange is returned when a Change has all its properties set to nil.
22 ErrEmptyChange = errors.New("change must have at least one property set")
23 // ErrClientNameTooShort is returned when a Client's Name property is too short.
24 ErrClientNameTooShort = errors.New("client name must be at least 2 characters")
25 // ErrClientNameTooLong is returned when a Client's Name property is too long.
26 ErrClientNameTooLong = errors.New("client name must be at most 32 characters")
27 // ErrClientLogoTooLong is returned when a Client's Logo property is too long.
28 ErrClientLogoTooLong = errors.New("client logo must be at most 1024 characters")
29 // ErrClientLogoNotURL is returned when a Client's Logo property is not a valid absolute URL.
30 ErrClientLogoNotURL = errors.New("client logo must be a valid absolute URL")
31 // ErrClientWebsiteTooLong is returned when a Client's Website property is too long.
32 ErrClientWebsiteTooLong = errors.New("client website must be at most 1024 characters")
33 // ErrClientWebsiteNotURL is returned when a Client's Website property is not a valid absolute URL.
34 ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL")
37 // Client represents a client that grants access
38 // to the auth server, exchanging grants for tokens,
39 // and tokens for access.
50 // ApplyChange applies the properties of the passed
51 // ClientChange to the Client object it is called on.
52 func (c *Client) ApplyChange(change ClientChange) {
53 if change.Secret != nil {
54 c.Secret = *change.Secret
56 if change.OwnerID != nil {
57 c.OwnerID = change.OwnerID
59 if change.Name != nil {
62 if change.Logo != nil {
65 if change.Website != nil {
66 c.Website = *change.Website
70 // ClientChange represents a bundle of options for
71 // updating a Client's mutable data.
72 type ClientChange struct {
80 // Validate checks the ClientChange it is called on
81 // and asserts its internal validity, or lack thereof.
82 func (c ClientChange) Validate() error {
83 if c.Secret == nil && c.OwnerID == nil && c.Name == nil && c.Logo == nil && c.Website == nil {
86 if c.Name != nil && len(*c.Name) < 2 {
87 return ErrClientNameTooShort
89 if c.Name != nil && len(*c.Name) > 32 {
90 return ErrClientNameTooLong
92 if c.Logo != nil && *c.Logo != "" {
93 if len(*c.Logo) > 1024 {
94 return ErrClientLogoTooLong
96 u, err := url.Parse(*c.Logo)
97 if err != nil || !u.IsAbs() {
98 return ErrClientLogoNotURL
101 if c.Website != nil && *c.Website != "" {
102 if len(*c.Website) > 140 {
103 return ErrClientWebsiteTooLong
105 u, err := url.Parse(*c.Website)
106 if err != nil || !u.IsAbs() {
107 return ErrClientWebsiteNotURL
113 // Endpoint represents a single URI that a Client
114 // controls. Users will be redirected to these URIs
115 // following successful authorization grants and
116 // exchanges for access tokens.
117 type Endpoint struct {
124 type sortedEndpoints []Endpoint
126 func (s sortedEndpoints) Len() int {
130 func (s sortedEndpoints) Less(i, j int) bool {
131 return s[i].Added.Before(s[j].Added)
134 func (s sortedEndpoints) Swap(i, j int) {
135 s[i], s[j] = s[j], s[i]
138 type clientStore interface {
139 getClient(id uuid.ID) (Client, error)
140 saveClient(client Client) error
141 updateClient(id uuid.ID, change ClientChange) error
142 deleteClient(id uuid.ID) error
143 listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
145 addEndpoint(client uuid.ID, endpoint Endpoint) error
146 removeEndpoint(client, endpoint uuid.ID) error
147 checkEndpoint(client uuid.ID, endpoint string, strict bool) (bool, error)
148 listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
149 countEndpoints(client uuid.ID) (int64, error)
152 func (m *memstore) getClient(id uuid.ID) (Client, error) {
154 defer m.clientLock.RUnlock()
155 c, ok := m.clients[id.String()]
157 return Client{}, ErrClientNotFound
162 func (m *memstore) saveClient(client Client) error {
164 defer m.clientLock.Unlock()
165 if _, ok := m.clients[client.ID.String()]; ok {
166 return ErrClientAlreadyExists
168 m.clients[client.ID.String()] = client
169 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
173 func (m *memstore) updateClient(id uuid.ID, change ClientChange) error {
175 defer m.clientLock.Unlock()
176 c, ok := m.clients[id.String()]
178 return ErrClientNotFound
180 c.ApplyChange(change)
181 m.clients[id.String()] = c
185 func (m *memstore) deleteClient(id uuid.ID) error {
186 client, err := m.getClient(id)
191 defer m.clientLock.Unlock()
192 delete(m.clients, id.String())
194 for p, item := range m.profileClientLookup[client.OwnerID.String()] {
201 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...)
206 func (m *memstore) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
207 ids := m.lookupClientsByProfileID(ownerID.String())
208 if len(ids) > num+offset {
209 ids = ids[offset : num+offset]
210 } else if len(ids) > offset {
213 return []Client{}, nil
215 clients := []Client{}
216 for _, id := range ids {
217 client, err := m.getClient(id)
219 return []Client{}, err
221 clients = append(clients, client)
226 func (m *memstore) addEndpoint(client uuid.ID, endpoint Endpoint) error {
227 m.endpointLock.Lock()
228 defer m.endpointLock.Unlock()
229 m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint)
233 func (m *memstore) removeEndpoint(client, endpoint uuid.ID) error {
234 m.endpointLock.Lock()
235 defer m.endpointLock.Unlock()
237 for p, item := range m.endpoints[client.String()] {
238 if item.ID.Equal(endpoint) {
244 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
249 func (m *memstore) checkEndpoint(client uuid.ID, endpoint string, strict bool) (bool, error) {
250 m.endpointLock.RLock()
251 defer m.endpointLock.RUnlock()
252 for _, candidate := range m.endpoints[client.String()] {
253 if !strict && strings.HasPrefix(endpoint, candidate.URI.String()) {
255 } else if strict && endpoint == candidate.URI.String() {
262 func (m *memstore) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
263 m.endpointLock.RLock()
264 defer m.endpointLock.RUnlock()
265 return m.endpoints[client.String()], nil
268 func (m *memstore) countEndpoints(client uuid.ID) (int64, error) {
269 m.endpointLock.RLock()
270 defer m.endpointLock.RUnlock()
271 return int64(len(m.endpoints[client.String()])), nil