Building a MVC2 Template, Part 11, Finishing the Custom Membership Provider

No Comments

In part 8 of this series we began creating a custom Membership Provider. We’ll finish our custom Membership Provider with this post. This entails expanding our specifications, finishing our membership provider code, and writing a mocking class for our data repository.

Preparing to Write Specifications

If you look at the change_password_method specification you’ll see the statement should_change_the_users_password. This spec will be tested by calling our custom membership provider code. The membership provider class will need to actually change the user’s password and persist it to the data repository. How are we going to do this without having written our data repository yet? By mocking a simple data repository.

At this point it doesn’t matter if that repository is a text file, an XML file, SQL Server, or SQLite. However, in order to test the membership provider specifications we need to have a data repository. I also want to be able to test my Custom Membership Provider class without having to write and test the data repository at this stage of the project. There are a few options available to us:

  1. Use SQLite to build a small, fast, in-memory database.
  2. Use SQL Server Express to access a test database stored in an mdf file.
  3. Use a mocking framework.
  4. Write our own mocking classes.

There may be others (and I would like hear about them if there are), but these are the ones I chose for consideration. My first attempt (only attempt?) will be to use a mocking framework. If this doesn’t work the way I want, we’ll back track and try another option.

Well, the mock framework I selected just wasn’t working the way I wanted. I needed a class that first and foremost was inherited from our data repository interface. I couldn’t do this with the mocking framework I chose. So instead I’m going the route of writing a mocking class for our data repository.

One more thought. If you examine the AccountControllerSpecs.cs file you will see we hard coded some mocking of the membership provider service. That mocking allowed us to test our controller actions. Now we are mocking the repository so we can test the membership provider methods. At some point we’ll want to test the actual repository code.

Creating a Data Access Project

We’ll make a number of small changes to existing projects and class files as well as add the code and specifications for our custom Membership provider. Occasionally you should rebuild the solution and view the Report.html file. It will tell you exactly what parts you need to work on next by correcting the errors it reports.

SolutionExplorer11_01
Our membership provider will maintain membership or user information in a repository, so we need to create an object that will hold this data. Add a new class library project named Nehemiah.Data to our solution. Delete the Class1.cs file.

AddNewItem11_01
To this project add a new folder named Models. In the Model folder create a new class file named User.cs. The code for our User class is listed below.

using System;

namespace Nehemiah.Data.Models
{

    public partial class User
    {

        public Guid UserId { get; set; }
        public string ApplicationName { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
        public string Comment { get; set; }
        public string Password { get; set; }
        public string PasswordQuestion { get; set; }
        public string PasswordAnswer { get; set; }
        public DateTime? LastActivityDate { get; set; }
        public DateTime? LastLoginDate { get; set; }
        public DateTime? LastPasswordChangedDate { get; set; }
        public DateTime CreationDate { get; set; }
        public bool Approved { get; set; }
        public bool Online { get; set; }
        public bool LockedOut { get; set; }
        public DateTime? LastLockedOutDate { get; set; }
        public int FailedPasswordAttemptCount { get; set; }
        public DateTime? FailedPasswordAttemptWindowStart { get; set; }
        public int FailedPasswordAnswerAttemptCount { get; set; }
        public DateTime? FailedPasswordAnswerAttemptStartWindow { get; set; }

        public User()
        {
            Approved = false;
            CreationDate = DateTime.UtcNow;
            FailedPasswordAnswerAttemptCount = 0;
            FailedPasswordAnswerAttemptStartWindow = null;
            FailedPasswordAttemptCount = 0;
            FailedPasswordAttemptWindowStart = null;
            LastActivityDate = DateTime.UtcNow;
            LastLockedOutDate = null;
            LastLoginDate = null;
            LastPasswordChangedDate = null;
            LockedOut = false;
            Online = false;
        }

        public User(string applicationName, string username, string email, string password, string passwordQuestion, string passwordAnswer, bool isApproved, string comment) :this()
        {
            ApplicationName = applicationName;
            UserName = username;
            Email = email;
            Password = password;
            PasswordQuestion = passwordQuestion;
            PasswordAnswer = passwordAnswer;
            Approved = isApproved;
            Comment = comment;
        }

    }   // End Class

}       // End Namespace

AddNewItem11_02
Now we need to begin defining the interface that returns the User data objects. I began with just a few methods. Enough to test the first few specifications and built from there. Add a new interface to the Nehemiah.Data project named IProviderRepository.cs. Then paste the code from below into the file and save. All the interface code is there.

using System;
using Nehemiah.Data.Models;
using System.Collections;
using System.Collections.Generic;

namespace Nehemiah.Data
{
    public interface IProviderRepository
    {
		#region - User -
        bool Add(string applicationName, User user);
        bool DeleteUser(string applicationName, string username);
        IEnumerable<User> GetUsers(string applicationName);
        User GetUserByUserName(string applicationName, string username);
        User GetUserByKey(string applicationName, Guid userId);
        User GetUserByEmail(string applicationName, string email);
        IList<User> GetUserList(string applicationName, int index, int pageSize);
        IList<User> GetUserListByEmail(string applicationName, string email, int index, int pageSize);
        IList<User> GetUserListByUserName(string applicationName, string username, int index, int pageSize);
        int NumberOfUsers(string applicationName);
        int NumberOfUsersOnline(string applicationName, int timeWindow);
        bool Save(string applicationName, User user);
		#endregion

    }   // End Interface

}       // End Namespace

Below is our MembershipProviderSpec file. You will notice I have added quite a few new specifications. I also created a base class to prepare the data for all of the specifications. This base class, MembershipProviderContext, holds the membership provider and defines all of our test data.

Many of the specifications for our code came from the method and property descriptions found at http://msdn.microsoft.com/en-us/library/aa479031.aspx. There are easily a dozen or more specs that could be written for the custom membership provider. The current specification code is listed below.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web.Security;
using Machine.Specifications;
using Nehemiah.Data;
using Nehemiah.Data.Models;
using Nehemiah.Providers;
using Nehemiah.Specs.Repositories;
using System.Configuration.Provider;

namespace Nehemiah.Specs.Providers
{

    [Subject("Membership Provider")]
    public class membership_provider_initialize_method : MembershipProviderContext
    {
        It should_set_the_application_name = () =>
        {
            membershipProvider.ApplicationName.ShouldEqual(MembershipProviderContext.applicationName);
        };

        It should_set_the_maximum_invalid_password_attempts = () =>
        {
            membershipProvider.MaxInvalidPasswordAttempts.ShouldEqual(MembershipProviderContext.maxInvalidPasswordAttempts);
        };

        It should_set_the_password_attempt_window = () =>
        {
            membershipProvider.PasswordAttemptWindow.ShouldEqual(MembershipProviderContext.passwordAttemptWindow);
        };

        It should_set_the_minimum_required_non_alphamumeric_characters = () =>
        {
            membershipProvider.MinRequiredNonAlphanumericCharacters.ShouldEqual(MembershipProviderContext.minRequiredAlphaNumericCharacters);
        };

        It should_set_the_minimum_required_password_length = () =>
        {
            membershipProvider.MinRequiredPasswordLength.ShouldEqual(MembershipProviderContext.minRequiredPasswordLength);
        };

        It should_set_the_password_strength_regular_expression = () =>
        {
            membershipProvider.PasswordStrengthRegularExpression.ShouldEqual(MembershipProviderContext.passwordStrengthRegularExpression);
        };

        It should_set_the_enable_password_reset_flag = () =>
        {
            membershipProvider.EnablePasswordReset.ShouldEqual(MembershipProviderContext.enablePasswordReset);
        };

        It should_set_the_enable_password_retrieval_flag = () =>
        {
            membershipProvider.EnablePasswordRetrieval.ShouldEqual(MembershipProviderContext.enablePasswordRetrieval);
        };

        It should_set_the_requires_question_and_answer_flag = () =>
        {
            membershipProvider.RequiresQuestionAndAnswer.ShouldEqual(MembershipProviderContext.requiresQuestionAndAnswer);
        };

        It should_set_the_requires_unique_email_flag = () =>
        {
            membershipProvider.RequiresUniqueEmail.ShouldEqual(MembershipProviderContext.requiresUniqueEmail);
        };

        It should_set_the_password_format = () =>
        {
            membershipProvider.PasswordFormat.ShouldEqual(MembershipPasswordFormat.Clear);
        };

    }

    [Subject("Membership Provider")]
    public class change_password_method : MembershipProviderContext
    {
        static User user;
        static bool success;

        Because of = () =>
        {
            membershipProvider.ChangePassword(MembershipProviderContext.goodUsername, MembershipProviderContext.goodPassword, MembershipProviderContext.newPassword);
            user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.goodUsername);
        };

        It should_change_the_users_password = () =>
        {
            user.Password.ShouldEqual(MembershipProviderContext.newPassword);
        };

        It should_set_the_last_login_date = () =>
        {
            user.LastLoginDate.ShouldBeGreaterThan(DateTime.UtcNow.AddSeconds(-10));
            user.LastLoginDate.ShouldBeLessThan(DateTime.UtcNow.AddSeconds(10));
        };

        It should_return_false_for_invalid_user = () =>
        {
            bool success = membershipProvider.ChangePassword(MembershipProviderContext.invalidUsername, MembershipProviderContext.goodPassword, MembershipProviderContext.newPassword);
            success.ShouldBeFalse();
        };

        It should_return_false_for_invalid_old_password = () =>
        {
            bool success = membershipProvider.ChangePassword(MembershipProviderContext.goodUsername, MembershipProviderContext.invalidPassword, MembershipProviderContext.newPassword);
            success.ShouldBeFalse();
        };

        It should_return_false_for_a_new_password_that_is_too_short = () =>
        {
            bool success = membershipProvider.ChangePassword(MembershipProviderContext.goodUsername, MembershipProviderContext.goodPassword, MembershipProviderContext.shortPassword);
            success.ShouldBeFalse();
        };

