#region namespace
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Security;
using System.Configuration;
using umbraco.BusinessLogic;
using System.Security.Cryptography;
using System.Web.Util;
using System.Collections.Specialized;
using System.Configuration.Provider;
#endregion
namespace umbraco.providers
{
///
/// Custom Membership Provider for Umbraco Users (User authentication for Umbraco Backend CMS)
///
public class UsersMembershipProvider : MembershipProvider
{
#region Fields
private string _ApplicationName;
private bool _EnablePasswordReset;
private bool _EnablePasswordRetrieval;
private int _MaxInvalidPasswordAttempts;
private int _MinRequiredNonAlphanumericCharacters;
private int _MinRequiredPasswordLength;
private int _PasswordAttemptWindow;
private MembershipPasswordFormat _PasswordFormat;
private string _PasswordStrengthRegularExpression;
private bool _RequiresQuestionAndAnswer;
private bool _RequiresUniqueEmail;
#endregion
#region Properties
///
/// The name of the application using the custom membership provider.
///
///
/// The name of the application using the custom membership provider.
public override string ApplicationName
{
get
{
return _ApplicationName;
}
set
{
if (string.IsNullOrEmpty(value))
throw new ProviderException("ApplicationName cannot be empty.");
if (value.Length > 0x100)
throw new ProviderException("Provider application name too long.");
_ApplicationName = value;
}
}
///
/// Indicates whether the membership provider is configured to allow users to reset their passwords.
///
///
/// true if the membership provider supports password reset; otherwise, false. The default is true.
public override bool EnablePasswordReset
{
get { return _EnablePasswordReset; }
}
///
/// Indicates whether the membership provider is configured to allow users to retrieve their passwords.
///
///
/// true if the membership provider is configured to support password retrieval; otherwise, false. The default is false.
public override bool EnablePasswordRetrieval
{
get { return _EnablePasswordRetrieval; }
}
///
/// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out.
///
///
/// The number of invalid password or password-answer attempts allowed before the membership user is locked out.
public override int MaxInvalidPasswordAttempts
{
get { return _MaxInvalidPasswordAttempts; }
}
///
/// Gets the minimum number of special characters that must be present in a valid password.
///
///
/// The minimum number of special characters that must be present in a valid password.
public override int MinRequiredNonAlphanumericCharacters
{
get { return _MinRequiredNonAlphanumericCharacters; }
}
///
/// Gets the minimum length required for a password.
///
///
/// The minimum length required for a password.
public override int MinRequiredPasswordLength
{
get { return _MinRequiredPasswordLength; }
}
///
/// 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.
///
///
/// 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.
public override int PasswordAttemptWindow
{
get { return _PasswordAttemptWindow; }
}
///
/// Gets a value indicating the format for storing passwords in the membership data store.
///
///
/// One of the values indicating the format for storing passwords in the data store.
public override MembershipPasswordFormat PasswordFormat
{
get { return _PasswordFormat; }
}
///
/// Gets the regular expression used to evaluate a password.
///
///
/// A regular expression used to evaluate a password.
public override string PasswordStrengthRegularExpression
{
get { return _PasswordStrengthRegularExpression; }
}
///
/// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval.
///
///
/// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true.
public override bool RequiresQuestionAndAnswer
{
get { return _RequiresQuestionAndAnswer; }
}
///
/// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name.
///
///
/// true if the membership provider requires a unique e-mail address; otherwise, false. The default is true.
public override bool RequiresUniqueEmail
{
get { return _RequiresUniqueEmail; }
}
#endregion
#region Initialization Method
///
/// Initializes the provider.
///
/// The friendly name of the provider.
/// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.
/// The name of the provider is null.
/// An attempt is made to call
/// on a provider after the provider
/// has already been initialized.
/// The name of the provider has a length of zero.
public override void Initialize(string name, NameValueCollection config)
{
// Intialize values from web.config
if (config == null) throw new ArgumentNullException("config");
if (string.IsNullOrEmpty(name)) name = "UsersMembershipProvider";
// Initialize base provider class
base.Initialize(name, config);
this._EnablePasswordRetrieval = SecUtility.GetBooleanValue(config, "enablePasswordRetrieval", false);
this._EnablePasswordReset = SecUtility.GetBooleanValue(config, "enablePasswordReset", false);
this._RequiresQuestionAndAnswer = SecUtility.GetBooleanValue(config, "requiresQuestionAndAnswer", false);
this._RequiresUniqueEmail = SecUtility.GetBooleanValue(config, "requiresUniqueEmail", false);
this._MaxInvalidPasswordAttempts = SecUtility.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
this._PasswordAttemptWindow = SecUtility.GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
this._MinRequiredPasswordLength = SecUtility.GetIntValue(config, "minRequiredPasswordLength", 7, true, 0x80);
this._MinRequiredNonAlphanumericCharacters= SecUtility.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80);
this._PasswordStrengthRegularExpression = config["passwordStrengthRegularExpression"];
this._ApplicationName = config["applicationName"];
if (string.IsNullOrEmpty(this._ApplicationName))
this._ApplicationName = SecUtility.GetDefaultAppName();
// make sure password format is clear by default.
string str = config["passwordFormat"];
if (str == null) str = "Clear";
switch (str.ToLower())
{
case "clear":
this._PasswordFormat = MembershipPasswordFormat.Clear;
break;
case "encrypted":
this._PasswordFormat = MembershipPasswordFormat.Encrypted;
break;
case "hashed":
this._PasswordFormat = MembershipPasswordFormat.Hashed;
break;
default:
throw new ProviderException("Provider bad password format");
}
if ((this.PasswordFormat == MembershipPasswordFormat.Hashed) && this.EnablePasswordRetrieval)
throw new ProviderException("Provider can not retrieve hashed password");
}
#endregion
#region Methods
///
/// Processes a request to update the password for a membership user.
///
/// The user to update the password for.
/// The current password for the specified user.
/// The new password for the specified user.
///
/// true if the password was updated successfully; otherwise, false.
///
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
if (!User.validateCredentials(username, oldPassword))
return false;
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.");
User user = new User(username);
string encodedPassword = EncodePassword(newPassword);
user.Password = encodedPassword;
return (user.ValidatePassword(encodedPassword)) ? true : false;
}
///
/// Processes a request to update the password question and answer for a membership user.
///
/// The user to change the password question and answer for.
/// The password for the specified user.
/// The new password question for the specified user.
/// The new password answer for the specified user.
///
/// true if the password question and answer are updated successfully; otherwise, false.
///
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
{
throw new Exception("The method or operation is not implemented.");
}
///
/// Adds a new membership user to the data source.
///
/// The user name for the new user.
/// The password for the new user.
/// The e-mail address for the new user.
/// The password question for the new user.
/// The password answer for the new user
/// Whether or not the new user is approved to be validated.
/// The unique identifier from the membership data source for the user.
/// A enumeration value indicating whether the user was created successfully.
///
/// A object populated with the information for the newly created user.
///
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
OnValidatingPassword(args);
if (args.Cancel)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
// Does umbraco allow duplicate emails??
//if (RequiresUniqueEmail && !string.IsNullOrEmpty(GetUserNameByEmail(email)))
//{
// status = MembershipCreateStatus.DuplicateEmail;
// return null;
//}
UsersMembershipUser u = GetUser(username, false) as UsersMembershipUser;
if (u == null)
{
try
{
// Get the usertype of the current user
BusinessLogic.UserType ut = BusinessLogic.UserType.GetUserType(1);
if (umbraco.BasePages.UmbracoEnsuredPage.CurrentUser != null)
{
ut = umbraco.BasePages.UmbracoEnsuredPage.CurrentUser.UserType;
}
User.MakeNew(username, username, password, ut);
status = MembershipCreateStatus.Success;
}
catch(Exception)
{
status = MembershipCreateStatus.ProviderError;
}
return GetUser(username, false);
}
else
{
status = MembershipCreateStatus.DuplicateUserName;
return null;
}
}
///
/// Removes a user from the membership data source.
///
/// The name of the user to delete.
/// true to delete data related to the user from the database; false to leave data related to the user in the database.
///
/// true if the user was successfully deleted; otherwise, false.
///
public override bool DeleteUser(string username, bool deleteAllRelatedData)
{
try
{
User user = new User(username);
user.delete();
}
catch (Exception)
{
return false;
}
return true;
}
///
/// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match.
///
/// The e-mail address to search for.
/// The index of the page of results to return. pageIndex is zero-based.
/// The size of the page of results to return.
/// The total number of matched users.
///
/// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex.
///
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
int counter = 0;
int startIndex = pageSize * pageIndex;
int endIndex = startIndex + pageSize - 1;
MembershipUserCollection usersList = new MembershipUserCollection();
User[] usersArray = User.getAllByEmail(emailToMatch);
totalRecords = usersArray.Length;
foreach (User user in usersArray)
{
if (counter >= startIndex)
usersList.Add(ConvertToMembershipUser(user));
if (counter >= endIndex) break;
counter++;
}
return usersList;
}
///
/// Gets a collection of membership users where the user name contains the specified user name to match.
///
/// The user name to search for.
/// The index of the page of results to return. pageIndex is zero-based.
/// The size of the page of results to return.
/// The total number of matched users.
///
/// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex.
///
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
int counter = 0;
int startIndex = pageSize * pageIndex;
int endIndex = startIndex + pageSize - 1;
MembershipUserCollection usersList = new MembershipUserCollection();
User[] usersArray = User.getAllByLoginName(usernameToMatch);
totalRecords = usersArray.Length;
foreach (User user in usersArray)
{
if (counter >= startIndex)
usersList.Add(ConvertToMembershipUser(user));
if (counter >= endIndex) break;
counter++;
}
return usersList;
}
///
/// Gets a collection of all the users in the data source in pages of data.
///
/// The index of the page of results to return. pageIndex is zero-based.
/// The size of the page of results to return.
/// The total number of matched users.
///
/// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex.
///
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
int counter = 0;
int startIndex = pageSize * pageIndex;
int endIndex = startIndex + pageSize - 1;
MembershipUserCollection usersList = new MembershipUserCollection();
User[] usersArray = User.getAll();
totalRecords = usersArray.Length;
foreach (User user in usersArray)
{
if (counter >= startIndex)
usersList.Add(ConvertToMembershipUser(user));
if (counter >= endIndex) break;
counter++;
}
return usersList;
}
///
/// Gets the number of users currently accessing the application.
///
///
/// The number of users currently accessing the application.
///
public override int GetNumberOfUsersOnline()
{
throw new Exception("The method or operation is not implemented.");
}
///
/// Gets the password for the specified user name from the data source. This is - for security - not
/// supported for Umbraco Users and an exception will be thrown
///
/// The user to retrieve the password for.
/// The password answer for the user.
///
/// The password for the specified user name.
///
public override string GetPassword(string username, string answer)
{
throw new ProviderException("Password Retrieval Not Enabled.");
}
///
/// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
///
/// The name of the user to get information for.
/// 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.
///
/// A object populated with the specified user's information from the data source.
///
public override MembershipUser GetUser(string username, bool userIsOnline)
{
int userId = User.getUserId(username);
return (userId != -1) ? ConvertToMembershipUser(new User(userId)) : null;
}
///
/// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user.
///
/// The unique identifier for the membership user to get information for.
/// 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.
///
/// A object populated with the specified user's information from the data source.
///
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
return ConvertToMembershipUser(new User(Convert.ToInt32(providerUserKey)));
}
///
/// Gets the user name associated with the specified e-mail address.
///
/// The e-mail address to search for.
///
/// The user name associated with the specified e-mail address. If no match is found, return null.
///
public override string GetUserNameByEmail(string email)
{
throw new Exception("The method or operation is not implemented.");
}
///
/// Resets a user's password to a new, automatically generated password.
///
/// The user to reset the password for.
/// The password answer for the specified user.
/// The new password for the specified user.
public override string ResetPassword(string username, string answer)
{
int userId = User.getUserId(username);
return (userId == -1) ? null : Membership.GeneratePassword(
MinRequiredPasswordLength,
MinRequiredNonAlphanumericCharacters);
}
///
/// Clears a lock so that the membership user can be validated.
///
/// The membership user to clear the lock status for.
///
/// true if the membership user was successfully unlocked; otherwise, false.
///
public override bool UnlockUser(string userName)
{
try
{
User user = new User(userName);
user.Disabled = false;
}
catch (Exception)
{
return false;
}
return true;
}
///
/// Updates information about a user in the data source.
///
/// A object that represents the user to update and the updated information for the user.
public override void UpdateUser(MembershipUser user)
{
UsersMembershipUser umbracoUser = user as UsersMembershipUser;
int userID = 0;
if (int.TryParse(umbracoUser.ProviderUserKey.ToString(), out userID))
{
try
{
User.Update(userID, umbracoUser.FullName, umbracoUser.UserName, umbracoUser.Email, umbracoUser.UserType);
}
catch (Exception)
{
throw new ProviderException("User cannot be updated.");
}
}
}
///
/// Verifies that the specified user name and password exist in the data source.
///
/// The name of the user to validate.
/// The password for the specified user.
///
/// true if the specified username and password are valid; otherwise, false.
///
public override bool ValidateUser(string username, string password)
{
User user = new User(username);
if (user != null && user.Id != -1)
{
if (user.Disabled) return false;
else return user.ValidatePassword(EncodePassword(password));
}
return false;
}
#endregion
#region Helper Methods
///
/// Checks the password.
///
/// The password.
/// The dbPassword.
///
internal bool CheckPassword(string password, string dbPassword)
{
string pass1 = password;
string pass2 = dbPassword;
switch (PasswordFormat)
{
case MembershipPasswordFormat.Encrypted:
pass2 = UnEncodePassword(dbPassword);
break;
case MembershipPasswordFormat.Hashed:
pass1 = EncodePassword(password);
break;
default:
break;
}
return (pass1 == pass2) ? true : false;
}
///
/// Encodes the password.
///
/// The password.
/// The encoded password.
public 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 = Encoding.Unicode.GetBytes(password);
encodedPassword =
Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
break;
default:
throw new ProviderException("Unsupported password format.");
}
return encodedPassword;
}
///
/// Unencode password.
///
/// The encoded password.
/// The unencoded password.
public 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;
}
///
/// Converts to membership user.
///
/// The user.
///
private UsersMembershipUser ConvertToMembershipUser(User user)
{
if (user == null) return null;
else
{
return new UsersMembershipUser(base.Name, user.LoginName, user.Id, user.Email,
string.Empty, string.Empty, true, user.Disabled,
DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now,
DateTime.Now, user.Name, user.Language, user.UserType);
}
}
#endregion
}
}