auth
auth/profile_test.go
Test around client types and secrets. Implement a test that the CreateClient handler will correctly create a confidential client and issue a secret for it. Also, just generally test that clients that are confidential are issued secrets and clients that are public are not.
1 package auth
3 import (
4 "fmt"
5 "testing"
6 "time"
8 "code.secondbit.org/uuid.hg"
9 )
11 const (
12 profileChangeName = 1 << iota
13 profileChangePassphrase
14 profileChangeIterations
15 profileChangeSalt
16 profileChangePassphraseScheme
17 profileChangeCompromised
18 profileChangeLockedUntil
19 profileChangePassphraseReset
20 profileChangePassphraseResetCreated
21 profileChangeLastSeen
22 )
24 var profileStores = []profileStore{NewMemstore()}
26 func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) {
27 if !profile1.ID.Equal(profile2.ID) {
28 return false, "ID", profile1.ID, profile2.ID
29 }
30 if profile1.Name != profile2.Name {
31 return false, "name", profile1.Name, profile2.Name
32 }
33 if profile1.Passphrase != profile2.Passphrase {
34 return false, "passphrase", profile1.Passphrase, profile2.Passphrase
35 }
36 if profile1.Iterations != profile2.Iterations {
37 return false, "iterations", profile1.Iterations, profile2.Iterations
38 }
39 if profile1.Salt != profile2.Salt {
40 return false, "salt", profile1.Salt, profile2.Salt
41 }
42 if profile1.PassphraseScheme != profile2.PassphraseScheme {
43 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme
44 }
45 if profile1.Compromised != profile2.Compromised {
46 return false, "compromised", profile1.Compromised, profile2.Compromised
47 }
48 if !profile1.LockedUntil.Equal(profile2.LockedUntil) {
49 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil
50 }
51 if profile1.PassphraseReset != profile2.PassphraseReset {
52 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset
53 }
54 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) {
55 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated
56 }
57 if !profile1.Created.Equal(profile2.Created) {
58 return false, "created", profile1.Created, profile2.Created
59 }
60 if !profile1.LastSeen.Equal(profile2.LastSeen) {
61 return false, "last seen", profile1.LastSeen, profile2.LastSeen
62 }
63 return true, "", nil, nil
64 }
66 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) {
67 if login1.Type != login2.Type {
68 return false, "Type", login1.Type, login2.Type
69 }
70 if login1.Value != login2.Value {
71 return false, "Value", login1.Value, login2.Value
72 }
73 if !login1.ProfileID.Equal(login2.ProfileID) {
74 return false, "ProfileID", login1.ProfileID, login2.ProfileID
75 }
76 if !login1.Created.Equal(login2.Created) {
77 return false, "Created", login1.Created, login2.Created
78 }
79 if !login1.LastUsed.Equal(login2.LastUsed) {
80 return false, "LastUsed", login1.LastUsed, login2.LastUsed
81 }
82 return true, "", nil, nil
83 }
85 func TestProfileStoreSuccess(t *testing.T) {
86 t.Parallel()
87 profile := Profile{
88 ID: uuid.NewID(),
89 Name: "name",
90 Passphrase: "passphrase",
91 Iterations: 10000,
92 Salt: "salt",
93 PassphraseScheme: 1,
94 Compromised: false,
95 LockedUntil: time.Now().Add(time.Hour),
96 PassphraseReset: "passphrase reset",
97 PassphraseResetCreated: time.Now(),
98 Created: time.Now(),
99 LastSeen: time.Now(),
100 }
101 for _, store := range profileStores {
102 context := Context{profiles: store}
103 err := context.SaveProfile(profile)
104 if err != nil {
105 t.Errorf("Error saving profile to %T: %s", store, err)
106 }
107 err = context.SaveProfile(profile)
108 if err != ErrProfileAlreadyExists {
109 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
110 }
111 retrieved, err := context.GetProfileByID(profile.ID)
112 if err != nil {
113 t.Errorf("Error retrieving profile from %T: %s", store, err)
114 }
115 match, field, expectation, result := compareProfiles(profile, retrieved)
116 if !match {
117 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
118 }
119 err = context.DeleteProfile(profile.ID)
120 if err != nil {
121 t.Errorf("Error removing profile from %T: %s", store, err)
122 }
123 retrieved, err = context.GetProfileByID(profile.ID)
124 if err != ErrProfileNotFound {
125 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
126 }
127 err = context.DeleteProfile(profile.ID)
128 if err != ErrProfileNotFound {
129 t.Errorf("Expected ErrProfileNotFound from %T, got %+v", store, err)
130 }
131 }
132 }
134 func TestProfileUpdates(t *testing.T) {
135 t.Parallel()
136 variations := 1 << 10
137 profile := Profile{
138 ID: uuid.NewID(),
139 Name: "name",
140 Passphrase: "passphrase",
141 Iterations: 10000,
142 Salt: "salt",
143 PassphraseScheme: 1,
144 Compromised: false,
145 LockedUntil: time.Now().Add(time.Hour),
146 PassphraseReset: "passphrase reset",
147 PassphraseResetCreated: time.Now(),
148 Created: time.Now(),
149 LastSeen: time.Now(),
150 }
151 for i := 0; i < variations; i++ {
152 var name, passphrase, salt, passphraseReset string
153 var iterations int
154 var lockedUntil, passphraseResetCreated, lastSeen time.Time
155 var passphraseScheme int
156 var compromised bool
158 change := ProfileChange{}
159 expectation := profile
160 result := profile
161 if i&profileChangeName != 0 {
162 name = fmt.Sprintf("name-%d", i)
163 change.Name = &name
164 expectation.Name = name
165 }
166 if i&profileChangePassphrase != 0 {
167 passphrase = fmt.Sprintf("passphrase-%d", i)
168 change.Passphrase = &passphrase
169 expectation.Passphrase = passphrase
170 }
171 if i&profileChangeIterations != 0 {
172 iterations = i
173 change.Iterations = &iterations
174 expectation.Iterations = iterations
175 }
176 if i&profileChangeSalt != 0 {
177 salt = fmt.Sprintf("salt-%d", i)
178 change.Salt = &salt
179 expectation.Salt = salt
180 }
181 if i&profileChangePassphraseScheme != 0 {
182 passphraseScheme = i
183 change.PassphraseScheme = &passphraseScheme
184 expectation.PassphraseScheme = passphraseScheme
185 }
186 if i&profileChangeCompromised != 0 {
187 compromised = i%2 != 0
188 change.Compromised = &compromised
189 expectation.Compromised = compromised
190 }
191 if i&profileChangeLockedUntil != 0 {
192 lockedUntil = time.Now()
193 change.LockedUntil = &lockedUntil
194 expectation.LockedUntil = lockedUntil
195 }
196 if i&profileChangePassphraseReset != 0 {
197 passphraseReset = fmt.Sprintf("passphraseReset-%d", i)
198 change.PassphraseReset = &passphraseReset
199 expectation.PassphraseReset = passphraseReset
200 }
201 if i&profileChangePassphraseResetCreated != 0 {
202 passphraseResetCreated = time.Now()
203 change.PassphraseResetCreated = &passphraseResetCreated
204 expectation.PassphraseResetCreated = passphraseResetCreated
205 }
206 if i&profileChangeLastSeen != 0 {
207 lastSeen = time.Now()
208 change.LastSeen = &lastSeen
209 expectation.LastSeen = lastSeen
210 }
211 result.ApplyChange(change)
212 match, field, expected, got := compareProfiles(expectation, result)
213 if !match {
214 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
215 }
216 for _, store := range profileStores {
217 context := Context{profiles: store}
218 err := context.SaveProfile(profile)
219 if err != nil {
220 t.Errorf("Error saving profile in %T: %s", store, err)
221 }
222 err = context.UpdateProfile(profile.ID, change)
223 if err != nil {
224 t.Errorf("Error updating profile in %T: %s", store, err)
225 }
226 retrieved, err := context.GetProfileByID(profile.ID)
227 if err != nil {
228 t.Errorf("Error getting profile from %T: %s", store, err)
229 }
230 match, field, expected, got = compareProfiles(expectation, retrieved)
231 if !match {
232 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
233 }
234 err = context.DeleteProfile(profile.ID)
235 if err != nil {
236 t.Errorf("Error deleting profile from %T: %s", store, err)
237 }
238 err = context.UpdateProfile(profile.ID, change)
239 if err != ErrProfileNotFound {
240 t.Errorf("Expected ErrProfileNotFound, got %v from %T", err, store)
241 }
242 }
243 }
244 }
246 func TestProfilesUpdates(t *testing.T) {
247 profile1 := Profile{
248 ID: uuid.NewID(),
249 }
250 profile2 := Profile{
251 ID: uuid.NewID(),
252 }
253 profile3 := Profile{
254 ID: uuid.NewID(),
255 }
256 truth := true
257 change := BulkProfileChange{
258 Compromised: &truth,
259 }
260 for _, store := range profileStores {
261 context := Context{profiles: store}
262 err := context.SaveProfile(profile1)
263 if err != nil {
264 t.Errorf("Error saving profile in %T: %s", store, err)
265 }
266 err = context.SaveProfile(profile2)
267 if err != nil {
268 t.Errorf("Error saving profile in %T: %s", store, err)
269 }
270 err = context.SaveProfile(profile3)
271 if err != nil {
272 t.Errorf("Error saving profile in %T: %s", store, err)
273 }
274 err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
275 if err != nil {
276 t.Errorf("Error updating profile in %T: %s", store, err)
277 }
278 profile1.Compromised = truth
279 profile3.Compromised = truth
280 retrieved, err := context.GetProfileByID(profile1.ID)
281 if err != nil {
282 t.Errorf("Error getting profile from %T: %s", store, err)
283 }
284 match, field, expected, got := compareProfiles(profile1, retrieved)
285 if !match {
286 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
287 }
288 retrieved, err = context.GetProfileByID(profile2.ID)
289 if err != nil {
290 t.Errorf("Error getting profile from %T: %s", store, err)
291 }
292 match, field, expected, got = compareProfiles(profile2, retrieved)
293 if !match {
294 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
295 }
296 retrieved, err = context.GetProfileByID(profile3.ID)
297 if err != nil {
298 t.Errorf("Error getting profile from %T: %s", store, err)
299 }
300 match, field, expected, got = compareProfiles(profile3, retrieved)
301 if !match {
302 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
303 }
304 }
305 }
307 func TestProfileStoreLoginSuccess(t *testing.T) {
308 t.Parallel()
309 login := Login{
310 Type: "type",
311 Value: "value",
312 ProfileID: uuid.NewID(),
313 Created: time.Now().Add(-1 * time.Hour),
314 LastUsed: time.Now().Add(-1 * time.Minute),
315 }
316 for _, store := range profileStores {
317 context := Context{profiles: store}
318 err := context.AddLogin(login)
319 if err != nil {
320 t.Errorf("Error adding login to %T: %s", store, err)
321 }
322 err = context.AddLogin(login)
323 if err != ErrLoginAlreadyExists {
324 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
325 }
326 retrieved, err := context.ListLogins(login.ProfileID, 10, 0)
327 if err != nil {
328 t.Errorf("Error retrieving logins from %T: %s", store, err)
329 }
330 if len(retrieved) != 1 {
331 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
332 }
333 match, field, expectation, result := compareLogins(login, retrieved[0])
334 if !match {
335 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
336 }
337 lastUsed := time.Now()
338 err = context.RecordLoginUse(login.Value, lastUsed)
339 if err != nil {
340 t.Errorf("Error recording use of login to %T: %s", store, err)
341 }
342 login.LastUsed = lastUsed
343 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
344 if err != nil {
345 t.Errorf("Error retrieving logins from %T: %s", store, err)
346 }
347 if len(retrieved) != 1 {
348 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
349 }
350 match, field, expectation, result = compareLogins(login, retrieved[0])
351 if !match {
352 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
353 }
354 err = context.RemoveLogin(login.Value, login.ProfileID)
355 if err != nil {
356 t.Errorf("Error removing login from %T: %s", store, err)
357 }
358 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
359 if len(retrieved) != 0 {
360 t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved)
361 }
362 err = context.RemoveLogin(login.Value, login.ProfileID)
363 if err != ErrLoginNotFound {
364 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
365 }
366 }
367 }
369 func TestProfileStoreLoginRetrieval(t *testing.T) {
370 t.Parallel()
371 profile := Profile{
372 ID: uuid.NewID(),
373 Name: "name",
374 Passphrase: "passphrase",
375 Iterations: 10000,
376 Salt: "salt",
377 PassphraseScheme: 1,
378 Compromised: false,
379 LockedUntil: time.Now().Add(time.Hour),
380 PassphraseReset: "passphrase reset",
381 PassphraseResetCreated: time.Now(),
382 Created: time.Now(),
383 LastSeen: time.Now(),
384 }
385 login := Login{
386 Type: "type",
387 Value: "value",
388 ProfileID: profile.ID,
389 Created: time.Now().Add(-1 * time.Hour),
390 LastUsed: time.Now().Add(-1 * time.Minute),
391 }
392 for _, store := range profileStores {
393 context := Context{profiles: store}
394 err := context.SaveProfile(profile)
395 if err != nil {
396 t.Errorf("Error saving profile in %T: %s", store, err)
397 }
398 err = context.AddLogin(login)
399 if err != nil {
400 t.Errorf("Error storing login in %T: %s", store, err)
401 }
402 retrieved, err := context.GetProfileByLogin(login.Value)
403 if err != nil {
404 t.Errorf("Error retrieving profile by login from %T: %s", store, err)
405 }
406 match, field, expectation, result := compareProfiles(profile, retrieved)
407 if !match {
408 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
409 }
410 }
411 }
413 func TestProfileChangeValidation(t *testing.T) {
414 t.Parallel()
415 passphraseScheme := 1
416 passphraseReset := "reset"
417 salt := "salt"
418 iterations := 100
419 shortPassphrase := "a"
420 longPassphrase := "this passphrase is much too long for anyone to remember, and therefore should probably be discouraged by the software, don't you think?"
421 emptyName := ""
422 enteredName := "Paddy"
423 okPassphrase := "this is a decent passphrase"
424 compromised := true
425 lockedUntil := time.Now()
426 resetCreated := time.Now()
427 lastSeen := time.Now()
428 changes := map[*ProfileChange]error{
429 &ProfileChange{}: ErrEmptyChange,
430 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase,
431 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil,
432 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated,
433 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil,
434 &ProfileChange{Salt: &salt}: ErrMissingPassphrase,
435 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil,
436 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase,
437 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil,
438 &ProfileChange{Passphrase: &shortPassphrase}: ErrPassphraseTooShort,
439 &ProfileChange{Passphrase: &longPassphrase}: ErrPassphraseTooLong,
440 &ProfileChange{Passphrase: &okPassphrase}: nil,
441 &ProfileChange{Name: &emptyName}: nil,
442 &ProfileChange{Name: &enteredName}: nil,
443 &ProfileChange{Compromised: &compromised}: nil,
444 &ProfileChange{LockedUntil: &lockedUntil}: nil,
445 &ProfileChange{LastSeen: &lastSeen}: nil,
446 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset,
447 }
448 for change, expectedErr := range changes {
449 if err := change.Validate(); err != expectedErr {
450 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
451 }
452 }
453 }
455 func TestBulkProfileChangeValidation(t *testing.T) {
456 t.Parallel()
457 compromised := true
458 changes := map[*BulkProfileChange]error{
459 &BulkProfileChange{}: ErrEmptyChange,
460 &BulkProfileChange{Compromised: &compromised}: nil,
461 }
462 for change, expectedErr := range changes {
463 if err := change.Validate(); err != expectedErr {
464 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
465 }
466 }
467 }