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