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.
8 "code.secondbit.org/uuid.hg"
12 profileChangeName = 1 << iota
13 profileChangePassphrase
14 profileChangeIterations
16 profileChangePassphraseScheme
17 profileChangeCompromised
18 profileChangeLockedUntil
19 profileChangePassphraseReset
20 profileChangePassphraseResetCreated
25 p, err := NewPostgres("dbname=testdb sslmode=disable")
30 profileStores = append(profileStores, &p)
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
40 if profile1.Name != profile2.Name {
41 return false, "name", profile1.Name, profile2.Name
43 if profile1.Passphrase != profile2.Passphrase {
44 return false, "passphrase", profile1.Passphrase, profile2.Passphrase
46 if profile1.Iterations != profile2.Iterations {
47 return false, "iterations", profile1.Iterations, profile2.Iterations
49 if profile1.Salt != profile2.Salt {
50 return false, "salt", profile1.Salt, profile2.Salt
52 if profile1.PassphraseScheme != profile2.PassphraseScheme {
53 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme
55 if profile1.Compromised != profile2.Compromised {
56 return false, "compromised", profile1.Compromised, profile2.Compromised
58 if !profile1.LockedUntil.Equal(profile2.LockedUntil) {
59 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil
61 if profile1.PassphraseReset != profile2.PassphraseReset {
62 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset
64 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) {
65 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated
67 if !profile1.Created.Equal(profile2.Created) {
68 return false, "created", profile1.Created, profile2.Created
70 if !profile1.LastSeen.Equal(profile2.LastSeen) {
71 return false, "last seen", profile1.LastSeen, profile2.LastSeen
73 if profile1.Deleted != profile2.Deleted {
74 return false, "deleted", profile1.Deleted, profile2.Deleted
76 return true, "", nil, nil
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
83 if login1.Value != login2.Value {
84 return false, "Value", login1.Value, login2.Value
86 if !login1.ProfileID.Equal(login2.ProfileID) {
87 return false, "ProfileID", login1.ProfileID, login2.ProfileID
89 if !login1.Created.Equal(login2.Created) {
90 return false, "Created", login1.Created, login2.Created
92 if !login1.LastUsed.Equal(login2.LastUsed) {
93 return false, "LastUsed", login1.LastUsed, login2.LastUsed
95 return true, "", nil, nil
98 func TestProfileStoreSuccess(t *testing.T) {
103 Passphrase: "passphrase",
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),
114 for _, store := range profileStores {
115 context := Context{profiles: store}
116 err := context.SaveProfile(profile)
118 t.Errorf("Error saving profile to %T: %s", store, err)
120 err = context.SaveProfile(profile)
121 if err != ErrProfileAlreadyExists {
122 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
124 retrieved, err := context.GetProfileByID(profile.ID)
126 t.Errorf("Error retrieving profile from %T: %s", store, err)
128 match, field, expectation, result := compareProfiles(profile, retrieved)
130 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
133 err = context.UpdateProfile(profile.ID, ProfileChange{Deleted: &deleted})
135 t.Errorf("Error removing profile from %T: %s", store, err)
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)
144 func TestProfileUpdates(t *testing.T) {
146 variations := 1 << 10
150 Passphrase: "passphrase",
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),
161 for i := 0; i < variations; i++ {
162 var name, passphrase, salt, passphraseReset string
164 var lockedUntil, passphraseResetCreated, lastSeen time.Time
165 var passphraseScheme int
168 profile.ID = uuid.NewID()
169 change := ProfileChange{}
170 expectation := profile
172 if i&profileChangeName != 0 {
173 name = fmt.Sprintf("name-%d", i)
175 expectation.Name = name
177 if i&profileChangePassphrase != 0 {
178 passphrase = fmt.Sprintf("passphrase-%d", i)
179 change.Passphrase = &passphrase
180 expectation.Passphrase = passphrase
182 if i&profileChangeIterations != 0 {
184 change.Iterations = &iterations
185 expectation.Iterations = iterations
187 if i&profileChangeSalt != 0 {
188 salt = fmt.Sprintf("salt-%d", i)
190 expectation.Salt = salt
192 if i&profileChangePassphraseScheme != 0 {
194 change.PassphraseScheme = &passphraseScheme
195 expectation.PassphraseScheme = passphraseScheme
197 if i&profileChangeCompromised != 0 {
198 compromised = i%2 != 0
199 change.Compromised = &compromised
200 expectation.Compromised = compromised
202 if i&profileChangeLockedUntil != 0 {
203 lockedUntil = time.Now().Round(time.Millisecond)
204 change.LockedUntil = &lockedUntil
205 expectation.LockedUntil = lockedUntil
207 if i&profileChangePassphraseReset != 0 {
208 passphraseReset = fmt.Sprintf("passphraseReset-%d", i)
209 change.PassphraseReset = &passphraseReset
210 expectation.PassphraseReset = passphraseReset
212 if i&profileChangePassphraseResetCreated != 0 {
213 passphraseResetCreated = time.Now().Round(time.Millisecond)
214 change.PassphraseResetCreated = &passphraseResetCreated
215 expectation.PassphraseResetCreated = passphraseResetCreated
217 if i&profileChangeLastSeen != 0 {
218 lastSeen = time.Now().Round(time.Millisecond)
219 change.LastSeen = &lastSeen
220 expectation.LastSeen = lastSeen
222 result.ApplyChange(change)
223 match, field, expected, got := compareProfiles(expectation, result)
225 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
227 for _, store := range profileStores {
228 context := Context{profiles: store}
229 err := context.SaveProfile(profile)
231 t.Errorf("Error saving profile in %T: %s", store, err)
233 err = context.UpdateProfile(profile.ID, change)
235 t.Errorf("Error updating profile in %T: %s", store, err)
237 retrieved, err := context.GetProfileByID(profile.ID)
239 t.Errorf("Error getting profile from %T: %s", store, err)
241 match, field, expected, got = compareProfiles(expectation, retrieved)
243 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
249 func TestProfilesUpdates(t *testing.T) {
260 change := BulkProfileChange{
263 for _, store := range profileStores {
264 context := Context{profiles: store}
265 err := context.SaveProfile(profile1)
267 t.Errorf("Error saving profile in %T: %s", store, err)
269 err = context.SaveProfile(profile2)
271 t.Errorf("Error saving profile in %T: %s", store, err)
273 err = context.SaveProfile(profile3)
275 t.Errorf("Error saving profile in %T: %s", store, err)
277 err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
279 t.Errorf("Error updating profile in %T: %s", store, err)
281 profile1.Compromised = truth
282 profile3.Compromised = truth
283 retrieved, err := context.GetProfileByID(profile1.ID)
285 t.Errorf("Error getting profile from %T: %s", store, err)
287 match, field, expected, got := compareProfiles(profile1, retrieved)
289 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
291 retrieved, err = context.GetProfileByID(profile2.ID)
293 t.Errorf("Error getting profile from %T: %s", store, err)
295 match, field, expected, got = compareProfiles(profile2, retrieved)
297 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
299 retrieved, err = context.GetProfileByID(profile3.ID)
301 t.Errorf("Error getting profile from %T: %s", store, err)
303 match, field, expected, got = compareProfiles(profile3, retrieved)
305 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
310 func TestProfileStoreLoginSuccess(t *testing.T) {
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),
319 for _, store := range profileStores {
320 context := Context{profiles: store}
321 err := context.AddLogin(login)
323 t.Errorf("Error adding login to %T: %s", store, err)
325 err = context.AddLogin(login)
326 if err != ErrLoginAlreadyExists {
327 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
329 retrieved, err := context.ListLogins(login.ProfileID, 10, 0)
331 t.Errorf("Error retrieving logins from %T: %s", store, err)
333 if len(retrieved) != 1 {
334 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
336 match, field, expectation, result := compareLogins(login, retrieved[0])
338 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
340 lastUsed := time.Now().Round(time.Millisecond)
341 err = context.RecordLoginUse(login.Value, lastUsed)
343 t.Errorf("Error recording use of login to %T: %s", store, err)
345 login.LastUsed = lastUsed
346 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
348 t.Errorf("Error retrieving logins from %T: %s", store, err)
350 if len(retrieved) != 1 {
351 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
353 match, field, expectation, result = compareLogins(login, retrieved[0])
355 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
357 err = context.RemoveLogin(login.Value, login.ProfileID)
359 t.Errorf("Error removing login from %T: %s", store, err)
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)
365 err = context.RemoveLogin(login.Value, login.ProfileID)
366 if err != ErrLoginNotFound {
367 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
372 func TestProfileStoreLoginRetrieval(t *testing.T) {
377 Passphrase: "passphrase",
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),
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),
395 for _, store := range profileStores {
396 context := Context{profiles: store}
397 err := context.SaveProfile(profile)
399 t.Errorf("Error saving profile in %T: %s", store, err)
401 err = context.AddLogin(login)
403 t.Errorf("Error storing login in %T: %s", store, err)
405 retrieved, err := context.GetProfileByLogin(login.Value)
407 t.Errorf("Error retrieving profile by login from %T: %s", store, err)
409 match, field, expectation, result := compareProfiles(profile, retrieved)
411 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
416 func TestProfileChangeValidation(t *testing.T) {
418 passphraseScheme := 1
419 passphraseReset := "reset"
423 enteredName := "Paddy"
424 okPassphrase := "this is a decent passphrase"
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,
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)
454 func TestBulkProfileChangeValidation(t *testing.T) {
457 changes := map[*BulkProfileChange]error{
458 &BulkProfileChange{}: ErrEmptyChange,
459 &BulkProfileChange{Compromised: &compromised}: nil,
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)
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.