From 1995b25092841a750a880e0402f8e4a221b5c5ac Mon Sep 17 00:00:00 2001 From: Darren Ferguson Date: Wed, 28 Aug 2013 12:48:04 +0100 Subject: [PATCH] Adding new Memership provider implementation Obsolete SecUtility class Add Security Helper class --- src/Umbraco.Core/Services/IMemberService.cs | 18 +- src/Umbraco.Core/Services/MemberService.cs | 70 +- src/Umbraco.Web/Helpers/SecurityHelper.cs | 88 ++ .../Providers/MembersMembershipProvider.cs | 768 ++++++++++++++++-- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/umbraco.providers/SecUtility.cs | 1 + 6 files changed, 883 insertions(+), 63 deletions(-) create mode 100644 src/Umbraco.Web/Helpers/SecurityHelper.cs diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 9c0f9a14cf..0216639cb3 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Services +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Services { /// /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. @@ -15,5 +17,17 @@ /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. /// internal interface IMembershipMemberService : IService - {} + { + IMembershipUser CreateMember(string username, string email, string password, string memberType, int userId = 0); + + IMembershipUser GetByUsername(string login); + + IMembershipUser GetByEmail(string email); + + IMembershipUser GetById(object id); + + void Delete(IMembershipUser membershipUser); + + void Save(IMembershipUser membershipUser); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index a977069045..0d6a6e471b 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -1,5 +1,8 @@ -using Umbraco.Core.Persistence; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; +using System.Linq; namespace Umbraco.Core.Services { @@ -26,5 +29,70 @@ namespace Umbraco.Core.Services _repositoryFactory = repositoryFactory; _uowProvider = provider; } + + public IMembershipUser CreateMember(string username, string email, string password, string memberType, int userId = 0) + { + var uow = _uowProvider.GetUnitOfWork(); + + var member = new Member(); + + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + member.Username = username; + member.Email = email; + member.Password = password; + + repository.AddOrUpdate(member); + uow.Commit(); + } + + return member; + } + + public IMembershipUser GetByUsername(string userName) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Username == userName); + var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + + return membershipUser; + } + } + + public IMembershipUser GetByEmail(string email) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Email == email); + var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + + return membershipUser; + } + } + + public IMembershipUser GetById(object id) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Id == id); + var membershipUser = repository.GetByQuery(query).FirstOrDefault(); + + return membershipUser; + } + } + + public void Delete(IMembershipUser membershipUser) + { + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + repository.Delete(membershipUser); + } + } + + public void Save(IMembershipUser membershipUser) + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Helpers/SecurityHelper.cs b/src/Umbraco.Web/Helpers/SecurityHelper.cs new file mode 100644 index 0000000000..a731d516b2 --- /dev/null +++ b/src/Umbraco.Web/Helpers/SecurityHelper.cs @@ -0,0 +1,88 @@ +using System.Collections.Specialized; +using System.Configuration.Provider; +using System.Web.Hosting; + +namespace Umbraco.Web.Helpers +{ + internal class SecurityHelper + { + /// + /// Gets the boolean value. + /// + /// The config. + /// Name of the value. + /// if set to true [default value]. + /// + internal static bool GetBooleanValue(NameValueCollection config, string valueName, bool defaultValue) + { + bool flag; + var str = config[valueName]; + if (str == null) + return defaultValue; + + if (bool.TryParse(str, out flag) == false) + { + throw new ProviderException("Value must be boolean."); + } + return flag; + } + + /// + /// Gets the int value. + /// + /// The config. + /// Name of the value. + /// The default value. + /// if set to true [zero allowed]. + /// The max value allowed. + /// + internal static int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) + { + int num; + var s = config[valueName]; + if (s == null) + { + return defaultValue; + } + if (int.TryParse(s, out num) == false) + { + if (zeroAllowed) + { + throw new ProviderException("Value must be non negative integer"); + } + throw new ProviderException("Value must be positive integer"); + } + if (zeroAllowed && (num < 0)) + { + throw new ProviderException("Value must be non negativeinteger"); + } + if (zeroAllowed == false && (num <= 0)) + { + throw new ProviderException("Value must be positive integer"); + } + if ((maxValueAllowed > 0) && (num > maxValueAllowed)) + { + throw new ProviderException("Value too big"); + } + return num; + } + + + /// + /// Gets the name of the default app. + /// + /// + internal static string GetDefaultAppName() + { + try + { + var applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; + return string.IsNullOrEmpty(applicationVirtualPath) ? "/" : applicationVirtualPath; + } + catch + { + return "/"; + } + } + } +} diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 8518b52362..b4e9ebbb2b 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -1,14 +1,209 @@ -using System.Web.Security; +using System; +using System.Collections.Specialized; +using System.Configuration.Provider; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; +using Umbraco.Web.Helpers; namespace Umbraco.Web.Security.Providers { /// - /// Custom Membership Provider for Umbraco Members (User authentication for Umbraco based Websites) + /// Extension methods + /// + static internal class Extensions + { + public static MembershipUser AsConcreteMembershipUser(this IMembershipUser member) + { + // TODO: Provider name?? first constructor arg... Replace DateTime.Now; + var m = new MembershipUser("", + member.Username, + member.ProviderUserKey, + member.Email, + member.PasswordQuestion, + member.Comments, + member.IsApproved, + member.IsLockedOut, + member.CreateDate, + member.LastLoginDate, + member.UpdateDate, + member.LastPasswordChangeDate, + member.LastLockoutDate); + + return m; + } + + public static IMembershipUser AsIMembershipUser(this MembershipUser membershipUser) + { + var m = new Member + { + Username = membershipUser.UserName, + Password = membershipUser.GetPassword(), + Email = membershipUser.Email, + CreateDate = membershipUser.CreationDate, + Comments = membershipUser.Comment, + IsApproved = membershipUser.IsApproved, + IsLockedOut = membershipUser.IsLockedOut, + LastLockoutDate = membershipUser.LastLockoutDate, + LastLoginDate = membershipUser.LastLoginDate, + LastPasswordChangeDate = membershipUser.LastPasswordChangedDate + }; + + return m; + } + + } + + /// + /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) /// internal class MembersMembershipProvider : 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 + + /// + /// 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 { _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; } + } + + /// + /// The default Umbraco member type alias to create when registration is performed using .net Membership + /// + /// + /// Member type alias + public string DefaultMemberTypeAlias { get; private set; } + + public string ProviderName { + get { return "MembersMembershipProvider"; } + } + private IMembershipMemberService _memberService; protected IMembershipMemberService MemberService @@ -16,138 +211,591 @@ namespace Umbraco.Web.Security.Providers get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } } + /// + /// 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) + { + if (config == null) {throw new ArgumentNullException("config");} + + if (string.IsNullOrEmpty(name)) name = ProviderName; + + // Initialize base provider class + base.Initialize(name, config); + + _applicationName = string.IsNullOrEmpty(config["applicationName"]) ? SecurityHelper.GetDefaultAppName() : config["applicationName"]; + + _enablePasswordRetrieval = SecurityHelper.GetBooleanValue(config, "enablePasswordRetrieval", false); + _enablePasswordReset = SecurityHelper.GetBooleanValue(config, "enablePasswordReset", false); + _requiresQuestionAndAnswer = SecurityHelper.GetBooleanValue(config, "requiresQuestionAndAnswer", false); + _requiresUniqueEmail = SecurityHelper.GetBooleanValue(config, "requiresUniqueEmail", true); + _maxInvalidPasswordAttempts = SecurityHelper.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); + _passwordAttemptWindow = SecurityHelper.GetIntValue(config, "passwordAttemptWindow", 10, false, 0); + _minRequiredPasswordLength = SecurityHelper.GetIntValue(config, "minRequiredPasswordLength", 7, true, 0x80); + _minRequiredNonAlphanumericCharacters = SecurityHelper.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80); + _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; + + // make sure password format is Hashed by default. + var str = config["passwordFormat"] ?? "Hashed"; + + LogHelper.Debug("Loaded membership provider properties"); + LogHelper.Debug(ToString()); + + switch (str.ToLower()) + { + case "clear": + _passwordFormat = MembershipPasswordFormat.Clear; + break; + case "encrypted": + _passwordFormat = MembershipPasswordFormat.Encrypted; + break; + case "hashed": + _passwordFormat = MembershipPasswordFormat.Hashed; + break; + default: + var e = new ProviderException("Provider bad password format"); + LogHelper.Error(e.Message, e); + throw e; + } + + if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) + { + var e = new ProviderException("Provider can not retrieve hashed password"); + LogHelper.Error(e.Message, e); + throw e; + } + + // TODO: rationalise what happens when no member alias is specified.... + DefaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + + LogHelper.Debug("Finished initialising member ship provider " + GetType().FullName); + + } + + public override string ToString() + { + var result = base.ToString(); + + result += "_applicationName =" + _applicationName + Environment.NewLine; + result += "_enablePasswordReset=" + _enablePasswordReset + Environment.NewLine; + result += "_enablePasswordRetrieval=" + _enablePasswordRetrieval + Environment.NewLine; + result += "_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts + Environment.NewLine; + result += "_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters + Environment.NewLine; + result += "_minRequiredPasswordLength=" + _minRequiredPasswordLength + Environment.NewLine; + result += "_passwordAttemptWindow=" + _passwordAttemptWindow + Environment.NewLine; + + result += "_passwordFormat=" + _passwordFormat + Environment.NewLine; + + result += "_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression + Environment.NewLine; + result += "_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer + Environment.NewLine; + result += "_requiresUniqueEmail=" + _requiresUniqueEmail + Environment.NewLine; + result += "DefaultMemberTypeAlias=" + DefaultMemberTypeAlias + Environment.NewLine; + + return result; + } + + /// + /// 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) { - throw new System.NotImplementedException(); + LogHelper.Debug("Member signup requested: username -> " + username + ". email -> " +email); + + // Validate password + if (IsPasswordValid(password) == false) + { + status = MembershipCreateStatus.InvalidPassword; + return null; + } + + // Validate email + if (IsEmaiValid(email) == false) + { + status = MembershipCreateStatus.InvalidEmail; + return null; + } + + // Make sure username isn't all whitespace + if (string.IsNullOrWhiteSpace(username.Trim())) + { + status = MembershipCreateStatus.InvalidUserName; + return null; + } + + // Check password question + if (string.IsNullOrWhiteSpace(passwordQuestion) && _requiresQuestionAndAnswer) + { + status = MembershipCreateStatus.InvalidQuestion; + return null; + } + + // Check password answer + if (string.IsNullOrWhiteSpace(passwordAnswer) && _requiresQuestionAndAnswer) + { + status = MembershipCreateStatus.InvalidAnswer; + return null; + } + + // See if the user already exists + if (MemberService.GetByUsername(username) != null) + { + status = MembershipCreateStatus.DuplicateUserName; + LogHelper.Warn("Cannot create member as username already exists: " + username); + return null; + } + + // See if the email is unique + if (MemberService.GetByEmail(email) != null && RequiresUniqueEmail) + { + status = MembershipCreateStatus.DuplicateEmail; + LogHelper.Warn( + "Cannot create member as a member with the same email address exists: " + email); + return null; + } + + var member = MemberService.CreateMember(username, email, password, DefaultMemberTypeAlias); + + member.IsApproved = isApproved; + member.PasswordQuestion = passwordQuestion; + member.PasswordAnswer = passwordAnswer; + + MemberService.Save(member); + + status = MembershipCreateStatus.Success; + return member.AsConcreteMembershipUser(); } + /// + /// 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 System.NotImplementedException(); + if (_requiresQuestionAndAnswer == false) + { + throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); + } + + if (ValidateUser(username, password) == false) + { + throw new MembershipPasswordException("Invalid username and password combinatio"); + } + + var member = MemberService.GetByUsername(username); + var encodedPassword = EncodePassword(password); + + if (member.Password == encodedPassword) + { + member.PasswordQuestion = newPasswordQuestion; + member.PasswordAnswer = newPasswordAnswer; + + MemberService.Save(member); + + return true; + } + else + { + //TODO: Throw here? or just return false; + } + + return false; } + /// + /// Gets the password for the specified user name from the data source. + /// + /// 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 System.NotImplementedException(); + if (_enablePasswordRetrieval == false) + throw new ProviderException("Password Retrieval Not Enabled."); + + if (_passwordFormat == MembershipPasswordFormat.Hashed) + throw new ProviderException("Cannot retrieve Hashed passwords."); + + var member = MemberService.GetByUsername(username); + + if (_requiresQuestionAndAnswer && member.PasswordAnswer != answer) + { + throw new ProviderException("Password retrieval answer doesn't match"); + } + + return member.Password; } + + /// + /// 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) { - throw new System.NotImplementedException(); + // Validate new password + if (IsPasswordValid(newPassword) == false) + { + var e = new MembershipPasswordException("Change password canceled due to new password validation failure."); + LogHelper.WarnWithException(e.Message, e); + throw e; + } + + var member = MemberService.GetByUsername(username); + if (member == null) return false; + + var encodedPassword = EncodePassword(oldPassword); + + if (member.Password == encodedPassword) + { + + member.Password = EncodePassword(newPassword); + MemberService.Save(member); + + return true; + } + + LogHelper.Warn("Can't change password as old password was incorrect"); + return false; } + /// + /// 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 (not used with Umbraco). + /// The new password for the specified user. public override string ResetPassword(string username, string answer) { - throw new System.NotImplementedException(); + if (_enablePasswordReset == false) + throw new ProviderException("Password reset is Not Enabled."); + + var member = MemberService.GetByUsername(username); + + if (member == null) + throw new ProviderException("The supplied user is not found"); + + if(member.IsLockedOut) + throw new ProviderException("The member is locked out."); + + if (_requiresQuestionAndAnswer == false || (_requiresQuestionAndAnswer && answer == member.PasswordAnswer)) + { + member.Password = + EncodePassword(Membership.GeneratePassword(_minRequiredPasswordLength, + _minRequiredNonAlphanumericCharacters)); + MemberService.Save(member); + } + else + { + throw new MembershipPasswordException("Incorrect password answer"); + } + + return null; } + + /// + /// Updates e-mail and potentially approved status, lock status and comment on a user. + /// Note: To automatically support lock, approve and comments you'll need to add references to the membertype properties in the + /// 'Member' element in web.config by adding their aliases to the 'umbracoApprovePropertyTypeAlias', 'umbracoLockPropertyTypeAlias' and 'umbracoCommentPropertyTypeAlias' attributes + /// + /// A object that represents the user to update and the updated information for the user. public override void UpdateUser(MembershipUser user) { - throw new System.NotImplementedException(); + var member = user.AsIMembershipUser(); + MemberService.Save(member); } + /// + /// 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) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + + if (member == null || member.IsApproved == false) return false; + + if (member.IsLockedOut) + throw new ProviderException("The member is locked out."); + + var encodedPassword = EncodePassword(password); + + var authenticated = (encodedPassword == member.Password); + + if (authenticated == false) + { + // TODO: Increment login attempts - lock if too many. + + //var count = member.GetValue("loginAttempts"); + //count++; + + //if (count >= _maxInvalidPasswordAttempts) + //{ + // member.SetValue("loginAttempts", 0); + // member.IsLockedOut = true; + // throw new ProviderException("The member " + member.Username + " is locked out."); + //} + //else + //{ + // member.SetValue("loginAttempts", count); + //} + } + else + { + // add this later - member.SetValue("loginAttempts", 0); + member.LastLoginDate = DateTime.Now; + } + + MemberService.Save(member); + return authenticated; } - public override bool UnlockUser(string userName) + /// + /// 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) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + + if(member == null) + throw new ProviderException("Cannot find member " + username); + + // Non need to update + if (member.IsLockedOut == false) return true; + + member.IsLockedOut = false; + // TODO: add this later - member.SetValue("loginAttempts", 0); + + MemberService.Save(member); + + return true; } + + /// + /// 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) { - throw new System.NotImplementedException(); + var member = MemberService.GetById(providerUserKey); + + if (userIsOnline) + { + member.UpdateDate = DateTime.Now; + MemberService.Save(member); + } + + return member.AsConcreteMembershipUser(); } + /// + /// 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) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + + if (userIsOnline) + { + member.UpdateDate = DateTime.Now; + MemberService.Save(member); + } + + return member.AsConcreteMembershipUser(); } + /// + /// 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 System.NotImplementedException(); + var member = MemberService.GetByEmail(email); + + return member == null ? null : member.Username; } + /// + /// 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) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + if (member == null) return false; + + MemberService.Delete(member); + return true; } + /// + /// 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) { throw new System.NotImplementedException(); } + /// + /// Gets the number of users currently accessing the application. + /// + /// + /// The number of users currently accessing the application. + /// public override int GetNumberOfUsersOnline() { throw new System.NotImplementedException(); } + + /// + /// 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) { throw new System.NotImplementedException(); } + /// + /// 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) { throw new System.NotImplementedException(); } - public override bool EnablePasswordRetrieval + private bool IsPasswordValid(string password) { - get { throw new System.NotImplementedException(); } + if (_minRequiredNonAlphanumericCharacters > 0) + { + var nonAlphaNumeric = Regex.Replace(password, "[a-zA-Z0-9]", "", RegexOptions.Multiline | RegexOptions.IgnoreCase); + if (nonAlphaNumeric.Length < _minRequiredNonAlphanumericCharacters) + { + return false; + } + } + + var valid = true; + if(string.IsNullOrEmpty(_passwordStrengthRegularExpression) == false) + { + valid = Regex.IsMatch(password, _passwordStrengthRegularExpression, RegexOptions.Compiled); + } + + return valid && password.Length >= _minRequiredPasswordLength; } - public override bool EnablePasswordReset + private bool IsEmaiValid(string email) { - get { throw new System.NotImplementedException(); } - } + const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" + + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? + /// Encodes the password. + /// + /// The password. + /// The encoded password. + private string EncodePassword(string password) { - get { throw new System.NotImplementedException(); } - } - - public override string ApplicationName { get; set; } - - public override int MaxInvalidPasswordAttempts - { - get { throw new System.NotImplementedException(); } - } - - public override int PasswordAttemptWindow - { - get { throw new System.NotImplementedException(); } - } - - public override bool RequiresUniqueEmail - { - get { throw new System.NotImplementedException(); } - } - - public override MembershipPasswordFormat PasswordFormat - { - get { throw new System.NotImplementedException(); } - } - - public override int MinRequiredPasswordLength - { - get { throw new System.NotImplementedException(); } - } - - public override int MinRequiredNonAlphanumericCharacters - { - get { throw new System.NotImplementedException(); } - } - - public override string PasswordStrengthRegularExpression - { - get { throw new System.NotImplementedException(); } - } + var encodedPassword = password; + switch (PasswordFormat) + { + case MembershipPasswordFormat.Clear: + break; + case MembershipPasswordFormat.Encrypted: + encodedPassword = + Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password))); + break; + case MembershipPasswordFormat.Hashed: + var hash = new HMACSHA1 {Key = Encoding.Unicode.GetBytes(password)}; + encodedPassword = + Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password))); + break; + default: + throw new ProviderException("Unsupported password format."); + } + return encodedPassword; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ef5cd23446..5b3ad0cfc5 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -291,6 +291,7 @@ + diff --git a/src/umbraco.providers/SecUtility.cs b/src/umbraco.providers/SecUtility.cs index 72af5f45e0..0bf43bfa08 100644 --- a/src/umbraco.providers/SecUtility.cs +++ b/src/umbraco.providers/SecUtility.cs @@ -13,6 +13,7 @@ namespace umbraco.providers /// /// Security Helper Methods /// + [ObsoleteAttribute("This clas is obsolete. Use Umbraco.Web.Security.Helper.SecurityHelper instead.", false)] internal class SecUtility { ///