auth

Paddy 2015-03-24 Parent:8267e1c8bcd1 Child:3e8964a914ef

152:de5e09680f6b Go to Latest

auth/profile_test.go

Implement postgres version of scopeStore. Update the authd server to use postgres as its scopeStore, instead of memstore. panic when starting the authd server if the CreateScopes call fails. This should, ideally, ignore ErrScopeAlreadyExists errors, but does not as of this commit. Update the simple.gotmpl template to properly display scopes, after switching to the Scope type instead of simply passing around the string the client supplied broke the template and I never bothered fixing it. Update the updateScopes method on the scopeStore (and the corresponding UpdateScopes method on the Context type) to be updateScope/UpdateScope. Operating on several scopes at a time like that is simply too challenging in SQL and I can't justify the complexity with a use case. Add a helper method to ScopeChange called Empty(), which returns true if the ScopeChange is full of nil values. Remove the ID from the ScopeChange type, because we're no longer accepting multiple ScopeChange types in UpdateScope, so we can supply that information outside the ScopeChange, which matches the rest of our update* methods. Correct our tests in scope_test.go to correctly use the updateScope method instead of the old updateScopes method. This generally just resulted in calling updateScope multiple times, as opposed to just once. Add a scope table initialization to the sql/postgres_init.sql script.

History
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.