        It should_return_false_for_a_new_password_that_does_not_have_enough_alpahnumeric_characters = () =>
        {
            bool success = membershipProvider.ChangePassword(MembershipProviderContext.goodUsername, MembershipProviderContext.goodPassword, MembershipProviderContext.badAlphaPassword);
            success.ShouldBeFalse();
        };

    }

    [Subject("Membership Provider")]
    public class change_password_question_and_answer_method : MembershipProviderContext
    {

        Because of = () =>
        {
            membershipProvider.ChangePasswordQuestionAndAnswer(MembershipProviderContext.goodUsername, MembershipProviderContext.goodPassword, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer);
        };

        It should_change_the_password_question = () =>
        {
            User user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.goodUsername);
            user.PasswordQuestion.ShouldEqual(MembershipProviderContext.newPasswordQuestion);
        };

        It should_change_the_password_answer = () =>
        {
            User user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.goodUsername);
            user.PasswordAnswer.ShouldEqual(MembershipProviderContext.newPasswordAnswer);
        };

        It should_return_false_for_invalid_user = () =>
        {
            bool success = membershipProvider.ChangePasswordQuestionAndAnswer(MembershipProviderContext.invalidUsername, MembershipProviderContext.goodPassword, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer);
            success.ShouldBeFalse();
        };

        It should_return_false_for_invalid_old_password = () =>
        {
            bool success = membershipProvider.ChangePasswordQuestionAndAnswer(MembershipProviderContext.goodUsername, MembershipProviderContext.invalidPassword, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer);
            success.ShouldBeFalse();
        };

    }

    [Subject("Membership Provider")]
    public class create_user_method : MembershipProviderContext
    {
        It should_create_a_new_user = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.newEmail, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.newUserId, out status);

            member.ShouldNotBeNull();
            status.ShouldEqual(MembershipCreateStatus.Success);
        };

        It should_fail_if_the_email_already_exists = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.goodEmail, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.newUserId, out status);

            member.ShouldBeNull();
            status.ShouldEqual(MembershipCreateStatus.DuplicateEmail);
        };

        It should_fail_if_the_userId_already_exists = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.newEmail2, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.goodUserId, out status);

            member.ShouldBeNull();
            status.ShouldEqual(MembershipCreateStatus.DuplicateProviderUserKey);
        };

        It should_fail_if_the_username_already_exists = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.goodUsername, MembershipProviderContext.newPassword, MembershipProviderContext.newEmail2, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.newUserId, out status);

            member.ShouldBeNull();
            status.ShouldEqual(MembershipCreateStatus.DuplicateUserName);
        };

        It should_fail_if_the_password_answer_is_invalid = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.newEmail2, MembershipProviderContext.newPasswordQuestion, "", false, MembershipProviderContext.newUserId, out status);

            member.ShouldBeNull();
            status.ShouldEqual(MembershipCreateStatus.InvalidAnswer);
        };

        It should_fail_if_the_email_is_invalid = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.invalidEmail, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.newUserId, out status);

            member.ShouldBeNull();
            status.ShouldEqual(MembershipCreateStatus.InvalidEmail);
        };

        It should_fail_if_the_password_is_invalid = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.shortPassword, MembershipProviderContext.newEmail2, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.newUserId, out status);

            member.ShouldBeNull();
            status.ShouldEqual(MembershipCreateStatus.InvalidPassword);
        };

        It should_fail_if_the_userId_is_invalid = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.newEmail2, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.invalidUserId, out status);

            member.ShouldBeNull();
            status.ShouldEqual(MembershipCreateStatus.InvalidProviderUserKey);
        };

        It should_fail_if_the_password_question_is_invalid = () =>
        {
            MembershipCreateStatus status;
            MembershipUser member;

            member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.newEmail2, "", MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.newUserId, out status);

            member.ShouldBeNull();
            status.ShouldEqual(MembershipCreateStatus.InvalidQuestion);
        };

        //It should_fail_if_there_is_a_provider_error = () =>
        //{
        //    MembershipCreateStatus status;
        //    MembershipUser member;

        //    member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.newEmail2, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.newUserId, out status);

        //    member.ShouldBeNull();
        //    status.ShouldEqual(MembershipCreateStatus.ProviderError);
        //};

        //It should_fail_if_the_user_is_rejected = () =>
        //{
        //    MembershipCreateStatus status;
        //    MembershipUser member;

        //    member = membershipProvider.CreateUser(MembershipProviderContext.newUsername, MembershipProviderContext.newPassword, MembershipProviderContext.newEmail2, MembershipProviderContext.newPasswordQuestion, MembershipProviderContext.newPasswordAnswer, false, MembershipProviderContext.newUserId, out status);

        //    member.ShouldBeNull();
        //    status.ShouldEqual(MembershipCreateStatus.UserRejected);
        //};

    }

    [Subject("Membership Provider")]
    public class delete_user_method : MembershipProviderContext
    {
        It should_delete_the_user = () =>
        {
            bool success = membershipProvider.DeleteUser(MembershipProviderContext.deleteUsername, false);
            success.ShouldBeTrue();
        };

        // Should delete role data, profile data, and all other data associated with that user
        //It should_delete_associated_data_if_deleteAllRelatedData_is_true;

    }

    [Subject("Membership Provider")]
    public class find_users_by_email_method : MembershipProviderContext
    {
        It should_return_an_empty_collection_if_no_matching_email_is_found = () =>
        {
            int TotalRecords;
            MembershipUserCollection users = membershipProvider.FindUsersByEmail(MembershipProviderContext.matchNoEmail, 0, 10, out TotalRecords);

            TotalRecords.ShouldEqual(7);
            users.Count.ShouldEqual(0);
        };

        It should_find_one_user_with_an_exact_email = () =>
        {
            int TotalRecords;
            MembershipUserCollection users = membershipProvider.FindUsersByEmail(MembershipProviderContext.matchExactEmail, 0, 10, out TotalRecords);

            TotalRecords.ShouldEqual(7);
            users.Count.ShouldEqual(1);
        };

        It should_find_one_or_more_users_with_a_wildcard_email = () =>
        {
            int TotalRecords;
            MembershipUserCollection users = membershipProvider.FindUsersByEmail(MembershipProviderContext.matchWildcardEmail, 0, 10, out TotalRecords);

            TotalRecords.ShouldEqual(7);
            users.Count.ShouldEqual(3);
        };

    }

    [Subject("Membership Provider")]
    public class find_users_by_name_method : MembershipProviderContext
    {
        It should_return_an_empty_collection_if_no_matching_username_is_found = () =>
        {
            int TotalRecords;
            MembershipUserCollection users = membershipProvider.FindUsersByName(MembershipProviderContext.matchNoUsername, 0, 10, out TotalRecords);

            TotalRecords.ShouldEqual(7);
            users.Count.ShouldEqual(0);
        };

        It should_find_one_user_with_an_exact_username = () =>
        {
            int TotalRecords;
            MembershipUserCollection users = membershipProvider.FindUsersByName(MembershipProviderContext.matchExactUsername, 0, 10, out TotalRecords);

            TotalRecords.ShouldEqual(7);
            users.Count.ShouldEqual(1);
        };

        It should_find_one_or_more_users_with_a_wildcard_username = () =>
        {
            int TotalRecords;
            MembershipUserCollection users = membershipProvider.FindUsersByName(MembershipProviderContext.matchWildcardUsername, 0, 10, out TotalRecords);

            TotalRecords.ShouldEqual(7);
            users.Count.ShouldEqual(3);
        };

    }

    [Subject("Membership Provider")]
    public class get_all_users_method : MembershipProviderContext
    {
        //It should_return_an_empty_collection_if_there_are_no_users = () =>
        //{
        //    int TotalRecords;
        //    MembershipUserCollection users = membershipProvider.GetAllUsers(0, 10, out TotalRecords);
        //};

        It should_return_all_users = () =>
        {
            int TotalRecords;
            MembershipUserCollection users = membershipProvider.GetAllUsers(0, 10, out TotalRecords);

            TotalRecords.ShouldEqual(7);
            users.Count.ShouldEqual(7);
        };

    }

    [Subject("Membership Provider")]
    public class get_number_of_users_online_method : MembershipProviderContext
    {
        It should_return_the_number_of_users_flagged_as_online = () =>
        {
            int NumOnline = membershipProvider.GetNumberOfUsersOnline();
            NumOnline.ShouldEqual(2);
        };

    }

    [Subject("Membership Provider")]
    public class get_password_method : MembershipProviderContext
    {
        It should_return_the_users_password = () =>
        {
            // Password required
            string password = membershipProvider.GetPassword(MembershipProviderContext.goodUsername, MembershipProviderContext.oldPasswordAnswer);
            password.ShouldEqual(goodPassword);
        };

        It should_throw_a_ProviderException_when_the_username_is_invalid = () =>
        {
            string password;
            bool CorrectType = false;

            try
            {
                password = membershipProvider.GetPassword(MembershipProviderContext.invalidUsername, MembershipProviderContext.oldPasswordAnswer);
            }
            catch (Exception ex)
            {
                CorrectType = (ex is ProviderException);
            }

            CorrectType.ShouldBeTrue();
        };

        It should_throw_a_MembershipPasswordException_when_the_password_is_invalid = () =>
        {
            string password;
            bool CorrectType = false;

            try
            {
                password = membershipProvider.GetPassword(MembershipProviderContext.goodUsername, MembershipProviderContext.newPasswordAnswer);
            }
            catch (Exception ex)
            {
                CorrectType = (ex is MembershipPasswordException);
            }

            CorrectType.ShouldBeTrue();
        };

        It should_throw_a_MembershipPasswordException_when_the_user_is_locked_out = () =>
        {
            string password;
            bool CorrectType = false;

            try
            {
                User user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.lockedOutUsername);
                user.LockedOut.ShouldBeTrue();
                membershipProvider.EnablePasswordRetrieval.ShouldBeTrue();
                membershipProvider.PasswordFormat.ShouldNotEqual(MembershipPasswordFormat.Hashed);
                password = membershipProvider.GetPassword(MembershipProviderContext.lockedOutUsername, MembershipProviderContext.oldPasswordAnswer);
            }
            catch (Exception ex)
            {
                CorrectType = (ex is MembershipPasswordException);
                ex.Message.ShouldEqual("The user account is locked.");
            }

            CorrectType.ShouldBeTrue();
        };

        It should_throw_a_ProviderException_when_the_password_format_is_hashed = () =>
        {
            string password;
            bool CorrectType = false;

            try
            {
                MembershipPasswordFormat savedValue = membershipProvider.PasswordFormat;
                ((NehemiahMembershipProvider)membershipProvider).passwordFormat = MembershipPasswordFormat.Hashed;

                password = membershipProvider.GetPassword(MembershipProviderContext.goodUsername, MembershipProviderContext.oldPasswordAnswer);

                ((NehemiahMembershipProvider)membershipProvider).passwordFormat = savedValue;
            }
            catch (Exception ex)
            {
                CorrectType = (ex is ProviderException);
                ex.Message.ShouldEqual("Cannot retrieve Hashed passwords.");
            }

            CorrectType.ShouldBeTrue();
        };

        It should_throw_a_NotSupportedException_when_password_retrieval_is_not_supported = () =>
        {
            string password;
            bool CorrectType = false;

            try
            {
                bool savedValue = membershipProvider.EnablePasswordRetrieval;
                ((NehemiahMembershipProvider)membershipProvider).enablePasswordRetrieval = false;

                password = membershipProvider.GetPassword(MembershipProviderContext.goodUsername, MembershipProviderContext.oldPasswordAnswer);

                ((NehemiahMembershipProvider)membershipProvider).enablePasswordRetrieval = savedValue;
            }
            catch (Exception ex)
            {
                CorrectType = (ex is NotSupportedException);
                ex.Message.ShouldEqual("Password Retrieval Not Enabled.");
            }

            CorrectType.ShouldBeTrue();
        };

    }

    [Subject("Membership Provider")]
    public class get_user_by_user_key_method : MembershipProviderContext
    {
        static MembershipUser member = null;

        Because of = () =>
        {
            member = membershipProvider.GetUser(MembershipProviderContext.goodUserId, true);
        };

        It should_return_the_user_identified_by_the_provider_key = () =>
        {
            member.UserName.ShouldEqual(MembershipProviderContext.goodUsername);
        };

        It should_update_the_last_activity_date_for_the_username = () =>
        {
            User user = providerRepository.GetUserByKey(MembershipProviderContext.applicationName, MembershipProviderContext.goodUserId);
            user.LastActivityDate.ShouldBeGreaterThan(DateTime.UtcNow.AddSeconds(-10));
            user.LastActivityDate.ShouldBeLessThan(DateTime.UtcNow.AddSeconds(10));
        };

        It should_return_null_if_the_provider_key_is_not_found = () =>
        {
            member = membershipProvider.GetUser(MembershipProviderContext.noUserId, true);
            member.ShouldBeNull();
        };

        It should_return_null_if_the_provider_key_is_invalid = () =>
        {
            member = membershipProvider.GetUser(MembershipProviderContext.invalidUserId, true);
            member.ShouldBeNull();
        };

    }

    [Subject("Membership Provider")]
    public class get_user_by_user_name_method : MembershipProviderContext
    {
        static MembershipUser member = null;

        Because of = () =>
        {
            member = membershipProvider.GetUser(MembershipProviderContext.goodUsername, true);
        };

        It should_return_the_user_identified_by_the_provider_key = () =>
        {
            member.UserName.ShouldEqual(MembershipProviderContext.goodUsername);
        };

        It should_update_the_last_activity_date_for_the_username = () =>
        {
            User user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.goodUsername);
            user.LastActivityDate.ShouldBeGreaterThan(DateTime.UtcNow.AddSeconds(-10));
            user.LastActivityDate.ShouldBeLessThan(DateTime.UtcNow.AddSeconds(10));
        };

        It should_return_null_if_the_username_is_not_found = () =>
        {
            member = membershipProvider.GetUser(MembershipProviderContext.noUsername, true);
            member.ShouldBeNull();
        };

        It should_return_null_if_the_username_is_invalid = () =>
        {
            member = membershipProvider.GetUser(MembershipProviderContext.invalidUsername, true);
            member.ShouldBeNull();
        };

    }

    [Subject("Membership Provider")]
    public class get_user_name_by_email_method : MembershipProviderContext
    {
        static string username = null;

        It should_return_the_user_identified_by_the_email = () =>
        {
            username = membershipProvider.GetUserNameByEmail(MembershipProviderContext.goodEmail);
            username.ShouldEqual(MembershipProviderContext.goodUsername);
        };

        It should_return_an_empty_string_if_the_user_is_not_found = () =>
        {
            username = membershipProvider.GetUserNameByEmail(MembershipProviderContext.noEmail);
            username.ShouldEqual(string.Empty);
        };

    }

    [Subject("Membership Provider")]
    public class reset_password_method : MembershipProviderContext
    {
        static string password = "";
        static User user = null;

        Because of = () =>
        {
            password = membershipProvider.ResetPassword(MembershipProviderContext.goodUsername, MembershipProviderContext.oldPasswordAnswer);
            user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.goodUsername);
        };

        It should_change_the_users_password = () =>
        {
            user.Password.ShouldNotBeEmpty();
            user.Password.ShouldNotEqual(MembershipProviderContext.goodPassword);
        };

        It should_throw_a_ProviderException_when_the_username_is_invalid = () =>
        {
            string password;
            bool CorrectType = false;

            try
            {
                password = membershipProvider.ResetPassword(MembershipProviderContext.invalidUsername, MembershipProviderContext.oldPasswordAnswer);
            }
            catch (Exception ex)
            {
                CorrectType = (ex is ProviderException);
                ex.Message.ShouldEqual(String.Format("User {0} does not exist.", MembershipProviderContext.invalidUsername));
            }

            CorrectType.ShouldBeTrue();
        };

        It should_throw_a_MembershipPasswordException_when_the_password_answer_is_incorrect = () =>
        {
            string password;
            bool CorrectType = false;

            try
            {
                password = membershipProvider.ResetPassword(MembershipProviderContext.goodUsername, "");
            }
            catch (Exception ex)
            {
                CorrectType = (ex is MembershipPasswordException);
                ex.Message.ShouldEqual("Incorrect password answer.");
            }

            CorrectType.ShouldBeTrue();
        };

        It should_throw_a_NotSupportedException_when_EnablePasswordReset_is_not_true = () =>
        {
        };

        It should_throw_a_MembershipPasswordException_if_the_user_is_locked_out = () =>
        {
        };

        It should_throw_a_ProviderException_when_the_password_is_invalid = () =>
        {
        };

    }

    [Subject("Membership Provider")]
    public class unlock_user_method : MembershipProviderContext
    {
        It should_unlock_the_users_account = () =>
        {
            bool success = membershipProvider.UnlockUser(MembershipProviderContext.lockedOutUsername);
            success.ShouldBeTrue();

            User user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.lockedOutUsername);
            user.LockedOut.ShouldBeFalse();
        };

    }

    [Subject("Membership Provider")]
    public class update_user_method : MembershipProviderContext
    {
        static User user = null;
        static DateTime lastTimeStamp = MembershipProviderContext.newDate;  // DateTime.UtcNow;

        Because of = () =>
        {
            MembershipUser member = membershipProvider.GetUser(MembershipProviderContext.updateUsername, false);
            MembershipUser udpatedMember = new MembershipUser
                ( member.ProviderName
                , member.UserName
                , member.ProviderUserKey
                , MembershipProviderContext.updateEmail     // member.Email
                , MembershipProviderContext.updateQuestion  // member.PasswordQuestion
                , MembershipProviderContext.updateComment   // member.Comment
                , true                                      // member.IsApproved
                , false                                     // member.IsLockedOut
                , MembershipProviderContext.newDate         // member.CreationDate
                , lastTimeStamp                             // member.LastLoginDate
                , lastTimeStamp                             // member.LastActivityDate
                , lastTimeStamp                             // member.LastPasswordChangedDate
                , lastTimeStamp                             // member.LastLockoutDate
                );

            membershipProvider.UpdateUser(udpatedMember);
            user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.updateUsername);
        };

        It should_update_the_comment = () =>
        {
            user.Comment.ShouldEqual(MembershipProviderContext.updateComment);
        };

        It should_update_the_creation_date = () =>
        {
            user.CreationDate.ShouldEqual(MembershipProviderContext.newDate);
        };

        It should_update_the_email = () =>
        {
            user.Email.ShouldEqual(MembershipProviderContext.updateEmail);
        };

        It should_update_the_last_activity_date = () =>
        {
            user.LastActivityDate.ShouldEqual(lastTimeStamp);
        };

        It should_update_the_last_locked_out_date = () =>
        {
            user.LastLockedOutDate.ShouldEqual(lastTimeStamp);
        };

        It should_update_the_last_login_date = () =>
        {
            user.LastLoginDate.ShouldEqual(lastTimeStamp);
        };

        It should_update_the_last_password_changed_date = () =>
        {
            user.LastPasswordChangedDate.ShouldEqual(lastTimeStamp);
        };

        It should_update_the_locked_out_flag = () =>
        {
            user.LockedOut.ShouldBeFalse();
        };

        It should_update_the_password_question = () =>
        {
            user.PasswordQuestion.ShouldEqual(MembershipProviderContext.updateQuestion);
        };

    }

    [Subject("Membership Provider")]
    public class validate_user_method : MembershipProviderContext
    {
        static bool success;

        Because of = () =>
        {
            success = membershipProvider.ValidateUser(MembershipProviderContext.goodUsername, MembershipProviderContext.goodPassword);
        };

        It should_return_true_if_the_user_exists_is_approved_and_is_not_locked_out = () =>
        {
            success.ShouldBeTrue();
        };

        It should_update_the_last_login_date_on_successful_validation = () =>
        {
            User user = providerRepository.GetUserByUserName(MembershipProviderContext.applicationName, MembershipProviderContext.goodUsername);
            user.LastLoginDate.ShouldBeGreaterThan(DateTime.UtcNow.AddSeconds(-10));
            user.LastLoginDate.ShouldBeLessThan(DateTime.UtcNow.AddSeconds(10));
        };

        It should_return_false_if_the_user_does_not_exist = () =>
        {
            success = membershipProvider.ValidateUser(MembershipProviderContext.noUsername, MembershipProviderContext.goodPassword);
            success.ShouldBeFalse();
        };

        It should_return_false_if_the_user_is_locked_out = () =>
        {
            success = membershipProvider.ValidateUser(MembershipProviderContext.lockedOutUsername, MembershipProviderContext.goodPassword);
            success.ShouldBeFalse();
        };

        It should_return_false_if_the_user_is_not_approved = () =>
        {
            success = membershipProvider.ValidateUser(MembershipProviderContext.unapprovedUsername, MembershipProviderContext.goodPassword);
            success.ShouldBeFalse();
        };

    }

    public abstract class MembershipProviderContext
    {
        protected static IproviderRepository providerRepository;

        // Test Data
        protected static string applicationName = "Nehemiah";
        protected static int maxInvalidPasswordAttempts = 3;
        protected static int passwordAttemptWindow = 12;
        protected static int minRequiredAlphaNumericCharacters = 7;
        protected static int minRequiredPasswordLength = 8;
        protected static string passwordStrengthRegularExpression = "";
        protected static bool enablePasswordReset = true;
        protected static bool enablePasswordRetrieval = true;
        protected static bool requiresQuestionAndAnswer = true;
        protected static bool requiresUniqueEmail = true;
        protected static string passwordFormat = "Clear";

        protected static string goodUsername = "UserName";
        protected static string newUsername = "NewUsername";
        protected static string invalidUsername = "a";
        protected static string deleteUsername = "DeleteMe";
        protected static string noUsername = "NoUserWithThisUsername";
        protected static string updateUsername = "UpdateUser";
        protected static string lockedOutUsername = "LockedOutUser";
        protected static string unapprovedUsername = "NotApprovedUser";

        protected static Guid goodUserId = new Guid("01234567-89AB-CDEF-0123-456789ABCDEF");
        protected static Guid newUserId = new Guid("12121212-1212-1212-1212-121212121212");
        protected static object invalidUserId = "aBcDeF";
        protected static Guid noUserId = Guid.Empty;
        protected static Guid updateuserId = new Guid("44444444-4444-4444-4444-444444444444");

        protected static string goodEmail = "me@there.com";
        protected static string newEmail = "new@there.com";
        protected static string newEmail2 = "new@second.com";
        protected static string invalidEmail = "bademail";
        protected static string noEmail = "no@noway.com";
        protected static string updateEmail = "udpated@email.com";

        protected static string goodPassword = "GoodPassword";
        protected static string newPassword = "NewPassword";
        protected static string invalidPassword = "pwd";
        protected static string shortPassword = "123";
        protected static string badAlphaPassword = "abc******";

        protected static string oldPasswordQuestion = "This is an old question";
        protected static string newPasswordQuestion = "This is a new question";
        protected static string updateQuestion = "This is an updated question";

        protected static string oldPasswordAnswer = "Old answer";
        protected static string newPasswordAnswer = "New answer";

        protected static string matchNoEmail = "noemail@anywhere.com";
        protected static string matchExactEmail = "me3@there.com";
        protected static string matchWildcardEmail = "there.com";

        protected static string matchNoUsername = "NoUsername";
        protected static string matchExactUsername = "UserName3";
        protected static string matchWildcardUsername = "UserName";

        protected static DateTime newDate = new DateTime(2010, 1, 1, 2, 2, 2);

        protected static string updateComment = "This is an updated comment.";

        protected static string name;
        protected static MembershipProvider membershipProvider;
        protected static NameValueCollection config;

        Establish context = () =>
        {
            name = "Nehemiah";

            config = new NameValueCollection();
            config.Add("applicationName", applicationName);
            config.Add("maxInvalidPasswordAttempts", maxInvalidPasswordAttempts.ToString());
            config.Add("passwordAttemptWindow", passwordAttemptWindow.ToString());
            config.Add("minRequiredAlphaNumericCharacters", minRequiredAlphaNumericCharacters.ToString());
            config.Add("minRequiredPasswordLength", minRequiredPasswordLength.ToString());
            config.Add("passwordStrengthRegularExpression", passwordStrengthRegularExpression);
            config.Add("enablePasswordReset", enablePasswordReset.ToString());
            config.Add("enablePasswordRetrieval", enablePasswordRetrieval.ToString());
            config.Add("requiresQuestionAndAnswer", requiresQuestionAndAnswer.ToString());
            config.Add("requiresUniqueEmail", requiresUniqueEmail.ToString());
            config.Add("passwordFormat", passwordFormat);

            providerRepository = new MockProviderRepository();

            membershipProvider = new NehemiahMembershipProvider(providerRepository, applicationName);
            membershipProvider.Initialize(MembershipProviderContext.name, MembershipProviderContext.config);

        };

    }

}       // End Namespace

