Test our GetClientHandler function, add isAuthError helper.
Add a helper that identifies whether the error passed to it is an authentication
error or is some other type of error. This is useful fo checking whether or not
an internal error occurred while authenticating users.
Update all instances where we call our authentication helper to make them use
the new error helper. All tests continue to pass.
Add a new test case for retrieving a client as an unauthenticated user. This
clears the client's secret from the response before sending it.
Update the GetClientHandler function to return the secret when the owner of the
client used Basic Auth in the request.
Add a new test case for retrieving a client as an authenticated user, both the
owner and a non-owner user. This makes sure the secret is divulged only in the
appropriate cases.
8 "code.secondbit.org/uuid.hg"
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 context := Context{profiles: store}
103 err := context.SaveProfile(profile)
105 t.Errorf("Error saving profile to %T: %s", store, err)
107 err = context.SaveProfile(profile)
108 if err != ErrProfileAlreadyExists {
109 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
111 retrieved, err := context.GetProfileByID(profile.ID)
113 t.Errorf("Error retrieving profile from %T: %s", store, err)
115 match, field, expectation, result := compareProfiles(profile, retrieved)
117 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
119 err = context.DeleteProfile(profile.ID)
121 t.Errorf("Error removing profile from %T: %s", store, err)
123 retrieved, err = context.GetProfileByID(profile.ID)
124 if err != ErrProfileNotFound {
125 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
127 err = context.DeleteProfile(profile.ID)
128 if err != ErrProfileNotFound {
129 t.Errorf("Expected ErrProfileNotFound from %T, got %+v", store, err)
134 func TestProfileUpdates(t *testing.T) {
136 variations := 1 << 10
140 Passphrase: "passphrase",
145 LockedUntil: time.Now().Add(time.Hour),
146 PassphraseReset: "passphrase reset",
147 PassphraseResetCreated: time.Now(),
149 LastSeen: time.Now(),
151 for i := 0; i < variations; i++ {
152 var name, passphrase, salt, passphraseReset string
154 var lockedUntil, passphraseResetCreated, lastSeen time.Time
155 var passphraseScheme int
158 change := ProfileChange{}
159 expectation := profile
161 if i&profileChangeName != 0 {
162 name = fmt.Sprintf("name-%d", i)
164 expectation.Name = name
166 if i&profileChangePassphrase != 0 {
167 passphrase = fmt.Sprintf("passphrase-%d", i)
168 change.Passphrase = &passphrase
169 expectation.Passphrase = passphrase
171 if i&profileChangeIterations != 0 {
173 change.Iterations = &iterations
174 expectation.Iterations = iterations
176 if i&profileChangeSalt != 0 {
177 salt = fmt.Sprintf("salt-%d", i)
179 expectation.Salt = salt
181 if i&profileChangePassphraseScheme != 0 {
183 change.PassphraseScheme = &passphraseScheme
184 expectation.PassphraseScheme = passphraseScheme
186 if i&profileChangeCompromised != 0 {
187 compromised = i%2 != 0
188 change.Compromised = &compromised
189 expectation.Compromised = compromised
191 if i&profileChangeLockedUntil != 0 {
192 lockedUntil = time.Now()
193 change.LockedUntil = &lockedUntil
194 expectation.LockedUntil = lockedUntil
196 if i&profileChangePassphraseReset != 0 {
197 passphraseReset = fmt.Sprintf("passphraseReset-%d", i)
198 change.PassphraseReset = &passphraseReset
199 expectation.PassphraseReset = passphraseReset
201 if i&profileChangePassphraseResetCreated != 0 {
202 passphraseResetCreated = time.Now()
203 change.PassphraseResetCreated = &passphraseResetCreated
204 expectation.PassphraseResetCreated = passphraseResetCreated
206 if i&profileChangeLastSeen != 0 {
207 lastSeen = time.Now()
208 change.LastSeen = &lastSeen
209 expectation.LastSeen = lastSeen
211 result.ApplyChange(change)
212 match, field, expected, got := compareProfiles(expectation, result)
214 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
216 for _, store := range profileStores {
217 context := Context{profiles: store}
218 err := context.SaveProfile(profile)
220 t.Errorf("Error saving profile in %T: %s", store, err)
222 err = context.UpdateProfile(profile.ID, change)
224 t.Errorf("Error updating profile in %T: %s", store, err)
226 retrieved, err := context.GetProfileByID(profile.ID)
228 t.Errorf("Error getting profile from %T: %s", store, err)
230 match, field, expected, got = compareProfiles(expectation, retrieved)
232 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
234 err = context.DeleteProfile(profile.ID)
236 t.Errorf("Error deleting profile from %T: %s", store, err)
238 err = context.UpdateProfile(profile.ID, change)
239 if err != ErrProfileNotFound {
240 t.Errorf("Expected ErrProfileNotFound, got %v from %T", err, store)
246 func TestProfilesUpdates(t *testing.T) {
257 change := BulkProfileChange{
260 for _, store := range profileStores {
261 context := Context{profiles: store}
262 err := context.SaveProfile(profile1)
264 t.Errorf("Error saving profile in %T: %s", store, err)
266 err = context.SaveProfile(profile2)
268 t.Errorf("Error saving profile in %T: %s", store, err)
270 err = context.SaveProfile(profile3)
272 t.Errorf("Error saving profile in %T: %s", store, err)
274 err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
276 t.Errorf("Error updating profile in %T: %s", store, err)
278 profile1.Compromised = truth
279 profile3.Compromised = truth
280 retrieved, err := context.GetProfileByID(profile1.ID)
282 t.Errorf("Error getting profile from %T: %s", store, err)
284 match, field, expected, got := compareProfiles(profile1, retrieved)
286 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
288 retrieved, err = context.GetProfileByID(profile2.ID)
290 t.Errorf("Error getting profile from %T: %s", store, err)
292 match, field, expected, got = compareProfiles(profile2, retrieved)
294 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
296 retrieved, err = context.GetProfileByID(profile3.ID)
298 t.Errorf("Error getting profile from %T: %s", store, err)
300 match, field, expected, got = compareProfiles(profile3, retrieved)
302 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
307 func TestProfileStoreLoginSuccess(t *testing.T) {
312 ProfileID: uuid.NewID(),
313 Created: time.Now().Add(-1 * time.Hour),
314 LastUsed: time.Now().Add(-1 * time.Minute),
316 for _, store := range profileStores {
317 context := Context{profiles: store}
318 err := context.AddLogin(login)
320 t.Errorf("Error adding login to %T: %s", store, err)
322 err = context.AddLogin(login)
323 if err != ErrLoginAlreadyExists {
324 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
326 retrieved, err := context.ListLogins(login.ProfileID, 10, 0)
328 t.Errorf("Error retrieving logins from %T: %s", store, err)
330 if len(retrieved) != 1 {
331 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
333 match, field, expectation, result := compareLogins(login, retrieved[0])
335 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
337 lastUsed := time.Now()
338 err = context.RecordLoginUse(login.Value, lastUsed)
340 t.Errorf("Error recording use of login to %T: %s", store, err)
342 login.LastUsed = lastUsed
343 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
345 t.Errorf("Error retrieving logins from %T: %s", store, err)
347 if len(retrieved) != 1 {
348 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
350 match, field, expectation, result = compareLogins(login, retrieved[0])
352 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
354 err = context.RemoveLogin(login.Value, login.ProfileID)
356 t.Errorf("Error removing login from %T: %s", store, err)
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)
362 err = context.RemoveLogin(login.Value, login.ProfileID)
363 if err != ErrLoginNotFound {
364 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
369 func TestProfileStoreLoginRetrieval(t *testing.T) {
374 Passphrase: "passphrase",
379 LockedUntil: time.Now().Add(time.Hour),
380 PassphraseReset: "passphrase reset",
381 PassphraseResetCreated: time.Now(),
383 LastSeen: time.Now(),
388 ProfileID: profile.ID,
389 Created: time.Now().Add(-1 * time.Hour),
390 LastUsed: time.Now().Add(-1 * time.Minute),
392 for _, store := range profileStores {
393 context := Context{profiles: store}
394 err := context.SaveProfile(profile)
396 t.Errorf("Error saving profile in %T: %s", store, err)
398 err = context.AddLogin(login)
400 t.Errorf("Error storing login in %T: %s", store, err)
402 retrieved, err := context.GetProfileByLogin(login.Value)
404 t.Errorf("Error retrieving profile by login from %T: %s", store, err)
406 match, field, expectation, result := compareProfiles(profile, retrieved)
408 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
413 func TestProfileChangeValidation(t *testing.T) {
415 passphraseScheme := 1
416 passphraseReset := "reset"
419 shortPassphrase := "a"
420 longPassphrase := "this passphrase is much too long for anyone to remember, and therefore should probably be discouraged by the software, don't you think?"
422 enteredName := "Paddy"
423 okPassphrase := "this is a decent passphrase"
425 lockedUntil := time.Now()
426 resetCreated := time.Now()
427 lastSeen := time.Now()
428 changes := map[*ProfileChange]error{
429 &ProfileChange{}: ErrEmptyChange,
430 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase,
431 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil,
432 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated,
433 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil,
434 &ProfileChange{Salt: &salt}: ErrMissingPassphrase,
435 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil,
436 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase,
437 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil,
438 &ProfileChange{Passphrase: &shortPassphrase}: ErrPassphraseTooShort,
439 &ProfileChange{Passphrase: &longPassphrase}: ErrPassphraseTooLong,
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,
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)
455 func TestBulkProfileChangeValidation(t *testing.T) {
458 changes := map[*BulkProfileChange]error{
459 &BulkProfileChange{}: ErrEmptyChange,
460 &BulkProfileChange{Compromised: &compromised}: nil,
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)
469 // BUG(paddy): We need to test the validateNewProfileRequest helper.
470 // BUG(paddy): We need to test the CreateProfileHandler.