auth
auth/profile_test.go
Enable terminating sessions through the API. Add a terminateSession method to the sessionStore that sets the Active property of the Session to false. Create a Context.TerminateSession wrapper for the terminateSession method on the sessionStore. Add a Sessions property to our response type so we can return a []Session in API responses. Use the URL-safe encoding when base64 encoding our session ID and CSRFToken, so the ID can be passed in the URL and so our encodings are consistent. Add a TerminateSessionHandler function that will extract a Session ID from the request URL, authenticate the user, check that the authenticated user owns the session in question, and terminate the session. Add implementations for our new terminateSession method for the memstore and postgres types. Test both the memstore and postgres implementation of our terminateSession helper in session_test.go.
| paddy@38 | 1 package auth |
| paddy@38 | 2 |
| paddy@38 | 3 import ( |
| paddy@38 | 4 "fmt" |
| paddy@153 | 5 "os" |
| paddy@38 | 6 "testing" |
| paddy@38 | 7 "time" |
| paddy@38 | 8 |
| paddy@107 | 9 "code.secondbit.org/uuid.hg" |
| paddy@38 | 10 ) |
| paddy@38 | 11 |
| paddy@38 | 12 const ( |
| paddy@38 | 13 profileChangeName = 1 << iota |
| paddy@38 | 14 profileChangePassphrase |
| paddy@38 | 15 profileChangeIterations |
| paddy@38 | 16 profileChangeSalt |
| paddy@38 | 17 profileChangePassphraseScheme |
| paddy@38 | 18 profileChangeCompromised |
| paddy@38 | 19 profileChangeLockedUntil |
| paddy@38 | 20 profileChangePassphraseReset |
| paddy@38 | 21 profileChangePassphraseResetCreated |
| paddy@38 | 22 profileChangeLastSeen |
| paddy@38 | 23 ) |
| paddy@38 | 24 |
| paddy@149 | 25 func init() { |
| paddy@153 | 26 if os.Getenv("PG_TEST_DB") != "" { |
| paddy@153 | 27 p, err := NewPostgres(os.Getenv("PG_TEST_DB")) |
| paddy@153 | 28 if err != nil { |
| paddy@153 | 29 panic(err) |
| paddy@153 | 30 } |
| paddy@149 | 31 profileStores = append(profileStores, &p) |
| paddy@149 | 32 } |
| paddy@149 | 33 } |
| paddy@149 | 34 |
| paddy@57 | 35 var profileStores = []profileStore{NewMemstore()} |
| paddy@38 | 36 |
| paddy@38 | 37 func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) { |
| paddy@38 | 38 if !profile1.ID.Equal(profile2.ID) { |
| paddy@38 | 39 return false, "ID", profile1.ID, profile2.ID |
| paddy@38 | 40 } |
| paddy@38 | 41 if profile1.Name != profile2.Name { |
| paddy@38 | 42 return false, "name", profile1.Name, profile2.Name |
| paddy@38 | 43 } |
| paddy@38 | 44 if profile1.Passphrase != profile2.Passphrase { |
| paddy@38 | 45 return false, "passphrase", profile1.Passphrase, profile2.Passphrase |
| paddy@38 | 46 } |
| paddy@38 | 47 if profile1.Iterations != profile2.Iterations { |
| paddy@38 | 48 return false, "iterations", profile1.Iterations, profile2.Iterations |
| paddy@38 | 49 } |
| paddy@38 | 50 if profile1.Salt != profile2.Salt { |
| paddy@38 | 51 return false, "salt", profile1.Salt, profile2.Salt |
| paddy@38 | 52 } |
| paddy@38 | 53 if profile1.PassphraseScheme != profile2.PassphraseScheme { |
| paddy@38 | 54 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme |
| paddy@38 | 55 } |
| paddy@38 | 56 if profile1.Compromised != profile2.Compromised { |
| paddy@38 | 57 return false, "compromised", profile1.Compromised, profile2.Compromised |
| paddy@38 | 58 } |
| paddy@38 | 59 if !profile1.LockedUntil.Equal(profile2.LockedUntil) { |
| paddy@38 | 60 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil |
| paddy@38 | 61 } |
| paddy@38 | 62 if profile1.PassphraseReset != profile2.PassphraseReset { |
| paddy@38 | 63 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset |
| paddy@38 | 64 } |
| paddy@38 | 65 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) { |
| paddy@38 | 66 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated |
| paddy@38 | 67 } |
| paddy@38 | 68 if !profile1.Created.Equal(profile2.Created) { |
| paddy@38 | 69 return false, "created", profile1.Created, profile2.Created |
| paddy@38 | 70 } |
| paddy@38 | 71 if !profile1.LastSeen.Equal(profile2.LastSeen) { |
| paddy@38 | 72 return false, "last seen", profile1.LastSeen, profile2.LastSeen |
| paddy@38 | 73 } |
| paddy@148 | 74 if profile1.Deleted != profile2.Deleted { |
| paddy@148 | 75 return false, "deleted", profile1.Deleted, profile2.Deleted |
| paddy@148 | 76 } |
| paddy@38 | 77 return true, "", nil, nil |
| paddy@38 | 78 } |
| paddy@38 | 79 |
| paddy@46 | 80 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) { |
| paddy@46 | 81 if login1.Type != login2.Type { |
| paddy@46 | 82 return false, "Type", login1.Type, login2.Type |
| paddy@46 | 83 } |
| paddy@46 | 84 if login1.Value != login2.Value { |
| paddy@46 | 85 return false, "Value", login1.Value, login2.Value |
| paddy@46 | 86 } |
| paddy@46 | 87 if !login1.ProfileID.Equal(login2.ProfileID) { |
| paddy@46 | 88 return false, "ProfileID", login1.ProfileID, login2.ProfileID |
| paddy@46 | 89 } |
| paddy@46 | 90 if !login1.Created.Equal(login2.Created) { |
| paddy@46 | 91 return false, "Created", login1.Created, login2.Created |
| paddy@46 | 92 } |
| paddy@46 | 93 if !login1.LastUsed.Equal(login2.LastUsed) { |
| paddy@46 | 94 return false, "LastUsed", login1.LastUsed, login2.LastUsed |
| paddy@46 | 95 } |
| paddy@46 | 96 return true, "", nil, nil |
| paddy@46 | 97 } |
| paddy@46 | 98 |
| paddy@38 | 99 func TestProfileStoreSuccess(t *testing.T) { |
| paddy@38 | 100 t.Parallel() |
| paddy@38 | 101 profile := Profile{ |
| paddy@38 | 102 ID: uuid.NewID(), |
| paddy@38 | 103 Name: "name", |
| paddy@38 | 104 Passphrase: "passphrase", |
| paddy@38 | 105 Iterations: 10000, |
| paddy@38 | 106 Salt: "salt", |
| paddy@38 | 107 PassphraseScheme: 1, |
| paddy@38 | 108 Compromised: false, |
| paddy@149 | 109 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond), |
| paddy@38 | 110 PassphraseReset: "passphrase reset", |
| paddy@149 | 111 PassphraseResetCreated: time.Now().Round(time.Millisecond), |
| paddy@149 | 112 Created: time.Now().Round(time.Millisecond), |
| paddy@149 | 113 LastSeen: time.Now().Round(time.Millisecond), |
| paddy@38 | 114 } |
| paddy@38 | 115 for _, store := range profileStores { |
| paddy@116 | 116 context := Context{profiles: store} |
| paddy@116 | 117 err := context.SaveProfile(profile) |
| paddy@38 | 118 if err != nil { |
| paddy@38 | 119 t.Errorf("Error saving profile to %T: %s", store, err) |
| paddy@38 | 120 } |
| paddy@116 | 121 err = context.SaveProfile(profile) |
| paddy@38 | 122 if err != ErrProfileAlreadyExists { |
| paddy@38 | 123 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err) |
| paddy@38 | 124 } |
| paddy@116 | 125 retrieved, err := context.GetProfileByID(profile.ID) |
| paddy@38 | 126 if err != nil { |
| paddy@38 | 127 t.Errorf("Error retrieving profile from %T: %s", store, err) |
| paddy@38 | 128 } |
| paddy@38 | 129 match, field, expectation, result := compareProfiles(profile, retrieved) |
| paddy@38 | 130 if !match { |
| paddy@38 | 131 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@38 | 132 } |
| paddy@148 | 133 deleted := true |
| paddy@148 | 134 err = context.UpdateProfile(profile.ID, ProfileChange{Deleted: &deleted}) |
| paddy@38 | 135 if err != nil { |
| paddy@38 | 136 t.Errorf("Error removing profile from %T: %s", store, err) |
| paddy@38 | 137 } |
| paddy@116 | 138 retrieved, err = context.GetProfileByID(profile.ID) |
| paddy@38 | 139 if err != ErrProfileNotFound { |
| paddy@38 | 140 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err) |
| paddy@38 | 141 } |
| paddy@38 | 142 } |
| paddy@38 | 143 } |
| paddy@38 | 144 |
| paddy@38 | 145 func TestProfileUpdates(t *testing.T) { |
| paddy@38 | 146 t.Parallel() |
| paddy@38 | 147 variations := 1 << 10 |
| paddy@38 | 148 profile := Profile{ |
| paddy@38 | 149 ID: uuid.NewID(), |
| paddy@38 | 150 Name: "name", |
| paddy@38 | 151 Passphrase: "passphrase", |
| paddy@38 | 152 Iterations: 10000, |
| paddy@38 | 153 Salt: "salt", |
| paddy@38 | 154 PassphraseScheme: 1, |
| paddy@38 | 155 Compromised: false, |
| paddy@149 | 156 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond), |
| paddy@38 | 157 PassphraseReset: "passphrase reset", |
| paddy@149 | 158 PassphraseResetCreated: time.Now().Round(time.Millisecond), |
| paddy@149 | 159 Created: time.Now().Round(time.Millisecond), |
| paddy@149 | 160 LastSeen: time.Now().Round(time.Millisecond), |
| paddy@38 | 161 } |
| paddy@38 | 162 for i := 0; i < variations; i++ { |
| paddy@38 | 163 var name, passphrase, salt, passphraseReset string |
| paddy@69 | 164 var iterations int |
| paddy@38 | 165 var lockedUntil, passphraseResetCreated, lastSeen time.Time |
| paddy@38 | 166 var passphraseScheme int |
| paddy@38 | 167 var compromised bool |
| paddy@38 | 168 |
| paddy@148 | 169 profile.ID = uuid.NewID() |
| paddy@38 | 170 change := ProfileChange{} |
| paddy@38 | 171 expectation := profile |
| paddy@38 | 172 result := profile |
| paddy@38 | 173 if i&profileChangeName != 0 { |
| paddy@38 | 174 name = fmt.Sprintf("name-%d", i) |
| paddy@38 | 175 change.Name = &name |
| paddy@38 | 176 expectation.Name = name |
| paddy@38 | 177 } |
| paddy@38 | 178 if i&profileChangePassphrase != 0 { |
| paddy@38 | 179 passphrase = fmt.Sprintf("passphrase-%d", i) |
| paddy@38 | 180 change.Passphrase = &passphrase |
| paddy@38 | 181 expectation.Passphrase = passphrase |
| paddy@38 | 182 } |
| paddy@38 | 183 if i&profileChangeIterations != 0 { |
| paddy@69 | 184 iterations = i |
| paddy@38 | 185 change.Iterations = &iterations |
| paddy@38 | 186 expectation.Iterations = iterations |
| paddy@38 | 187 } |
| paddy@38 | 188 if i&profileChangeSalt != 0 { |
| paddy@38 | 189 salt = fmt.Sprintf("salt-%d", i) |
| paddy@38 | 190 change.Salt = &salt |
| paddy@38 | 191 expectation.Salt = salt |
| paddy@38 | 192 } |
| paddy@38 | 193 if i&profileChangePassphraseScheme != 0 { |
| paddy@38 | 194 passphraseScheme = i |
| paddy@38 | 195 change.PassphraseScheme = &passphraseScheme |
| paddy@38 | 196 expectation.PassphraseScheme = passphraseScheme |
| paddy@38 | 197 } |
| paddy@38 | 198 if i&profileChangeCompromised != 0 { |
| paddy@38 | 199 compromised = i%2 != 0 |
| paddy@38 | 200 change.Compromised = &compromised |
| paddy@38 | 201 expectation.Compromised = compromised |
| paddy@38 | 202 } |
| paddy@38 | 203 if i&profileChangeLockedUntil != 0 { |
| paddy@149 | 204 lockedUntil = time.Now().Round(time.Millisecond) |
| paddy@38 | 205 change.LockedUntil = &lockedUntil |
| paddy@38 | 206 expectation.LockedUntil = lockedUntil |
| paddy@38 | 207 } |
| paddy@38 | 208 if i&profileChangePassphraseReset != 0 { |
| paddy@38 | 209 passphraseReset = fmt.Sprintf("passphraseReset-%d", i) |
| paddy@38 | 210 change.PassphraseReset = &passphraseReset |
| paddy@38 | 211 expectation.PassphraseReset = passphraseReset |
| paddy@38 | 212 } |
| paddy@38 | 213 if i&profileChangePassphraseResetCreated != 0 { |
| paddy@149 | 214 passphraseResetCreated = time.Now().Round(time.Millisecond) |
| paddy@38 | 215 change.PassphraseResetCreated = &passphraseResetCreated |
| paddy@38 | 216 expectation.PassphraseResetCreated = passphraseResetCreated |
| paddy@38 | 217 } |
| paddy@38 | 218 if i&profileChangeLastSeen != 0 { |
| paddy@149 | 219 lastSeen = time.Now().Round(time.Millisecond) |
| paddy@38 | 220 change.LastSeen = &lastSeen |
| paddy@38 | 221 expectation.LastSeen = lastSeen |
| paddy@38 | 222 } |
| paddy@38 | 223 result.ApplyChange(change) |
| paddy@38 | 224 match, field, expected, got := compareProfiles(expectation, result) |
| paddy@38 | 225 if !match { |
| paddy@38 | 226 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got) |
| paddy@38 | 227 } |
| paddy@38 | 228 for _, store := range profileStores { |
| paddy@116 | 229 context := Context{profiles: store} |
| paddy@116 | 230 err := context.SaveProfile(profile) |
| paddy@38 | 231 if err != nil { |
| paddy@38 | 232 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@38 | 233 } |
| paddy@116 | 234 err = context.UpdateProfile(profile.ID, change) |
| paddy@38 | 235 if err != nil { |
| paddy@38 | 236 t.Errorf("Error updating profile in %T: %s", store, err) |
| paddy@38 | 237 } |
| paddy@116 | 238 retrieved, err := context.GetProfileByID(profile.ID) |
| paddy@38 | 239 if err != nil { |
| paddy@38 | 240 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@38 | 241 } |
| paddy@38 | 242 match, field, expected, got = compareProfiles(expectation, retrieved) |
| paddy@38 | 243 if !match { |
| paddy@38 | 244 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@38 | 245 } |
| paddy@38 | 246 } |
| paddy@38 | 247 } |
| paddy@38 | 248 } |
| paddy@45 | 249 |
| paddy@45 | 250 func TestProfilesUpdates(t *testing.T) { |
| paddy@45 | 251 profile1 := Profile{ |
| paddy@45 | 252 ID: uuid.NewID(), |
| paddy@45 | 253 } |
| paddy@45 | 254 profile2 := Profile{ |
| paddy@45 | 255 ID: uuid.NewID(), |
| paddy@45 | 256 } |
| paddy@45 | 257 profile3 := Profile{ |
| paddy@45 | 258 ID: uuid.NewID(), |
| paddy@45 | 259 } |
| paddy@45 | 260 truth := true |
| paddy@45 | 261 change := BulkProfileChange{ |
| paddy@45 | 262 Compromised: &truth, |
| paddy@45 | 263 } |
| paddy@45 | 264 for _, store := range profileStores { |
| paddy@116 | 265 context := Context{profiles: store} |
| paddy@116 | 266 err := context.SaveProfile(profile1) |
| paddy@45 | 267 if err != nil { |
| paddy@45 | 268 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 269 } |
| paddy@116 | 270 err = context.SaveProfile(profile2) |
| paddy@45 | 271 if err != nil { |
| paddy@45 | 272 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 273 } |
| paddy@116 | 274 err = context.SaveProfile(profile3) |
| paddy@45 | 275 if err != nil { |
| paddy@45 | 276 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 277 } |
| paddy@116 | 278 err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change) |
| paddy@45 | 279 if err != nil { |
| paddy@45 | 280 t.Errorf("Error updating profile in %T: %s", store, err) |
| paddy@45 | 281 } |
| paddy@45 | 282 profile1.Compromised = truth |
| paddy@45 | 283 profile3.Compromised = truth |
| paddy@116 | 284 retrieved, err := context.GetProfileByID(profile1.ID) |
| paddy@45 | 285 if err != nil { |
| paddy@45 | 286 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 287 } |
| paddy@45 | 288 match, field, expected, got := compareProfiles(profile1, retrieved) |
| paddy@45 | 289 if !match { |
| paddy@45 | 290 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 291 } |
| paddy@116 | 292 retrieved, err = context.GetProfileByID(profile2.ID) |
| paddy@45 | 293 if err != nil { |
| paddy@45 | 294 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 295 } |
| paddy@45 | 296 match, field, expected, got = compareProfiles(profile2, retrieved) |
| paddy@45 | 297 if !match { |
| paddy@45 | 298 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 299 } |
| paddy@116 | 300 retrieved, err = context.GetProfileByID(profile3.ID) |
| paddy@45 | 301 if err != nil { |
| paddy@45 | 302 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 303 } |
| paddy@45 | 304 match, field, expected, got = compareProfiles(profile3, retrieved) |
| paddy@45 | 305 if !match { |
| paddy@45 | 306 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 307 } |
| paddy@45 | 308 } |
| paddy@45 | 309 } |
| paddy@46 | 310 |
| paddy@46 | 311 func TestProfileStoreLoginSuccess(t *testing.T) { |
| paddy@46 | 312 t.Parallel() |
| paddy@46 | 313 login := Login{ |
| paddy@46 | 314 Type: "type", |
| paddy@46 | 315 Value: "value", |
| paddy@46 | 316 ProfileID: uuid.NewID(), |
| paddy@149 | 317 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond), |
| paddy@149 | 318 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond), |
| paddy@46 | 319 } |
| paddy@46 | 320 for _, store := range profileStores { |
| paddy@116 | 321 context := Context{profiles: store} |
| paddy@116 | 322 err := context.AddLogin(login) |
| paddy@46 | 323 if err != nil { |
| paddy@46 | 324 t.Errorf("Error adding login to %T: %s", store, err) |
| paddy@46 | 325 } |
| paddy@116 | 326 err = context.AddLogin(login) |
| paddy@46 | 327 if err != ErrLoginAlreadyExists { |
| paddy@46 | 328 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err) |
| paddy@46 | 329 } |
| paddy@116 | 330 retrieved, err := context.ListLogins(login.ProfileID, 10, 0) |
| paddy@46 | 331 if err != nil { |
| paddy@46 | 332 t.Errorf("Error retrieving logins from %T: %s", store, err) |
| paddy@46 | 333 } |
| paddy@46 | 334 if len(retrieved) != 1 { |
| paddy@46 | 335 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved)) |
| paddy@46 | 336 } |
| paddy@46 | 337 match, field, expectation, result := compareLogins(login, retrieved[0]) |
| paddy@46 | 338 if !match { |
| paddy@46 | 339 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@46 | 340 } |
| paddy@149 | 341 lastUsed := time.Now().Round(time.Millisecond) |
| paddy@116 | 342 err = context.RecordLoginUse(login.Value, lastUsed) |
| paddy@46 | 343 if err != nil { |
| paddy@46 | 344 t.Errorf("Error recording use of login to %T: %s", store, err) |
| paddy@46 | 345 } |
| paddy@46 | 346 login.LastUsed = lastUsed |
| paddy@116 | 347 retrieved, err = context.ListLogins(login.ProfileID, 10, 0) |
| paddy@46 | 348 if err != nil { |
| paddy@46 | 349 t.Errorf("Error retrieving logins from %T: %s", store, err) |
| paddy@46 | 350 } |
| paddy@46 | 351 if len(retrieved) != 1 { |
| paddy@46 | 352 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved)) |
| paddy@46 | 353 } |
| paddy@46 | 354 match, field, expectation, result = compareLogins(login, retrieved[0]) |
| paddy@46 | 355 if !match { |
| paddy@46 | 356 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@46 | 357 } |
| paddy@116 | 358 err = context.RemoveLogin(login.Value, login.ProfileID) |
| paddy@46 | 359 if err != nil { |
| paddy@46 | 360 t.Errorf("Error removing login from %T: %s", store, err) |
| paddy@46 | 361 } |
| paddy@116 | 362 retrieved, err = context.ListLogins(login.ProfileID, 10, 0) |
| paddy@46 | 363 if len(retrieved) != 0 { |
| paddy@46 | 364 t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved) |
| paddy@46 | 365 } |
| paddy@116 | 366 err = context.RemoveLogin(login.Value, login.ProfileID) |
| paddy@46 | 367 if err != ErrLoginNotFound { |
| paddy@46 | 368 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err) |
| paddy@46 | 369 } |
| paddy@46 | 370 } |
| paddy@46 | 371 } |
| paddy@47 | 372 |
| paddy@47 | 373 func TestProfileStoreLoginRetrieval(t *testing.T) { |
| paddy@47 | 374 t.Parallel() |
| paddy@47 | 375 profile := Profile{ |
| paddy@47 | 376 ID: uuid.NewID(), |
| paddy@47 | 377 Name: "name", |
| paddy@47 | 378 Passphrase: "passphrase", |
| paddy@47 | 379 Iterations: 10000, |
| paddy@47 | 380 Salt: "salt", |
| paddy@47 | 381 PassphraseScheme: 1, |
| paddy@47 | 382 Compromised: false, |
| paddy@149 | 383 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond), |
| paddy@47 | 384 PassphraseReset: "passphrase reset", |
| paddy@149 | 385 PassphraseResetCreated: time.Now().Round(time.Millisecond), |
| paddy@149 | 386 Created: time.Now().Round(time.Millisecond), |
| paddy@149 | 387 LastSeen: time.Now().Round(time.Millisecond), |
| paddy@47 | 388 } |
| paddy@47 | 389 login := Login{ |
| paddy@47 | 390 Type: "type", |
| paddy@47 | 391 Value: "value", |
| paddy@47 | 392 ProfileID: profile.ID, |
| paddy@149 | 393 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond), |
| paddy@149 | 394 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond), |
| paddy@47 | 395 } |
| paddy@47 | 396 for _, store := range profileStores { |
| paddy@116 | 397 context := Context{profiles: store} |
| paddy@116 | 398 err := context.SaveProfile(profile) |
| paddy@47 | 399 if err != nil { |
| paddy@47 | 400 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@47 | 401 } |
| paddy@116 | 402 err = context.AddLogin(login) |
| paddy@47 | 403 if err != nil { |
| paddy@47 | 404 t.Errorf("Error storing login in %T: %s", store, err) |
| paddy@47 | 405 } |
| paddy@116 | 406 retrieved, err := context.GetProfileByLogin(login.Value) |
| paddy@47 | 407 if err != nil { |
| paddy@47 | 408 t.Errorf("Error retrieving profile by login from %T: %s", store, err) |
| paddy@47 | 409 } |
| paddy@47 | 410 match, field, expectation, result := compareProfiles(profile, retrieved) |
| paddy@47 | 411 if !match { |
| paddy@47 | 412 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@47 | 413 } |
| paddy@47 | 414 } |
| paddy@47 | 415 } |
| paddy@48 | 416 |
| paddy@48 | 417 func TestProfileChangeValidation(t *testing.T) { |
| paddy@48 | 418 t.Parallel() |
| paddy@48 | 419 passphraseScheme := 1 |
| paddy@48 | 420 passphraseReset := "reset" |
| paddy@48 | 421 salt := "salt" |
| paddy@69 | 422 iterations := 100 |
| paddy@48 | 423 emptyName := "" |
| paddy@48 | 424 enteredName := "Paddy" |
| paddy@48 | 425 okPassphrase := "this is a decent passphrase" |
| paddy@48 | 426 compromised := true |
| paddy@149 | 427 lockedUntil := time.Now().Round(time.Millisecond) |
| paddy@149 | 428 resetCreated := time.Now().Round(time.Millisecond) |
| paddy@149 | 429 lastSeen := time.Now().Round(time.Millisecond) |
| paddy@48 | 430 changes := map[*ProfileChange]error{ |
| paddy@48 | 431 &ProfileChange{}: ErrEmptyChange, |
| paddy@48 | 432 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase, |
| paddy@48 | 433 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 434 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated, |
| paddy@48 | 435 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil, |
| paddy@48 | 436 &ProfileChange{Salt: &salt}: ErrMissingPassphrase, |
| paddy@48 | 437 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 438 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase, |
| paddy@48 | 439 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 440 &ProfileChange{Passphrase: &okPassphrase}: nil, |
| paddy@48 | 441 &ProfileChange{Name: &emptyName}: nil, |
| paddy@48 | 442 &ProfileChange{Name: &enteredName}: nil, |
| paddy@48 | 443 &ProfileChange{Compromised: &compromised}: nil, |
| paddy@48 | 444 &ProfileChange{LockedUntil: &lockedUntil}: nil, |
| paddy@48 | 445 &ProfileChange{LastSeen: &lastSeen}: nil, |
| paddy@48 | 446 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset, |
| paddy@48 | 447 } |
| paddy@48 | 448 for change, expectedErr := range changes { |
| paddy@48 | 449 if err := change.Validate(); err != expectedErr { |
| paddy@48 | 450 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err) |
| paddy@48 | 451 } |
| paddy@48 | 452 } |
| paddy@48 | 453 } |
| paddy@48 | 454 |
| paddy@48 | 455 func TestBulkProfileChangeValidation(t *testing.T) { |
| paddy@48 | 456 t.Parallel() |
| paddy@48 | 457 compromised := true |
| paddy@48 | 458 changes := map[*BulkProfileChange]error{ |
| paddy@48 | 459 &BulkProfileChange{}: ErrEmptyChange, |
| paddy@48 | 460 &BulkProfileChange{Compromised: &compromised}: nil, |
| paddy@48 | 461 } |
| paddy@48 | 462 for change, expectedErr := range changes { |
| paddy@48 | 463 if err := change.Validate(); err != expectedErr { |
| paddy@48 | 464 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err) |
| paddy@48 | 465 } |
| paddy@48 | 466 } |
| paddy@48 | 467 } |
| paddy@128 | 468 |
| paddy@128 | 469 // BUG(paddy): We need to test the validateNewProfileRequest helper. |
| paddy@128 | 470 // BUG(paddy): We need to test the CreateProfileHandler. |
| paddy@148 | 471 // BUG(paddy): We need to test that deleting works as we expect. |