SolutionExplorer11_02
In the Nehemiah.Specs project add a reference to the Nehemiah.Data project. In the Nehemiah.Providers project add a reference to the Nehemiah.data project. Now our User Repository interface is known to both projects.

What we need now is a mock ProviderRepository that implements our IProviderRepository interface. Create a new folder named Repositories in the Nehemiah.Specs project. Then add a class file to that folder and name it MockProviderRepository.cs. The code for this class is shown below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nehemiah.Data;
using Nehemiah.Data.Models;

namespace Nehemiah.Specs.Repositories
{
    public class MockProviderRepository : IProviderRepository
    {

        IList<User> UserList;

        public MockProviderRepository()
        {
            UserList = new List<User>
            {
                new User { ApplicationName = "Nehemiah"
                         , Approved = true
                         , Comment = "No comment"
                         , CreationDate = DateTime.UtcNow
                         , Email = "me@there.com"
                         , FailedPasswordAnswerAttemptCount = 0
                         , FailedPasswordAnswerAttemptStartWindow = null
                         , FailedPasswordAttemptCount = 0
                         , FailedPasswordAttemptWindowStart = null
                         , LastActivityDate = DateTime.UtcNow
                         , LastLockedOutDate = null
                         , LastLoginDate = null
                         , LastPasswordChangedDate = null
                         , LockedOut = false
                         , Online = true
                         , Password = "GoodPassword"
                         , PasswordAnswer = "Old answer"
                         , PasswordQuestion = "This is an old question"
                         , UserId = new Guid("01234567-89AB-CDEF-0123-456789ABCDEF")
                         , UserName = "UserName"
                } ,
                new User { ApplicationName = "Nehemiah"
                         , Approved = true
                         , Comment = "No comment"
                         , CreationDate = DateTime.UtcNow
                         , Email = "me2@there.com"
                         , FailedPasswordAnswerAttemptCount = 0
                         , FailedPasswordAnswerAttemptStartWindow = null
                         , FailedPasswordAttemptCount = 0
                         , FailedPasswordAttemptWindowStart = null
                         , LastActivityDate = DateTime.UtcNow
                         , LastLockedOutDate = null
                         , LastLoginDate = null
                         , LastPasswordChangedDate = null
                         , LockedOut = false
                         , Online = true
                         , Password = "GoodPassword"
                         , PasswordAnswer = "Old answer"
                         , PasswordQuestion = "This is an old question"
                         , UserId = new Guid("22222222-2222-2222-2222-222222222222")
                         , UserName = "UserName2"
                } ,
                new User { ApplicationName = "Nehemiah"
                         , Approved = true
                         , Comment = "No comment"
                         , CreationDate = DateTime.UtcNow
                         , Email = "me3@there.com"
                         , FailedPasswordAnswerAttemptCount = 0
                         , FailedPasswordAnswerAttemptStartWindow = null
                         , FailedPasswordAttemptCount = 0
                         , FailedPasswordAttemptWindowStart = null
                         , LastActivityDate = null
                         , LastLockedOutDate = null
                         , LastLoginDate = null
                         , LastPasswordChangedDate = null
                         , LockedOut = false
                         , Online = false
                         , Password = "GoodPassword"
                         , PasswordAnswer = "Old answer"
                         , PasswordQuestion = "This is an old question"
                         , UserId = new Guid("33333333-3333-3333-3333-333333333333")
                         , UserName = "UserName3"
                } ,
                new User { ApplicationName = "Nehemiah"
                         , Approved = false
                         , Comment = ""
                         , CreationDate = DateTime.UtcNow
                         , Email = "update@somewhere.com"
                         , FailedPasswordAnswerAttemptCount = 0
                         , FailedPasswordAnswerAttemptStartWindow = null
                         , FailedPasswordAttemptCount = 0
                         , FailedPasswordAttemptWindowStart = null
                         , LastActivityDate = null
                         , LastLockedOutDate = null
                         , LastLoginDate = null
                         , LastPasswordChangedDate = null
                         , LockedOut = true
                         , Online = false
                         , Password = "GoodPassword"
                         , PasswordAnswer = "Old answer"
                         , PasswordQuestion = "This is an old question"
                         , UserId = new Guid("44444444-4444-4444-4444-444444444444")
                         , UserName = "UpdateUser"
                } ,
                new User { ApplicationName = "Nehemiah"
                         , Approved = false
                         , Comment = ""
                         , CreationDate = DateTime.UtcNow
                         , Email = "update@somewhere.com"
                         , FailedPasswordAnswerAttemptCount = 0
                         , FailedPasswordAnswerAttemptStartWindow = null
                         , FailedPasswordAttemptCount = 0
                         , FailedPasswordAttemptWindowStart = null
                         , LastActivityDate = null
                         , LastLockedOutDate = null
                         , LastLoginDate = null
                         , LastPasswordChangedDate = null
                         , LockedOut = true
                         , Online = false
                         , Password = "GoodPassword"
                         , PasswordAnswer = "Old answer"
                         , PasswordQuestion = "This is an old question"
                         , UserId = new Guid("55555555-5555-5555-5555-555555555555")
                         , UserName = "LockedOutUser"
                } ,
                new User { ApplicationName = "Nehemiah"
                         , Approved = false
                         , Comment = ""
                         , CreationDate = DateTime.UtcNow
                         , Email = "update@somewhere.com"
                         , FailedPasswordAnswerAttemptCount = 0
                         , FailedPasswordAnswerAttemptStartWindow = null
                         , FailedPasswordAttemptCount = 0
                         , FailedPasswordAttemptWindowStart = null
                         , LastActivityDate = null
                         , LastLockedOutDate = null
                         , LastLoginDate = null
                         , LastPasswordChangedDate = null
                         , LockedOut = false
                         , Online = false
                         , Password = "GoodPassword"
                         , PasswordAnswer = "Old answer"
                         , PasswordQuestion = "This is an old question"
                         , UserId = new Guid("66666666-6666-6666-6666-666666666666")
                         , UserName = "NotApprovedUser"
                } ,
                new User { ApplicationName = "Nehemiah"
                         , Approved = true
                         , Comment = null
                         , CreationDate = DateTime.UtcNow
                         , Email = "deleteme@delete.com"
                         , FailedPasswordAnswerAttemptCount = 0
                         , FailedPasswordAnswerAttemptStartWindow = null
                         , FailedPasswordAttemptCount = 0
                         , FailedPasswordAttemptWindowStart = null
                         , LastActivityDate = null
                         , LastLockedOutDate = null
                         , LastLoginDate = null
                         , LastPasswordChangedDate = null
                         , LockedOut = false
                         , Online = false
                         , Password = "GoodPassword"
                         , PasswordAnswer = "Old answer"
                         , PasswordQuestion = "This is an old question"
                         , UserId = new Guid("11111111-2222-3333-4444-555555555555")
                         , UserName = "DeleteMe"
                }
            };

        }

