Click here to Skip to main content
15,897,518 members
Articles / Web Development / IIS

Custom Membership, Role Providers, Website administration tool, and Role based access to individual files

Rate me:
Please Sign up or sign in to vote.
4.00/5 (11 votes)
12 Jul 2011CPOL5 min read 157.8K   11.4K   93  
Custom Membership and Role Providers, a website administration tool, and Role based access to individual files.
using System;
using System.Web;
using System.Web.Hosting;
using System.Web.Security;
using System.Web.Configuration;
using System.Security.Principal;
using System.Security.Permissions;
using System.Globalization;
using System.Runtime.Serialization;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Configuration.Provider;
using System.Configuration;
using System.Data.Sql;
using System.Web.DataAccess;
using System.Web.Management;
using System.Web.Util;

/// <summary>
/// Summary description for MyMembershipProvider
/// </summary>
public class MyMembershipProvider : MembershipProvider
{
    private const int SALT_SIZE_IN_BYTES = 16;
    //public properties
    private bool _EnablePasswordRetrieval;
    private bool _EnablePasswordReset;
    private bool _RequiresQuestionAndAnswer;
    private string _AppName;
    private bool _RequiresUniqueEmail;
    private string _DatabaseFileName;
    private string _HashAlgorithmType;
    private int _ApplicationId = 0;
    private int _MaxInvalidPasswordAttempts;
    private int _PasswordAttemptWindow;
    private int _MinRequiredPasswordLength;
    private int _MinRequiredNonalphanumericCharacters;
    private string _PasswordStrengthRegularExpression;
    private DateTime _ApplicationIDCacheDate;
    private MembershipPasswordFormat _PasswordFormat;

    public override bool EnablePasswordRetrieval { get { return _EnablePasswordRetrieval; } }

    public override bool EnablePasswordReset { get { return _EnablePasswordReset; } }

    public override bool RequiresQuestionAndAnswer { get { return _RequiresQuestionAndAnswer; } }

    public override bool RequiresUniqueEmail { get { return _RequiresUniqueEmail; } }

    public override MembershipPasswordFormat PasswordFormat { get { return _PasswordFormat; } }

    public override int MaxInvalidPasswordAttempts { get { return _MaxInvalidPasswordAttempts; } }
    public override int PasswordAttemptWindow { get { return _PasswordAttemptWindow; } }

    public override int MinRequiredPasswordLength
    {
        get { return _MinRequiredPasswordLength; }
    }

    public override int MinRequiredNonAlphanumericCharacters
    {
        get { return _MinRequiredNonalphanumericCharacters; }
    }

    public override string PasswordStrengthRegularExpression
    {
        get { return _PasswordStrengthRegularExpression; }
    }

    public override string ApplicationName
    {
        get { return _AppName; }
        set
        {
            if (_AppName != value)
            {
                _ApplicationId = 0;
                _AppName = value;
            }
        }
    }


// Initialize the methods

    public override void Initialize(string name, NameValueCollection config)
    {
        if (config == null)
            throw new ArgumentNullException("config");
        if (String.IsNullOrEmpty(name))
            name = "AccessMembershipProvider";
        if (string.IsNullOrEmpty(config["description"]))
        {
            config.Remove("description");
            config.Add("description", "Membership $safeprojectname$ Provider");
        }
        base.Initialize(name, config);

        _EnablePasswordRetrieval = SecUtility.GetBooleanValue(config, "enablePasswordRetrieval", false);
        _EnablePasswordReset = SecUtility.GetBooleanValue(config, "enablePasswordReset", true);
        _RequiresQuestionAndAnswer = SecUtility.GetBooleanValue(config, "requiresQuestionAndAnswer", false);
        _RequiresUniqueEmail = SecUtility.GetBooleanValue(config, "requiresUniqueEmail", false);
        _MaxInvalidPasswordAttempts = SecUtility.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
        _PasswordAttemptWindow = SecUtility.GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
        _MinRequiredPasswordLength = SecUtility.GetIntValue(config, "minRequiredPasswordLength", 7, false, 128);
        _MinRequiredNonalphanumericCharacters = SecUtility.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 128);

        _HashAlgorithmType = config["hashAlgorithmType"];
        if (String.IsNullOrEmpty(_HashAlgorithmType))
        {
            _HashAlgorithmType = "MD5";
        }

