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 ed3c6a1822..a3e57c95f6 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -294,6 +294,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
{
///