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;
        }
Beispiel #10
0
        /// <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;
        }