#region namespace using System; using System.Collections.Generic; using System.Web.Security; using System.Configuration; using Umbraco.Core; using Umbraco.Core.Security; using umbraco.BusinessLogic; using System.Web.Util; using System.Configuration.Provider; using System.Linq; using Umbraco.Core.Logging; #endregion namespace umbraco.providers { /// /// Custom Membership Provider for Umbraco Users (User authentication for Umbraco Backend CMS) /// [Obsolete("This has been superceded by Umbraco.Web.Security.Providers.UsersMembershipProvider")] public class UsersMembershipProvider : MembershipProviderBase, IUsersMembershipProvider { /// /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars /// public override int DefaultMinNonAlphanumericChars { get { return 0; } } /// /// Override to maintain backwards compatibility with only 4 required length /// public override int DefaultMinPasswordLength { get { return 4; } } /// /// Override to maintain backwards compatibility /// public override bool DefaultUseLegacyEncoding { get { return true; } } /// /// For backwards compatibility, this provider supports this option /// public override bool AllowManuallyChangingPassword { get { return true; } } public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { if (config == null) throw new ArgumentNullException("config"); if (string.IsNullOrEmpty(name)) name = UmbracoSettings.DefaultBackofficeProvider; base.Initialize(name, config); } #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. /// protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) { //NOTE: due to backwards compatibilty reasons (and UX reasons), this provider doesn't care about the old password and // allows simply setting the password manually so we don't really care about the old password. // This is allowed based on the overridden AllowManuallyChangingPassword option. var user = new User(username); //encrypt/hash the new one string salt; var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); //Yes, it's true, this actually makes a db call to set the password user.Password = FormatPasswordForStorage(encodedPassword, salt); //call this just for fun. user.Save(); return true; } /// /// 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. /// protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { throw new NotImplementedException(); } /// /// 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. /// protected override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { // TODO: Does umbraco allow duplicate emails?? //if (RequiresUniqueEmail && !string.IsNullOrEmpty(GetUserNameByEmail(email))) //{ // status = MembershipCreateStatus.DuplicateEmail; // return null; //} var u = GetUser(username, false) as UsersMembershipUser; if (u == null) { try { // Get the usertype of the current user var ut = UserType.GetUserType(1); if (BasePages.UmbracoEnsuredPage.CurrentUser != null) { ut = BasePages.UmbracoEnsuredPage.CurrentUser.UserType; } //ensure the password is encrypted/hashed string salt; var encodedPass = EncryptOrHashNewPassword(password, out salt); User.MakeNew(username, username, FormatPasswordForStorage(encodedPass, salt), email, ut); status = MembershipCreateStatus.Success; } catch (Exception) { status = MembershipCreateStatus.ProviderError; } return GetUser(username, false); } 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) { var counter = 0; var startIndex = pageSize * pageIndex; var endIndex = startIndex + pageSize - 1; var usersList = new MembershipUserCollection(); var usersArray = User.getAllByEmail(emailToMatch); totalRecords = usersArray.Length; foreach (var 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) { var counter = 0; var startIndex = pageSize * pageIndex; var endIndex = startIndex + pageSize - 1; var usersList = new MembershipUserCollection(); var usersArray = User.GetAllByLoginName(usernameToMatch, true).ToArray(); totalRecords = usersArray.Length; foreach (var 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) { var counter = 0; var startIndex = pageSize * pageIndex; var endIndex = startIndex + pageSize - 1; var usersList = new MembershipUserCollection(); var 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() { var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); return Log.Instance.GetLogItems(LogTypes.Login, fromDate).Count; } /// /// 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. /// protected override string PerformGetPassword(string username, string answer) { var found = User.GetAllByLoginName(username, false).ToArray(); if (found == null || found.Any() == false) { throw new MembershipPasswordException("The supplied user is not found"); } // check if user is locked out if (found.First().NoConsole) { throw new MembershipPasswordException("The supplied user is locked out"); } if (RequiresQuestionAndAnswer) { throw new NotImplementedException("Question/answer is not supported with this membership provider"); } var decodedPassword = DecryptPassword(found.First().GetPassword()); return decodedPassword; } /// /// 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) { var userId = User.getUserId(username); if (userId == -1) { return null; } try { var user = new User(userId); //We need to log this since it's the only way we can determine the number of users online Log.Add(LogTypes.Login, user, -1, "User " + username + " has logged in"); return (userId != -1) ? ConvertToMembershipUser(user) : null; } catch (Exception) { return 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) { var user = new User(Convert.ToInt32(providerUserKey)); //We need to log this since it's the only way we can determine the number of users online Log.Add(LogTypes.Login, user, -1, "User " + user.LoginName + " has logged in"); return ConvertToMembershipUser(user); } /// /// 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) { var found = User.getAllByEmail(email.Trim().ToLower(), true); if (found == null || found.Any() == false) { return null; } return found.First().LoginName; } /// /// 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. protected override string PerformResetPassword(string username, string answer, string generatedPassword) { //TODO: This should be here - but how do we update failure count in this provider?? //if (answer == null && RequiresQuestionAndAnswer) //{ // UpdateFailureCount(username, "passwordAnswer"); // throw new ProviderException("Password answer required for password reset."); //} var found = User.GetAllByLoginName(username, false).ToArray(); if (found == null || found.Any() == false) throw new MembershipPasswordException("The supplied user is not found"); var user = found.First(); //Yes, it's true, this actually makes a db call to set the password string salt; var encPass = EncryptOrHashNewPassword(generatedPassword, out salt); user.Password = FormatPasswordForStorage(encPass, salt); //call this just for fun. user.Save(); return generatedPassword; } /// /// 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 { var user = new User(userName) { NoConsole = false }; user.Save(); } 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) { var found = User.GetAllByLoginName(user.UserName, false).ToArray(); if (found == null || found.Any() == false) { throw new ProviderException("The supplied user is not found"); } var m = found.First(); if (RequiresUniqueEmail && user.Email.Trim().IsNullOrWhiteSpace() == false) { var byEmail = User.getAllByEmail(user.Email, true); if (byEmail.Count(x => x.Id != m.Id) > 0) { throw new ProviderException(string.Format("A member with the email '{0}' already exists", user.Email)); } } var typedUser = user as UsersMembershipUser; if (typedUser == null) { // update approve status // update lock status // TODO: Update last lockout time // TODO: update comment User.Update(m.Id, user.Email, user.IsApproved == false, user.IsLockedOut); } else { //This keeps compatibility - even though this logic to update name and user type should not exist here User.Update(m.Id, typedUser.FullName.Trim(), typedUser.UserName, typedUser.Email, user.IsApproved == false, user.IsLockedOut, typedUser.UserType); } m.Save(); } /// /// 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) { var userId = User.getUserId(username); if (userId != -1) { var user = User.GetUser(userId); if (user != null) { if (user.Disabled) { LogHelper.Info( string.Format( "Login attempt failed for username {0} from IP address {1}, the user is locked", username, GetCurrentRequestIpAddress())); return false; } var result = CheckPassword(password, user.Password); if (result == false) { LogHelper.Info( string.Format( "Login attempt failed for username {0} from IP address {1}", username, GetCurrentRequestIpAddress())); } else { LogHelper.Info( string.Format( "Login attempt succeeded for username {0} from IP address {1}", username, GetCurrentRequestIpAddress())); } return result; } } return false; } #endregion #region Helper Methods /// /// Encodes the password. /// /// The password. /// The encoded password. [Obsolete("Do not use this, it is the legacy way to encode a password")] public string EncodePassword(string password) { return base.LegacyEncodePassword(password); } /// /// Unencode password. /// /// The encoded password. /// The unencoded password. [Obsolete("Do not use this, it is the legacy way to decode a password")] public string UnEncodePassword(string encodedPassword) { return LegacyUnEncodePassword(encodedPassword); } /// /// Converts to membership user. /// /// The user. /// private UsersMembershipUser ConvertToMembershipUser(User user) { if (user == null) return null; 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 } }