        public bool Add(string applicationName, User user)
        {
            UserList.Add(user);
            User rec = UserList.Where(u => u.ApplicationName == applicationName && u.UserName == user.UserName).SingleOrDefault();
            return (rec != null);
        }

        public bool DeleteUser(string applicationName, string username)
        {
            User rec = UserList.Where(u => u.ApplicationName == applicationName && u.UserName == username).SingleOrDefault();
            UserList.Remove(rec);
            return true;
        }

        public IEnumerable<User> GetUsers(string applicationName)
        {
            return UserList.Where(u => u.ApplicationName == applicationName);
        }

        public User GetUserByEmail(string applicationName, string email)
        {
            return GetUsers(applicationName).Where(u => u.Email == email).SingleOrDefault();
        }

        public User GetUserByKey(string applicationName, Guid userId)
        {
            return GetUsers(applicationName).Where(u => u.UserId == userId).SingleOrDefault();
        }

        public User GetUserByUserName(string applicationName, string username)
        {
            return GetUsers(applicationName).Where(u => u.UserName == username).SingleOrDefault();
        }

        public IList<User> GetUserList(string applicationName, int index, int pageSize)
        {
            return GetUsers(applicationName).OrderBy(u => u.UserName).ToList();
        }

        public IList<User> GetUserListByEmail(string applicationName, string email, int index, int pageSize)
        {
            return GetUsers(applicationName).Where(u => u.Email.Contains(email) || u.Email == email).OrderBy(u => u.Email).ToList();
        }

