auth

Paddy 2014-09-29 Parent:3a6a65ed380c Child:73a9f7a6af54

48:d78418fb9f56 Go to Latest

auth/profile.go

Validate profile changes. Implement validation for ProfileChange and BulkProfileChange structs. Add unit tests to ensure that validation is working as intended.

History
1 package auth
3 import (
4 "errors"
5 "time"
7 "code.secondbit.org/uuid"
8 )
10 const (
11 MinPassphraseLength = 6
12 MaxPassphraseLength = 64
13 )
15 var (
16 ErrProfileAlreadyExists = errors.New("profile already exists in ProfileStore")
17 ErrProfileNotFound = errors.New("profile not found in ProfileStore")
18 ErrLoginAlreadyExists = errors.New("login already exists in ProfileStore")
19 ErrLoginNotFound = errors.New("login not found in ProfileStore")
21 ErrMissingPassphrase = errors.New("missing passphrase")
22 ErrMissingPassphraseReset = errors.New("missing passphrase reset")
23 ErrMissingPassphraseResetCreated = errors.New("missing passphrase reset created timestamp")
24 ErrPassphraseTooShort = errors.New("passphrase too short")
25 ErrPassphraseTooLong = errors.New("passphrase too long")
26 )
28 type Profile struct {
29 ID uuid.ID
30 Name string
31 Passphrase string
32 Iterations int64
33 Salt string
34 PassphraseScheme int
35 Compromised bool
36 LockedUntil time.Time
37 PassphraseReset string
38 PassphraseResetCreated time.Time
39 Created time.Time
40 LastSeen time.Time
41 }
43 func (p *Profile) ApplyChange(change ProfileChange) {
44 if change.Name != nil {
45 p.Name = *change.Name
46 }
47 if change.Passphrase != nil {
48 p.Passphrase = *change.Passphrase
49 }
50 if change.Iterations != nil {
51 p.Iterations = *change.Iterations
52 }
53 if change.Salt != nil {
54 p.Salt = *change.Salt
55 }
56 if change.PassphraseScheme != nil {
57 p.PassphraseScheme = *change.PassphraseScheme
58 }
59 if change.Compromised != nil {
60 p.Compromised = *change.Compromised
61 }
62 if change.LockedUntil != nil {
63 p.LockedUntil = *change.LockedUntil
64 }
65 if change.PassphraseReset != nil {
66 p.PassphraseReset = *change.PassphraseReset
67 }
68 if change.PassphraseResetCreated != nil {
69 p.PassphraseResetCreated = *change.PassphraseResetCreated
70 }
71 if change.LastSeen != nil {
72 p.LastSeen = *change.LastSeen
73 }
74 }
76 func (p *Profile) ApplyBulkChange(change BulkProfileChange) {
77 if change.Compromised != nil {
78 p.Compromised = *change.Compromised
79 }
80 }
82 type ProfileChange struct {
83 Name *string
84 Passphrase *string
85 Iterations *int64
86 Salt *string
87 PassphraseScheme *int
88 Compromised *bool
89 LockedUntil *time.Time
90 PassphraseReset *string
91 PassphraseResetCreated *time.Time
92 LastSeen *time.Time
93 }
95 func (c ProfileChange) Validate() error {
96 if c.Name == nil && c.Passphrase == nil && c.Iterations == nil && c.Salt == nil && c.PassphraseScheme == nil && c.Compromised == nil && c.LockedUntil == nil && c.PassphraseReset == nil && c.PassphraseResetCreated == nil && c.LastSeen == nil {
97 return ErrEmptyChange
98 }
99 if c.PassphraseScheme != nil && c.Passphrase == nil {
100 return ErrMissingPassphrase
101 }
102 if c.PassphraseReset != nil && c.PassphraseResetCreated == nil {
103 return ErrMissingPassphraseResetCreated
104 }
105 if c.PassphraseReset == nil && c.PassphraseResetCreated != nil {
106 return ErrMissingPassphraseReset
107 }
108 if c.Salt != nil && c.Passphrase == nil {
109 return ErrMissingPassphrase
110 }
111 if c.Iterations != nil && c.Passphrase == nil {
112 return ErrMissingPassphrase
113 }
114 if c.Passphrase != nil && len(*c.Passphrase) < MinPassphraseLength {
115 return ErrPassphraseTooShort
116 }
117 if c.Passphrase != nil && len(*c.Passphrase) > MaxPassphraseLength {
118 return ErrPassphraseTooLong
119 }
120 return nil
121 }
123 type BulkProfileChange struct {
124 Compromised *bool
125 }
127 func (b BulkProfileChange) Validate() error {
128 if b.Compromised == nil {
129 return ErrEmptyChange
130 }
131 return nil
132 }
134 type Login struct {
135 Type string
136 Value string
137 ProfileID uuid.ID
138 Created time.Time
139 LastUsed time.Time
140 }
142 type ProfileStore interface {
143 GetProfileByID(id uuid.ID) (Profile, error)
144 GetProfileByLogin(loginType, value string) (Profile, error)
145 SaveProfile(profile Profile) error
146 UpdateProfile(id uuid.ID, change ProfileChange) error
147 UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error
148 DeleteProfile(id uuid.ID) error
150 AddLogin(login Login) error
151 RemoveLogin(loginType, value string, profile uuid.ID) error
152 RecordLoginUse(loginType, value string, when time.Time) error
153 ListLogins(profile uuid.ID, num, offset int) ([]Login, error)
154 }
156 func (m *Memstore) GetProfileByID(id uuid.ID) (Profile, error) {
157 m.profileLock.RLock()
158 defer m.profileLock.RUnlock()
159 p, ok := m.profiles[id.String()]
160 if !ok {
161 return Profile{}, ErrProfileNotFound
162 }
163 return p, nil
164 }
166 func (m *Memstore) GetProfileByLogin(loginType, value string) (Profile, error) {
167 m.loginLock.RLock()
168 defer m.loginLock.RUnlock()
169 login, ok := m.logins[loginType+":"+value]
170 if !ok {
171 return Profile{}, ErrLoginNotFound
172 }
173 m.profileLock.RLock()
174 defer m.profileLock.RUnlock()
175 profile, ok := m.profiles[login.ProfileID.String()]
176 if !ok {
177 return Profile{}, ErrProfileNotFound
178 }
179 return profile, nil
180 }
182 func (m *Memstore) SaveProfile(profile Profile) error {
183 m.profileLock.Lock()
184 defer m.profileLock.Unlock()
185 _, ok := m.profiles[profile.ID.String()]
186 if ok {
187 return ErrProfileAlreadyExists
188 }
189 m.profiles[profile.ID.String()] = profile
190 return nil
191 }
193 func (m *Memstore) UpdateProfile(id uuid.ID, change ProfileChange) error {
194 m.profileLock.Lock()
195 defer m.profileLock.Unlock()
196 p, ok := m.profiles[id.String()]
197 if !ok {
198 return ErrProfileNotFound
199 }
200 p.ApplyChange(change)
201 m.profiles[id.String()] = p
202 return nil
203 }
205 func (m *Memstore) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
206 m.profileLock.Lock()
207 defer m.profileLock.Unlock()
208 for id, profile := range m.profiles {
209 for _, i := range ids {
210 if id == i.String() {
211 profile.ApplyBulkChange(change)
212 m.profiles[id] = profile
213 break
214 }
215 }
216 }
217 return nil
218 }
220 func (m *Memstore) DeleteProfile(id uuid.ID) error {
221 m.profileLock.Lock()
222 defer m.profileLock.Unlock()
223 _, ok := m.profiles[id.String()]
224 if !ok {
225 return ErrProfileNotFound
226 }
227 delete(m.profiles, id.String())
228 return nil
229 }
231 func (m *Memstore) AddLogin(login Login) error {
232 m.loginLock.Lock()
233 defer m.loginLock.Unlock()
234 _, ok := m.logins[login.Type+":"+login.Value]
235 if ok {
236 return ErrLoginAlreadyExists
237 }
238 m.logins[login.Type+":"+login.Value] = login
239 m.profileLoginLookup[login.ProfileID.String()] = append(m.profileLoginLookup[login.ProfileID.String()], login.Type+":"+login.Value)
240 return nil
241 }
243 func (m *Memstore) RemoveLogin(loginType, value string, profile uuid.ID) error {
244 m.loginLock.Lock()
245 defer m.loginLock.Unlock()
246 l, ok := m.logins[loginType+":"+value]
247 if !ok {
248 return ErrLoginNotFound
249 }
250 if !l.ProfileID.Equal(profile) {
251 return ErrLoginNotFound
252 }
253 delete(m.logins, loginType+":"+value)
254 pos := -1
255 for p, id := range m.profileLoginLookup[profile.String()] {
256 if id == loginType+":"+value {
257 pos = p
258 break
259 }
260 }
261 if pos >= 0 {
262 m.profileLoginLookup[profile.String()] = append(m.profileLoginLookup[profile.String()][:pos], m.profileLoginLookup[profile.String()][pos+1:]...)
263 }
264 return nil
265 }
267 func (m *Memstore) RecordLoginUse(loginType, value string, when time.Time) error {
268 m.loginLock.Lock()
269 defer m.loginLock.Unlock()
270 l, ok := m.logins[loginType+":"+value]
271 if !ok {
272 return ErrLoginNotFound
273 }
274 l.LastUsed = when
275 m.logins[loginType+":"+value] = l
276 return nil
277 }
279 func (m *Memstore) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
280 m.loginLock.RLock()
281 defer m.loginLock.RUnlock()
282 ids, ok := m.profileLoginLookup[profile.String()]
283 if !ok {
284 return []Login{}, nil
285 }
286 if len(ids) > num+offset {
287 ids = ids[offset : num+offset]
288 } else if len(ids) > offset {
289 ids = ids[offset:]
290 } else {
291 return []Login{}, nil
292 }
293 logins := []Login{}
294 for _, id := range ids {
295 login, ok := m.logins[id]
296 if !ok {
297 continue
298 }
299 logins = append(logins, login)
300 }
301 return logins, nil
302 }