auth

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

155:762953f6a7f2 Go to Latest

auth/profile_test.go

Implement postgres version of the tokenStore. Create a postgres implementation for the tokenStore. Note that because pq doesn't support Postgres' array types (see https://github.com/lib/pq/issues/49), we couldn't just store the token.Scopes field as a Postgres array of varchars, which would have been the ideal. Instead, we need a many-to-many table that maps tokens to scopes. This meant we needed a special tokenScope type for our database mapping, and we needed to complicate the token storage/retrieval functions a little bit. It's kind of ugly, I'm not a huge fan of it, and I'd much rather be using the Postgres array types, but... well, here we are. We also added the postgres tokenStore to our slice of tokenStores to test when the correct environment variables are present. We wrote initialization SQL for the tables required by the postgres tokenStore. Also, added a helper script for emptying the test database, because I got tired of doing it by hand. We should be doing that in an automated fashion in the tests themselves, but that would mean extending the *Store interfaces.

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.