#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 } }