public async Task WriteAccountAsync(UserAccount account, CancellationToken cancelToken) { if (Accounts == null) return; // REMOVE FOR PRODUCTION // For testing the mipact of Task.Run() on performance //if (true) //{ // Accounts[account.UsernameOrAccountId] = account; // return; //} await Task.Run(() => { Accounts[account.UsernameOrAccountId] = account; }, cancelToken); }
public List<RemoteHost> GetServersResponsibleForCachingAnAccount(UserAccount account) { return GetServersResponsibleForCachingAnAccount(account.UsernameOrAccountId); }
/// <summary> /// This analysis will examine the client IP's previous failed attempts to login to this account /// to determine if any failed attempts were due to typos. /// </summary> /// <param name="clientsIpHistory">Records of this client's previous attempts to examine.</param> /// <param name="account">The account that the client is currently trying to login to.</param> /// <param name="correctPassword">The correct password for this account. (We can only know it because /// the client must have provided the correct one this loginAttempt.)</param> /// <param name="phase1HashOfCorrectPassword">The phase1 hash of that correct password (which we could /// recalculate from the information in the previous parameters, but doing so would be expensive.)</param> /// <returns></returns> protected void UpdateOutcomesUsingTypoAnalysis( IpHistory clientsIpHistory, UserAccount account, string correctPassword, byte[] phase1HashOfCorrectPassword) { if (clientsIpHistory == null) return; List<LoginAttempt> loginAttemptsWithOutcompesUpdatedDueToTypoAnalysis = account.UpdateLoginAttemptOutcomeUsingTypoAnalysis(correctPassword, phase1HashOfCorrectPassword, _options.MaxEditDistanceConsideredATypo, clientsIpHistory.RecentLoginFailures.MostRecentToOldest.Where( attempt => attempt.UsernameOrAccountId == account.UsernameOrAccountId && attempt.Outcome == AuthenticationOutcome.CredentialsInvalidIncorrectPassword && !string.IsNullOrEmpty(attempt.EncryptedIncorrectPassword)) ); foreach (LoginAttempt updatedLoginAttempt in loginAttemptsWithOutcompesUpdatedDueToTypoAnalysis) { WriteLoginAttemptInBackground(updatedLoginAttempt); } }
public async Task PutCacheOnlyAsync(UserAccount account, List<RemoteHost> serversResponsibleForCachingThisAccount, TimeSpan? timeout = null, CancellationToken cancellationToken = default(CancellationToken)) { await Task.WhenAll(serversResponsibleForCachingThisAccount.Where(server => !server.Equals(_localHost)).Select( async server => await RestClientHelper.PutAsync( server.Uri, "/api/UserAccount/" + Uri.EscapeUriString(account.UsernameOrAccountId), new Object[] { new KeyValuePair<string, UserAccount>("account", account), new KeyValuePair<string, bool>("cacheOnly", true), }, timeout, cancellationToken) )); }
public void PutCacheOnlyInBackground(UserAccount account, List<RemoteHost> serversResponsibleForCachingThisAccount, TimeSpan? timeout = null, CancellationToken cancellationToken = default(CancellationToken)) { Task.Run(() => PutCacheOnlyAsync(account, serversResponsibleForCachingThisAccount, timeout, cancellationToken), cancellationToken); }
/// <summary> /// Calls UserAccountController.PutAsync, via a REST PUT request or directly (if the UserAccount is managed locally), /// to store UserAccount to stable store and to the local cache of whichever machines are in charge of maintaining /// a copy in memory. /// </summary> /// <param name="account">The account to store.</param> /// <param name="timeout"></param> /// <param name="cancellationToken">To allow the async call to be cancelled, such as in the event of a timeout.</param> /// <returns>The account record as stored.</returns> public async Task<UserAccount> PutAsync(UserAccount account, TimeSpan? timeout = null, CancellationToken cancellationToken = default(CancellationToken)) { List<RemoteHost> serversResponsibleFOrCachingAnAccount = GetServersResponsibleForCachingAnAccount(account); return await RestClientHelper.TryServersUntilOneResponds( serversResponsibleFOrCachingAnAccount, timeout ?? DefaultTimeout, async (server, innerTimeout) => server.Equals(_localHost) ? await _localUserAccountController.PutAsync(account, false, serversResponsibleFOrCachingAnAccount, cancellationToken) : await RestClientHelper.PutAsync<UserAccount>( server.Uri, "/api/UserAccount/" + Uri.EscapeUriString(account.UsernameOrAccountId), new Object[] { new KeyValuePair<string, UserAccount>("account", account), new KeyValuePair<string, bool>("cacheOnly", false), new KeyValuePair<string, List<RemoteHost>>("serversResponsibleFOrCachingAnAccount", serversResponsibleFOrCachingAnAccount), }, innerTimeout, cancellationToken), cancellationToken); }
/// <summary> /// Combines update the local account cache with a background write to stable store. /// </summary> /// <param name="account">The account to write to cache/stable store.</param> /// <param name="serversResponsibleForCachingThisAccount"></param> /// <param name="updateTheLocalCache"></param> /// <param name="updateRemoteCaches"></param> /// <param name="updateStableStore"></param> /// <param name="cancellationToken">To allow the async call to be cancelled, such as in the event of a timeout.</param> protected void WriteAccountInBackground( UserAccount account, List<RemoteHost> serversResponsibleForCachingThisAccount = null, bool updateTheLocalCache = true, bool updateRemoteCaches = true, bool updateStableStore = true, CancellationToken cancellationToken = default(CancellationToken)) { // ReSharper disable once UnusedVariable -- unused variable used to signify background task Task.Run(() => WriteAccountAsync(account, serversResponsibleForCachingThisAccount, updateTheLocalCache, updateRemoteCaches, updateStableStore, cancellationToken), cancellationToken); }
/// <summary> /// Combines update the local account cache with an asyncronous write to stable store. /// </summary> /// <param name="account">The account to write to cache/stable store.</param> /// <param name="serversResponsibleForCachingThisAccount"></param> /// <param name="updateTheLocalCache"></param> /// <param name="updateRemoteCaches"></param> /// <param name="updateStableStore"></param> /// <param name="cancellationToken">To allow the async call to be cancelled, such as in the event of a timeout.</param> protected async Task WriteAccountAsync( UserAccount account, List<RemoteHost> serversResponsibleForCachingThisAccount = null, bool updateTheLocalCache = true, bool updateRemoteCaches = true, bool updateStableStore = true, CancellationToken cancellationToken = default(CancellationToken)) { Task stableStoreTask = null; if (updateTheLocalCache) { // Write to the local cache on this server _userAccountCache[account.UsernameOrAccountId] = account; } if (updateStableStore) { // Write to stable consistent storage (e.g. database) that the system is configured to use stableStoreTask = _stableStore.WriteAccountAsync(account, cancellationToken); } if (updateRemoteCaches) { // Identify the servers that cache this LoginAttempt and will need their cache entries updated if (serversResponsibleForCachingThisAccount == null) { serversResponsibleForCachingThisAccount = _userAccountClient.GetServersResponsibleForCachingAnAccount(account); } // Update the cache entries for this LoginAttempt on the remote servers. _userAccountClient.PutCacheOnlyInBackground(account, serversResponsibleForCachingThisAccount, cancellationToken: cancellationToken); } // If writing to stable store, wait until the write has completed before returning. if (stableStoreTask != null) await stableStoreTask; }
public async Task<UserAccount> PutAsync(UserAccount account, bool onlyUpdateTheInMemoryCacheOfTheAccount = false, List<RemoteHost> serversResponsibleForCachingThisAccount = null, CancellationToken cancellationToken = default(CancellationToken)) { await WriteAccountAsync(account, serversResponsibleForCachingThisAccount, updateTheLocalCache: true, updateStableStore: !onlyUpdateTheInMemoryCacheOfTheAccount, updateRemoteCaches: !onlyUpdateTheInMemoryCacheOfTheAccount, cancellationToken: cancellationToken); return account; }
/// <summary> /// Create a UserAccount record to match a given username or account id. /// </summary> /// <param name="usernameOrAccountId">A unique identifier for this account, such as a username, email address, or data index for the account record.</param> /// <param name="consumedCreditSequenceLength"></param> /// <param name="password">The password for the account. If null or not provided, no password is set.</param> /// <param name="numberOfIterationsToUseForPhase1Hash">The number of iterations to use when hashing the password.</param> /// <param name="saltUniqueToThisAccount">The salt for this account. If null or not provided, a random salt is generated with length determined /// by parameter <paramref name="saltLength"/>.</param> /// <param name="maxNumberOfCookiesToTrack">This class tracks cookies associated with browsers that have /// successfully logged into this account. This parameter, if set, overrides the default maximum number of such cookies to track.</param> /// <param name="maxAccountPasswordVerificationFailuresToTrack">This class keeps information about failed attempts to login /// to this account that can be examined on the next successful login. This parameter ovverrides the default number of failures to track.</param> /// <param name="phase1HashFunctionName">A hash function that is expensive enough to calculate to make offline dictionary attacks /// expensive, but not so expensive as to slow the authentication system to a halt. If not specified, a default function will be /// used.</param> /// <param name="saltLength">If <paramref name="saltUniqueToThisAccount"/>is not specified or null, the constructor will create /// a random salt of this length. If this length is not specified, a default will be used.</param> public static UserAccount Create(string usernameOrAccountId, int consumedCreditSequenceLength, string password = null, string phase1HashFunctionName = ExpensiveHashFunctionFactory.DefaultFunctionName, int numberOfIterationsToUseForPhase1Hash = 10000, byte[] saltUniqueToThisAccount = null, int maxNumberOfCookiesToTrack = DefaultMaxNumberOfCookiesToTrack, int maxAccountPasswordVerificationFailuresToTrack = DefaultMaxAccountPasswordVerificationFailuresToTrack, int saltLength = DefaultSaltLength) { if (saltUniqueToThisAccount == null) { saltUniqueToThisAccount = new byte[DefaultSaltLength]; RandomNumberGenerator.Create().GetBytes(saltUniqueToThisAccount); } UserAccount newAccount = new UserAccount { UsernameOrAccountId = usernameOrAccountId, SaltUniqueToThisAccount = saltUniqueToThisAccount, HashesOfDeviceCookiesThatHaveSuccessfullyLoggedIntoThisAccount = new CapacityConstrainedSet<string>(maxNumberOfCookiesToTrack), PasswordVerificationFailures = new Sequence<LoginAttempt>(maxAccountPasswordVerificationFailuresToTrack), ConsumedCredits = new Sequence<UserAccount.ConsumedCredit>(consumedCreditSequenceLength), PasswordHashPhase1FunctionName = phase1HashFunctionName, NumberOfIterationsToUseForPhase1Hash = numberOfIterationsToUseForPhase1Hash //Password = password }; if (password != null) { newAccount.SetPassword(password); } return newAccount; }