        public IList<User> GetUserListByUserName(string applicationName, string username, int index, int pageSize)
        {
            return GetUsers(applicationName).Where(u => u.UserName.Contains(username) || u.UserName == username).OrderBy(u => u.UserName).ToList();
        }

        public int NumberOfUsers(string applicationName)
        {
            return GetUsers(applicationName).Count();
        }

        public int NumberOfUsersOnline(string applicationName, int timeWindow)
        {
            return GetUsers(applicationName).Where(u => u.LastActivityDate >= DateTime.UtcNow.AddMinutes(-1 * timeWindow)).Count();
        }

        public bool Save(string applicationName, User user)
        {
            bool success = true;
            User rec = UserList.Where(u => u.ApplicationName == applicationName && u.UserId == user.UserId).SingleOrDefault();

            if (rec == null)
            {
                UserList.Add(user);
            }
            else
            {
                UserList.Remove(rec);
                UserList.Add(user);
            }

            return success;
        }

    }   // End Class

}       // End Namespace

ReportHtml_01

If you rebuild the solution now you should see the error shown above when you open the Report.html file. The reason why is we haven’t actually written any code in the NehemiahMembershipProvider.cs file yet. The code for our custom membership provider is listed below.

using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Provider;
using System.Web.Configuration;
using System.Web.Security;
using Nehemiah.Data;
using Nehemiah.Data.Models;
using System.Text;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Data.SqlTypes;

namespace Nehemiah.Providers
{
    public class NehemiahMembershipProvider : MembershipProvider
    {

        #region - Enums -

        private enum FailureType
        {
            Password = 1,
            PasswordAnswer = 2
        }

        #endregion

        #region - Properties -

        private int NewPasswordLength { get; set; }
        private string ConnectionString { get; set; }
        private MachineKeySection MachineKey { get; set; } //Used when determining encryption key values.
        private IProviderRepository ProviderRepository { get; set; }

        private string providerName { get; set; }
        public bool enablePasswordReset { get; set; }
        public bool enablePasswordRetrieval { get; set; }
        public bool requiresQuestionAndAnswer { get; set; }
        public bool requiresUniqueEmail { get; set; }
        public int maxInvalidPasswordAttempts { get; set; }
        public int passwordAttemptWindow { get; set; }
        public MembershipPasswordFormat passwordFormat { get; set; }
        public int minRequiredNonAlphanumericCharacters { get; set; }
        public int minRequiredPasswordLength { get; set; }
        public string passwordStrengthRegularExpression { get; set; }

        public override string Name { get { return providerName; } }

        // The name of the application using the membership provider.
        // ApplicationName is used to scope membership data so that applications can choose whether to share membership data with other applications.
        // This property can be read and written.
        public override string ApplicationName { get; set; }

        // Indicates whether passwords can be retrieved using the provider's GetPassword method.
        // This property is read-only.
        public override bool EnablePasswordRetrieval
        {
            get { return enablePasswordRetrieval; }
        }

        // Indicates whether passwords can be reset using the provider's ResetPassword method.
        // This property is read-only.
        public override bool EnablePasswordReset
        {
            get { return enablePasswordReset; }
        }

        // Indicates whether a password answer must be supplied when calling the provider's GetPassword and ResetPassword methods.
        // This property is read-only.
        public override bool RequiresQuestionAndAnswer
        {
            get { return requiresQuestionAndAnswer; }
        }

        // Works in conjunction with PasswordAttemptWindow to provide a safeguard against password guessing.
        // If the number of consecutive invalid passwords or password questions ("invalid attempts") submitted to the
        // provider for a given user reaches MaxInvalidPasswordAttempts within the number of minutes specified by
        // PasswordAttemptWindow, the user is locked out of the system. The user remains locked out until the provider's
        // UnlockUser method is called to remove the lock.
        //
        // The count of consecutive invalid attempts is incremented when an invalid password or password answer is submitted
        // to the provider's ValidateUser, ChangePassword, ChangePasswordQuestionAndAnswer, GetPassword, and ResetPassword methods.
        //
        // If a valid password or password answer is supplied before the MaxInvalidPasswordAttempts is reached, the count of
        // consecutive invalid attempts is reset to zero. If the RequiresQuestionAndAnswer property is false, invalid password
        // answer attempts are not tracked.
        //
        // This property is read-only.
        public override int MaxInvalidPasswordAttempts
        {
            get { return maxInvalidPasswordAttempts; }
        }

        // For a description, see MaxInvalidPasswordAttempts.
        // This property is read-only.
        public override int PasswordAttemptWindow
        {
            get { return passwordAttemptWindow; }
        }

        // Indicates whether each registered user must have a unique e-mail address.
        // This property is read-only.
        public override bool RequiresUniqueEmail
        {
            get { return requiresUniqueEmail; }
        }

        // Indicates what format that passwords are stored in: clear (plaintext), encrypted, or hashed. Clear and encrypted
        // passwords can be retrieved; hashed passwords cannot.
        // This property is read-only.
        public override MembershipPasswordFormat PasswordFormat
        {
            get { return passwordFormat; }
        }

        // The minimum number of characters required in a password.
        // This property is read-only.
        public override int MinRequiredPasswordLength
        {
            get { return minRequiredPasswordLength; }
        }

