auth
auth/profile_test.go
Add support for bulk changes and for logins. Logins now get stored, listed, removed, and updated. You can select a profile by the login associated with it. Also added support for bulk changing profiles, because it may be necesary to set many profiles to compromised at the same time, and there's no sense in requiring a statement per profile.
| paddy@38 | 1 package auth |
| paddy@38 | 2 |
| paddy@38 | 3 import ( |
| paddy@38 | 4 "fmt" |
| paddy@38 | 5 "testing" |
| paddy@38 | 6 "time" |
| paddy@38 | 7 |
| paddy@38 | 8 "secondbit.org/uuid" |
| paddy@38 | 9 ) |
| paddy@38 | 10 |
| paddy@38 | 11 const ( |
| paddy@38 | 12 profileChangeName = 1 << iota |
| paddy@38 | 13 profileChangePassphrase |
| paddy@38 | 14 profileChangeIterations |
| paddy@38 | 15 profileChangeSalt |
| paddy@38 | 16 profileChangePassphraseScheme |
| paddy@38 | 17 profileChangeCompromised |
| paddy@38 | 18 profileChangeLockedUntil |
| paddy@38 | 19 profileChangePassphraseReset |
| paddy@38 | 20 profileChangePassphraseResetCreated |
| paddy@38 | 21 profileChangeLastSeen |
| paddy@38 | 22 ) |
| paddy@38 | 23 |
| paddy@38 | 24 var profileStores = []ProfileStore{NewMemstore()} |
| paddy@38 | 25 |
| paddy@38 | 26 func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) { |
| paddy@38 | 27 if !profile1.ID.Equal(profile2.ID) { |
| paddy@38 | 28 return false, "ID", profile1.ID, profile2.ID |
| paddy@38 | 29 } |
| paddy@38 | 30 if profile1.Name != profile2.Name { |
| paddy@38 | 31 return false, "name", profile1.Name, profile2.Name |
| paddy@38 | 32 } |
| paddy@38 | 33 if profile1.Passphrase != profile2.Passphrase { |
| paddy@38 | 34 return false, "passphrase", profile1.Passphrase, profile2.Passphrase |
| paddy@38 | 35 } |
| paddy@38 | 36 if profile1.Iterations != profile2.Iterations { |
| paddy@38 | 37 return false, "iterations", profile1.Iterations, profile2.Iterations |
| paddy@38 | 38 } |
| paddy@38 | 39 if profile1.Salt != profile2.Salt { |
| paddy@38 | 40 return false, "salt", profile1.Salt, profile2.Salt |
| paddy@38 | 41 } |
| paddy@38 | 42 if profile1.PassphraseScheme != profile2.PassphraseScheme { |
| paddy@38 | 43 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme |
| paddy@38 | 44 } |
| paddy@38 | 45 if profile1.Compromised != profile2.Compromised { |
| paddy@38 | 46 return false, "compromised", profile1.Compromised, profile2.Compromised |
| paddy@38 | 47 } |
| paddy@38 | 48 if !profile1.LockedUntil.Equal(profile2.LockedUntil) { |
| paddy@38 | 49 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil |
| paddy@38 | 50 } |
| paddy@38 | 51 if profile1.PassphraseReset != profile2.PassphraseReset { |
| paddy@38 | 52 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset |
| paddy@38 | 53 } |
| paddy@38 | 54 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) { |
| paddy@38 | 55 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated |
| paddy@38 | 56 } |
| paddy@38 | 57 if !profile1.Created.Equal(profile2.Created) { |
| paddy@38 | 58 return false, "created", profile1.Created, profile2.Created |
| paddy@38 | 59 } |
| paddy@38 | 60 if !profile1.LastSeen.Equal(profile2.LastSeen) { |
| paddy@38 | 61 return false, "last seen", profile1.LastSeen, profile2.LastSeen |
| paddy@38 | 62 } |
| paddy@38 | 63 return true, "", nil, nil |
| paddy@38 | 64 } |
| paddy@38 | 65 |
| paddy@38 | 66 func TestProfileStoreSuccess(t *testing.T) { |
| paddy@38 | 67 t.Parallel() |
| paddy@38 | 68 profile := Profile{ |
| paddy@38 | 69 ID: uuid.NewID(), |
| paddy@38 | 70 Name: "name", |
| paddy@38 | 71 Passphrase: "passphrase", |
| paddy@38 | 72 Iterations: 10000, |
| paddy@38 | 73 Salt: "salt", |
| paddy@38 | 74 PassphraseScheme: 1, |
| paddy@38 | 75 Compromised: false, |
| paddy@38 | 76 LockedUntil: time.Now().Add(time.Hour), |
| paddy@38 | 77 PassphraseReset: "passphrase reset", |
| paddy@38 | 78 PassphraseResetCreated: time.Now(), |
| paddy@38 | 79 Created: time.Now(), |
| paddy@38 | 80 LastSeen: time.Now(), |
| paddy@38 | 81 } |
| paddy@38 | 82 for _, store := range profileStores { |
| paddy@38 | 83 err := store.SaveProfile(profile) |
| paddy@38 | 84 if err != nil { |
| paddy@38 | 85 t.Errorf("Error saving profile to %T: %s", store, err) |
| paddy@38 | 86 } |
| paddy@38 | 87 err = store.SaveProfile(profile) |
| paddy@38 | 88 if err != ErrProfileAlreadyExists { |
| paddy@38 | 89 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err) |
| paddy@38 | 90 } |
| paddy@38 | 91 retrieved, err := store.GetProfileByID(profile.ID) |
| paddy@38 | 92 if err != nil { |
| paddy@38 | 93 t.Errorf("Error retrieving profile from %T: %s", store, err) |
| paddy@38 | 94 } |
| paddy@38 | 95 match, field, expectation, result := compareProfiles(profile, retrieved) |
| paddy@38 | 96 if !match { |
| paddy@38 | 97 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@38 | 98 } |
| paddy@38 | 99 err = store.DeleteProfile(profile.ID) |
| paddy@38 | 100 if err != nil { |
| paddy@38 | 101 t.Errorf("Error removing profile from %T: %s", store, err) |
| paddy@38 | 102 } |
| paddy@38 | 103 retrieved, err = store.GetProfileByID(profile.ID) |
| paddy@38 | 104 if err != ErrProfileNotFound { |
| paddy@38 | 105 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err) |
| paddy@38 | 106 } |
| paddy@38 | 107 err = store.DeleteProfile(profile.ID) |
| paddy@38 | 108 if err != ErrProfileNotFound { |
| paddy@38 | 109 t.Errorf("Expected ErrProfileNotFound from %T, got %+v", store, err) |
| paddy@38 | 110 } |
| paddy@38 | 111 } |
| paddy@38 | 112 } |
| paddy@38 | 113 |
| paddy@38 | 114 func TestProfileUpdates(t *testing.T) { |
| paddy@38 | 115 t.Parallel() |
| paddy@38 | 116 variations := 1 << 10 |
| paddy@38 | 117 profile := Profile{ |
| paddy@38 | 118 ID: uuid.NewID(), |
| paddy@38 | 119 Name: "name", |
| paddy@38 | 120 Passphrase: "passphrase", |
| paddy@38 | 121 Iterations: 10000, |
| paddy@38 | 122 Salt: "salt", |
| paddy@38 | 123 PassphraseScheme: 1, |
| paddy@38 | 124 Compromised: false, |
| paddy@38 | 125 LockedUntil: time.Now().Add(time.Hour), |
| paddy@38 | 126 PassphraseReset: "passphrase reset", |
| paddy@38 | 127 PassphraseResetCreated: time.Now(), |
| paddy@38 | 128 Created: time.Now(), |
| paddy@38 | 129 LastSeen: time.Now(), |
| paddy@38 | 130 } |
| paddy@38 | 131 for i := 0; i < variations; i++ { |
| paddy@38 | 132 var name, passphrase, salt, passphraseReset string |
| paddy@38 | 133 var iterations int64 |
| paddy@38 | 134 var lockedUntil, passphraseResetCreated, lastSeen time.Time |
| paddy@38 | 135 var passphraseScheme int |
| paddy@38 | 136 var compromised bool |
| paddy@38 | 137 |
| paddy@38 | 138 change := ProfileChange{} |
| paddy@38 | 139 expectation := profile |
| paddy@38 | 140 result := profile |
| paddy@38 | 141 if i&profileChangeName != 0 { |
| paddy@38 | 142 name = fmt.Sprintf("name-%d", i) |
| paddy@38 | 143 change.Name = &name |
| paddy@38 | 144 expectation.Name = name |
| paddy@38 | 145 } |
| paddy@38 | 146 if i&profileChangePassphrase != 0 { |
| paddy@38 | 147 passphrase = fmt.Sprintf("passphrase-%d", i) |
| paddy@38 | 148 change.Passphrase = &passphrase |
| paddy@38 | 149 expectation.Passphrase = passphrase |
| paddy@38 | 150 } |
| paddy@38 | 151 if i&profileChangeIterations != 0 { |
| paddy@38 | 152 iterations = int64(i) |
| paddy@38 | 153 change.Iterations = &iterations |
| paddy@38 | 154 expectation.Iterations = iterations |
| paddy@38 | 155 } |
| paddy@38 | 156 if i&profileChangeSalt != 0 { |
| paddy@38 | 157 salt = fmt.Sprintf("salt-%d", i) |
| paddy@38 | 158 change.Salt = &salt |
| paddy@38 | 159 expectation.Salt = salt |
| paddy@38 | 160 } |
| paddy@38 | 161 if i&profileChangePassphraseScheme != 0 { |
| paddy@38 | 162 passphraseScheme = i |
| paddy@38 | 163 change.PassphraseScheme = &passphraseScheme |
| paddy@38 | 164 expectation.PassphraseScheme = passphraseScheme |
| paddy@38 | 165 } |
| paddy@38 | 166 if i&profileChangeCompromised != 0 { |
| paddy@38 | 167 compromised = i%2 != 0 |
| paddy@38 | 168 change.Compromised = &compromised |
| paddy@38 | 169 expectation.Compromised = compromised |
| paddy@38 | 170 } |
| paddy@38 | 171 if i&profileChangeLockedUntil != 0 { |
| paddy@38 | 172 lockedUntil = time.Now() |
| paddy@38 | 173 change.LockedUntil = &lockedUntil |
| paddy@38 | 174 expectation.LockedUntil = lockedUntil |
| paddy@38 | 175 } |
| paddy@38 | 176 if i&profileChangePassphraseReset != 0 { |
| paddy@38 | 177 passphraseReset = fmt.Sprintf("passphraseReset-%d", i) |
| paddy@38 | 178 change.PassphraseReset = &passphraseReset |
| paddy@38 | 179 expectation.PassphraseReset = passphraseReset |
| paddy@38 | 180 } |
| paddy@38 | 181 if i&profileChangePassphraseResetCreated != 0 { |
| paddy@38 | 182 passphraseResetCreated = time.Now() |
| paddy@38 | 183 change.PassphraseResetCreated = &passphraseResetCreated |
| paddy@38 | 184 expectation.PassphraseResetCreated = passphraseResetCreated |
| paddy@38 | 185 } |
| paddy@38 | 186 if i&profileChangeLastSeen != 0 { |
| paddy@38 | 187 lastSeen = time.Now() |
| paddy@38 | 188 change.LastSeen = &lastSeen |
| paddy@38 | 189 expectation.LastSeen = lastSeen |
| paddy@38 | 190 } |
| paddy@38 | 191 result.ApplyChange(change) |
| paddy@38 | 192 match, field, expected, got := compareProfiles(expectation, result) |
| paddy@38 | 193 if !match { |
| paddy@38 | 194 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got) |
| paddy@38 | 195 } |
| paddy@38 | 196 for _, store := range profileStores { |
| paddy@38 | 197 err := store.SaveProfile(profile) |
| paddy@38 | 198 if err != nil { |
| paddy@38 | 199 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@38 | 200 } |
| paddy@38 | 201 err = store.UpdateProfile(profile.ID, change) |
| paddy@38 | 202 if err != nil { |
| paddy@38 | 203 t.Errorf("Error updating profile in %T: %s", store, err) |
| paddy@38 | 204 } |
| paddy@38 | 205 retrieved, err := store.GetProfileByID(profile.ID) |
| paddy@38 | 206 if err != nil { |
| paddy@38 | 207 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@38 | 208 } |
| paddy@38 | 209 match, field, expected, got = compareProfiles(expectation, retrieved) |
| paddy@38 | 210 if !match { |
| paddy@38 | 211 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@38 | 212 } |
| paddy@38 | 213 err = store.DeleteProfile(profile.ID) |
| paddy@38 | 214 if err != nil { |
| paddy@38 | 215 t.Errorf("Error deleting profile from %T: %s", store, err) |
| paddy@38 | 216 } |
| paddy@38 | 217 err = store.UpdateProfile(profile.ID, change) |
| paddy@38 | 218 if err != ErrProfileNotFound { |
| paddy@38 | 219 t.Errorf("Expected ErrProfileNotFound, got %v from %T", err, store) |
| paddy@38 | 220 } |
| paddy@38 | 221 } |
| paddy@38 | 222 } |
| paddy@38 | 223 } |