auth
auth/profile_test.go
Stub out sessions. Stop using the Login type when getting profile by Login, removing Logins, or recording Login use. The Login value has to be unique, anyways, and we don't actually know the Login type when getting a profile by Login. That's sort of the point. Create the concept of Sessions and a sessionStore type to manage our authentication sessions with the server. As per OWASP, we're basically just going to use a transparent, SHA256-generated random string as an ID, and store it client-side and server-side and just pass it back and forth. Add the ProfileID to the Grant type, because we need to remember who granted access. That's sort of important. Set a defaultGrantExpiration constant to an hour, so we have that one constant when creating new Grants. Create a helper that pulls the session ID out of an auth cookie, checks it against the sessionStore, and returns the Session if it's valid. Create a helper that pulls the username and password out of a basic auth header. Create a helper that authenticates a user's login and passphrase, checking them against the profileStore securely. Stub out how the cookie checking is going to work for getting grant approval. Fix the stored Grant RedirectURI to be the passed in redirect URI, not the RedirectURI that we ultimately redirect to. This is in accordance with the spec. Store the profile ID from our session in the created Grant. Stub out a GetTokenHandler that will allow users to exchange a Grant for a Token. Set a constant for the current passphrase scheme, which we will increment for each revision to the passphrase scheme, for backwards compatibility. Change the Profile iterations property to an int, not an int64, to match the code.secondbit.org/pass library (which is matching the PBKDF2 library).
| 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@45 | 8 "code.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@57 | 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@46 | 66 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) { |
| paddy@46 | 67 if login1.Type != login2.Type { |
| paddy@46 | 68 return false, "Type", login1.Type, login2.Type |
| paddy@46 | 69 } |
| paddy@46 | 70 if login1.Value != login2.Value { |
| paddy@46 | 71 return false, "Value", login1.Value, login2.Value |
| paddy@46 | 72 } |
| paddy@46 | 73 if !login1.ProfileID.Equal(login2.ProfileID) { |
| paddy@46 | 74 return false, "ProfileID", login1.ProfileID, login2.ProfileID |
| paddy@46 | 75 } |
| paddy@46 | 76 if !login1.Created.Equal(login2.Created) { |
| paddy@46 | 77 return false, "Created", login1.Created, login2.Created |
| paddy@46 | 78 } |
| paddy@46 | 79 if !login1.LastUsed.Equal(login2.LastUsed) { |
| paddy@46 | 80 return false, "LastUsed", login1.LastUsed, login2.LastUsed |
| paddy@46 | 81 } |
| paddy@46 | 82 return true, "", nil, nil |
| paddy@46 | 83 } |
| paddy@46 | 84 |
| paddy@38 | 85 func TestProfileStoreSuccess(t *testing.T) { |
| paddy@38 | 86 t.Parallel() |
| paddy@38 | 87 profile := Profile{ |
| paddy@38 | 88 ID: uuid.NewID(), |
| paddy@38 | 89 Name: "name", |
| paddy@38 | 90 Passphrase: "passphrase", |
| paddy@38 | 91 Iterations: 10000, |
| paddy@38 | 92 Salt: "salt", |
| paddy@38 | 93 PassphraseScheme: 1, |
| paddy@38 | 94 Compromised: false, |
| paddy@38 | 95 LockedUntil: time.Now().Add(time.Hour), |
| paddy@38 | 96 PassphraseReset: "passphrase reset", |
| paddy@38 | 97 PassphraseResetCreated: time.Now(), |
| paddy@38 | 98 Created: time.Now(), |
| paddy@38 | 99 LastSeen: time.Now(), |
| paddy@38 | 100 } |
| paddy@38 | 101 for _, store := range profileStores { |
| paddy@57 | 102 err := store.saveProfile(profile) |
| paddy@38 | 103 if err != nil { |
| paddy@38 | 104 t.Errorf("Error saving profile to %T: %s", store, err) |
| paddy@38 | 105 } |
| paddy@57 | 106 err = store.saveProfile(profile) |
| paddy@38 | 107 if err != ErrProfileAlreadyExists { |
| paddy@38 | 108 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err) |
| paddy@38 | 109 } |
| paddy@57 | 110 retrieved, err := store.getProfileByID(profile.ID) |
| paddy@38 | 111 if err != nil { |
| paddy@38 | 112 t.Errorf("Error retrieving profile from %T: %s", store, err) |
| paddy@38 | 113 } |
| paddy@38 | 114 match, field, expectation, result := compareProfiles(profile, retrieved) |
| paddy@38 | 115 if !match { |
| paddy@38 | 116 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@38 | 117 } |
| paddy@57 | 118 err = store.deleteProfile(profile.ID) |
| paddy@38 | 119 if err != nil { |
| paddy@38 | 120 t.Errorf("Error removing profile from %T: %s", store, err) |
| paddy@38 | 121 } |
| paddy@57 | 122 retrieved, err = store.getProfileByID(profile.ID) |
| paddy@38 | 123 if err != ErrProfileNotFound { |
| paddy@38 | 124 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err) |
| paddy@38 | 125 } |
| paddy@57 | 126 err = store.deleteProfile(profile.ID) |
| paddy@38 | 127 if err != ErrProfileNotFound { |
| paddy@38 | 128 t.Errorf("Expected ErrProfileNotFound from %T, got %+v", store, err) |
| paddy@38 | 129 } |
| paddy@38 | 130 } |
| paddy@38 | 131 } |
| paddy@38 | 132 |
| paddy@38 | 133 func TestProfileUpdates(t *testing.T) { |
| paddy@38 | 134 t.Parallel() |
| paddy@38 | 135 variations := 1 << 10 |
| paddy@38 | 136 profile := Profile{ |
| paddy@38 | 137 ID: uuid.NewID(), |
| paddy@38 | 138 Name: "name", |
| paddy@38 | 139 Passphrase: "passphrase", |
| paddy@38 | 140 Iterations: 10000, |
| paddy@38 | 141 Salt: "salt", |
| paddy@38 | 142 PassphraseScheme: 1, |
| paddy@38 | 143 Compromised: false, |
| paddy@38 | 144 LockedUntil: time.Now().Add(time.Hour), |
| paddy@38 | 145 PassphraseReset: "passphrase reset", |
| paddy@38 | 146 PassphraseResetCreated: time.Now(), |
| paddy@38 | 147 Created: time.Now(), |
| paddy@38 | 148 LastSeen: time.Now(), |
| paddy@38 | 149 } |
| paddy@38 | 150 for i := 0; i < variations; i++ { |
| paddy@38 | 151 var name, passphrase, salt, passphraseReset string |
| paddy@69 | 152 var iterations int |
| paddy@38 | 153 var lockedUntil, passphraseResetCreated, lastSeen time.Time |
| paddy@38 | 154 var passphraseScheme int |
| paddy@38 | 155 var compromised bool |
| paddy@38 | 156 |
| paddy@38 | 157 change := ProfileChange{} |
| paddy@38 | 158 expectation := profile |
| paddy@38 | 159 result := profile |
| paddy@38 | 160 if i&profileChangeName != 0 { |
| paddy@38 | 161 name = fmt.Sprintf("name-%d", i) |
| paddy@38 | 162 change.Name = &name |
| paddy@38 | 163 expectation.Name = name |
| paddy@38 | 164 } |
| paddy@38 | 165 if i&profileChangePassphrase != 0 { |
| paddy@38 | 166 passphrase = fmt.Sprintf("passphrase-%d", i) |
| paddy@38 | 167 change.Passphrase = &passphrase |
| paddy@38 | 168 expectation.Passphrase = passphrase |
| paddy@38 | 169 } |
| paddy@38 | 170 if i&profileChangeIterations != 0 { |
| paddy@69 | 171 iterations = i |
| paddy@38 | 172 change.Iterations = &iterations |
| paddy@38 | 173 expectation.Iterations = iterations |
| paddy@38 | 174 } |
| paddy@38 | 175 if i&profileChangeSalt != 0 { |
| paddy@38 | 176 salt = fmt.Sprintf("salt-%d", i) |
| paddy@38 | 177 change.Salt = &salt |
| paddy@38 | 178 expectation.Salt = salt |
| paddy@38 | 179 } |
| paddy@38 | 180 if i&profileChangePassphraseScheme != 0 { |
| paddy@38 | 181 passphraseScheme = i |
| paddy@38 | 182 change.PassphraseScheme = &passphraseScheme |
| paddy@38 | 183 expectation.PassphraseScheme = passphraseScheme |
| paddy@38 | 184 } |
| paddy@38 | 185 if i&profileChangeCompromised != 0 { |
| paddy@38 | 186 compromised = i%2 != 0 |
| paddy@38 | 187 change.Compromised = &compromised |
| paddy@38 | 188 expectation.Compromised = compromised |
| paddy@38 | 189 } |
| paddy@38 | 190 if i&profileChangeLockedUntil != 0 { |
| paddy@38 | 191 lockedUntil = time.Now() |
| paddy@38 | 192 change.LockedUntil = &lockedUntil |
| paddy@38 | 193 expectation.LockedUntil = lockedUntil |
| paddy@38 | 194 } |
| paddy@38 | 195 if i&profileChangePassphraseReset != 0 { |
| paddy@38 | 196 passphraseReset = fmt.Sprintf("passphraseReset-%d", i) |
| paddy@38 | 197 change.PassphraseReset = &passphraseReset |
| paddy@38 | 198 expectation.PassphraseReset = passphraseReset |
| paddy@38 | 199 } |
| paddy@38 | 200 if i&profileChangePassphraseResetCreated != 0 { |
| paddy@38 | 201 passphraseResetCreated = time.Now() |
| paddy@38 | 202 change.PassphraseResetCreated = &passphraseResetCreated |
| paddy@38 | 203 expectation.PassphraseResetCreated = passphraseResetCreated |
| paddy@38 | 204 } |
| paddy@38 | 205 if i&profileChangeLastSeen != 0 { |
| paddy@38 | 206 lastSeen = time.Now() |
| paddy@38 | 207 change.LastSeen = &lastSeen |
| paddy@38 | 208 expectation.LastSeen = lastSeen |
| paddy@38 | 209 } |
| paddy@38 | 210 result.ApplyChange(change) |
| paddy@38 | 211 match, field, expected, got := compareProfiles(expectation, result) |
| paddy@38 | 212 if !match { |
| paddy@38 | 213 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got) |
| paddy@38 | 214 } |
| paddy@38 | 215 for _, store := range profileStores { |
| paddy@57 | 216 err := store.saveProfile(profile) |
| paddy@38 | 217 if err != nil { |
| paddy@38 | 218 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@38 | 219 } |
| paddy@57 | 220 err = store.updateProfile(profile.ID, change) |
| paddy@38 | 221 if err != nil { |
| paddy@38 | 222 t.Errorf("Error updating profile in %T: %s", store, err) |
| paddy@38 | 223 } |
| paddy@57 | 224 retrieved, err := store.getProfileByID(profile.ID) |
| paddy@38 | 225 if err != nil { |
| paddy@38 | 226 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@38 | 227 } |
| paddy@38 | 228 match, field, expected, got = compareProfiles(expectation, retrieved) |
| paddy@38 | 229 if !match { |
| paddy@38 | 230 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@38 | 231 } |
| paddy@57 | 232 err = store.deleteProfile(profile.ID) |
| paddy@38 | 233 if err != nil { |
| paddy@38 | 234 t.Errorf("Error deleting profile from %T: %s", store, err) |
| paddy@38 | 235 } |
| paddy@57 | 236 err = store.updateProfile(profile.ID, change) |
| paddy@38 | 237 if err != ErrProfileNotFound { |
| paddy@38 | 238 t.Errorf("Expected ErrProfileNotFound, got %v from %T", err, store) |
| paddy@38 | 239 } |
| paddy@38 | 240 } |
| paddy@38 | 241 } |
| paddy@38 | 242 } |
| paddy@45 | 243 |
| paddy@45 | 244 func TestProfilesUpdates(t *testing.T) { |
| paddy@45 | 245 profile1 := Profile{ |
| paddy@45 | 246 ID: uuid.NewID(), |
| paddy@45 | 247 } |
| paddy@45 | 248 profile2 := Profile{ |
| paddy@45 | 249 ID: uuid.NewID(), |
| paddy@45 | 250 } |
| paddy@45 | 251 profile3 := Profile{ |
| paddy@45 | 252 ID: uuid.NewID(), |
| paddy@45 | 253 } |
| paddy@45 | 254 truth := true |
| paddy@45 | 255 change := BulkProfileChange{ |
| paddy@45 | 256 Compromised: &truth, |
| paddy@45 | 257 } |
| paddy@45 | 258 for _, store := range profileStores { |
| paddy@57 | 259 err := store.saveProfile(profile1) |
| paddy@45 | 260 if err != nil { |
| paddy@45 | 261 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 262 } |
| paddy@57 | 263 err = store.saveProfile(profile2) |
| paddy@45 | 264 if err != nil { |
| paddy@45 | 265 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 266 } |
| paddy@57 | 267 err = store.saveProfile(profile3) |
| paddy@45 | 268 if err != nil { |
| paddy@45 | 269 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 270 } |
| paddy@57 | 271 err = store.updateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change) |
| paddy@45 | 272 if err != nil { |
| paddy@45 | 273 t.Errorf("Error updating profile in %T: %s", store, err) |
| paddy@45 | 274 } |
| paddy@45 | 275 profile1.Compromised = truth |
| paddy@45 | 276 profile3.Compromised = truth |
| paddy@57 | 277 retrieved, err := store.getProfileByID(profile1.ID) |
| paddy@45 | 278 if err != nil { |
| paddy@45 | 279 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 280 } |
| paddy@45 | 281 match, field, expected, got := compareProfiles(profile1, retrieved) |
| paddy@45 | 282 if !match { |
| paddy@45 | 283 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 284 } |
| paddy@57 | 285 retrieved, err = store.getProfileByID(profile2.ID) |
| paddy@45 | 286 if err != nil { |
| paddy@45 | 287 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 288 } |
| paddy@45 | 289 match, field, expected, got = compareProfiles(profile2, retrieved) |
| paddy@45 | 290 if !match { |
| paddy@45 | 291 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 292 } |
| paddy@57 | 293 retrieved, err = store.getProfileByID(profile3.ID) |
| paddy@45 | 294 if err != nil { |
| paddy@45 | 295 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 296 } |
| paddy@45 | 297 match, field, expected, got = compareProfiles(profile3, retrieved) |
| paddy@45 | 298 if !match { |
| paddy@45 | 299 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 300 } |
| paddy@45 | 301 } |
| paddy@45 | 302 } |
| paddy@46 | 303 |
| paddy@46 | 304 func TestProfileStoreLoginSuccess(t *testing.T) { |
| paddy@46 | 305 t.Parallel() |
| paddy@46 | 306 login := Login{ |
| paddy@46 | 307 Type: "type", |
| paddy@46 | 308 Value: "value", |
| paddy@46 | 309 ProfileID: uuid.NewID(), |
| paddy@46 | 310 Created: time.Now().Add(-1 * time.Hour), |
| paddy@46 | 311 LastUsed: time.Now().Add(-1 * time.Minute), |
| paddy@46 | 312 } |
| paddy@46 | 313 for _, store := range profileStores { |
| paddy@57 | 314 err := store.addLogin(login) |
| paddy@46 | 315 if err != nil { |
| paddy@46 | 316 t.Errorf("Error adding login to %T: %s", store, err) |
| paddy@46 | 317 } |
| paddy@57 | 318 err = store.addLogin(login) |
| paddy@46 | 319 if err != ErrLoginAlreadyExists { |
| paddy@46 | 320 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err) |
| paddy@46 | 321 } |
| paddy@57 | 322 retrieved, err := store.listLogins(login.ProfileID, 10, 0) |
| paddy@46 | 323 if err != nil { |
| paddy@46 | 324 t.Errorf("Error retrieving logins from %T: %s", store, err) |
| paddy@46 | 325 } |
| paddy@46 | 326 if len(retrieved) != 1 { |
| paddy@46 | 327 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved)) |
| paddy@46 | 328 } |
| paddy@46 | 329 match, field, expectation, result := compareLogins(login, retrieved[0]) |
| paddy@46 | 330 if !match { |
| paddy@46 | 331 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@46 | 332 } |
| paddy@46 | 333 lastUsed := time.Now() |
| paddy@69 | 334 err = store.recordLoginUse(login.Value, lastUsed) |
| paddy@46 | 335 if err != nil { |
| paddy@46 | 336 t.Errorf("Error recording use of login to %T: %s", store, err) |
| paddy@46 | 337 } |
| paddy@46 | 338 login.LastUsed = lastUsed |
| paddy@57 | 339 retrieved, err = store.listLogins(login.ProfileID, 10, 0) |
| paddy@46 | 340 if err != nil { |
| paddy@46 | 341 t.Errorf("Error retrieving logins from %T: %s", store, err) |
| paddy@46 | 342 } |
| paddy@46 | 343 if len(retrieved) != 1 { |
| paddy@46 | 344 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved)) |
| paddy@46 | 345 } |
| paddy@46 | 346 match, field, expectation, result = compareLogins(login, retrieved[0]) |
| paddy@46 | 347 if !match { |
| paddy@46 | 348 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@46 | 349 } |
| paddy@69 | 350 err = store.removeLogin(login.Value, login.ProfileID) |
| paddy@46 | 351 if err != nil { |
| paddy@46 | 352 t.Errorf("Error removing login from %T: %s", store, err) |
| paddy@46 | 353 } |
| paddy@57 | 354 retrieved, err = store.listLogins(login.ProfileID, 10, 0) |
| paddy@46 | 355 if len(retrieved) != 0 { |
| paddy@46 | 356 t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved) |
| paddy@46 | 357 } |
| paddy@69 | 358 err = store.removeLogin(login.Value, login.ProfileID) |
| paddy@46 | 359 if err != ErrLoginNotFound { |
| paddy@46 | 360 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err) |
| paddy@46 | 361 } |
| paddy@46 | 362 } |
| paddy@46 | 363 } |
| paddy@47 | 364 |
| paddy@47 | 365 func TestProfileStoreLoginRetrieval(t *testing.T) { |
| paddy@47 | 366 t.Parallel() |
| paddy@47 | 367 profile := Profile{ |
| paddy@47 | 368 ID: uuid.NewID(), |
| paddy@47 | 369 Name: "name", |
| paddy@47 | 370 Passphrase: "passphrase", |
| paddy@47 | 371 Iterations: 10000, |
| paddy@47 | 372 Salt: "salt", |
| paddy@47 | 373 PassphraseScheme: 1, |
| paddy@47 | 374 Compromised: false, |
| paddy@47 | 375 LockedUntil: time.Now().Add(time.Hour), |
| paddy@47 | 376 PassphraseReset: "passphrase reset", |
| paddy@47 | 377 PassphraseResetCreated: time.Now(), |
| paddy@47 | 378 Created: time.Now(), |
| paddy@47 | 379 LastSeen: time.Now(), |
| paddy@47 | 380 } |
| paddy@47 | 381 login := Login{ |
| paddy@47 | 382 Type: "type", |
| paddy@47 | 383 Value: "value", |
| paddy@47 | 384 ProfileID: profile.ID, |
| paddy@47 | 385 Created: time.Now().Add(-1 * time.Hour), |
| paddy@47 | 386 LastUsed: time.Now().Add(-1 * time.Minute), |
| paddy@47 | 387 } |
| paddy@47 | 388 for _, store := range profileStores { |
| paddy@57 | 389 err := store.saveProfile(profile) |
| paddy@47 | 390 if err != nil { |
| paddy@47 | 391 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@47 | 392 } |
| paddy@57 | 393 err = store.addLogin(login) |
| paddy@47 | 394 if err != nil { |
| paddy@47 | 395 t.Errorf("Error storing login in %T: %s", store, err) |
| paddy@47 | 396 } |
| paddy@69 | 397 retrieved, err := store.getProfileByLogin(login.Value) |
| paddy@47 | 398 if err != nil { |
| paddy@47 | 399 t.Errorf("Error retrieving profile by login from %T: %s", store, err) |
| paddy@47 | 400 } |
| paddy@47 | 401 match, field, expectation, result := compareProfiles(profile, retrieved) |
| paddy@47 | 402 if !match { |
| paddy@47 | 403 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@47 | 404 } |
| paddy@47 | 405 } |
| paddy@47 | 406 } |
| paddy@48 | 407 |
| paddy@48 | 408 func TestProfileChangeValidation(t *testing.T) { |
| paddy@48 | 409 t.Parallel() |
| paddy@48 | 410 passphraseScheme := 1 |
| paddy@48 | 411 passphraseReset := "reset" |
| paddy@48 | 412 salt := "salt" |
| paddy@69 | 413 iterations := 100 |
| paddy@48 | 414 shortPassphrase := "a" |
| paddy@48 | 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?" |
| paddy@48 | 416 emptyName := "" |
| paddy@48 | 417 enteredName := "Paddy" |
| paddy@48 | 418 okPassphrase := "this is a decent passphrase" |
| paddy@48 | 419 compromised := true |
| paddy@48 | 420 lockedUntil := time.Now() |
| paddy@48 | 421 resetCreated := time.Now() |
| paddy@48 | 422 lastSeen := time.Now() |
| paddy@48 | 423 changes := map[*ProfileChange]error{ |
| paddy@48 | 424 &ProfileChange{}: ErrEmptyChange, |
| paddy@48 | 425 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase, |
| paddy@48 | 426 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 427 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated, |
| paddy@48 | 428 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil, |
| paddy@48 | 429 &ProfileChange{Salt: &salt}: ErrMissingPassphrase, |
| paddy@48 | 430 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 431 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase, |
| paddy@48 | 432 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 433 &ProfileChange{Passphrase: &shortPassphrase}: ErrPassphraseTooShort, |
| paddy@48 | 434 &ProfileChange{Passphrase: &longPassphrase}: ErrPassphraseTooLong, |
| paddy@48 | 435 &ProfileChange{Passphrase: &okPassphrase}: nil, |
| paddy@48 | 436 &ProfileChange{Name: &emptyName}: nil, |
| paddy@48 | 437 &ProfileChange{Name: &enteredName}: nil, |
| paddy@48 | 438 &ProfileChange{Compromised: &compromised}: nil, |
| paddy@48 | 439 &ProfileChange{LockedUntil: &lockedUntil}: nil, |
| paddy@48 | 440 &ProfileChange{LastSeen: &lastSeen}: nil, |
| paddy@48 | 441 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset, |
| paddy@48 | 442 } |
| paddy@48 | 443 for change, expectedErr := range changes { |
| paddy@48 | 444 if err := change.Validate(); err != expectedErr { |
| paddy@48 | 445 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err) |
| paddy@48 | 446 } |
| paddy@48 | 447 } |
| paddy@48 | 448 } |
| paddy@48 | 449 |
| paddy@48 | 450 func TestBulkProfileChangeValidation(t *testing.T) { |
| paddy@48 | 451 t.Parallel() |
| paddy@48 | 452 compromised := true |
| paddy@48 | 453 changes := map[*BulkProfileChange]error{ |
| paddy@48 | 454 &BulkProfileChange{}: ErrEmptyChange, |
| paddy@48 | 455 &BulkProfileChange{Compromised: &compromised}: nil, |
| paddy@48 | 456 } |
| paddy@48 | 457 for change, expectedErr := range changes { |
| paddy@48 | 458 if err := change.Validate(); err != expectedErr { |
| paddy@48 | 459 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err) |
| paddy@48 | 460 } |
| paddy@48 | 461 } |
| paddy@48 | 462 } |