        // The minimum number of non-alphanumeric characters required in a password.
        // This property is read-only.
        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return minRequiredNonAlphanumericCharacters; }
        }

        // A regular expression specifying a pattern to which passwords must conform.
        // This property is read-only.
        public override string PasswordStrengthRegularExpression
        {
            get { return passwordStrengthRegularExpression; }
        }

        #endregion

        #region - Constructors -

        public NehemiahMembershipProvider() : base() { }

        public NehemiahMembershipProvider(IProviderRepository repository, string applicationName) : this()
        {
            ProviderRepository = repository;
            ApplicationName = applicationName;
        }

        #endregion

        #region - Override Methods -

        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            //Name = "NehemiahMembershipProvider";
            providerName = "NehemiahMembershipProvider";
            ApplicationName = (String.IsNullOrEmpty(name)) ? Name : name;

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Nehemiah Asp.Net MVC membership provider");
            }

            //Initialize the abstract base class.
            base.Initialize(name, config);

            NewPasswordLength = 12;

            ApplicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            maxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5"));
            passwordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10"));
            minRequiredNonAlphanumericCharacters = Convert.ToInt32(GetConfigValue(config["minRequiredAlphaNumericCharacters"], "1"));
            minRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7"));
            passwordStrengthRegularExpression = Convert.ToString(GetConfigValue(config["passwordStrengthRegularExpression"], String.Empty));
            enablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true"));
            enablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true"));
            requiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "false"));
            requiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true"));

            string temp_format = config["passwordFormat"];
            if (String.IsNullOrEmpty(temp_format) == true)
            {
                temp_format = "Clear";
            }

            switch (temp_format.ToLower())
            {
                case "hashed":
                    passwordFormat = MembershipPasswordFormat.Hashed;
                    if (EnablePasswordRetrieval == true)
                    {
                        // Todo: write a spec for this exception
                        throw new ProviderException("Enable password Retrieval cannot be turned on if the password format is Hashed.");
                    }
                    break;

                case "encrypted":
                    passwordFormat = MembershipPasswordFormat.Encrypted;
                    break;

                case "clear":
                    passwordFormat = MembershipPasswordFormat.Clear;
                    break;

                default:
                    throw new ProviderException("Password format not supported.");
            }

            // Get our connection string
            ConnectionString = ConfigurationManager.AppSettings["Connection"];

            // Get encryption and decryption key information from the configuration.
            System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            MachineKey = cfg.GetSection("system.web/machineKey") as MachineKeySection;

            if (MachineKey.ValidationKey.Contains("AutoGenerate"))
            {
                if (passwordFormat != MembershipPasswordFormat.Clear)
                    throw new ProviderException("Hashed or Encrypted passwords are not supported with auto-generated keys.");
            }
        }

        // Takes, as input, a user name, a password (the user's current password), and a new password and
        // updates the password in the membership data source. ChangePassword returns true if the password
        // was updated successfully. Otherwise, it returns false.
        // Before changing a password, ChangePassword calls the provider's virtual OnValidatingPassword
        // method to validate the new password. It then changes the password or cancels the action based on
        // the outcome of the call.
        //
        // If the user name, password, new password, or password answer is not valid, ChangePassword does
        // not throw an exception; it simply returns false.
        //
        // Following a successful password change, ChangePassword updates the user's LastPasswordChangedDate.
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            bool success = false;

            User user = ProviderRepository.GetUserByUserName(ApplicationName, username);

            if (user == null || ValidateUser(ref user, oldPassword) == false || ValidatePassword(newPassword) == false)
                return success;

            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null)
                    throw args.FailureInformation;
                else
                    throw new MembershipPasswordException("Change password canceled due to new password validation failure.");
            }

            try
            {
                user.Password = EncodePassword(newPassword);
                user.LastPasswordChangedDate = DateTime.UtcNow;
                success = ProviderRepository.Save(ApplicationName, user);
            }
            catch
            {
                success = false;
            }

            return success;
        }

        // Takes, as input, a user name, password, password question, and password answer and updates the
        // password question and answer in the data source if the user name and password are valid. This method
        // returns true if the password question and answer are successfully updated. Otherwise, it returns false.
        // ChangePasswordQuestionAndAnswer returns false if either the user name or password is invalid.
        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            bool success = false;
            User user = ProviderRepository.GetUserByUserName(ApplicationName, username);

            if (user == null || ValidateUser(ref user, password) == false)
                return success;

            try
            {
                user.PasswordAnswer = newPasswordAnswer;
                user.PasswordQuestion = newPasswordQuestion;
                success = ProviderRepository.Save(ApplicationName, user);
            }
            catch (Exception ex)
            {
                success = false;
                //throw new ProviderException(ex.Message);
            }

            return success;
        }

        // Takes, as input, a user name, password, e-mail address, and other information and adds a new user
        // to the membership data source. CreateUser returns a MembershipUser object representing the newly
        // created user. It also accepts an out parameter (in Visual Basic, ByRef) that returns a
        // MembershipCreateStatus value indicating whether the user was successfully created or, if the user
        // was not created, the reason why. If the user was not created, CreateUser returns null.
        // Before creating a new user, CreateUser calls the provider's virtual OnValidatingPassword method to
        // validate the supplied password. It then creates the user or cancels the action based on the outcome of the call.
        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            string pattern = @"^[a-z][a-z|0-9|]*([_][a-z|0-9]+)*([.][a-z|"
                           + @"0-9]+([_][a-z|0-9]+)*)?@[a-z][a-z|0-9|]*([-][a-z|0-9]+)*\.([a-z]"
                           + @"[a-z|0-9]*(\.[a-z][a-z|0-9]*)?)$";
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
            User user = null;

            OnValidatingPassword(args);
            if (args.Cancel || ValidatePassword(password) == false)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresQuestionAndAnswer == true)
            {
                if (string.IsNullOrEmpty(passwordQuestion) == true)
                {
                    status = MembershipCreateStatus.InvalidQuestion;
                    return null;
                }

                if (string.IsNullOrEmpty(passwordAnswer) == true)
                {
                    status = MembershipCreateStatus.InvalidAnswer;
                    return null;
                }
            }

            System.Text.RegularExpressions.Match match = System.Text.RegularExpressions.Regex.Match(email, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            if (!match.Success)
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }

            user = ProviderRepository.GetUserByEmail(ApplicationName, email);
            if (requiresUniqueEmail && user != null)
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            if (providerUserKey is Guid && (Guid)providerUserKey != Guid.Empty)
            {
                user = ProviderRepository.GetUserByKey(ApplicationName, (Guid)providerUserKey);
                if (user != null)
                {
                    status = MembershipCreateStatus.DuplicateProviderUserKey;
                    return null;
                }
            }
            else
            {
                status = MembershipCreateStatus.InvalidProviderUserKey;
                return null;
            }

            user = ProviderRepository.GetUserByUserName(ApplicationName, username);

            if (user == null)
            {
                try
                {
                    user = new User(ApplicationName, username, email, EncodePassword(password), passwordQuestion, EncodePassword(passwordAnswer), isApproved, null);
                    ProviderRepository.Save(ApplicationName, user);
                    status = MembershipCreateStatus.Success;
                }
                catch
                {
                    status = MembershipCreateStatus.UserRejected;
                    return null;
                }

                return GetUser(username, false);
            }
            else
            {
                status = MembershipCreateStatus.DuplicateUserName;
            }

            return null;
        }

        // Takes, as input, a byte array containing an encrypted password and returns a byte array containing
        // the password in plaintext form. The default implementation in MembershipProvider decrypts the password
        // using <machineKey>'s decryptionKey, but throws an exception if the decryption key is autogenerated.
        // Override only if you want to customize the decryption process. Do not call the base class's
        // DecryptPassword method if you override this method.
        //public override byte[] DecryptPassword(byte[] encodedPassword)
        //{
        //    throw new NotImplementedException();
        //}

        // Takes, as input, a user name and deletes that user from the membership data source. DeleteUser returns
        // true if the user was successfully deleted. Otherwise, it returns false.
        // DeleteUser takes a third parameter-a Boolean named deleteAllRelatedData-that specifies whether related
        // data for that user should be deleted also. If deleteAllRelatedData is true, DeleteUser should delete
        // role data, profile data, and all other data associated with that user.
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            bool success = false;
            if ((success = ProviderRepository.Delete(ApplicationName, username)) && deleteAllRelatedData == true)
            {
                // Todo: Process commands to delete all data for the user in the database.
            }

            return success;
        }

        // Takes, as input, a byte array containing a plaintext password and returns a byte array containing the
        // password in encrypted form. The default implementation in MembershipProvider encrypts the password
        // using <machineKey>'s decryptionKey, but throws an exception if the decryption key is autogenerated.
        // Override only if you want to customize the encryption process. Do not call the base class's
        // EncryptPassword method if you override this method.
        //public override byte[] EncryptPassword(byte[] password)
        //{
        //    throw new NotImplementedException();
        //}

        // Returns a MembershipUserCollection containing MembershipUser objects representing users whose e-mail
        // addresses match the emailToMatch input parameter. Wildcard syntax is data source-dependent. MembershipUser
        // objects in the MembershipUserCollection are sorted by e-mail address. If FindUsersByEmail finds no
        // matching users, it returns an empty MembershipUserCollection.
        // For an explanation of the pageIndex, pageSize, and totalRecords parameters, see the GetAllUsers method.
        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {

            int startIndex = pageSize * pageIndex;
            MembershipUserCollection membershipUsers = new MembershipUserCollection();
            totalRecords = ProviderRepository.NumberOfUsers(ApplicationName);

            try
            {
                foreach (User user in ProviderRepository.GetUserListByEmail(ApplicationName, emailToMatch, pageIndex, pageSize))
                {
                    membershipUsers.Add(GetMembershipUserFromUser(user));
                }

            }
            catch (Exception ex)
            {
            }

            return membershipUsers;
        }

        // Returns a MembershipUserCollection containing MembershipUser objects representing users whose user names
        // match the usernameToMatch input parameter. Wildcard syntax is data source-dependent. MembershipUser objects
        // in the MembershipUserCollection are sorted by user name. If FindUsersByName finds no matching users, it
        // returns an empty MembershipUserCollection.
        // For an explanation of the pageIndex, pageSize, and totalRecords parameters, see the GetAllUsers method.
        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {

            int startIndex = pageSize * pageIndex;
            MembershipUserCollection membershipUsers = new MembershipUserCollection();
            totalRecords = ProviderRepository.NumberOfUsers(ApplicationName);

            try
            {
                foreach (User user in ProviderRepository.GetUserListByUserName(ApplicationName, usernameToMatch, pageIndex, pageSize))
                {
                    membershipUsers.Add(GetMembershipUserFromUser(user));
                }

            }
            catch (Exception ex)
            {
            }

            return membershipUsers;
        }

        // Returns a MembershipUserCollection containing MembershipUser objects representing all registered users. If
        // there are no registered users, GetAllUsers returns an empty MembershipUserCollection
        // The results returned by GetAllUsers are constrained by the pageIndex and pageSize input parameters. pageSize
        // specifies the maximum number of MembershipUser objects to return. pageIndex identifies which page of results
        // to return. Page indexes are 0-based.
        //
        // GetAllUsers also takes an out parameter (in Visual Basic, ByRef) named totalRecords that, on return, holds
        // a count of all registered users.
        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {

            int startIndex = pageSize * pageIndex;
            MembershipUserCollection membershipUsers = new MembershipUserCollection();
            totalRecords = ProviderRepository.NumberOfUsers(ApplicationName);

            try
            {
                foreach (User user in ProviderRepository.GetUserList(ApplicationName, pageIndex, pageSize))
                {
                    membershipUsers.Add(GetMembershipUserFromUser(user));
                }

            }
            catch (Exception ex)
            {
            }

            return membershipUsers;
        }

        // Returns a count of users that are currently online-that is, whose LastActivityDate is greater than the current
        // date and time minus the value of the membership service's UserIsOnlineTimeWindow property, which can be read
        // from Membership.UserIsOnlineTimeWindow. UserIsOnlineTimeWindow specifies a time in minutes and is set using
        // the <membership> element's userIsOnlineTimeWindow attribute.
        public override int GetNumberOfUsersOnline()
        {
            return ProviderRepository.NumberOfUsersOnline(ApplicationName, Membership.UserIsOnlineTimeWindow);
        }

        // Takes, as input, a user name and a password answer and returns that user's password. If the user name is not
        // valid, GetPassword throws a ProviderException.
        // Before retrieving a password, GetPassword verifies that EnablePasswordRetrieval is true. If
        // EnablePasswordRetrieval is false, GetPassword throws a NotSupportedException. If EnablePasswordRetrieval is
        // true but the password format is hashed, GetPassword throws a ProviderException since hashed passwords cannot,
        // by definition, be retrieved. A membership provider should also throw a ProviderException from Initialize if
        // EnablePasswordRetrieval is true but the password format is hashed.
        //
        // GetPassword also checks the value of the RequiresQuestionAndAnswer property before retrieving a password. If
        // RequiresQuestionAndAnswer is true, GetPassword compares the supplied password answer to the stored password
        // answer and throws a MembershipPasswordException if the two don't match. GetPassword also throws a
        // MembershipPasswordException if the user whose password is being retrieved is currently locked out.
        public override string GetPassword(string username, string answer)
        {

            if (enablePasswordRetrieval == false)
                throw new NotSupportedException("Password Retrieval Not Enabled.");

            if (passwordFormat == MembershipPasswordFormat.Hashed)
                throw new ProviderException("Cannot retrieve Hashed passwords.");

            User user = ProviderRepository.GetUserByUserName(ApplicationName, username);
            string password = String.Empty;
            string passwordAnswer = String.Empty;

            if (user != null)
            {
                if (user.LockedOut == true)
                {
                    throw new MembershipPasswordException("The user account is locked.");
                }

                if (RequiresQuestionAndAnswer && !CheckPassword(answer, user.PasswordAnswer))
                {
                    UpdateFailureCount(ref user, FailureType.PasswordAnswer);
                    throw new MembershipPasswordException("Incorrect password answer.");
                }

                password = user.Password;
                if (PasswordFormat == MembershipPasswordFormat.Encrypted)
                {
                    password = UnEncodePassword(password);
                }

            }

            else
            {
                throw new ProviderException("The user name was not found.");
            }

            return password;
        }

        // Takes, as input, a user name or user ID (the method is overloaded) and a Boolean value indicating whether
        // to update the user's LastActivityDate to show that the user is currently online. GetUser returns a MembershipUser
        // object representing the specified user. If the user name or user ID is invalid (that is, if it doesn't represent
        // a registered user) GetUser returns null (Nothing in Visual Basic).
        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {

            MembershipUser membershipUser = null;

            if (providerUserKey is Guid)
            {
                try
                {
                    User user = ProviderRepository.GetUserByKey(ApplicationName, (Guid)providerUserKey);

                    if (user != null)
                    {
                        if (userIsOnline == true)
                        {
                            user.LastActivityDate = DateTime.UtcNow;
                            ProviderRepository.Save(ApplicationName, user);
                        }

                        membershipUser = GetMembershipUserFromUser(user);
                    }
                }
                catch (Exception ex)
                {
                    throw new ProviderException(ex.Message);
                }
            }

            return membershipUser;
        }

        // Takes, as input, a user name or user ID (the method is overloaded) and a Boolean value indicating whether to
        // update the user's LastActivityDate to show that the user is currently online. GetUser returns a MembershipUser
        // object representing the specified user. If the user name or user ID is invalid (that is, if it doesn't represent
        // a registered user) GetUser returns null (Nothing in Visual Basic).
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {

            MembershipUser membershipUser = null;

            if (String.IsNullOrEmpty(username) == false)
            {
                try
                {
                    User user = ProviderRepository.GetUserByUserName(ApplicationName, username);

                    if (user != null)
                    {
                        if (userIsOnline)
                        {
                            user.LastActivityDate = DateTime.UtcNow;
                            ProviderRepository.Save(ApplicationName, user);
                        }
                        membershipUser = GetMembershipUserFromUser(user);
                    }
                }
                catch (Exception ex)
                {
                    membershipUser = null;
                    throw new ProviderException(ex.Message);
                }
            }

            return membershipUser;
        }

        // Takes, as input, an e-mail address and returns the first registered user name whose e-mail address matches the
        // one supplied.
        // If it doesn't find a user with a matching e-mail address, GetUserNameByEmail returns an empty string.
        public override string GetUserNameByEmail(string email)
        {
            string username = string.Empty;

            try
            {
                User user = ProviderRepository.GetUserByEmail(ApplicationName, email);
                if (user != null)
                    username = user.UserName.Trim();
            }
            catch (Exception ex)
            {
                throw new ProviderException(ex.Message);
            }

            return username;
        }

        // Virtual method called when a password is created. The default implementation in MembershipProvider fires a
        // ValidatingPassword event, so be sure to call the base class's OnValidatingPassword method if you override
        // this method. The ValidatingPassword event allows applications to apply additional tests to passwords by
        // registering event handlers.
        // A custom provider's CreateUser, ChangePassword, and ResetPassword methods (in short, all methods that record
        // new passwords) should call this method.
        protected override void OnValidatingPassword(ValidatePasswordEventArgs e)
        {
            base.OnValidatingPassword(e);
        }

        // Takes, as input, a user name and a password answer and replaces the user's current password with a new, random
        // password. ResetPassword then returns the new password. A convenient mechanism for generating a random password
        // is the Membership.GeneratePassword method.
        // If the user name is not valid, ResetPassword throws a ProviderException. ResetPassword also checks the value of
        // the RequiresQuestionAndAnswer property before resetting a password. If RequiresQuestionAndAnswer is true,
        // ResetPassword compares the supplied password answer to the stored password answer and throws a
        // MembershipPasswordException if the two don't match.
        //
        // Before resetting a password, ResetPassword verifies that EnablePasswordReset is true. If EnablePasswordReset is
        // false, ResetPassword throws a NotSupportedException. If the user whose password is being changed is currently
        // locked out, ResetPassword throws a MembershipPasswordException.
        //
        // Before resetting a password, ResetPassword calls the provider's virtual OnValidatingPassword method to validate
        // the new password. It then resets the password or cancels the action based on the outcome of the call. If the new
        // password is invalid, ResetPassword throws a ProviderException.
        //
        // Following a successful password reset, ResetPassword updates the user's LastPasswordChangedDate.
        public override string ResetPassword(string username, string answer)
        {

            User user = ProviderRepository.GetUserByUserName(ApplicationName, username);

            if (user == null)
                throw new ProviderException(String.Format("User {0} does not exist.", username));

            if (!enablePasswordReset)
                throw new NotSupportedException("Password Reset is not enabled.");

            if ((answer == null) && (requiresQuestionAndAnswer))
            {
                UpdateFailureCount(ref user, FailureType.PasswordAnswer);
                throw new MembershipPasswordException("Password answer required for password Reset.");
            }

            string newPassword = Membership.GeneratePassword(NewPasswordLength, minRequiredNonAlphanumericCharacters);

            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null)
                {
                    throw args.FailureInformation;
                }
                else
                {
                    throw new MembershipPasswordException("Reset password canceled due to password validation failure.");
                }
            }

            string passwordAnswer = String.Empty;

            if (user != null)
            {
                if (user.LockedOut == true)
                {
                    throw new MembershipPasswordException("This user is locked.");
                }

                passwordAnswer = user.PasswordAnswer;

                if (requiresQuestionAndAnswer == true && CheckPassword(answer, passwordAnswer) == false)
                {
                    UpdateFailureCount(ref user, FailureType.PasswordAnswer);
                    throw new MembershipPasswordException("Incorrect password answer.");
                }

                user.LockedOut = false;
                user.LastPasswordChangedDate = DateTime.UtcNow;
                user.Password = EncodePassword(newPassword);
                ProviderRepository.Save(ApplicationName, user);
            }
            else
            {
                throw new MembershipPasswordException("User not found, or user is locked out. Password not Reset.");
            }

            return newPassword;
        }

        // Unlocks (that is, restores login privileges for) the specified user. UnlockUser returns true if the user is
        // successfully unlocked. Otherwise, it returns false. If the user is already unlocked, UnlockUser simply returns true.
        public override bool UnlockUser(string username)
        {
            bool success = false;
            User user = ProviderRepository.GetUserByUserName(ApplicationName, username);
            if (user != null)
            {
                user.LockedOut = false;
                success = ProviderRepository.Save(ApplicationName, user);
            }

            return success;
        }

        // Takes, as input, a MembershipUser object representing a registered user and updates the information stored for
        // that user in the membership data source. If any of the input submitted in the MembershipUser object is not valid,
        // UpdateUser throws a ProviderException.
        // Note that UpdateUser is not obligated to allow all the data that can be encapsulated in a MembershipUser object to
        // be updated in the data source.
        public override void UpdateUser(MembershipUser member)
        {
            try
            {
                User user = ProviderRepository.GetUserByUserName(ApplicationName, member.UserName);

                if (user != null)
                {
                    user.Approved = member.IsApproved;
                    user.Comment = member.Comment;
                    user.CreationDate = member.CreationDate;
                    user.Email = member.Email;
                    user.LastActivityDate = member.LastActivityDate;
                    user.LastLockedOutDate = member.LastLockoutDate;
                    user.LastLoginDate = member.LastLoginDate;
                    user.LastPasswordChangedDate = member.LastPasswordChangedDate;
                    user.LockedOut = member.IsLockedOut;
                    user.PasswordQuestion = member.PasswordQuestion;

                    ProviderRepository.Save(ApplicationName, user);
                }
                else
                {
                    throw new ProviderException(String.Format("User {0} not found.",member.UserName));
                }
            }
            catch (Exception ex)
            {
                throw new ProviderException(ex.Message);
            }
        }

        // Takes, as input, a user name and a password and verifies that they are valid-that is, that the membership data
        // source contains a matching user name and password. ValidateUser returns true if the user name and password are
        // valid, if the user is approved (that is, if MembershipUser.IsApproved is true), and if the user isn't currently
        // locked out. Otherwise, it returns false.
        // Following a successful validation, ValidateUser updates the user's LastLoginDate and fires an
        // AuditMembershipAuthenticationSuccess Web event. Following a failed validation, it fires an
        // AuditMembershipAuthenticationFailure Web event.
        public override bool ValidateUser(string username, string password)
        {
            //throw new NotImplementedException();
            bool success = false;
            User user = ProviderRepository.GetUserByUserName(ApplicationName, username);

            if (user == null)
                return success;

            success = ValidateUser(ref user, password);

            return success;

        }

        #endregion

        #region - Extra methods -

        public bool ValidateUser(ref User user, string password)
        {
            bool success = false;

            try
            {
                if (CheckPassword(password, user.Password))
                {
                    if (user.Approved)
                    {
                        success = true;
                        user.LastLoginDate = DateTime.UtcNow;
                        user.FailedPasswordAttemptCount = 0;
                        user.ApplicationName = ApplicationName;
                    }
                }
                else
                {
                    UpdateFailureCount(ref user, FailureType.Password);
                }

                // Save our changes
                ProviderRepository.Save(ApplicationName, user);
            }
            catch (Exception ex)
            {
                throw new ProviderException(ex.Message);
            }

            return success;
        }

        private void UpdateFailureCount(ref User user, FailureType failureType)
        {
            DateTime windowStart = DateTime.UtcNow;
            int failureCount = 0;

            try
            {

                if (failureType == FailureType.Password)
                {
                    failureCount = user.FailedPasswordAttemptCount;
                    windowStart = (user.FailedPasswordAttemptWindowStart == null) ? windowStart : (DateTime)user.FailedPasswordAttemptWindowStart;
                }
                else if (failureType == FailureType.PasswordAnswer)
                {
                    failureCount = user.FailedPasswordAnswerAttemptCount;
                    windowStart = (user.FailedPasswordAttemptWindowStart == null) ? windowStart : (DateTime)user.FailedPasswordAttemptWindowStart;
                }

                DateTime windowEnd = windowStart.AddMinutes(PasswordAttemptWindow);

                // First password failure attempt
                if (failureCount == 0 || DateTime.UtcNow > windowEnd)
                {
                    // First password failure or outside of PasswordAttemptWindow.
                    // Start a new password failure count from 1 and a new window starting now.
                    if (failureType == FailureType.Password)
                    {
                        user.FailedPasswordAttemptCount = 1;
                        user.FailedPasswordAttemptWindowStart = DateTime.UtcNow;
                    }
                    else if (failureType == FailureType.PasswordAnswer)
                    {
                        user.FailedPasswordAnswerAttemptCount = 1;
                        user.FailedPasswordAttemptWindowStart = DateTime.UtcNow;
                    }

                }

                // Password failure attempts exceed maximum attempts
                else if (++failureCount >= MaxInvalidPasswordAttempts)
                {
                    // Password attempts have exceeded the failure threshold. Lock out
                    // the user.
                    user.LockedOut = true;
                    user.LastLockedOutDate = DateTime.UtcNow;
                }

                // Not the first failure, and not the last password attempt
                else
                {
                    // Password attempts have not exceeded the failure threshold. Update
                    // the failure counts. Leave the window the same.
                    if (failureType == FailureType.Password)
                    {
                        user.FailedPasswordAttemptCount++;
                    }
                    else if (failureType == FailureType.PasswordAnswer)
                    {
                        user.FailedPasswordAnswerAttemptCount++;
                    }

                }
            }

            catch (Exception ex)
            {
                throw new ProviderException(ex.Message);
            }
        }

        #endregion

        #region - Helper Methods -
        private string GetConfigValue(string configValue, string defaultValue)
        {
            if (String.IsNullOrEmpty(configValue))
            {
                return defaultValue;
            }

            return configValue;
        }

        private bool ValidatePassword(string password)
        {
            if (String.IsNullOrEmpty(password) == true || password.Length < MinRequiredPasswordLength)
            {
                return false;
            }

            int count = 0;

            for (int i = 0; i < password.Length; i++)
            {
                count += (char.IsLetterOrDigit(password, i)) ? 1 : 0;
            }

            if (count < MinRequiredNonAlphanumericCharacters)
            {
                return false;
            }

            if (String.IsNullOrEmpty(PasswordStrengthRegularExpression) == false)
            {
                if (!Regex.IsMatch(password, PasswordStrengthRegularExpression))
                {
                    return false;
                }
            }

            return true;
        }

        private bool CheckPassword(string enteredPassword, string storedPassword)
        {

            bool success = false;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    success = (enteredPassword == storedPassword);
                    break;

                case MembershipPasswordFormat.Encrypted:
                    success = (enteredPassword == UnEncodePassword(storedPassword));
                    break;

                case MembershipPasswordFormat.Hashed:
                    success = (EncodePassword(enteredPassword) == storedPassword);
                    break;

                default:
                    break;
            }

            return success;

        }

        private string EncodePassword(string password)
        {
            string encodedPassword = password;

            switch (passwordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;

                case MembershipPasswordFormat.Encrypted:
                    encodedPassword = Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
                    break;

                case MembershipPasswordFormat.Hashed:
                    HMACSHA1 hash = new HMACSHA1();
                    hash.Key = HexToByte(MachineKey.ValidationKey);
                    encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
                    break;

                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return encodedPassword;
        }

        private string UnEncodePassword(string encodedPassword)
        {
            string password = encodedPassword;

            switch (passwordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;

                case MembershipPasswordFormat.Encrypted:
                    password = Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
                    break;

                case MembershipPasswordFormat.Hashed:
                    throw new ProviderException("Cannot unencode a hashed password.");

                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return password;
        }

        private byte[] HexToByte(string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            return returnBytes;
        }

        private MembershipUser GetMembershipUserFromUser(User user)
        {
            DateTime nullDate = new DateTime(1900, 1, 1, 0, 0, 0);
            MembershipUser member = null;

            // Todo: Determine why the tests fail when the provider name is not AspNetSqlMembershipProvider
            member = new MembershipUser( Name   // "AspNetSqlMembershipProvider"
                                       , user.UserName
                                       , user.UserId
                                       , user.Email
                                       , user.PasswordQuestion
                                       , user.Comment
                                       , user.Approved
                                       , user.LockedOut
                                       , user.CreationDate
                                       , (user.LastLoginDate == null) ? nullDate : (DateTime)user.LastLoginDate
                                       , (user.LastActivityDate == null) ? nullDate : (DateTime)user.LastActivityDate
                                       , (user.LastPasswordChangedDate == null) ? nullDate : (DateTime)user.LastPasswordChangedDate
                                       , (user.LastLockedOutDate == null) ? nullDate : (DateTime)user.LastLockedOutDate
                                       );

            return member;
        }
        #endregion

    }   // End Class

}       // End Namespace

