The great documentation and exported interface cleanup.
Modify all our *Store interfaces to be unexported, as there's no real good
reason they need to be exported, especially as they can be implemented without
being exported. The interfaces shouldn't matter to 99% of users of the package,
so let's not pollute our package API.
Further, all methods of the interfaces are now unexported, for pretty much the
same reasoning.
Add a doc.go file with documentation explaining the choices the package is
making and what it provides.
Implement documentation on all our exported types and methods and functions,
which makes golint happy. The only remaining golint warning is about
NewMemstore, which will stay the way it is. The memstore type is useful outside
tests for things like standing up a server quickly when we don't care about the
storage, and because the type is unexported, we _need_ a New function to create
an instance that can be passed to the Context.
8 "code.secondbit.org/uuid"
12 profileChangeName = 1 << iota
13 profileChangePassphrase
14 profileChangeIterations
16 profileChangePassphraseScheme
17 profileChangeCompromised
18 profileChangeLockedUntil
19 profileChangePassphraseReset
20 profileChangePassphraseResetCreated
24 var profileStores = []profileStore{NewMemstore()}
26 func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) {
27 if !profile1.ID.Equal(profile2.ID) {
28 return false, "ID", profile1.ID, profile2.ID
30 if profile1.Name != profile2.Name {
31 return false, "name", profile1.Name, profile2.Name
33 if profile1.Passphrase != profile2.Passphrase {
34 return false, "passphrase", profile1.Passphrase, profile2.Passphrase
36 if profile1.Iterations != profile2.Iterations {
37 return false, "iterations", profile1.Iterations, profile2.Iterations
39 if profile1.Salt != profile2.Salt {
40 return false, "salt", profile1.Salt, profile2.Salt
42 if profile1.PassphraseScheme != profile2.PassphraseScheme {
43 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme
45 if profile1.Compromised != profile2.Compromised {
46 return false, "compromised", profile1.Compromised, profile2.Compromised
48 if !profile1.LockedUntil.Equal(profile2.LockedUntil) {
49 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil
51 if profile1.PassphraseReset != profile2.PassphraseReset {
52 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset
54 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) {
55 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated
57 if !profile1.Created.Equal(profile2.Created) {
58 return false, "created", profile1.Created, profile2.Created
60 if !profile1.LastSeen.Equal(profile2.LastSeen) {
61 return false, "last seen", profile1.LastSeen, profile2.LastSeen
63 return true, "", nil, nil
66 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) {
67 if login1.Type != login2.Type {
68 return false, "Type", login1.Type, login2.Type
70 if login1.Value != login2.Value {
71 return false, "Value", login1.Value, login2.Value
73 if !login1.ProfileID.Equal(login2.ProfileID) {
74 return false, "ProfileID", login1.ProfileID, login2.ProfileID
76 if !login1.Created.Equal(login2.Created) {
77 return false, "Created", login1.Created, login2.Created
79 if !login1.LastUsed.Equal(login2.LastUsed) {
80 return false, "LastUsed", login1.LastUsed, login2.LastUsed
82 return true, "", nil, nil
85 func TestProfileStoreSuccess(t *testing.T) {
90 Passphrase: "passphrase",
95 LockedUntil: time.Now().Add(time.Hour),
96 PassphraseReset: "passphrase reset",
97 PassphraseResetCreated: time.Now(),
101 for _, store := range profileStores {
102 err := store.saveProfile(profile)
104 t.Errorf("Error saving profile to %T: %s", store, err)
106 err = store.saveProfile(profile)
107 if err != ErrProfileAlreadyExists {
108 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
110 retrieved, err := store.getProfileByID(profile.ID)
112 t.Errorf("Error retrieving profile from %T: %s", store, err)
114 match, field, expectation, result := compareProfiles(profile, retrieved)
116 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
118 err = store.deleteProfile(profile.ID)
120 t.Errorf("Error removing profile from %T: %s", store, err)
122 retrieved, err = store.getProfileByID(profile.ID)
123 if err != ErrProfileNotFound {
124 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
126 err = store.deleteProfile(profile.ID)
127 if err != ErrProfileNotFound {
128 t.Errorf("Expected ErrProfileNotFound from %T, got %+v", store, err)
133 func TestProfileUpdates(t *testing.T) {
135 variations := 1 << 10
139 Passphrase: "passphrase",
144 LockedUntil: time.Now().Add(time.Hour),
145 PassphraseReset: "passphrase reset",
146 PassphraseResetCreated: time.Now(),
148 LastSeen: time.Now(),
150 for i := 0; i < variations; i++ {
151 var name, passphrase, salt, passphraseReset string
153 var lockedUntil, passphraseResetCreated, lastSeen time.Time
154 var passphraseScheme int
157 change := ProfileChange{}
158 expectation := profile
160 if i&profileChangeName != 0 {
161 name = fmt.Sprintf("name-%d", i)
163 expectation.Name = name
165 if i&profileChangePassphrase != 0 {
166 passphrase = fmt.Sprintf("passphrase-%d", i)
167 change.Passphrase = &passphrase
168 expectation.Passphrase = passphrase
170 if i&profileChangeIterations != 0 {
171 iterations = int64(i)
172 change.Iterations = &iterations
173 expectation.Iterations = iterations
175 if i&profileChangeSalt != 0 {
176 salt = fmt.Sprintf("salt-%d", i)
178 expectation.Salt = salt
180 if i&profileChangePassphraseScheme != 0 {
182 change.PassphraseScheme = &passphraseScheme
183 expectation.PassphraseScheme = passphraseScheme
185 if i&profileChangeCompromised != 0 {
186 compromised = i%2 != 0
187 change.Compromised = &compromised
188 expectation.Compromised = compromised
190 if i&profileChangeLockedUntil != 0 {
191 lockedUntil = time.Now()
192 change.LockedUntil = &lockedUntil
193 expectation.LockedUntil = lockedUntil
195 if i&profileChangePassphraseReset != 0 {
196 passphraseReset = fmt.Sprintf("passphraseReset-%d", i)
197 change.PassphraseReset = &passphraseReset
198 expectation.PassphraseReset = passphraseReset
200 if i&profileChangePassphraseResetCreated != 0 {
201 passphraseResetCreated = time.Now()
202 change.PassphraseResetCreated = &passphraseResetCreated
203 expectation.PassphraseResetCreated = passphraseResetCreated
205 if i&profileChangeLastSeen != 0 {
206 lastSeen = time.Now()
207 change.LastSeen = &lastSeen
208 expectation.LastSeen = lastSeen
210 result.ApplyChange(change)
211 match, field, expected, got := compareProfiles(expectation, result)
213 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
215 for _, store := range profileStores {
216 err := store.saveProfile(profile)
218 t.Errorf("Error saving profile in %T: %s", store, err)
220 err = store.updateProfile(profile.ID, change)
222 t.Errorf("Error updating profile in %T: %s", store, err)
224 retrieved, err := store.getProfileByID(profile.ID)
226 t.Errorf("Error getting profile from %T: %s", store, err)
228 match, field, expected, got = compareProfiles(expectation, retrieved)
230 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
232 err = store.deleteProfile(profile.ID)
234 t.Errorf("Error deleting profile from %T: %s", store, err)
236 err = store.updateProfile(profile.ID, change)
237 if err != ErrProfileNotFound {
238 t.Errorf("Expected ErrProfileNotFound, got %v from %T", err, store)
244 func TestProfilesUpdates(t *testing.T) {
255 change := BulkProfileChange{
258 for _, store := range profileStores {
259 err := store.saveProfile(profile1)
261 t.Errorf("Error saving profile in %T: %s", store, err)
263 err = store.saveProfile(profile2)
265 t.Errorf("Error saving profile in %T: %s", store, err)
267 err = store.saveProfile(profile3)
269 t.Errorf("Error saving profile in %T: %s", store, err)
271 err = store.updateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
273 t.Errorf("Error updating profile in %T: %s", store, err)
275 profile1.Compromised = truth
276 profile3.Compromised = truth
277 retrieved, err := store.getProfileByID(profile1.ID)
279 t.Errorf("Error getting profile from %T: %s", store, err)
281 match, field, expected, got := compareProfiles(profile1, retrieved)
283 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
285 retrieved, err = store.getProfileByID(profile2.ID)
287 t.Errorf("Error getting profile from %T: %s", store, err)
289 match, field, expected, got = compareProfiles(profile2, retrieved)
291 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
293 retrieved, err = store.getProfileByID(profile3.ID)
295 t.Errorf("Error getting profile from %T: %s", store, err)
297 match, field, expected, got = compareProfiles(profile3, retrieved)
299 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
304 func TestProfileStoreLoginSuccess(t *testing.T) {
309 ProfileID: uuid.NewID(),
310 Created: time.Now().Add(-1 * time.Hour),
311 LastUsed: time.Now().Add(-1 * time.Minute),
313 for _, store := range profileStores {
314 err := store.addLogin(login)
316 t.Errorf("Error adding login to %T: %s", store, err)
318 err = store.addLogin(login)
319 if err != ErrLoginAlreadyExists {
320 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
322 retrieved, err := store.listLogins(login.ProfileID, 10, 0)
324 t.Errorf("Error retrieving logins from %T: %s", store, err)
326 if len(retrieved) != 1 {
327 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
329 match, field, expectation, result := compareLogins(login, retrieved[0])
331 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
333 lastUsed := time.Now()
334 err = store.recordLoginUse(login.Type, login.Value, lastUsed)
336 t.Errorf("Error recording use of login to %T: %s", store, err)
338 login.LastUsed = lastUsed
339 retrieved, err = store.listLogins(login.ProfileID, 10, 0)
341 t.Errorf("Error retrieving logins from %T: %s", store, err)
343 if len(retrieved) != 1 {
344 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
346 match, field, expectation, result = compareLogins(login, retrieved[0])
348 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
350 err = store.removeLogin(login.Type, login.Value, login.ProfileID)
352 t.Errorf("Error removing login from %T: %s", store, err)
354 retrieved, err = store.listLogins(login.ProfileID, 10, 0)
355 if len(retrieved) != 0 {
356 t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved)
358 err = store.removeLogin(login.Type, login.Value, login.ProfileID)
359 if err != ErrLoginNotFound {
360 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
365 func TestProfileStoreLoginRetrieval(t *testing.T) {
370 Passphrase: "passphrase",
375 LockedUntil: time.Now().Add(time.Hour),
376 PassphraseReset: "passphrase reset",
377 PassphraseResetCreated: time.Now(),
379 LastSeen: time.Now(),
384 ProfileID: profile.ID,
385 Created: time.Now().Add(-1 * time.Hour),
386 LastUsed: time.Now().Add(-1 * time.Minute),
388 for _, store := range profileStores {
389 err := store.saveProfile(profile)
391 t.Errorf("Error saving profile in %T: %s", store, err)
393 err = store.addLogin(login)
395 t.Errorf("Error storing login in %T: %s", store, err)
397 retrieved, err := store.getProfileByLogin(login.Type, login.Value)
399 t.Errorf("Error retrieving profile by login from %T: %s", store, err)
401 match, field, expectation, result := compareProfiles(profile, retrieved)
403 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
408 func TestProfileChangeValidation(t *testing.T) {
410 passphraseScheme := 1
411 passphraseReset := "reset"
413 iterations := int64(100)
414 shortPassphrase := "a"
415 longPassphrase := "this passphrase is much too long for anyone to remember, and therefore should probably be discouraged by the software, don't you think?"
417 enteredName := "Paddy"
418 okPassphrase := "this is a decent passphrase"
420 lockedUntil := time.Now()
421 resetCreated := time.Now()
422 lastSeen := time.Now()
423 changes := map[*ProfileChange]error{
424 &ProfileChange{}: ErrEmptyChange,
425 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase,
426 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil,
427 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated,
428 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil,
429 &ProfileChange{Salt: &salt}: ErrMissingPassphrase,
430 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil,
431 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase,
432 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil,
433 &ProfileChange{Passphrase: &shortPassphrase}: ErrPassphraseTooShort,
434 &ProfileChange{Passphrase: &longPassphrase}: ErrPassphraseTooLong,
435 &ProfileChange{Passphrase: &okPassphrase}: nil,
436 &ProfileChange{Name: &emptyName}: nil,
437 &ProfileChange{Name: &enteredName}: nil,
438 &ProfileChange{Compromised: &compromised}: nil,
439 &ProfileChange{LockedUntil: &lockedUntil}: nil,
440 &ProfileChange{LastSeen: &lastSeen}: nil,
441 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset,
443 for change, expectedErr := range changes {
444 if err := change.Validate(); err != expectedErr {
445 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
450 func TestBulkProfileChangeValidation(t *testing.T) {
453 changes := map[*BulkProfileChange]error{
454 &BulkProfileChange{}: ErrEmptyChange,
455 &BulkProfileChange{Compromised: &compromised}: nil,
457 for change, expectedErr := range changes {
458 if err := change.Validate(); err != expectedErr {
459 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)