auth

Paddy 2015-04-11 Parent:849f3820b164 Child:8ecb60d29b0d

162:6f473576c6ae Go to Latest

auth/profile_test.go

Clean up sessions and tokens after Profile is deleted. Add a terminateSessionsByProfile method to our sessionStore to mark Sessions associated with a Profile as inactive. Implement memstore and postgres implementations of the terminateSessionsByProfile method. Add a TerminateSessionsByProfile wrapper method to Context. Add a revokeTokensByProfileID method to our tokenStore to mark Tokens associated with a Profile as revoked. Implement memstore and postgres implementation of the revokeTokensByProfileID method. Add a RevokeTokensByProfileID wrapper method to Context. Call our RevokeTokensByProfileID and TerminateSessionsByProfile methods after a Profile is deleted, to clean up the Tokens and Sessions associated with it.

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 return true, "", nil, nil
75 }
77 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) {
78 if login1.Type != login2.Type {
79 return false, "Type", login1.Type, login2.Type
80 }
81 if login1.Value != login2.Value {
82 return false, "Value", login1.Value, login2.Value
83 }
84 if !login1.ProfileID.Equal(login2.ProfileID) {
85 return false, "ProfileID", login1.ProfileID, login2.ProfileID
86 }
87 if !login1.Created.Equal(login2.Created) {
88 return false, "Created", login1.Created, login2.Created
89 }
90 if !login1.LastUsed.Equal(login2.LastUsed) {
91 return false, "LastUsed", login1.LastUsed, login2.LastUsed
92 }
93 return true, "", nil, nil
94 }
96 func TestProfileStoreSuccess(t *testing.T) {
97 t.Parallel()
98 profile := Profile{
99 ID: uuid.NewID(),
100 Name: "name",
101 Passphrase: "passphrase",
102 Iterations: 10000,
103 Salt: "salt",
104 PassphraseScheme: 1,
105 Compromised: false,
106 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond),
107 PassphraseReset: "passphrase reset",
108 PassphraseResetCreated: time.Now().Round(time.Millisecond),
109 Created: time.Now().Round(time.Millisecond),
110 LastSeen: time.Now().Round(time.Millisecond),
111 }
112 for _, store := range profileStores {
113 context := Context{profiles: store}
114 err := context.SaveProfile(profile)
115 if err != nil {
116 t.Errorf("Error saving profile to %T: %s", store, err)
117 }
118 err = context.SaveProfile(profile)
119 if err != ErrProfileAlreadyExists {
120 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
121 }
122 retrieved, err := context.GetProfileByID(profile.ID)
123 if err != nil {
124 t.Errorf("Error retrieving profile from %T: %s", store, err)
125 }
126 match, field, expectation, result := compareProfiles(profile, retrieved)
127 if !match {
128 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
129 }
130 err = context.DeleteProfile(profile.ID)
131 if err != nil {
132 t.Errorf("Error removing profile from %T: %s", store, err)
133 }
134 retrieved, err = context.GetProfileByID(profile.ID)
135 if err != ErrProfileNotFound {
136 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
137 }
138 }
139 }
141 func TestProfileUpdates(t *testing.T) {
142 t.Parallel()
143 variations := 1 << 10
144 profile := Profile{
145 ID: uuid.NewID(),
146 Name: "name",
147 Passphrase: "passphrase",
148 Iterations: 10000,
149 Salt: "salt",
150 PassphraseScheme: 1,
151 Compromised: false,
152 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond),
153 PassphraseReset: "passphrase reset",
154 PassphraseResetCreated: time.Now().Round(time.Millisecond),
155 Created: time.Now().Round(time.Millisecond),
156 LastSeen: time.Now().Round(time.Millisecond),
157 }
158 for i := 0; i < variations; i++ {
159 var name, passphrase, salt, passphraseReset string
160 var iterations int
161 var lockedUntil, passphraseResetCreated, lastSeen time.Time
162 var passphraseScheme int
163 var compromised bool
165 profile.ID = uuid.NewID()
166 change := ProfileChange{}
167 expectation := profile
168 result := profile
169 if i&profileChangeName != 0 {
170 name = fmt.Sprintf("name-%d", i)
171 change.Name = &name
172 expectation.Name = name
173 }
174 if i&profileChangePassphrase != 0 {
175 passphrase = fmt.Sprintf("passphrase-%d", i)
176 change.Passphrase = &passphrase
177 expectation.Passphrase = passphrase
178 }
179 if i&profileChangeIterations != 0 {
180 iterations = i
181 change.Iterations = &iterations
182 expectation.Iterations = iterations
183 }
184 if i&profileChangeSalt != 0 {
185 salt = fmt.Sprintf("salt-%d", i)
186 change.Salt = &salt
187 expectation.Salt = salt
188 }
189 if i&profileChangePassphraseScheme != 0 {
190 passphraseScheme = i
191 change.PassphraseScheme = &passphraseScheme
192 expectation.PassphraseScheme = passphraseScheme
193 }
194 if i&profileChangeCompromised != 0 {
195 compromised = i%2 != 0
196 change.Compromised = &compromised
197 expectation.Compromised = compromised
198 }
199 if i&profileChangeLockedUntil != 0 {
200 lockedUntil = time.Now().Round(time.Millisecond)
201 change.LockedUntil = &lockedUntil
202 expectation.LockedUntil = lockedUntil
203 }
204 if i&profileChangePassphraseReset != 0 {
205 passphraseReset = fmt.Sprintf("passphraseReset-%d", i)
206 change.PassphraseReset = &passphraseReset
207 expectation.PassphraseReset = passphraseReset
208 }
209 if i&profileChangePassphraseResetCreated != 0 {
210 passphraseResetCreated = time.Now().Round(time.Millisecond)
211 change.PassphraseResetCreated = &passphraseResetCreated
212 expectation.PassphraseResetCreated = passphraseResetCreated
213 }
214 if i&profileChangeLastSeen != 0 {
215 lastSeen = time.Now().Round(time.Millisecond)
216 change.LastSeen = &lastSeen
217 expectation.LastSeen = lastSeen
218 }
219 result.ApplyChange(change)
220 match, field, expected, got := compareProfiles(expectation, result)
221 if !match {
222 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
223 }
224 for _, store := range profileStores {
225 context := Context{profiles: store}
226 err := context.SaveProfile(profile)
227 if err != nil {
228 t.Errorf("Error saving profile in %T: %s", store, err)
229 }
230 err = context.UpdateProfile(profile.ID, change)
231 if err != nil {
232 t.Errorf("Error updating profile in %T: %s", store, err)
233 }
234 retrieved, err := context.GetProfileByID(profile.ID)
235 if err != nil {
236 t.Errorf("Error getting profile from %T: %s", store, err)
237 }
238 match, field, expected, got = compareProfiles(expectation, retrieved)
239 if !match {
240 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, 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).Round(time.Millisecond),
314 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond),
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().Round(time.Millisecond)
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).Round(time.Millisecond),
380 PassphraseReset: "passphrase reset",
381 PassphraseResetCreated: time.Now().Round(time.Millisecond),
382 Created: time.Now().Round(time.Millisecond),
383 LastSeen: time.Now().Round(time.Millisecond),
384 }
385 login := Login{
386 Type: "type",
387 Value: "value",
388 ProfileID: profile.ID,
389 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond),
390 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond),
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 emptyName := ""
420 enteredName := "Paddy"
421 okPassphrase := "this is a decent passphrase"
422 compromised := true
423 lockedUntil := time.Now().Round(time.Millisecond)
424 resetCreated := time.Now().Round(time.Millisecond)
425 lastSeen := time.Now().Round(time.Millisecond)
426 changes := map[*ProfileChange]error{
427 &ProfileChange{}: ErrEmptyChange,
428 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase,
429 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil,
430 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated,
431 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil,
432 &ProfileChange{Salt: &salt}: ErrMissingPassphrase,
433 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil,
434 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase,
435 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil,
436 &ProfileChange{Passphrase: &okPassphrase}: nil,
437 &ProfileChange{Name: &emptyName}: nil,
438 &ProfileChange{Name: &enteredName}: nil,
439 &ProfileChange{Compromised: &compromised}: nil,
440 &ProfileChange{LockedUntil: &lockedUntil}: nil,
441 &ProfileChange{LastSeen: &lastSeen}: nil,
442 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset,
443 }
444 for change, expectedErr := range changes {
445 if err := change.Validate(); err != expectedErr {
446 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
447 }
448 }
449 }
451 func TestBulkProfileChangeValidation(t *testing.T) {
452 t.Parallel()
453 compromised := true
454 changes := map[*BulkProfileChange]error{
455 &BulkProfileChange{}: ErrEmptyChange,
456 &BulkProfileChange{Compromised: &compromised}: nil,
457 }
458 for change, expectedErr := range changes {
459 if err := change.Validate(); err != expectedErr {
460 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
461 }
462 }
463 }
465 // BUG(paddy): We need to test the validateNewProfileRequest helper.
466 // BUG(paddy): We need to test the CreateProfileHandler.
467 // BUG(paddy): We need to test that deleting works as we expect.