/MongoProviders/MembershipProvider.cs
C# | 1551 lines | 936 code | 256 blank | 359 comment | 169 complexity | f43ab0d7a968bc400ff6fb52d3f9133c MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- // The MIT License (MIT)
- //
- // Copyright (c) 2011 Adrian Lanning <adrian@nimblejump.com>
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software
- // and associated documentation files (the "Software"), to deal in the Software without restriction,
- // including without limitation the rights to use, copy, modify, merge, publish, distribute,
- // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- // The above copyright notice and this permission notice shall be included in all copies or
- // substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
- // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
- // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- using System;
- using System.Web;
- using System.Web.Configuration;
- using System.Web.Security;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Configuration;
- using System.Configuration.Provider;
- using System.Collections.Specialized;
- using System.Text.RegularExpressions;
- using System.Security.Cryptography;
- using System.Diagnostics;
- using System.Globalization;
- using MongoDB.Driver;
- using MongoDB.Driver.Builders;
- using MongoDB.Bson;
- using MongoDB.Bson.Serialization;
- using MongoDB.Driver.Linq;
- namespace MongoProviders
- {
- /// <summary>
- /// ASP.NET Membership Provider that uses MongoDB
- ///
- /// Non-standard configuration attributes:
- ///
- /// invalidUsernameCharacters - characters that are illegal in Usernames. Default: ",%"
- /// Ex: invalidUsernameCharacters=",%&"
- /// Note: the percent character, "%", should generally always be illegal since it is used in the FindUsersBy*
- /// methods to indicate a wildcard. This matches the behavior of the SQL Membership provider in supporting
- /// basic SQL Like syntax, although only the "%" is supported (not "_" or "[]")
- ///
- /// invalidEmailCharacters - characters that are illegal in Email addresses. Default: ",%"
- /// Ex: invalidEmailCharacters=",!%*"
- /// Note: the percent character, "%", should generally always be illegal since it is used in the FindUsersBy*
- /// methods to indicate a wildcard. This matches the behavior of the SQL Membership provider in supporting
- /// basic SQL Like syntax, although only the "%" is supported (not "_" or "[]")
- ///
- /// writeExceptionsToEventLog - boolean indicating whether database exceptions should be
- /// written to the EventLog rather than returned to UI. Default: "true"
- /// Ex: writeExceptionsToEventLog="false"
- ///
- /// collectionSuffix - suffix of the collection to use for Membership User data. Default: "users"
- /// Ex: collectionSuffix="users"
- /// Note: the actual collection name used will be the combination of the ApplicationName and the CollectionSuffix.
- /// For example, if the ApplicationName is "/", then the default Collection name will be "/users".
- /// This relieves us from having to include the ApplicationName in every query and also saves space in two ways:
- /// 1. ApplicationName does not need to be stored with the User data
- /// 2. Indexes no longer need to be composite. ie. "LowercaseUsername" rather than "ApplicationName", "LowercaseUsername"
- ///
- /// </summary>
- public class MembershipProvider : System.Web.Security.MembershipProvider
- {
- #region Custom Public Properties
- public const string DEFAULT_NAME = "MongoMembershipProvider";
- public const string DEFAULT_DATABASE_NAME = "test";
- public const string DEFAULT_USER_COLLECTION_SUFFIX = "users";
- public const string DEFAULT_INVALID_CHARACTERS = ",%";
- public const int NEW_PASSWORD_LENGTH = 8;
- public const int MAX_USERNAME_LENGTH = 256;
- public const int MAX_PASSWORD_LENGTH = 128;
- public const int MAX_PASSWORD_ANSWER_LENGTH = 128;
- public const int MAX_EMAIL_LENGTH = 256;
- public const int MAX_PASSWORD_QUESTION_LENGTH = 256;
- public struct MembershipElements
- {
- public string LowercaseUsername;
- public string LowercaseEmail;
- public string LastActivityDate;
- }
- public MembershipElements ElementNames { get; protected set; }
- //
- // If false, exceptions are thrown to the caller. If true,
- // exceptions are written to the event log.
- //
- public bool WriteExceptionsToEventLog { get; set; }
- public string InvalidUsernameCharacters { get; protected set; }
- public string InvalidEmailCharacters { get; protected set; }
- public string CollectionName { get; protected set; }
- public MongoCollection<User> Collection { get; protected set; }
- public MongoDatabase Database { get; protected set; }
- #endregion
- #region Protected Properties
- protected string _connectionString;
- protected MachineKeySection _machineKey;
- protected string _collectionSuffix;
- protected int _newPasswordLength = 8;
- protected string _eventSource = DEFAULT_NAME;
- protected string _eventLog = "Application";
- protected string _exceptionMessage = Resources.ProviderException;
- #endregion
- #region MembershipProvider properties
- // System.Web.Security.MembershipProvider properties.
- protected string _applicationName;
- protected bool _enablePasswordReset;
- protected bool _enablePasswordRetrieval;
- protected bool _requiresQuestionAndAnswer;
- protected bool _requiresUniqueEmail;
- protected int _maxInvalidPasswordAttempts;
- protected int _passwordAttemptWindow;
- protected MembershipPasswordFormat _passwordFormat;
- protected int _minRequiredNonAlphanumericCharacters;
- protected int _minRequiredPasswordLength;
- protected string _passwordStrengthRegularExpression;
- /// <summary>
- /// The name of the application using the custom membership provider.
- /// </summary>
- /// <value></value>
- /// <returns>
- /// The name of the application using the custom membership provider.
- /// </returns>
- public override string ApplicationName
- {
- get { return _applicationName; }
- set
- {
- _applicationName = value;
- CollectionName = Helper.GenerateCollectionName(_applicationName, _collectionSuffix);
- Collection = Database.GetCollection<User>(CollectionName);
- }
- }
- /// <summary>
- /// Indicates whether the membership provider is configured to allow users to reset their passwords.
- /// </summary>
- /// <value></value>
- /// <returns>true if the membership provider supports password reset; otherwise, false. The default is true.
- /// </returns>
- public override bool EnablePasswordReset
- {
- get { return _enablePasswordReset; }
- }
- /// <summary>
- /// Indicates whether the membership provider is configured to allow users to retrieve their passwords.
- /// </summary>
- /// <value></value>
- /// <returns>true if the membership provider is configured to support password retrieval; otherwise, false. The default is false.
- /// </returns>
- public override bool EnablePasswordRetrieval
- {
- get { return _enablePasswordRetrieval; }
- }
- /// <summary>
- /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval.
- /// </summary>
- /// <value></value>
- /// <returns>true if a password answer is required for password reset and retrieval; otherwise, false. The default is true.
- /// </returns>
- public override bool RequiresQuestionAndAnswer
- {
- get { return _requiresQuestionAndAnswer; }
- }
- /// <summary>
- /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name.
- /// </summary>
- /// <value></value>
- /// <returns>true if the membership provider requires a unique e-mail address; otherwise, false. The default is true.
- /// </returns>
- public override bool RequiresUniqueEmail
- {
- get { return _requiresUniqueEmail; }
- }
- /// <summary>
- /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out.
- /// </summary>
- /// <value></value>
- /// <returns>
- /// The number of invalid password or password-answer attempts allowed before the membership user is locked out.
- /// </returns>
- public override int MaxInvalidPasswordAttempts
- {
- get { return _maxInvalidPasswordAttempts; }
- }
- /// <summary>
- /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
- /// </summary>
- /// <value></value>
- /// <returns>
- /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
- /// </returns>
- public override int PasswordAttemptWindow
- {
- get { return _passwordAttemptWindow; }
- }
- /// <summary>
- /// Gets a value indicating the format for storing passwords in the membership data store.
- /// </summary>
- /// <value></value>
- /// <returns>
- /// One of the <see cref="T:System.Web.Security.MembershipPasswordFormat"/> values indicating the format for storing passwords in the data store.
- /// </returns>
- public override MembershipPasswordFormat PasswordFormat
- {
- get { return _passwordFormat; }
- }
- /// <summary>
- /// Gets the minimum number of special characters that must be present in a valid password.
- /// </summary>
- /// <value></value>
- /// <returns>
- /// The minimum number of special characters that must be present in a valid password.
- /// </returns>
- public override int MinRequiredNonAlphanumericCharacters
- {
- get { return _minRequiredNonAlphanumericCharacters; }
- }
- /// <summary>
- /// Gets the minimum length required for a password.
- /// </summary>
- /// <value></value>
- /// <returns>
- /// The minimum length required for a password.
- /// </returns>
- public override int MinRequiredPasswordLength
- {
- get { return _minRequiredPasswordLength; }
- }
- /// <summary>
- /// Gets the regular expression used to evaluate a password.
- /// </summary>
- /// <value></value>
- /// <returns>
- /// A regular expression used to evaluate a password.
- /// </returns>
- public override string PasswordStrengthRegularExpression
- {
- get { return _passwordStrengthRegularExpression; }
- }
- #endregion
- #region Public Methods
- /// <summary>
- /// Initializes the provider.
- /// </summary>
- /// <param name="name">The friendly name of the provider.</param>
- /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
- /// <exception cref="T:System.ArgumentNullException">
- /// The config collection provided is null.
- /// </exception>
- /// <exception cref="T:System.InvalidOperationException">
- /// An attempt is made to call <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"/> on a provider after the provider has already been initialized.
- /// </exception>
- /// <exception cref="T:System.Configuration.Provider.ProviderException">
- /// </exception>
- public override void Initialize(string name, NameValueCollection config)
- {
- if (null == config)
- throw new ArgumentNullException("config");
- if (String.IsNullOrWhiteSpace(name))
- name = DEFAULT_NAME;
- if (String.IsNullOrEmpty(config["description"]))
- {
- config.Remove("description");
- config.Add("description", Resources.MembershipProvider_description);
- }
- base.Initialize(name, config);
- // get config values
- _applicationName = config["applicationName"] ?? System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
- _maxInvalidPasswordAttempts = Helper.GetConfigValue(config["maxInvalidPasswordAttempts"], 5);
- _passwordAttemptWindow = Helper.GetConfigValue(config["passwordAttemptWindow"], 10);
- _minRequiredNonAlphanumericCharacters = Helper.GetConfigValue(config["minRequiredNonAlphanumericCharacters"], 1);
- _minRequiredPasswordLength = Helper.GetConfigValue(config["minRequiredPasswordLength"], 7);
- _passwordStrengthRegularExpression = Helper.GetConfigValue(config["passwordStrengthRegularExpression"], "");
- _enablePasswordReset = Helper.GetConfigValue(config["enablePasswordReset"], true);
- _enablePasswordRetrieval = Helper.GetConfigValue(config["enablePasswordRetrieval"], false);
- _requiresQuestionAndAnswer = Helper.GetConfigValue(config["requiresQuestionAndAnswer"], false);
- _requiresUniqueEmail = Helper.GetConfigValue(config["requiresUniqueEmail"], true);
- InvalidUsernameCharacters = Helper.GetConfigValue(config["invalidUsernameCharacters"], DEFAULT_INVALID_CHARACTERS);
- InvalidEmailCharacters = Helper.GetConfigValue(config["invalidEmailCharacters"], DEFAULT_INVALID_CHARACTERS);
- WriteExceptionsToEventLog = Helper.GetConfigValue(config["writeExceptionsToEventLog"], true);
- _collectionSuffix = Helper.GetConfigValue(config["collectionSuffix"], DEFAULT_USER_COLLECTION_SUFFIX);
- ValidatePwdStrengthRegularExpression();
- if (_minRequiredNonAlphanumericCharacters > _minRequiredPasswordLength)
- throw new ProviderException(Resources.MinRequiredNonalphanumericCharacters_can_not_be_more_than_MinRequiredPasswordLength);
- string temp_format = config["passwordFormat"];
- if (null == temp_format)
- {
- temp_format = "Hashed";
- }
- switch (temp_format.ToLowerInvariant())
- {
- case "hashed":
- _passwordFormat = MembershipPasswordFormat.Hashed;
- break;
- case "encrypted":
- _passwordFormat = MembershipPasswordFormat.Encrypted;
- break;
- case "clear":
- _passwordFormat = MembershipPasswordFormat.Clear;
- break;
- default:
- throw new ProviderException(Resources.Provider_bad_password_format);
- }
- if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval)
- {
- throw new ProviderException(Resources.Provider_can_not_retrieve_hashed_password);
- }
- // Initialize Connection String
- string temp = config["connectionStringName"];
- if (String.IsNullOrWhiteSpace(temp))
- throw new ProviderException(Resources.Connection_name_not_specified);
- ConnectionStringSettings ConnectionStringSettings = ConfigurationManager.ConnectionStrings[temp];
- if (null == ConnectionStringSettings || String.IsNullOrWhiteSpace(ConnectionStringSettings.ConnectionString))
- throw new ProviderException(String.Format(Resources.Connection_string_not_found, temp));
- _connectionString = ConnectionStringSettings.ConnectionString;
- // Check for invalid parameters in the config
- 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("commandTimeout");
- config.Remove("passwordFormat");
- config.Remove("name");
- config.Remove("minRequiredPasswordLength");
- config.Remove("minRequiredNonAlphanumericCharacters");
- config.Remove("passwordStrengthRegularExpression");
- config.Remove("writeExceptionsToEventLog");
- config.Remove("invalidUsernameCharacters");
- config.Remove("invalidEmailCharacters");
- config.Remove("collectionSuffix");
- if (config.Count > 0)
- {
- string key = config.GetKey(0);
- if (!string.IsNullOrEmpty(key))
- {
- throw new ProviderException(String.Format(Resources.Provider_unrecognized_attribute, key));
- }
- }
- // Get encryption and decryption key information from the configuration.
- Configuration cfg =
- WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
- _machineKey = (MachineKeySection)ConfigurationManager.GetSection("system.web/machineKey");
- if (_machineKey.ValidationKey.Contains("AutoGenerate"))
- if (PasswordFormat != MembershipPasswordFormat.Clear)
- throw new ProviderException(Resources.Provider_can_not_autogenerate_machine_key_with_encrypted_or_hashed_password_format);
- // Initialize MongoDB Server
- UserClassMap.Register();
- Database = MongoDatabase.Create(_connectionString);
- CollectionName = Helper.GenerateCollectionName(_applicationName, _collectionSuffix);
- Collection = Database.GetCollection<User>(CollectionName);
- // store element names
- var names = new MembershipElements();
- names.LowercaseUsername = Helper.GetElementNameFor<User>(p => p.LowercaseUsername);
- names.LastActivityDate = Helper.GetElementNameFor<User, DateTime>(p => p.LastActivityDate);
- names.LowercaseEmail = Helper.GetElementNameFor<User>(p => p.LowercaseEmail);
- ElementNames = names;
- // ensure indexes
- this.Collection.EnsureIndex(ElementNames.LowercaseUsername);
- this.Collection.EnsureIndex(ElementNames.LowercaseEmail);
-
- }
- /// <summary>
- /// Processes a request to update the password for a membership user.
- /// </summary>
- /// <param name="username">The user to update the password for.</param>
- /// <param name="oldPassword">The current password for the specified user.</param>
- /// <param name="newPassword">The new password for the specified user.</param>
- /// <returns>
- /// true if the password was updated successfully; otherwise, false.
- /// </returns>
- public override bool ChangePassword(string username, string oldPassword, string newPassword)
- {
- SecUtility.CheckParameter(ref username, true, true, InvalidUsernameCharacters, MAX_USERNAME_LENGTH, "username");
- SecUtility.CheckParameter(ref oldPassword, true, true, null, MAX_PASSWORD_LENGTH, "oldPassword");
- SecUtility.CheckParameter(ref newPassword, true, true, null, MAX_PASSWORD_LENGTH, "newPassword");
- User user = GetUserByName(username, "ChangePassword");
- if (!CheckPassword(user, oldPassword, true))
- return false;
- if (newPassword.Length < this.MinRequiredPasswordLength)
- {
- throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
- Resources.Password_too_short,
- "newPassword", this.MinRequiredPasswordLength), "newPassword");
- }
- if (this.MinRequiredNonAlphanumericCharacters > 0)
- {
- int numNonAlphaNumericChars = 0;
- for (int i = 0; i < newPassword.Length; i++)
- {
- if (!char.IsLetterOrDigit(newPassword, i))
- {
- numNonAlphaNumericChars++;
- }
- }
- if (numNonAlphaNumericChars < this.MinRequiredNonAlphanumericCharacters)
- {
- throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
- Resources.Password_need_more_non_alpha_numeric_chars,
- "newPassword",
- this.MinRequiredNonAlphanumericCharacters), "newPassword");
- }
- }
- if ((this.PasswordStrengthRegularExpression.Length > 0) && !Regex.IsMatch(newPassword, this.PasswordStrengthRegularExpression))
- {
- throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
- Resources.Password_does_not_match_regular_expression,
- "newPassword"), "newPassword");
- }
- // Raise event to let others check new username/password
- ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, false);
- OnValidatingPassword(args);
- if (args.Cancel)
- {
- if (args.FailureInformation != null)
- throw args.FailureInformation;
- else
- throw new MembershipPasswordException(Resources.Membership_Custom_Password_Validation_Failure);
- }
- // Save new password
- string encodedPwd = EncodePassword(newPassword, PasswordFormat, user.PasswordSalt);
- user.Password = encodedPwd;
- user.PasswordFormat = PasswordFormat;
- user.LastPasswordChangedDate = DateTime.UtcNow;
- var msg = String.Format("Unable to save new password for user '{0}'", username);
- Save(user, msg, "ChangePassword");
- return true;
- }
- /// <summary>
- /// Processes a request to update the password question and answer for a membership user.
- /// </summary>
- /// <param name="username">The user to change the password question and answer for.</param>
- /// <param name="password">The password for the specified user.</param>
- /// <param name="newPasswordQuestion">The new password question for the specified user.</param>
- /// <param name="newPasswordAnswer">The new password answer for the specified user.</param>
- /// <returns>
- /// true if the password question and answer are updated successfully; otherwise, false.
- /// </returns>
- public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
- {
- SecUtility.CheckParameter(ref username, true, true, InvalidUsernameCharacters, MAX_USERNAME_LENGTH, "username");
- SecUtility.CheckParameter(ref password, true, true, null, MAX_PASSWORD_LENGTH, "password");
- User user = GetUserByName(username, "ChangePasswordQuestionAndAnswer");
- if (!CheckPassword(user, password, true))
- return false;
- SecUtility.CheckParameter(ref newPasswordQuestion, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, null, MAX_PASSWORD_QUESTION_LENGTH, "newPasswordQuestion");
- if (newPasswordAnswer != null)
- {
- newPasswordAnswer = newPasswordAnswer.Trim();
- }
- SecUtility.CheckParameter(ref newPasswordAnswer, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, null, MAX_PASSWORD_ANSWER_LENGTH, "newPasswordAnswer");
- string encodedPasswordAnswer = null;
- if (!string.IsNullOrEmpty(newPasswordAnswer))
- {
- encodedPasswordAnswer = EncodePassword(newPasswordAnswer.ToLowerInvariant(), user.PasswordFormat, user.PasswordSalt);
- }
- //SecUtility.CheckParameter(ref encodedPasswordAnswer, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, false, MAX_PASSWORD_ANSWER_LENGTH, "newPasswordAnswer");
- user.PasswordQuestion = newPasswordQuestion;
- user.PasswordAnswer = encodedPasswordAnswer;
- var msg = String.Format("Unable to save new password question and answer for user '{0}'", username);
- Save(user, msg, "ChangePasswordQuestionAndAnswer");
- return true;
- }
- /// <summary>
- /// Adds a new membership user to the data source.
- /// </summary>
- /// <param name="username">The user name for the new user.</param>
- /// <param name="password">The password for the new user.</param>
- /// <param name="email">The e-mail address for the new user.</param>
- /// <param name="passwordQuestion">The password question for the new user.</param>
- /// <param name="passwordAnswer">The password answer for the new user</param>
- /// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
- /// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
- /// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"/> enumeration value indicating whether the user was created successfully.</param>
- /// <returns>
- /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the information for the newly created user.
- /// </returns>
- public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
- {
- #region Validation
- if (!SecUtility.ValidateParameter(ref username, true, true, InvalidUsernameCharacters, MAX_USERNAME_LENGTH))
- {
- status = MembershipCreateStatus.InvalidUserName;
- return null;
- }
- if (!SecUtility.ValidateParameter(ref email, this.RequiresUniqueEmail, this.RequiresUniqueEmail, InvalidEmailCharacters, MAX_EMAIL_LENGTH))
- {
- status = MembershipCreateStatus.InvalidEmail;
- return null;
- }
- if (!SecUtility.ValidateParameter(ref password, true, true, null, MAX_PASSWORD_LENGTH))
- {
- status = MembershipCreateStatus.InvalidPassword;
- return null;
- }
- if (password.Length > MAX_PASSWORD_LENGTH)
- {
- status = MembershipCreateStatus.InvalidPassword;
- return null;
- }
- if (null != passwordAnswer)
- {
- passwordAnswer = passwordAnswer.Trim();
- }
- if (string.IsNullOrEmpty(passwordAnswer))
- {
- if (RequiresQuestionAndAnswer)
- {
- status = MembershipCreateStatus.InvalidAnswer;
- return null;
- }
- }
- else
- {
- if (passwordAnswer.Length > MAX_PASSWORD_ANSWER_LENGTH)
- {
- status = MembershipCreateStatus.InvalidAnswer;
- return null;
- }
- }
- if (!SecUtility.ValidateParameter(ref passwordQuestion, this.RequiresQuestionAndAnswer, true, null, MAX_PASSWORD_QUESTION_LENGTH))
- {
- status = MembershipCreateStatus.InvalidQuestion;
- return null;
- }
- if ((null != providerUserKey) && !(providerUserKey is Guid))
- {
- status = MembershipCreateStatus.InvalidProviderUserKey;
- return null;
- }
- if (password.Length < this.MinRequiredPasswordLength)
- {
- status = MembershipCreateStatus.InvalidPassword;
- return null;
- }
- if (this.MinRequiredNonAlphanumericCharacters > 0)
- {
- int numNonAlphaNumericChars = 0;
- for (int i = 0; i < password.Length; i++)
- {
- if (!char.IsLetterOrDigit(password, i))
- {
- numNonAlphaNumericChars++;
- }
- }
- if (numNonAlphaNumericChars < this.MinRequiredNonAlphanumericCharacters)
- {
- status = MembershipCreateStatus.InvalidPassword;
- return null;
- }
- }
- if ((this.PasswordStrengthRegularExpression.Length > 0) && !Regex.IsMatch(password, this.PasswordStrengthRegularExpression))
- {
- status = MembershipCreateStatus.InvalidPassword;
- return null;
- }
- #endregion
- ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
- OnValidatingPassword(args);
- if (args.Cancel)
- {
- status = MembershipCreateStatus.InvalidPassword;
- return null;
- }
- if (RequiresUniqueEmail && !String.IsNullOrEmpty(GetUserNameByEmail(email)))
- {
- status = MembershipCreateStatus.DuplicateEmail;
- return null;
- }
- MembershipUser u = GetUser(username, false);
- if (null != u)
- {
- status = MembershipCreateStatus.DuplicateUserName;
- return null;
- }
- DateTime createDate = DateTime.UtcNow;
- if (null == providerUserKey)
- {
- providerUserKey = Guid.NewGuid();
- }
- else
- {
- if (!(providerUserKey is Guid))
- {
- status = MembershipCreateStatus.InvalidProviderUserKey;
- return null;
- }
- }
- var createAt = DateTime.UtcNow;
- string salt = GenerateSalt();
- var answer = passwordAnswer;
- if (null != answer)
- {
- answer = EncodePassword(passwordAnswer.ToLowerInvariant(), PasswordFormat, salt);
- }
- var user = new User();
- user.Id = (Guid)providerUserKey;
- user.Username = username;
- user.LowercaseUsername = username.ToLowerInvariant();
- user.DisplayName = username;
- user.Email = email;
- user.LowercaseEmail = (null == email) ? null : email.ToLowerInvariant();
- user.Password = EncodePassword(password, PasswordFormat, salt);
- user.PasswordQuestion = passwordQuestion;
- user.PasswordAnswer = answer;
- user.PasswordFormat = PasswordFormat;
- user.PasswordSalt = salt;
- user.IsApproved = isApproved;
- user.LastPasswordChangedDate = DateTime.MinValue;
- user.CreateDate = createAt;
- user.IsLockedOut = false;
- user.LastLockedOutDate = DateTime.MinValue;
- user.LastActivityDate = createAt;
- user.FailedPasswordAnswerAttemptCount = 0;
- user.FailedPasswordAnswerAttemptWindowStart = DateTime.MinValue;
- user.FailedPasswordAttemptCount = 0;
- user.FailedPasswordAttemptWindowStart = DateTime.MinValue;
- var msg = String.Format("Error creating new User '{0}'", username);
- Save(user, msg, "CreateUser");
- status = MembershipCreateStatus.Success;
- return GetUser(username, false);
- }
- /// <summary>
- /// Removes a user from the membership data source.
- /// </summary>
- /// <param name="username">The name of the user to delete.</param>
- /// <param name="deleteAllRelatedData">true to delete data related to the user from the database; false to leave data related to the user in the database.</param>
- /// <returns>
- /// true if the user was successfully deleted; otherwise, false.
- /// </returns>
- public override bool DeleteUser(string username, bool deleteAllRelatedData)
- {
- if (String.IsNullOrWhiteSpace(username)) return false;
- var query = Query.EQ(ElementNames.LowercaseUsername, username.ToLowerInvariant());
- var result = Collection.Remove(query, SafeMode.True);
- return result.Ok;
- }
- /// <summary>
- /// Gets a collection of membership users where the user name matches the specified string.
- /// </summary>
- /// <param name="usernameToMatch">The user name to search for.
- /// Match string may contain the standard SQL LIKE wildcard: %
- /// "sm%" -> StartsWith("sm")
- /// "%ith" -> EndsWith("ith")
- /// "%mit%" -> Contains("mit")
- /// </param>
- /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex"/> is zero-based.</param>
- /// <param name="pageSize">The size of the page of results to return.</param>
- /// <param name="totalRecords">The total number of matched users.</param>
- /// <returns>
- /// A <see cref="T:System.Web.Security.MembershipUserCollection"/> collection that contains a page of <paramref name="pageSize"/><see cref="T:System.Web.Security.MembershipUser"/> objects beginning at the page specified by <paramref name="pageIndex"/>.
- /// </returns>
- public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
- {
- return FindUsersBy(ElementNames.LowercaseUsername, usernameToMatch, pageIndex, pageSize, out totalRecords);
- }
- /// <summary>
- /// Gets a collection of membership users where the e-mail address matches the specified string.
- /// </summary>
- /// <param name="emailToMatch">The e-mail address to search for.
- /// Match string may contain the standard SQL LIKE wildcard: %
- /// "sm%" -> StartsWith("sm")
- /// "%ith" -> EndsWith("ith")
- /// "%mit%" -> Contains("mit")
- /// </param>
- /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex"/> is zero-based.</param>
- /// <param name="pageSize">The size of the page of results to return.</param>
- /// <param name="totalRecords">The total number of matched users.</param>
- /// <returns>
- /// A <see cref="T:System.Web.Security.MembershipUserCollection"/> collection that contains a page of <paramref name="pageSize"/><see cref="T:System.Web.Security.MembershipUser"/> objects beginning at the page specified by <paramref name="pageIndex"/>.
- /// </returns>
- public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
- {
- return FindUsersBy(ElementNames.LowercaseEmail, emailToMatch, pageIndex, pageSize, out totalRecords);
- }
- /// <summary>
- /// Gets a collection of all the users in the data source in pages of data.
- /// </summary>
- /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex"/> is zero-based.</param>
- /// <param name="pageSize">The size of the page of results to return.</param>
- /// <param name="totalRecords">The total number of matched users.</param>
- /// <returns>
- /// A <see cref="T:System.Web.Security.MembershipUserCollection"/> collection that contains a page of <paramref name="pageSize"/><see cref="T:System.Web.Security.MembershipUser"/> objects beginning at the page specified by <paramref name="pageIndex"/>.
- /// </returns>
- public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
- {
- MembershipUserCollection users = new MembershipUserCollection();
- var matches = Collection.AsQueryable().Skip(pageIndex * pageSize)
- .Take(pageSize)
- .ToList();
- if (null == matches)
- {
- totalRecords = 0;
- return users;
- }
- // execute second query to get total count
- totalRecords = (int)Collection.Count();
- matches.ForEach(u => users.Add(ToMembershipUser(u)));
- return users;
- }
- /// <summary>
- /// Gets the number of users currently accessing the application.
- /// </summary>
- /// <returns>
- /// The number of users currently accessing the application.
- /// </returns>
- public override int GetNumberOfUsersOnline()
- {
- // http://msdn.microsoft.com/en-us/library/system.web.security.membership.userisonlinetimewindow.aspx
- TimeSpan onlineSpan = new TimeSpan(0, Membership.UserIsOnlineTimeWindow, 0);
- DateTime compareTime = DateTime.UtcNow.Subtract(onlineSpan);
- var count = Collection.AsQueryable().Where(u => u.LastActivityDate > compareTime).Count();
- return count;
- }
- /// <summary>
- /// Gets the password for the specified user name from the data source.
- /// </summary>
- /// <param name="username">The user to retrieve the password for.</param>
- /// <param name="answer">The password answer for the user.</param>
- /// <returns>
- /// The password for the specified user name.
- /// </returns>
- public override string GetPassword(string username, string answer)
- {
- if (!EnablePasswordRetrieval)
- {
- throw new ProviderException(Resources.Membership_PasswordRetrieval_not_supported);
- }
- User user = GetUserByName(username, "GetPassword");
- if (null == user) {
- throw new MembershipPasswordException(Resources.Membership_UserNotFound);
- }
- if (user.IsLockedOut)
- {
- throw new MembershipPasswordException(Resources.Membership_AccountLockOut);
- }
- if (RequiresQuestionAndAnswer && !ComparePasswords(answer, user.PasswordAnswer, user.PasswordSalt, user.PasswordFormat))
- {
- UpdateFailureCount(user, "passwordAnswer", false);
- throw new MembershipPasswordException(Resources.Membership_WrongAnswer);
- }
- var password = user.Password;
- if (user.PasswordFormat == MembershipPasswordFormat.Encrypted)
- {
- password = UnEncodePassword(password, user.PasswordFormat);
- }
- return password;
- }
- /// <summary>
- /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
- /// </summary>
- /// <param name="username">The name of the user to get information for.</param>
- /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
- /// <returns>
- /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the specified user's information from the data source.
- /// </returns>
- public override MembershipUser GetUser(string username, bool userIsOnline)
- {
- if (String.IsNullOrWhiteSpace(username)) return null;
- if (userIsOnline)
- {
- // update the last-activity date
- var users = Collection;
- var map = BsonClassMap.LookupClassMap(typeof(User));
- var query = Query.EQ(ElementNames.LowercaseUsername, username.ToLowerInvariant());
- var update = Update.Set(ElementNames.LastActivityDate, DateTime.UtcNow);
- var result = users.FindAndModify(query, SortBy.Null, update, returnNew: true);
- if (!result.Ok)
- {
- HandleDataExceptionAndThrow(new ProviderException(result.ErrorMessage), "GetUser");
- }
- var user = BsonSerializer.Deserialize<User>(result.ModifiedDocument);
- return ToMembershipUser(user);
- }
- else
- {
- User user = Collection.AsQueryable().Where(u => u.LowercaseUsername == username.ToLowerInvariant()).FirstOrDefault();
- return ToMembershipUser(user);
- }
- }
- /// <summary>
- /// Gets user information from the data source based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user.
- /// </summary>
- /// <param name="providerUserKey">The unique identifier for the membership user to get information for.</param>
- /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
- /// <returns>
- /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the specified user's information from the data source.
- /// </returns>
- public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
- {
- if (!(providerUserKey is Guid)){
- throw new ArgumentException(Resources.Membership_InvalidProviderUserKey, "providerUserKey");
- }
- if (userIsOnline)
- {
- // update the last-activity date
- var query = Query.EQ("_id", (Guid)providerUserKey);
- var update = Update.Set(ElementNames.LastActivityDate, DateTime.UtcNow);
- var result = Collection.FindAndModify(query, SortBy.Null, update, returnNew: true);
- if (!result.Ok)
- {
- HandleDataExceptionAndThrow(new ProviderException(result.ErrorMessage), "GetUser");
- }
- var user = BsonSerializer.Deserialize<User>(result.ModifiedDocument);
- return ToMembershipUser(user);
- }
- else
- {
- User user = Collection.AsQueryable().Where(u => u.Id == (Guid)providerUserKey).FirstOrDefault();
- return ToMembershipUser(user);
- }
- }
- /// <summary>
- /// Gets the user name associated with the specified e-mail address.
- /// </summary>
- /// <param name="email">The e-mail address to search for.</param>
- /// <returns>
- /// The user name associated with the specified e-mail address. If no match is found, return null.
- /// </returns>
- public override string GetUserNameByEmail(string email)
- {
- if (null == email)
- return null;
- var username = Collection.AsQueryable().Where(u => u.LowercaseEmail == email.ToLowerInvariant()).Select(u => u.Username).FirstOrDefault();
- return username;
- }
- /// <summary>
- /// Resets a user's password to a new, automatically generated password.
- /// </summary>
- /// <param name="username">The user to reset the password for.</param>
- /// <param name="passwordAnswer">The password answer for the specified user.</param>
- /// <returns>The new password for the specified user.</returns>
- /// <exception cref="T:System.Configuration.Provider.ProviderException">username is not found in the membership database.- or -The
- /// change password action was canceled by a subscriber to the System.Web.Security.Membership.ValidatePassword
- /// event and the <see cref="P:System.Web.Security.ValidatePasswordEventArgs.FailureInformation"></see> property was null.- or -An
- /// error occurred while retrieving the password from the database. </exception>
- /// <exception cref="T:System.NotSupportedException"><see cref="P:System.Web.Security.SqlMembershipProvider.EnablePasswordReset"></see>
- /// is set to false. </exception>
- /// <exception cref="T:System.ArgumentException">username is an empty string (""), contains a comma, or is longer than 256 characters.
- /// - or -passwordAnswer is an empty string or is longer than 128 characters and
- /// <see cref="P:System.Web.Security.SqlMembershipProvider.RequiresQuestionAndAnswer"></see> is true.- or -passwordAnswer is longer
- /// than 128 characters after encoding.</exception>
- /// <exception cref="T:System.ArgumentNullException">username is null.- or -passwordAnswer is null and
- /// <see cref="P:System.Web.Security.SqlMembershipProvider.RequiresQuestionAndAnswer"></see> is true.</exception>
- /// <exception cref="T:System.Web.Security.MembershipPasswordException">passwordAnswer is invalid. - or -The user account is currently locked out.</exception>
- public override string ResetPassword(string username, string answer)
- {
- if (!this.EnablePasswordReset)
- {
- throw new NotSupportedException(Resources.Not_configured_to_support_password_resets);
- }
- User user = GetUserByName(username, "ResetPassword");
- if (null == user)
- {
- throw new ProviderException(Resources.Membership_UserNotFound);
- }
- if (user.IsLockedOut)
- {
- throw new ProviderException(Resources.Membership_AccountLockOut);
- }
- if (RequiresQuestionAndAnswer &&
- (null == answer || !ComparePasswords(answer, user.PasswordAnswer, user.PasswordSalt, user.PasswordFormat)))
- {
- UpdateFailureCount(user, "passwordAnswer", false);
- throw new MembershipPasswordException(Resources.Membership_InvalidAnswer);
- }
- string newPassword = Membership.GeneratePassword(NEW_PASSWORD_LENGTH, MinRequiredNonAlphanumericCharacters);
- ValidatePasswordEventArgs args =
- new ValidatePasswordEventArgs(username, newPassword, true);
- OnValidatingPassword(args);
- if (args.Cancel)
- if (args.FailureInformation != null)
- throw args.FailureInformation;
- else
- throw new MembershipPasswordException(Resources.Membership_Custom_Password_Validation_Failure);
- user.Password = EncodePassword(newPassword, user.PasswordFormat, user.PasswordSalt);
- user.LastPasswordChangedDate = DateTime.UtcNow;
- {
- var msg = String.Format("Error saving User '{0}' while resetting password", username);
- Save(user, msg, "ResetPassword");
- }
- return newPassword;
- }
- /// <summary>
- /// Unlocks the user.
- /// </summary>
- /// <param name="username">The username.</param>
- /// <returns>Returns true if user was unlocked; otherwise returns false.</returns>
- public override bool UnlockUser(string username)
- {
- User user = GetUserByName(username, "UnlockUser");
- if (null == user)
- {
- return false;
- }
- user.IsLockedOut = false;
- user.LastLockedOutDate = DateTime.MinValue;
- user.FailedPasswordAnswerAttemptCount = 0;
- user.FailedPasswordAnswerAttemptWindowStart = DateTime.MinValue;
- user.FailedPasswordAttemptCount = 0;
- user.FailedPasswordAttemptWindowStart = DateTime.MinValue;
- var msg = String.Format("Error saving User '{0}' while attempting to remove account lock", username);
- Save(user, msg, "UnlockUser");
- return true;
- }
- /// <summary>
- /// Updates information about a user in the data source.
- /// </summary>
- /// <param name="user">A <see cref="T:System.Web.Security.MembershipUser"/> object that represents the user to update and the updated information for the user.</param>
- public override void UpdateUser(MembershipUser user)
- {
- User u = GetUserByName(user.UserName, "UpdateUser");
- if (null == user)
- {
- throw new ProviderException(Resources.Membership_UserNotFound);
- }
- u.Email = user.Email;
- u.Comment = user.Comment;
- u.IsApproved = user.IsApproved;
- u.LastLoginDate = user.LastLoginDate;
- u.LastActivityDate = user.LastActivityDate;
- {
- var msg = "Error saving user while attempting to update Email and IsApproved status";
- Save(u, msg, "UpdateUser");
- }
- }
- /// <summary>
- /// Verifies that the specified user name and password exist in the data source.
- /// </summary>
- /// <param name="username">The name of the user to validate.</param>
- /// <param name="password">The password for the specified user.</param>
- /// <returns>
- /// true if the specified username and password are valid; otherwise, false.
- /// </returns>
- public override bool ValidateUser(string username, string password)
- {
- if (!SecUtility.ValidateParameter(ref username, true, true, InvalidUsernameCharacters, MAX_USERNAME_LENGTH) || !SecUtility.ValidateParameter(ref password, true, true, null, MAX_PASSWORD_LENGTH))
- {
- return false;
- }
- User user = GetUserByName(username, "ValidateUser");
- if (null == user || user.IsLockedOut || !user.IsApproved)
- {
- return false;
- }
- bool passwordsMatch = ComparePasswords(password, user.Password, user.PasswordSalt, user.PasswordFormat);
- if (!passwordsMatch)
- {
- // update invalid try count
- UpdateFailureCount(user, "password", isAuthenticated: false);
- return false;
- }
- // User is authenticated. Update last activity and last login dates and failure counts.
- user.LastActivityDate = DateTime.UtcNow;
- user.LastLoginDate = DateTime.UtcNow;
- user.FailedPasswordAnswerAttemptCount = 0;
- user.FailedPasswordAttemptCount = 0;
- user.FailedPasswordAnswerAttemptWindowStart = DateTime.MinValue;
- user.FailedPasswordAttemptWindowStart = DateTime.MinValue;
- var msg = String.Format("Error updating User '{0}'s last login date while validating", username);
- Save(user, msg, "ValidateUser");
- return true;
- }
- #endregion
- #region Protected Methods
- protected void ValidatePwdStrengthRegularExpression()
- {
- // Validate regular expression, if supplied.
- if (null == _passwordStrengthRegularExpression)
- _passwordStrengthRegularExpression = String.Empty;
- _passwordStrengthRegularExpression = _passwordStrengthRegularExpression.Trim();
- if (_passwordStrengthRegularExpression.Length > 0)
- {
- try
- {
- new Regex(_passwordStrengthRegularExpression);
- }
- catch (ArgumentException ex)
- {
- throw new ProviderException(ex.Message, ex);
- }
- }
- }
- protected MembershipUser ToMembershipUser(User us…
Large files files are truncated, but you can click here to view the full file