auth

Paddy 2015-04-11 Parent:3e8964a914ef Child:849f3820b164

157:202e991accc2 Go to Latest

auth/profile_test.go

Wire up the postgres database for authd. Have authd use the AUTH_PG_DB environment variable to detect support for the postgres *Stores, and if postgres is supported, use it. If postgres isn't supported, fall back on the in-memory store. Also create-if-not-exists the test scopes, instead of panicking when the scope already exists.

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