Lock Out Support

Jun 24, 2010 at 4:47 AM

I've been using the XmlProviders for my website, but its lacking locking out of users. I wanted a bit more security, so I implemented lock out support. I wasn't quite sure of the best way to do it, but I did it by keeping track of the number of invalid attempts and the time limit of how long to track the attempts before resetting the count. I didn't know where to store this information, but I noticed a "Comment" property that didn't appear to be in use, so I just used that. If these changes are pulled into some sort of release, a seperate property or two should probably be created instead.

Also, the user validation didn't account for whether or not a user was approved. I've correct this as well in the code below. Just replace these functions in XmlMembershipProvider.cs:

public override bool ValidateUser(string username, string password)
{
   try
   {
      lock (SyncRoot)
      {
         XmlUser user = this.Store.GetUserByName(username);

         // Not a user
         if (user == null)
         {
            return false;
         }
         // User isn't allowed (either not approved, or locked out)
         else if ((!user.IsApproved) || user.IsLockedOut)
         {
            return false;
         }
         // Let's validate the user's password then
         else if (ValidateUserInternal(user, password))
         {
            user.LastLoginDate = DateTime.Now;
            user.LastActivityDate = DateTime.Now;
            user.Comment = String.Empty; // Clearing this, as we're using it to count invalid attempts
            this.Store.Save();
            return true;
         }
         // Invalid password was entered
         else
         {
            char StorageDelimeter = '|';
            int InvalidAttempts = 0;
            DateTime InvalidWindow = DateTime.Now.Add(TimeSpan.FromMinutes(PasswordAttemptWindow));

            // Already had at least one invalid attempt
            if ((user.Comment != null) && (String.Compare(user.Comment, String.Empty) != 0))
            {
               // Grab the invalid attempts in the comment string by removing everything after the delimeter
               InvalidAttempts =
                  Convert.ToInt32(
                     user.Comment.Remove(
                        user.Comment.IndexOf(StorageDelimeter)
                     )
                  );
               // Grab the window's time from the comment string by removing everything before delimeter
               InvalidWindow =
                  Convert.ToDateTime(
                     user.Comment.Substring(
                        user.Comment.IndexOf(StorageDelimeter) + 1
                     )
                  );
            }

            if (DateTime.Compare(DateTime.Now, InvalidWindow) > 0) // Exceeded our window, start over the count
            {
               InvalidWindow = DateTime.Now.Add(TimeSpan.FromMinutes(PasswordAttemptWindow));
               InvalidAttempts = 0;
            }

            InvalidAttempts++;
            user.LastActivityDate = DateTime.Now;

            if (InvalidAttempts == MaxInvalidPasswordAttempts) // Lock it, clear comments
            {
               user.IsLockedOut = true;
               user.LastLockoutDate = DateTime.Now;
               user.Comment = String.Empty;
            }
            else
            {
               user.Comment = InvalidAttempts.ToString() + StorageDelimeter + InvalidWindow.ToString();
            }

            this.Store.Save();

            return false;
         }
      }
   }
   catch
   {
      throw;
   }
}


public override bool UnlockUser(string userName)
{
  try
  {
     lock (SyncRoot)
     {
        XmlUser user = this.Store.GetUserByName(userName);
        user.IsLockedOut = false;
        this.Store.Save();
        return true; // Successfully unlocked user
     }
  }
  catch
  {
     // Problem unlocking user
     return false;
  }
}