auth
auth/client.go
Include client updates and tests. Flesh out updating clients, and include unit tests to ensure that clientstores actually update appropriately. Add TODO comments where functionality is planned but stubbed out.
| paddy@6 | 1 package auth |
| paddy@0 | 2 |
| paddy@0 | 3 import ( |
| paddy@31 | 4 "errors" |
| paddy@31 | 5 |
| paddy@0 | 6 "secondbit.org/uuid" |
| paddy@0 | 7 ) |
| paddy@0 | 8 |
| paddy@31 | 9 var ( |
| paddy@31 | 10 ErrClientNotFound = errors.New("Client not found in ClientStore.") |
| paddy@31 | 11 ErrClientAlreadyExists = errors.New("Client already exists in ClientStore.") |
| paddy@31 | 12 ) |
| paddy@31 | 13 |
| paddy@25 | 14 // Client represents a client that grants access |
| paddy@25 | 15 // to the auth server, exchanging grants for tokens, |
| paddy@25 | 16 // and tokens for access. |
| paddy@0 | 17 type Client struct { |
| paddy@0 | 18 ID uuid.ID |
| paddy@0 | 19 Secret string |
| paddy@0 | 20 RedirectURI string |
| paddy@0 | 21 OwnerID uuid.ID |
| paddy@0 | 22 Name string |
| paddy@0 | 23 Logo string |
| paddy@25 | 24 Website string |
| paddy@0 | 25 } |
| paddy@0 | 26 |
| paddy@39 | 27 func (c *Client) ApplyChange(change ClientChange) { |
| paddy@39 | 28 if change.Secret != nil { |
| paddy@39 | 29 c.Secret = *change.Secret |
| paddy@39 | 30 } |
| paddy@39 | 31 if change.RedirectURI != nil { |
| paddy@39 | 32 c.RedirectURI = *change.RedirectURI |
| paddy@39 | 33 } |
| paddy@39 | 34 if change.OwnerID != nil { |
| paddy@39 | 35 c.OwnerID = change.OwnerID |
| paddy@39 | 36 } |
| paddy@39 | 37 if change.Name != nil { |
| paddy@39 | 38 c.Name = *change.Name |
| paddy@39 | 39 } |
| paddy@39 | 40 if change.Logo != nil { |
| paddy@39 | 41 c.Logo = *change.Logo |
| paddy@39 | 42 } |
| paddy@39 | 43 if change.Website != nil { |
| paddy@39 | 44 c.Website = *change.Website |
| paddy@39 | 45 } |
| paddy@39 | 46 } |
| paddy@39 | 47 |
| paddy@31 | 48 type ClientChange struct { |
| paddy@31 | 49 Secret *string |
| paddy@31 | 50 RedirectURI *string |
| paddy@31 | 51 OwnerID uuid.ID |
| paddy@31 | 52 Name *string |
| paddy@31 | 53 Logo *string |
| paddy@31 | 54 Website *string |
| paddy@31 | 55 } |
| paddy@31 | 56 |
| paddy@39 | 57 func (c ClientChange) Validate() error { |
| paddy@39 | 58 // TODO: validate client changes |
| paddy@39 | 59 return nil |
| paddy@39 | 60 } |
| paddy@39 | 61 |
| paddy@25 | 62 // ClientStore abstracts the storage interface for |
| paddy@25 | 63 // storing and retrieving Clients. |
| paddy@25 | 64 type ClientStore interface { |
| paddy@25 | 65 GetClient(id uuid.ID) (Client, error) |
| paddy@25 | 66 SaveClient(client Client) error |
| paddy@31 | 67 UpdateClient(id uuid.ID, change ClientChange) error |
| paddy@25 | 68 DeleteClient(id uuid.ID) error |
| paddy@31 | 69 ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) |
| paddy@0 | 70 } |
| paddy@31 | 71 |
| paddy@31 | 72 func (m *Memstore) GetClient(id uuid.ID) (Client, error) { |
| paddy@31 | 73 m.clientLock.RLock() |
| paddy@31 | 74 defer m.clientLock.RUnlock() |
| paddy@31 | 75 c, ok := m.clients[id.String()] |
| paddy@31 | 76 if !ok { |
| paddy@31 | 77 return Client{}, ErrClientNotFound |
| paddy@31 | 78 } |
| paddy@31 | 79 return c, nil |
| paddy@31 | 80 } |
| paddy@31 | 81 |
| paddy@31 | 82 func (m *Memstore) SaveClient(client Client) error { |
| paddy@31 | 83 m.clientLock.Lock() |
| paddy@31 | 84 defer m.clientLock.Unlock() |
| paddy@31 | 85 if _, ok := m.clients[client.ID.String()]; ok { |
| paddy@31 | 86 return ErrClientAlreadyExists |
| paddy@31 | 87 } |
| paddy@31 | 88 m.clients[client.ID.String()] = client |
| paddy@31 | 89 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID) |
| paddy@31 | 90 return nil |
| paddy@31 | 91 } |
| paddy@31 | 92 |
| paddy@31 | 93 func (m *Memstore) UpdateClient(id uuid.ID, change ClientChange) error { |
| paddy@39 | 94 m.clientLock.Lock() |
| paddy@39 | 95 defer m.clientLock.Unlock() |
| paddy@39 | 96 c, ok := m.clients[id.String()] |
| paddy@39 | 97 if !ok { |
| paddy@39 | 98 return ErrClientNotFound |
| paddy@39 | 99 } |
| paddy@39 | 100 c.ApplyChange(change) |
| paddy@39 | 101 m.clients[id.String()] = c |
| paddy@31 | 102 return nil |
| paddy@31 | 103 } |
| paddy@31 | 104 |
| paddy@31 | 105 func (m *Memstore) DeleteClient(id uuid.ID) error { |
| paddy@31 | 106 client, err := m.GetClient(id) |
| paddy@31 | 107 if err != nil { |
| paddy@31 | 108 return err |
| paddy@31 | 109 } |
| paddy@31 | 110 m.clientLock.Lock() |
| paddy@31 | 111 defer m.clientLock.Unlock() |
| paddy@31 | 112 delete(m.clients, id.String()) |
| paddy@31 | 113 pos := -1 |
| paddy@31 | 114 for p, item := range m.profileClientLookup[client.OwnerID.String()] { |
| paddy@31 | 115 if item.Equal(id) { |
| paddy@31 | 116 pos = p |
| paddy@31 | 117 break |
| paddy@31 | 118 } |
| paddy@31 | 119 } |
| paddy@31 | 120 if pos >= 0 { |
| paddy@31 | 121 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...) |
| paddy@31 | 122 } |
| paddy@31 | 123 return nil |
| paddy@31 | 124 } |
| paddy@31 | 125 |
| paddy@31 | 126 func (m *Memstore) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) { |
| paddy@33 | 127 ids := m.lookupClientsByProfileID(ownerID.String()) |
| paddy@31 | 128 if len(ids) > num+offset { |
| paddy@31 | 129 ids = ids[offset : num+offset] |
| paddy@31 | 130 } else if len(ids) > offset { |
| paddy@31 | 131 ids = ids[offset:] |
| paddy@31 | 132 } else { |
| paddy@31 | 133 return []Client{}, nil |
| paddy@31 | 134 } |
| paddy@31 | 135 clients := []Client{} |
| paddy@31 | 136 for _, id := range ids { |
| paddy@31 | 137 client, err := m.GetClient(id) |
| paddy@31 | 138 if err != nil { |
| paddy@31 | 139 return []Client{}, err |
| paddy@31 | 140 } |
| paddy@31 | 141 clients = append(clients, client) |
| paddy@31 | 142 } |
| paddy@31 | 143 return clients, nil |
| paddy@31 | 144 } |