ReportHtml11_02
If you rebuild the solution, you’ll see more errors. These errors are all related to the error “The membership provider name specified is invalid”. This is because our custom class is not listed as a membership provider. To correct this we need to add an app.config file to the Nehemiah.Specs project. The code for the app.config file is shown below. You will notice that I put the physical path to the mdf file. This will have to be changed according to your environment.

<?xml version="1.0"?>
<configuration>
  <appSettings />
  <connectionStrings>
    <add name="Nehemiah" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=C:\dev\Nehemiah\Nehemiah\app_data\Jerusalem.mdf;User Instance=true"/>
  </connectionStrings>
  <system.web>
    <membership defaultProvider="NehemiahMembershipProvider" userIsOnlineTimeWindow="15">
      <providers>
        <clear />
        <remove name="AspNetSqlMembershipProvider"/>
        <add name="NehemiahMembershipProvider"
             description =""
             type="Nehemiah.Providers.NehemiahMembershipProvider, Nehemiah.Providers"
             enablePasswordRetrieval="false"
             enablePasswordReset="true"
             requiresQuestionAndAnswer="false"
             requiresUniqueEmail="false"
             passwordFormat="Clear"
             maxInvalidPasswordAttempts="5"
             minRequiredPasswordLength="6"
             minRequiredNonalphanumericCharacters="0"
             passwordAttemptWindow="10"
             passwordStrengthRegularExpression=""
             applicationName="Nehemiah"/>
      </providers>
    </membership>
  </system.web>
</configuration>

The solution should now compile without errors. The report file should show 132 specifications and 20 not implemented specs. Commit the changes to subversion. Open http://localhost:8080 and verify TeamCity built the solution without errors as well.

References

Previous Articles in this Series

Shout it


Kick It on DotNetKicks.com
Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)

Leave a Reply

Comment moderation is enabled. Your comment may take some time to appear.