/// <summary>
/// Get a benign login attempt to simulate
/// </summary>
/// <returns></returns>
public SimulatedLoginAttempt BenignLoginAttempt(DateTime eventTimeUtc)
{
// If there is a benign login attempt already scheduled to occur by now,
// send it instaed
lock (ScheduledBenignAttempts)
{
if (ScheduledBenignAttempts.Count > 0 &&
ScheduledBenignAttempts.First().TimeOfAttemptUtc < eventTimeUtc)
{
SimulatedLoginAttempt result = ScheduledBenignAttempts.First();
ScheduledBenignAttempts.Remove(result);
return(result);
}
}
string mistake = "";
//1. Pick a user at random
SimulatedUserAccount account = _simAccounts.BenignAccountSelector.GetItemByWeightedRandom();
//2. Deal with cookies
string cookie;
// Add a new cookie if there are no cookies, or with if we haven't reached the max number of cookies and lose a roll of the dice
if (account.Cookies.Count == 0 ||
(account.Cookies.Count < _experimentalConfiguration.MaxCookiesPerUserAccount && StrongRandomNumberGenerator.GetFraction() > _experimentalConfiguration.ChanceOfCoookieReUse))
{
// We'll use the decimal represenation of a 64-bit unsigned integer as our cookie
cookie = StrongRandomNumberGenerator.Get64Bits().ToString();
account.Cookies.Add(cookie);
}
else
{
// Use one of the user's existing cookies selected at random
cookie = account.Cookies.ToArray()[(int)StrongRandomNumberGenerator.Get32Bits(account.Cookies.Count)];
}
//Console.WriteLine("The user currently has " + account.Cookies.Count + " cookies. Using: " + cookie);
//3. Choose an IP address for the login
// 1/3 of times login with the primary IP address, otherwise, choose an IP randomly from the benign IP pool
IPAddress clientIp;
if (account.ClientAddresses.Count == 0 ||
(account.ClientAddresses.Count < _experimentalConfiguration.MaxIpPerUserAccount && StrongRandomNumberGenerator.GetFraction() < _experimentalConfiguration.ChanceOfIpReUse))
{
// Use a new IP for the user
account.ClientAddresses.Add(clientIp = _ipPool.GetNewRandomBenignIp());
}
else
{
// Use one of the user's existing IP Addresses selected at random
clientIp = account.ClientAddresses.ToArray()[(int)StrongRandomNumberGenerator.Get32Bits(account.ClientAddresses.Count)];
}
string password = account.Password;
//
// Add benign failures
// An automated client begins a string of login attempts using an old (stale) password
if (StrongRandomNumberGenerator.GetFraction() < _experimentalConfiguration.ChanceOfLongRepeatOfStalePassword)
{
// To cause this client to be out of date, we'll change the password here.
string newPassword = _simPasswords.GetPasswordFromWeightedDistribution();
_userAccountController.SetPassword(account, newPassword, account.Password);
mistake += "StalePassword";
// Schedule all the future failed attempts a fixed distance aparat
lock (ScheduledBenignAttempts)
{
double additionalMistakes = 0;
DateTime currentTimeUtc = eventTimeUtc;
for (additionalMistakes = 1; additionalMistakes < _experimentalConfiguration.LengthOfLongRepeatOfOldPassword; additionalMistakes++)
{
currentTimeUtc = currentTimeUtc.AddSeconds(_experimentalConfiguration.MinutesBetweenLongRepeatOfOldPassword);
ScheduledBenignAttempts.Add(new SimulatedLoginAttempt(
account, password, false, false, clientIp, cookie, mistake, currentTimeUtc));
}
for (uint correctLogins = 1; correctLogins < _experimentalConfiguration.LengthOfLongRepeatOfOldPassword; correctLogins++)
{
currentTimeUtc = currentTimeUtc.AddSeconds(_experimentalConfiguration.MinutesBetweenLongRepeatOfOldPassword);
ScheduledBenignAttempts.Add(new SimulatedLoginAttempt(
account, newPassword, false, false, clientIp, cookie, mistake, currentTimeUtc));
}
}
}
// The benign user may mistype her password causing a typo
if (StrongRandomNumberGenerator.GetFraction() < _experimentalConfiguration.ChanceOfBenignPasswordTypo)
{
mistake += "Typo";
// Typos tend to come in clusters, and are hopefully followed by a correct login
// Add additional typos to the schedule of future benign attempts and then a submission of the correct password
lock (ScheduledBenignAttempts)
{
double additionalMistakes = 0;
while (StrongRandomNumberGenerator.GetFraction() < _experimentalConfiguration.ChanceOfRepeatTypo)
{
ScheduledBenignAttempts.Add(new SimulatedLoginAttempt(
account, AddTypoToPassword(password), false, false, clientIp, cookie, mistake,
eventTimeUtc.AddSeconds(_experimentalConfiguration.DelayBetweenRepeatBenignErrorsInSeconds * ++additionalMistakes)));
}
// Add a correct login after the string of typos
ScheduledBenignAttempts.Add(new SimulatedLoginAttempt(
account, password, false, false, clientIp, cookie, "", eventTimeUtc.AddSeconds(
_experimentalConfiguration.DelayBetweenRepeatBenignErrorsInSeconds * (1 + additionalMistakes))));
}
// Put the typo into the password for the first typo failure, to be returned by this function.
password = AddTypoToPassword(password);
}
// The benign user may mistakenly use a password for another of her accounts, which we draw from same distribution
// we used to generate user account passwords
if (StrongRandomNumberGenerator.GetFraction() < _experimentalConfiguration.ChanceOfAccidentallyUsingAnotherAccountPassword)
{
mistake += "WrongPassword";
// Choices of the wrong account password may come in clusters, and are hopefully followed by a correct login
// Add additional typos to the schedule of future benign attempts and then a submission of the correct password
lock (ScheduledBenignAttempts)
{
double additionalMistakes = 0;
while (StrongRandomNumberGenerator.GetFraction() < _experimentalConfiguration.ChanceOfRepeatUseOfPasswordFromAnotherAccount)
{
ScheduledBenignAttempts.Add(new SimulatedLoginAttempt(
account, _simPasswords.GetPasswordFromWeightedDistribution(), false, false, clientIp, cookie,
mistake, eventTimeUtc.AddSeconds(_experimentalConfiguration.DelayBetweenRepeatBenignErrorsInSeconds * ++additionalMistakes)));
}
// Add a correct login after mistakes
ScheduledBenignAttempts.Add(new SimulatedLoginAttempt(
account, password, false, false, clientIp, cookie, "", eventTimeUtc.AddSeconds(
_experimentalConfiguration.DelayBetweenRepeatBenignErrorsInSeconds * (additionalMistakes + 1))));
}
// Make the current request have the wrong password
password = _simPasswords.GetPasswordFromWeightedDistribution();
}
// The benign user may mistype her account name, and land on someone else's account name
if (StrongRandomNumberGenerator.GetFraction() < _experimentalConfiguration.ChanceOfBenignAccountNameTypoResultingInAValidUserName)
{
mistake += "WrongAccountName";
// Choices of the wrong account password may come in clusters, and are hopefully followed by a correct login
// Add additional typos to the schedule of future benign attempts and then a submission of the correct password
lock (ScheduledBenignAttempts)
{
double additionalMistakes = 0;
while (StrongRandomNumberGenerator.GetFraction() < _experimentalConfiguration.ChanceOfRepeatWrongAccountName)
{
ScheduledBenignAttempts.Add(new SimulatedLoginAttempt(
_simAccounts.GetBenignAccountAtRandomUniform(), password, false, false, clientIp, cookie, mistake, eventTimeUtc.AddSeconds(
_experimentalConfiguration.DelayBetweenRepeatBenignErrorsInSeconds * ++additionalMistakes)));
}
// Add a correct login after mistakes
ScheduledBenignAttempts.Add(new SimulatedLoginAttempt(
account, password, false, false, clientIp, cookie, "", eventTimeUtc.AddSeconds(
_experimentalConfiguration.DelayBetweenRepeatBenignErrorsInSeconds * (additionalMistakes + 1))));
// Make the current request have the wrong account name
account = _simAccounts.GetBenignAccountAtRandomUniform();
}
}
return(new SimulatedLoginAttempt(account, password, false, false, clientIp, cookie, mistake, eventTimeUtc));
}