        _PasswordStrengthRegularExpression = config["passwordStrengthRegularExpression"];
        if (_PasswordStrengthRegularExpression != null)
        {
            _PasswordStrengthRegularExpression = _PasswordStrengthRegularExpression.Trim();
            if (_PasswordStrengthRegularExpression.Length != 0)
            {
                try
                {
                    Regex regex = new Regex(_PasswordStrengthRegularExpression);
                }
                catch (ArgumentException e)
                {
                    throw new ProviderException(e.Message, e);
                }
            }
        }
        else
        {
            _PasswordStrengthRegularExpression = string.Empty;
        }

        _AppName = config["applicationName"];
        if (string.IsNullOrEmpty(_AppName))
            _AppName = SecUtility.GetDefaultAppName();

        if (_AppName.Length > 255)
        {
            throw new ProviderException("Provider application name is too long, max length is 255.");
        }

        string strTemp = config["passwordFormat"];
        if (strTemp == null)
            strTemp = "Hashed";

        switch (strTemp)
        {
            case "Clear":
                _PasswordFormat = MembershipPasswordFormat.Clear;
                break;
            case "Encrypted":
                _PasswordFormat = MembershipPasswordFormat.Encrypted;
                break;
            case "Hashed":
                _PasswordFormat = MembershipPasswordFormat.Hashed;
                break;
            default:
                throw new ProviderException("Bad password format");
        }

        if (_PasswordFormat == MembershipPasswordFormat.Hashed && _EnablePasswordRetrieval)
            throw new ProviderException("Provider cannot retrieve hashed password");

        _DatabaseFileName = config["connectionStringName"];
        if (_DatabaseFileName == null || _DatabaseFileName.Length < 1)
            throw new ProviderException("Connection name not specified");

        string temp = MyConnectionHelper.GetFileNameFromConnectionName(_DatabaseFileName, true);
        if (temp == null || temp.Length < 1)
            throw new ProviderException("Connection string not found: " + _DatabaseFileName);
        _DatabaseFileName = temp;

        // Make sure connection is good
        MyConnectionHelper.CheckConnectionString(_DatabaseFileName);

        config.Remove("connectionStringName");
        config.Remove("enablePasswordRetrieval");
        config.Remove("enablePasswordReset");
        config.Remove("requiresQuestionAndAnswer");
        config.Remove("applicationName");
        config.Remove("requiresUniqueEmail");
        config.Remove("maxInvalidPasswordAttempts");
        config.Remove("passwordAttemptWindow");
        config.Remove("passwordFormat");
        config.Remove("name");
        config.Remove("description");
        config.Remove("minRequiredPasswordLength");
        config.Remove("minRequiredNonalphanumericCharacters");
        config.Remove("passwordStrengthRegularExpression");
        config.Remove("hashAlgorithmType");
        if (config.Count > 0)
        {
            string attribUnrecognized = config.GetKey(0);
            if (!String.IsNullOrEmpty(attribUnrecognized))
                throw new ProviderException("Provider unrecognized attribute: " + attribUnrecognized);
        }
    }


    public override bool ChangePassword(string username, string oldPassword, string newPassword)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
    {
        if (!SecUtility.ValidateParameter(ref password,
                                           true,
                                           true,
                                           false,
                                           0))
        {
            status = MembershipCreateStatus.InvalidPassword;
            return null;
        }

        string salt = GenerateSalt();
        string pass = EncodePassword(password, (int)_PasswordFormat, salt);
        if (pass.Length > 128)
        {
            status = MembershipCreateStatus.InvalidPassword;
            return null;
        }

        if (!SecUtility.ValidateParameter(ref username,
                                           true,
                                           true,
                                           true,
                                           255))
        {
            status = MembershipCreateStatus.InvalidUserName;
            return null;
        }


        AccessConnectionHolder holder = MyConnectionHelper.GetConnection(_DatabaseFileName, true);
        SqlConnection connection = holder.Connection;

        try
        {
            try
            {
                //
                // Start transaction
                //

                SqlCommand command = new SqlCommand();

                int appId = GetAppplicationId(holder);
                object result;
                int uid;

                ////////////////////////////////////////////////////////////
                // Step 1: Check if the user exists in the Users table: create if not
                uid = MyConnectionHelper.GetUserID(connection, appId, username, false);
                if (uid != 0)
                { // User not created successfully!
                    status = MembershipCreateStatus.DuplicateUserName;
                    return null;
                }

                ////////////////////////////////////////////////////////////
                // Step 4: Create user in Membership table
                DateTime dt = MyConnectionHelper.RoundToSeconds(DateTime.Now);
                command = new SqlCommand(@"INSERT INTO users " +
                                            "(UserName,PasswordHash, Salt) " +
                                            "VALUES (@UserName,@PasswordHash, @salt)",
                                            connection);
                int pFormat = (int)_PasswordFormat;
                command.Parameters.Add(new SqlParameter("@UserName", username));
                command.Parameters.Add(new SqlParameter("@PasswordHash", pass));
                command.Parameters.Add(new SqlParameter("@salt", salt));
                //
                // Error inserting row
                //

                if (command.ExecuteNonQuery() != 1)
                {
                    status = MembershipCreateStatus.ProviderError;
                    return null;
                }

                status = MembershipCreateStatus.Success;
                return new MembershipUser(this.Name,
                                           username,
                                           uid,
                                           email,
                                           passwordQuestion,
                                           null,
                                           isApproved,
                                           false,
                                           dt,
                                           dt,
                                           dt,
                                           dt,
                                           DateTime.MinValue);
            }
            catch (Exception e)
            {
                throw MyConnectionHelper.GetBetterException(e, holder);
            }
            finally
            {
                holder.Close();
            }
        }
        catch
        {
            throw;
        }
    }

    public override bool DeleteUser(string username, bool deleteAllRelatedData)
    {
        SecUtility.CheckParameter(ref username, true, true, true, 255, "username");

        AccessConnectionHolder holder = MyConnectionHelper.GetConnection(_DatabaseFileName, true);
        SqlConnection connection = holder.Connection;
        bool fBeginTransCalled = false;

        try
        {
            try
            {
                int appId = GetAppplicationId(holder);
                int userId = MyConnectionHelper.GetUserID(connection, appId, username, false);

                if (userId == 0)
                    return false; // User not found
                SqlCommand command;

                //
                // Start transaction
                //

                command = new SqlCommand("BEGIN TRANSACTION", connection);
                command.ExecuteNonQuery();
                fBeginTransCalled = true;

                bool returnValue = false;
                if (deleteAllRelatedData)
                {
                    command = new SqlCommand(@"DELETE FROM UsersInRoles WHERE UserId = @UserId", connection);
                    command.Parameters.Add(new SqlParameter("@UserId", userId));
                    command.ExecuteNonQuery();

                    command = new SqlCommand(@"DELETE FROM Users WHERE UserId = @UserId", connection);
                    command.Parameters.Add(new SqlParameter("@UserId", userId));
                    returnValue = (command.ExecuteNonQuery() == 1);
                }

                //
                // End transaction
                //

                command = new SqlCommand("COMMIT TRANSACTION", connection);
                command.ExecuteNonQuery();
                fBeginTransCalled = false;

                return returnValue;
            }
            catch (Exception e)
            {
                throw MyConnectionHelper.GetBetterException(e, holder);
            }
            finally
            {
                if (fBeginTransCalled)
                {
                    try
                    {
                        SqlCommand cmd = new SqlCommand("ROLLBACK TRANSACTION",
                                                             connection);
                        cmd.ExecuteNonQuery();
                    }
                    catch { }
                }

                holder.Close();
            }
        }
        catch
        {
            throw;
        }
    }


    public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
    {
        throw new Exception("The method or operation is not implemented.");
    }
    private const string s_connPrefix = "Driver={SQL Native Client};Server=.\\SQLExpress; Database=UsersDb;Trusted_Connection=Yes; AttachDbFilename=|DataDirectory|";

    public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
    {
        if (pageIndex < 0)
            throw new ArgumentException("PageIndex cannot be negative");
        if (pageSize < 1)
            throw new ArgumentException("PageSize must be positive");

        long lBound = (long)pageIndex * pageSize;
        long uBound = lBound + pageSize - 1;

        if (uBound > System.Int32.MaxValue)
        {
            throw new ArgumentException("PageIndex too big");
        }

        AccessConnectionHolder holder = MyConnectionHelper.GetConnection(_DatabaseFileName, true);
        SqlConnection connection = holder.Connection;
        SqlDataReader reader = null;
        long recordCount = 0;
        try
        {
            try
            {
                int appId = GetAppplicationId(holder);
                SqlCommand command;
                MembershipUserCollection users = new MembershipUserCollection();

                command = new SqlCommand(@"SELECT UserName,UserID  from Users ORDER BY UserName",connection);
                //command.Parameters.Add(new SqlParameter("@AppId", appId));

                reader = command.ExecuteReader(CommandBehavior.SequentialAccess);

                while (reader.Read())
                {
                    recordCount++;
                    if (recordCount - 1 < lBound || recordCount - 1 > uBound)
                        continue;
                    string username, email, passwordQuestion, comment;
                    DateTime dtCreate, dtLastLogin, dtLastActivity, dtLastPassChange;
                    bool isApproved;
                    int userId;
                    username = GetNullableString(reader, 0);
                    email = "";//GetNullableString(reader, 1);
                    passwordQuestion = ""; //GetNullableString(reader, 2);
                    comment = ""; //GetNullableString(reader, 3);
                    dtCreate = DateTime.Now; //reader.GetDateTime(4);
                    dtLastLogin = DateTime.Now; //reader.GetDateTime(5);
                    dtLastActivity = DateTime.Now; //reader.GetDateTime(6);
                    dtLastPassChange = DateTime.Now; //reader.GetDateTime(7);
                    isApproved = true; //reader.GetBoolean(8);
                    userId = reader.GetInt32(1);
                    users.Add(new MembershipUser("MyMembershipProvider",
                                                  username,
                                                  userId,
                                                  email,
                                                  passwordQuestion,
                                                  comment,
                                                  isApproved,
                                                  false,
                                                  dtCreate,
                                                  dtLastLogin,
                                                  dtLastActivity,
                                                  dtLastPassChange,
                                                  DateTime.MinValue));
                }
                totalRecords = (int)recordCount;
                return users;
            }
            catch (Exception e)
            {
                throw new Exception("Exception in creating users Collection", e);
            }
            finally
            {
                if (reader != null)
                    reader.Close();
                holder.Close();
            }
        }
        catch(Exception e)
        {
            throw new Exception("Exception on line 490: " ,e);
        }
    }

    public override int GetNumberOfUsersOnline()
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override string GetPassword(string username, string answer)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUser GetUser(string username, bool userIsOnline)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override string GetUserNameByEmail(string email)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override string ResetPassword(string username, string answer)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool UnlockUser(string userName)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override void UpdateUser(MembershipUser user)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool ValidateUser(string username, string password)
    {
        if (!SecUtility.ValidateParameter(ref username,
                                   true,
                                   true,
                                   false,
                                   255))
        {
            return false;
        }

        if (!SecUtility.ValidateParameter(ref password,
                                           true,
                                           true,
                                           false,
                                           128))
        {
            return false;
        }

        AccessConnectionHolder holder = MyConnectionHelper.GetConnection(_DatabaseFileName, true);
        SqlConnection connection = holder.Connection;

        try
        {
            try
            {
                int appId = GetAppplicationId(holder);
                int userId = MyConnectionHelper.GetUserID(connection, appId, username, false);
                if (CheckPassword(connection, userId, password))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception e)
            {
                throw MyConnectionHelper.GetBetterException(e, holder);
            }
            finally
            {
                holder.Close();
            }
        }
        catch
        {
            throw;
        }

    }


    private string GenerateSalt()
    {
        byte[] buf = new byte[SALT_SIZE_IN_BYTES];
        (new RNGCryptoServiceProvider()).GetBytes(buf);
        return Convert.ToBase64String(buf);
    }

    private string EncodePassword(string pass, int passwordFormat, string salt)
    {
        if (passwordFormat == 0) // MembershipPasswordFormat.Clear
            return pass;

        byte[] bIn = Encoding.Unicode.GetBytes(pass);
        byte[] bSalt = Convert.FromBase64String(salt);
        byte[] bAll = new byte[bSalt.Length + bIn.Length];
        byte[] bRet = null;

        Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
        Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
        if (passwordFormat == 1)
        { // MembershipPasswordFormat.Hashed
            HashAlgorithm s = HashAlgorithm.Create(_HashAlgorithmType);

            // If the hash algorithm is null (and came from config), throw a config exception
            if (s == null)
            {
                throw new ProviderException("Could not create a hash algorithm");
            }
            bRet = s.ComputeHash(bAll);
        }
        else
        {
            bRet = EncryptPassword(bAll);
        }

        return Convert.ToBase64String(bRet);
    }

    private string UnEncodePassword(string pass, int passwordFormat)
    {
        switch (passwordFormat)
        {
            case 0: // MembershipPasswordFormat.Clear:
                return pass;
            case 1: // MembershipPasswordFormat.Hashed:
                throw new ProviderException("Provider can not decode hashed password");
            default:
                byte[] bIn = Convert.FromBase64String(pass);
                byte[] bRet = DecryptPassword(bIn);
                if (bRet == null)
                    return null;
                return Encoding.Unicode.GetString(bRet, SALT_SIZE_IN_BYTES, bRet.Length - SALT_SIZE_IN_BYTES);
        }
    }

    private string GetPasswordWithFormat(
                    SqlConnection connection,
                    int userId,
                    out int passwordFormat,
                    out string passwordSalt)
    {
        SqlCommand command;
        SqlDataReader reader;
        string pass;

        passwordFormat = 1;
        passwordSalt = String.Empty;
        if (userId == 0)
            return null;

        command = new SqlCommand(@"SELECT [PasswordHash],[Salt]" +
                                    @"FROM Users " +
                                    @"WHERE UserId = @UserId",
                                   connection);
        command.Parameters.Add(new SqlParameter("@UserId", userId));

        reader = command.ExecuteReader(CommandBehavior.SingleRow);

        if (!reader.Read())
        { // Zero rows read = user-not-found
            reader.Close();
            return null;
        }

        pass = GetNullableString(reader, 0);
        passwordSalt = GetNullableString(reader, 1);

        switch (_PasswordFormat)
        {
            case MembershipPasswordFormat.Clear:
                passwordFormat = 0;
                break;
            case MembershipPasswordFormat.Encrypted:
                passwordFormat = 2;
                break;
            case MembershipPasswordFormat.Hashed:
                passwordFormat = 1;
                break;
            default:
                throw new ProviderException("Bad password format");
        }

      reader.Close();
        return pass;
    }


    private int GetAppplicationId(AccessConnectionHolder holder)
    {
        if (_ApplicationId != 0 && holder.CreateDate < _ApplicationIDCacheDate) // Already cached?
            return _ApplicationId;
        string appName = _AppName;
        if (appName.Length > 255)
            appName = appName.Substring(0, 255);
        _ApplicationId = 10011;
        _ApplicationIDCacheDate = DateTime.Now;
        if (_ApplicationId != 0)
            return _ApplicationId;
        throw new ProviderException("sorry exception in GetApplicationId") ;
    }

    private SqlParameter CreateDateTimeSqlParameter(string parameterName, DateTime dt)
    {
        SqlParameter p = new SqlParameter(parameterName, SqlDbType.Timestamp);
        p.Direction = ParameterDirection.Input;
        p.Value = MyConnectionHelper.RoundToSeconds(dt);
        return p;
    }

    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    private SqlParameter CreateSqlParam(string paramName, Enum SqlDbType, object objValue)
    {

        SqlParameter param = new SqlParameter(paramName, SqlDbType);

        if (objValue == null)
        {
            param.IsNullable = true;
            param.Value = DBNull.Value;
        }
        else
        {
            param.Value = objValue;
        }

        return param;
    }

    private bool CheckPassword(SqlConnection connection, int userId, string password)
    {
        string salt;
        int passwordFormat, status;
        string pass = GetPasswordWithFormat(connection, userId, out passwordFormat, out salt);
        string pass2 = EncodePassword(password, passwordFormat, salt);
        return (pass == pass2);
    }

    /////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    private bool IsStatusDueToBadPassword(int status)
    {
        return (status >= 2 && status <= 6);
    }
    private const int PASSWORD_SIZE = 14;
    public virtual string GeneratePassword()
    {
        return Membership.GeneratePassword(
                  MinRequiredPasswordLength < PASSWORD_SIZE ? PASSWORD_SIZE : MinRequiredPasswordLength,
                  MinRequiredNonAlphanumericCharacters);
    }
    private string GetNullableString(SqlDataReader reader, int col)
    {
        if (reader.IsDBNull(col) == false)
        {
            return reader.GetString(col);
        }

        return null;
    }
    /////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////
    private string GetExceptionText(int status)
    {
        string key;
        switch (status)
        {
            case 0:
                return String.Empty;
            case 1:
                key = "User not found";
                break;
            case 2:
                key = "Wrong password";
                break;
            case 3:
                key = "Wrong answer";
                break;
            case 4:
                key = "Invalid password";
                break;
            case 5:
                key = "Invalid question";
                break;
            case 6:
                key = "Invalid answer";
                break;
            case 7:
                key = "Invalid email";
                break;
            default:
                key = "Unknown provider error";
                break;
        }
        return key;
    }

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Singapore Singapore
I love programming, reading, and meditation. I like to explore management and productivity.

Comments and Discussions