From cd82bc6d2110404820b327255b2acda48c5d51d8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 6 Jan 2014 15:38:34 +1100 Subject: [PATCH] streamlined the new membership provider classes so now they all share the same logic and use IMembershipMemberService which is used by both IMember and IUser services --- .../Models/Membership/IMembershipUser.cs | 1 - .../Models/Membership/MembershipExtensions.cs | 6 +- .../Membership/UmbracoMembershipMember.cs | 105 +++- src/Umbraco.Core/Models/Membership/User.cs | 90 +-- .../Factories/MemberReadOnlyFactory.cs | 2 +- .../Persistence/Factories/UserFactory.cs | 4 +- .../Security/UmbracoMembershipProviderBase.cs | 19 +- .../Services/IMembershipMemberService.cs | 3 +- .../MembersMembershipProviderTests.cs | 11 +- .../Providers/MembersMembershipProvider.cs | 533 +----------------- .../UmbracoServiceMembershipProvider.cs | 533 ++++++++++++++++++ .../Providers/UsersMembershipProvider.cs | 157 +----- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/umbraco.businesslogic/User.cs | 2 +- .../UsersMembershipProvider.cs | 18 +- .../members/UmbracoMembershipProvider.cs | 12 +- 16 files changed, 762 insertions(+), 735 deletions(-) create mode 100644 src/Umbraco.Web/Security/Providers/UmbracoServiceMembershipProvider.cs diff --git a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs index b88730f4cd..d6e3294c72 100644 --- a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs @@ -14,7 +14,6 @@ namespace Umbraco.Core.Models.Membership string PasswordAnswer { get; set; } string Comments { get; set; } bool IsApproved { get; set; } - //bool IsOnline { get; set; } bool IsLockedOut { get; set; } DateTime LastLoginDate { get; set; } DateTime LastPasswordChangeDate { get; set; } diff --git a/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs index 118ba46669..6be0afe441 100644 --- a/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs @@ -5,13 +5,13 @@ namespace Umbraco.Core.Models.Membership { internal static class MembershipExtensions { - internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMember member) + internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName) { - var membershipMember = new UmbracoMembershipMember(member); + var membershipMember = new UmbracoMembershipMember(member, providerName); return membershipMember; } - internal static IMember AsIMember(this UmbracoMembershipMember membershipMember) + internal static IMembershipUser AsIMember(this UmbracoMembershipMember membershipMember) { var member = membershipMember; if (member != null) diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs index d55cc77f66..d07cb46254 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs @@ -1,66 +1,127 @@ -using System.Web.Security; +using System; +using System.Web.Security; namespace Umbraco.Core.Models.Membership { - //TODO: THere's still a bunch of properties that don't exist in this use that need to be mapped somehow. internal class UmbracoMembershipMember : MembershipUser { - private readonly IMember _member; + private readonly IMembershipUser _member; + private readonly string _userName; + private readonly object _providerUserKey; + private readonly string _passwordQuestion; + private readonly bool _isLockedOut; + private readonly DateTime _lastLockoutDate; + private readonly DateTime _creationDate; + private DateTime _lastLoginDate; + private readonly DateTime _lastPasswordChangedDate; + private readonly string _providerName; + private string _email; + private string _comment; + private bool _isApproved; + private DateTime _lastActivityDate; - public UmbracoMembershipMember(IMember member) + //NOTE: We are only overriding the properties that matter, we don't override things like IsOnline since that is handled with the sub-class and the membership providers. + + //NOTE: We are not calling the base constructor which will validate that a provider with the specified name exists which causes issues with unit tests. The ctor + // validation for that doesn't need to be there anyways (have checked the source). + public UmbracoMembershipMember(IMembershipUser member, string providerName) { - _member = member; + _member = member; + //NOTE: We are copying the values here so that everything is consistent with how the underlying built-in ASP.Net membership user + // handles data! We don't want to do anything differently there but since we cannot use their ctor we'll need to handle this logic ourselves. + if (member.Username != null) + _userName = member.Username.Trim(); + if (member.Email != null) + _email = member.Email.Trim(); + if (member.PasswordQuestion != null) + _passwordQuestion = member.PasswordQuestion.Trim(); + _providerName = providerName; + _providerUserKey = member.ProviderUserKey; + _comment = member.Comments; + _isApproved = member.IsApproved; + _isLockedOut = member.IsLockedOut; + _creationDate = member.CreateDate.ToUniversalTime(); + _lastLoginDate = member.LastLoginDate.ToUniversalTime(); + //TODO: We currently don't really have any place to store this data!! + _lastActivityDate = member.LastLoginDate.ToUniversalTime(); + _lastPasswordChangedDate = member.LastPasswordChangeDate.ToUniversalTime(); + _lastLockoutDate = member.LastLockoutDate.ToUniversalTime(); } - internal IMember Member + internal IMembershipUser Member { get { return _member; } } - public override string Email + public override string UserName { - get { return _member.Email; } - set { _member.Email = value; } + get { return _userName; } } public override object ProviderUserKey { - get { return _member.Key; } + get { return _providerUserKey; } } - public override System.DateTime CreationDate + public override string Email { - get { return _member.CreateDate; } + get { return _email; } + set { _email = value; } } - public override string UserName + public override string PasswordQuestion { - get { return _member.Username; } + get { return _passwordQuestion; } } public override string Comment { - get { return _member.Comments; } - set { _member.Comments = value; } + get { return _comment; } + set { _comment = value; } } public override bool IsApproved { - get { return _member.IsApproved; } - set { _member.IsApproved = value; } + get { return _isApproved; } + set { _isApproved = value; } } public override bool IsLockedOut { - get { return _member.IsLockedOut; } + get { return _isLockedOut; } } - public override System.DateTime LastLoginDate + public override DateTime LastLockoutDate { - get { return _member.LastLoginDate; } - set { _member.LastLoginDate = value; } + get { return _lastLockoutDate; } } + public override DateTime CreationDate + { + get { return _creationDate; } + } + + public override DateTime LastLoginDate + { + get { return _lastLoginDate; } + set { _lastLoginDate = value; } + } + + public override DateTime LastActivityDate + { + get { return _lastActivityDate; } + set { _lastActivityDate = value; } + } + + public override DateTime LastPasswordChangedDate + { + get { return _lastPasswordChangedDate; } + } + + public override string ProviderName + { + get { return _providerName; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index ae4c339f78..bd06d0f99d 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -58,7 +58,7 @@ namespace Umbraco.Core.Models.Membership private string _username; private string _email; private string _password; - private object _providerUserKey; + private Guid _key; private bool _isApproved; private bool _isLockedOut; private string _language; @@ -71,7 +71,7 @@ namespace Umbraco.Core.Models.Membership private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); + private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); private static readonly PropertyInfo UserTypeKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKeyType); private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); @@ -102,7 +102,22 @@ namespace Umbraco.Core.Models.Membership } } - public Guid Key { get; set; } + [IgnoreDataMember] + public Guid Key + { + get + { + return _key; + } + set + { + SetPropertyValueAndDetectChanges(o => + { + _key = value; + return _key; + }, _key, KeySelector); + } + } #endregion @@ -111,17 +126,18 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public object ProviderUserKey { - get - { - return _providerUserKey; - } + get { return Key; } set { - SetPropertyValueAndDetectChanges(o => + var result = value.TryConvertTo(); + if (result.Success) { - _providerUserKey = value; - return _id; - }, _providerUserKey, ProviderUserKeySelector); + Key = result.Result; + } + else + { + throw new NotSupportedException("The User object cannot have a ProviderUserKey of anything other than a GUID"); + } } } @@ -197,25 +213,6 @@ namespace Umbraco.Core.Models.Membership } } - [DataMember] - public string PasswordQuestion - { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - [DataMember] - public string PasswordAnswer - { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - [DataMember] - public string Comments - { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - [DataMember] public bool IsApproved { @@ -242,39 +239,58 @@ namespace Umbraco.Core.Models.Membership return _isLockedOut; }, _isLockedOut, IsLockedOutSelector); } - } + } - [DataMember] + //TODO: Figure out how to support all of this! + [IgnoreDataMember] + public string PasswordQuestion + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + [IgnoreDataMember] + public string PasswordAnswer + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + [IgnoreDataMember] + public string Comments + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + [IgnoreDataMember] public DateTime CreateDate { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - [DataMember] + [IgnoreDataMember] public DateTime UpdateDate { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - [DataMember] + [IgnoreDataMember] public DateTime LastLoginDate { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - [DataMember] + [IgnoreDataMember] public DateTime LastPasswordChangeDate { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - [DataMember] + [IgnoreDataMember] public DateTime LastLockoutDate { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - [DataMember] + [IgnoreDataMember] public int FailedPasswordAttempts { get { throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs index 16879a7871..947d76413e 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberReadOnlyFactory.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Factories CreateDate = dto.CreateDate, UpdateDate = dto.UpdateDate, Name = dto.Text, - ProviderUserKey = dto.UniqueId, + ProviderUserKey = dto.UniqueId.Value, Trashed = dto.Trashed, Key = dto.UniqueId.Value, CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0, diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index ddbcb5557a..b30e2fe209 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -19,10 +19,12 @@ namespace Umbraco.Core.Persistence.Factories public IUser BuildEntity(UserDto dto) { + var guidId = dto.Id.ToGuid(); var user = new User(_userType) { Id = dto.Id, - //ProfileId = dto.Id, + Key = guidId, + ProviderUserKey = guidId, StartContentId = dto.ContentStartId, StartMediaId = dto.MediaStartId.HasValue ? dto.MediaStartId.Value : -1, Password = dto.Password, diff --git a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs index 6ecb6eedce..2b0b128b1c 100644 --- a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs @@ -1,20 +1,15 @@ +using System.Text; using System.Web.Security; namespace Umbraco.Core.Security { /// - /// A base membership provider class for umbraco members (not users) + /// A base membership provider class for umbraco providers /// public abstract class UmbracoMembershipProviderBase : MembershipProviderBase { - protected UmbracoMembershipProviderBase() - { - //Set the defaults! - DefaultMemberTypeAlias = "Member"; - } - - public string DefaultMemberTypeAlias { get; protected set; } + public abstract string DefaultMemberTypeAlias { get; } /// /// Adds a new membership user to the data source. @@ -74,5 +69,13 @@ namespace Umbraco.Core.Security /// A object populated with the information for the newly created user. /// protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + + public override string ToString() + { + var result = base.ToString(); + var sb = new StringBuilder(result); + sb.AppendLine("DefaultMemberTypeAlias=" + DefaultMemberTypeAlias); + return sb.ToString(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 522a4dcb86..fd0ed49b4f 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Services @@ -24,7 +25,7 @@ namespace Umbraco.Core.Services /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. /// public interface IMembershipMemberService : IService - where T: IEntity + where T : IMembershipUser { /// /// Checks if a member with the username exists diff --git a/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs index 411c22f750..b5d2648ebe 100644 --- a/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs +++ b/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs @@ -10,9 +10,10 @@ using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Membership { - [TestFixture] + [TestFixture, RequiresSTA] public class MembersMembershipProviderTests { + //[Test] //public void Set_Default_Member_Type_On_Init() @@ -47,7 +48,9 @@ namespace Umbraco.Tests.Membership createdMember = new Member("test", e, u, p, memberType); }) .Returns(() => createdMember); - var provider = new MembersMembershipProvider(mServiceMock.Object); + var provider = new MembersMembershipProvider(mServiceMock.Object); + provider.Initialize("test", new NameValueCollection()); + MembershipCreateStatus status; provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); @@ -78,6 +81,8 @@ namespace Umbraco.Tests.Membership var provider = new MembersMembershipProvider(mServiceMock.Object); provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + MembershipCreateStatus status; provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); @@ -108,6 +113,8 @@ namespace Umbraco.Tests.Membership var provider = new MembersMembershipProvider(mServiceMock.Object); provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + MembershipCreateStatus status; provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index a735e3a9a2..be087dbfda 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -1,19 +1,12 @@ -using System; -using System.Collections.Specialized; -using System.ComponentModel.DataAnnotations; -using System.Configuration.Provider; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; -using System.Web.Configuration; using System.Web.Hosting; using System.Web.Security; using Umbraco.Core; -using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Security; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Services; using Umbraco.Core.Models.Membership; @@ -22,531 +15,39 @@ namespace Umbraco.Web.Security.Providers /// /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) /// - internal class MembersMembershipProvider : UmbracoMembershipProviderBase + internal class MembersMembershipProvider : UmbracoServiceMembershipProvider { - private IMembershipMemberService _memberService; - - protected IMembershipMemberService MemberService - { - get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } - } - public MembersMembershipProvider() + : this(ApplicationContext.Current.Services.MemberService) { } - internal MembersMembershipProvider(IMemberService memberService) + public MembersMembershipProvider(IMembershipMemberService memberService) : base(memberService) { - _memberService = memberService; } - public string ProviderName + private string _defaultMemberTypeAlias = "Member"; + + public override string ProviderName { get { return "MembersMembershipProvider"; } } - - /// - /// 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) + + protected override MembershipUser ConvertToMembershipUser(IMember entity) { - if (config == null) {throw new ArgumentNullException("config");} + return entity.AsConcreteMembershipUser(Name); + } - if (string.IsNullOrEmpty(name)) name = ProviderName; - - // Initialize base provider class + public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) + { base.Initialize(name, config); - //// test for membertype (if not specified, choose the first member type available) - //if (config["defaultMemberTypeAlias"] != null) - // _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; - //else if (MemberType.GetAll.Length == 1) - // _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; - //else - // throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); - + //TODO: need to determine the default member type! } - /// - /// Processes a request to update the password for a membership user. - /// - /// The user to update the password for. - /// This property is ignore for this provider - /// 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) + public override string DefaultMemberTypeAlias { - //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. - - // in order to support updating passwords from the umbraco core, we can't validate the old password - var m = MemberService.GetByUsername(username); - if (m == null) return false; - - string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); - - m.Password = FormatPasswordForStorage(encodedPassword, salt); - m.LastPasswordChangeDate = DateTime.Now; - - MemberService.Save(m); - - return true; + get { return _defaultMemberTypeAlias; } } - - /// - /// 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) - { - var member = MemberService.GetByUsername(username); - if (member == null) - { - return false; - } - - member.PasswordQuestion = newPasswordQuestion; - member.PasswordAnswer = EncryptString(newPasswordAnswer); - - MemberService.Save(member); - - return true; - } - - /// - /// Adds a new membership user to the data source with the specified member type - /// - /// A specific member type to create the member for - /// 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 memberTypeAlias, string username, string password, string email, string passwordQuestion, - string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - // See if the user already exists - if (MemberService.Exists(username)) - { - 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; - } - - string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); - - var member = MemberService.CreateMember( - email, - username, - FormatPasswordForStorage(encodedPassword, salt), - memberTypeAlias); - - member.PasswordQuestion = passwordQuestion; - member.PasswordAnswer = EncryptString(passwordAnswer); - member.IsApproved = isApproved; - member.LastLoginDate = DateTime.Now; - member.LastPasswordChangeDate = DateTime.Now; - - MemberService.Save(member); - - status = MembershipCreateStatus.Success; - return member.AsConcreteMembershipUser(); - } - - /// - /// Removes a user from the membership data source. - /// - /// The name of the user to delete. - /// - /// TODO: This setting currently has no effect - /// - /// - /// true if the user was successfully deleted; otherwise, false. - /// - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - var member = MemberService.GetByUsername(username); - if (member == null) return false; - - MemberService.Delete(member); - 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 byEmail = MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - - var collection = new MembershipUserCollection(); - foreach (var m in byEmail) - { - collection.Add(m.AsConcreteMembershipUser()); - } - return collection; - } - - /// - /// 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 byEmail = MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - - var collection = new MembershipUserCollection(); - foreach (var m in byEmail) - { - collection.Add(m.AsConcreteMembershipUser()); - } - return collection; - } - - /// - /// 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 membersList = new MembershipUserCollection(); - - var pagedMembers = MemberService.GetAllMembers(pageIndex, pageSize, out totalRecords); - - foreach (var m in pagedMembers) - { - membersList.Add(m.AsConcreteMembershipUser()); - } - return membersList; - } - - /// - /// Gets the number of users currently accessing the application. - /// - /// - /// The number of users currently accessing the application. - /// - /// - /// The way this is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members - /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science - /// but that is how MS have made theirs so we'll follow that principal. - /// - public override int GetNumberOfUsersOnline() - { - return MemberService.GetMemberCount(MemberCountType.Online); - } - - /// - /// 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. - /// - protected override string PerformGetPassword(string username, string answer) - { - var m = MemberService.GetByUsername(username); - if (m == null) - { - throw new ProviderException("The supplied user is not found"); - } - - var encAnswer = EncryptString(answer); - - if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) - { - throw new ProviderException("Incorrect password answer"); - } - - var decodedPassword = DecryptPassword(m.Password); - - return decodedPassword; - } - - internal string EncryptString(string str) - { - var bytes = Encoding.Unicode.GetBytes(str); - var password = new byte[bytes.Length]; - Buffer.BlockCopy(bytes, 0, password, 0, bytes.Length); - var encBytes = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40); - return Convert.ToBase64String(encBytes); - } - - /// - /// 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 member = MemberService.GetByUsername(username); - if (member == null) - { - return null; - } - - if (userIsOnline) - { - member.LastLoginDate = DateTime.Now; - member.UpdateDate = DateTime.Now; - MemberService.Save(member); - } - - return member.AsConcreteMembershipUser(); - } - - /// - /// 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 member = MemberService.GetById(providerUserKey); - if (member == null) - { - return null; - } - - if (userIsOnline) - { - member.LastLoginDate = DateTime.Now; - 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) - { - var member = MemberService.GetByEmail(email); - - return member == null ? null : member.Username; - } - - /// - /// 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. - 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 m = MemberService.GetByUsername(username); - if (m == null) - { - throw new ProviderException("The supplied user is not found"); - } - - if (m.IsLockedOut) - { - throw new ProviderException("The member is locked out."); - } - - var encAnswer = EncryptString(answer); - - if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) - { - throw new ProviderException("Incorrect password answer"); - } - - string salt; - var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); - m.Password = FormatPasswordForStorage(encodedPassword, salt); - m.LastPasswordChangeDate = DateTime.Now; - MemberService.Save(m); - - 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) - { - var member = MemberService.GetByUsername(username); - - if (member == null) - { - throw new ProviderException(string.Format("No member with the username '{0}' found", username)); - } - - // Non need to update - if (member.IsLockedOut == false) return true; - - member.IsLockedOut = false; - member.FailedPasswordAttempts = 0; - - MemberService.Save(member); - - return true; - } - - /// - /// Updates e-mail approved status, lock status and comment on a user. - /// - /// A object that represents the user to update and the updated information for the user. - public override void UpdateUser(MembershipUser user) - { - var m = MemberService.GetByUsername(user.UserName); - - if (m == null) - { - throw new ProviderException(string.Format("No member with the username '{0}' found", user.UserName)); - } - - m.Email = user.Email; - m.IsApproved = user.IsApproved; - m.IsLockedOut = user.IsLockedOut; - if (user.IsLockedOut) - { - m.LastLockoutDate = DateTime.Now; - } - m.Comments = user.Comment; - - MemberService.Save(m); - } - - /// - /// 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 member = MemberService.GetByUsername(username); - - if (member == null) return false; - - if (member.IsApproved == false) - { - LogHelper.Info("Cannot validate member " + username + " because they are not approved"); - return false; - } - if (member.IsLockedOut) - { - LogHelper.Info("Cannot validate member " + username + " because they are currently locked out"); - return false; - } - - var authenticated = CheckPassword(password, member.Password); - - if (authenticated == false) - { - // TODO: Increment login attempts - lock if too many. - - var count = member.FailedPasswordAttempts; - count++; - member.FailedPasswordAttempts = count; - - if (count >= MaxInvalidPasswordAttempts) - { - member.IsLockedOut = true; - member.LastLockoutDate = DateTime.Now; - LogHelper.Info("Member " + username + " is now locked out, max invalid password attempts exceeded"); - } - } - else - { - member.FailedPasswordAttempts = 0; - member.LastLoginDate = DateTime.Now; - } - - MemberService.Save(member); - return authenticated; - } - - - - public override string ToString() - { - var result = base.ToString(); - var sb = new StringBuilder(result); - sb.AppendLine("DefaultMemberTypeAlias=" + DefaultMemberTypeAlias); - return sb.ToString(); - } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Providers/UmbracoServiceMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoServiceMembershipProvider.cs new file mode 100644 index 0000000000..497d33e11c --- /dev/null +++ b/src/Umbraco.Web/Security/Providers/UmbracoServiceMembershipProvider.cs @@ -0,0 +1,533 @@ +using System; +using System.Collections.Specialized; +using System.Configuration.Provider; +using System.Linq; +using System.Text; +using System.Web.Configuration; +using System.Web.Security; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Security; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Security.Providers +{ + /// + /// Abstract Membership Provider that users any implementation of IMembershipMemberService{TEntity} service + /// + internal abstract class UmbracoServiceMembershipProvider : UmbracoMembershipProviderBase + where T : IMembershipMemberService + where TEntity : IMembershipUser + { + + protected IMembershipMemberService MemberService { get; private set; } + + protected UmbracoServiceMembershipProvider(IMembershipMemberService memberService) + { + MemberService = memberService; + } + + public abstract string ProviderName { get; } + + protected abstract MembershipUser ConvertToMembershipUser(TEntity entity); + + /// + /// 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); + + //// test for membertype (if not specified, choose the first member type available) + //if (config["defaultMemberTypeAlias"] != null) + // _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + //else if (MemberType.GetAll.Length == 1) + // _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; + //else + // throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); + + } + + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// This property is ignore for this provider + /// 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. + + // in order to support updating passwords from the umbraco core, we can't validate the old password + var m = MemberService.GetByUsername(username); + if (m == null) return false; + + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + + m.Password = FormatPasswordForStorage(encodedPassword, salt); + m.LastPasswordChangeDate = DateTime.Now; + + MemberService.Save(m); + + 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) + { + var member = MemberService.GetByUsername(username); + if (member == null) + { + return false; + } + + member.PasswordQuestion = newPasswordQuestion; + member.PasswordAnswer = EncryptString(newPasswordAnswer); + + MemberService.Save(member); + + return true; + } + + /// + /// Adds a new membership user to the data source with the specified member type + /// + /// A specific member type to create the member for + /// 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 memberTypeAlias, string username, string password, string email, string passwordQuestion, + string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + // See if the user already exists + if (MemberService.Exists(username)) + { + 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; + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); + + var member = MemberService.CreateMember( + email, + username, + FormatPasswordForStorage(encodedPassword, salt), + memberTypeAlias); + + member.PasswordQuestion = passwordQuestion; + member.PasswordAnswer = EncryptString(passwordAnswer); + member.IsApproved = isApproved; + member.LastLoginDate = DateTime.Now; + member.LastPasswordChangeDate = DateTime.Now; + + MemberService.Save(member); + + status = MembershipCreateStatus.Success; + return ConvertToMembershipUser(member); + } + + /// + /// Removes a user from the membership data source. + /// + /// The name of the user to delete. + /// + /// TODO: This setting currently has no effect + /// + /// + /// true if the user was successfully deleted; otherwise, false. + /// + public override bool DeleteUser(string username, bool deleteAllRelatedData) + { + var member = MemberService.GetByUsername(username); + if (member == null) return false; + + MemberService.Delete(member); + 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 byEmail = MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail) + { + collection.Add(ConvertToMembershipUser(m)); + } + return collection; + } + + /// + /// 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 byEmail = MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail) + { + collection.Add(ConvertToMembershipUser(m)); + } + return collection; + } + + /// + /// 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 membersList = new MembershipUserCollection(); + + var pagedMembers = MemberService.GetAllMembers(pageIndex, pageSize, out totalRecords); + + foreach (var m in pagedMembers) + { + membersList.Add(ConvertToMembershipUser(m)); + } + return membersList; + } + + /// + /// Gets the number of users currently accessing the application. + /// + /// + /// The number of users currently accessing the application. + /// + /// + /// The way this is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science + /// but that is how MS have made theirs so we'll follow that principal. + /// + public override int GetNumberOfUsersOnline() + { + return MemberService.GetMemberCount(MemberCountType.Online); + } + + /// + /// 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. + /// + protected override string PerformGetPassword(string username, string answer) + { + var m = MemberService.GetByUsername(username); + if (m == null) + { + throw new ProviderException("The supplied user is not found"); + } + + var encAnswer = EncryptString(answer); + + if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) + { + throw new ProviderException("Incorrect password answer"); + } + + var decodedPassword = DecryptPassword(m.Password); + + return decodedPassword; + } + + internal string EncryptString(string str) + { + var bytes = Encoding.Unicode.GetBytes(str); + var password = new byte[bytes.Length]; + Buffer.BlockCopy(bytes, 0, password, 0, bytes.Length); + var encBytes = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40); + return Convert.ToBase64String(encBytes); + } + + /// + /// 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 member = MemberService.GetByUsername(username); + if (member == null) + { + return null; + } + + if (userIsOnline) + { + member.LastLoginDate = DateTime.Now; + member.UpdateDate = DateTime.Now; + MemberService.Save(member); + } + + return ConvertToMembershipUser(member); + } + + /// + /// 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 member = MemberService.GetById(providerUserKey); + if (member == null) + { + return null; + } + + if (userIsOnline) + { + member.LastLoginDate = DateTime.Now; + member.UpdateDate = DateTime.Now; + MemberService.Save(member); + } + + return ConvertToMembershipUser(member); + } + + /// + /// 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 member = MemberService.GetByEmail(email); + + return member == null ? null : member.Username; + } + + /// + /// 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. + 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 m = MemberService.GetByUsername(username); + if (m == null) + { + throw new ProviderException("The supplied user is not found"); + } + + if (m.IsLockedOut) + { + throw new ProviderException("The member is locked out."); + } + + var encAnswer = EncryptString(answer); + + if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) + { + throw new ProviderException("Incorrect password answer"); + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); + m.Password = FormatPasswordForStorage(encodedPassword, salt); + m.LastPasswordChangeDate = DateTime.Now; + MemberService.Save(m); + + 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) + { + var member = MemberService.GetByUsername(username); + + if (member == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", username)); + } + + // Non need to update + if (member.IsLockedOut == false) return true; + + member.IsLockedOut = false; + member.FailedPasswordAttempts = 0; + + MemberService.Save(member); + + return true; + } + + /// + /// Updates e-mail approved status, lock status and comment on a user. + /// + /// A object that represents the user to update and the updated information for the user. + public override void UpdateUser(MembershipUser user) + { + var m = MemberService.GetByUsername(user.UserName); + + if (m == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", user.UserName)); + } + + m.Email = user.Email; + m.IsApproved = user.IsApproved; + m.IsLockedOut = user.IsLockedOut; + if (user.IsLockedOut) + { + m.LastLockoutDate = DateTime.Now; + } + m.Comments = user.Comment; + + MemberService.Save(m); + } + + /// + /// 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 member = MemberService.GetByUsername(username); + + if (member == null) return false; + + if (member.IsApproved == false) + { + LogHelper.Info>("Cannot validate member " + username + " because they are not approved"); + return false; + } + if (member.IsLockedOut) + { + LogHelper.Info>("Cannot validate member " + username + " because they are currently locked out"); + return false; + } + + var authenticated = CheckPassword(password, member.Password); + + if (authenticated == false) + { + // TODO: Increment login attempts - lock if too many. + + var count = member.FailedPasswordAttempts; + count++; + member.FailedPasswordAttempts = count; + + if (count >= MaxInvalidPasswordAttempts) + { + member.IsLockedOut = true; + member.LastLockoutDate = DateTime.Now; + LogHelper.Info>("Member " + username + " is now locked out, max invalid password attempts exceeded"); + } + } + else + { + member.FailedPasswordAttempts = 0; + member.LastLoginDate = DateTime.Now; + } + + MemberService.Save(member); + return authenticated; + } + + + + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs index 2d04071523..2a4eac06b6 100644 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography; using System.Text; using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; namespace Umbraco.Web.Security.Providers @@ -10,155 +11,41 @@ namespace Umbraco.Web.Security.Providers /// /// Custom Membership Provider for Umbraco Users (User authentication for Umbraco Backend CMS) /// - internal class UsersMembershipProvider : MembershipProvider + internal class UsersMembershipProvider : UmbracoServiceMembershipProvider { - private IMembershipUserService _userService; - - protected IMembershipUserService UserService - { - get { return _userService ?? (_userService = ApplicationContext.Current.Services.UserService); } + + public UsersMembershipProvider() + : this(ApplicationContext.Current.Services.UserService) + { } - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, - bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - //Assuming the password is hashed - var hash = new HMACSHA1(); - hash.Key = Encoding.Unicode.GetBytes(password); - var encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password))); - - //var user = _userService.CreateUser(); - //status = MembershipCreateStatus.Success; - //return new UmbracoMembershipUser(user); - throw new System.NotImplementedException(); + public UsersMembershipProvider(IMembershipMemberService memberService) + : base(memberService) + { } - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, - string newPasswordAnswer) + private string _defaultMemberTypeAlias = "writer"; + + public override string ProviderName { - throw new System.NotImplementedException(); + get { return "UsersMembershipProvider"; } } - public override string GetPassword(string username, string answer) + protected override MembershipUser ConvertToMembershipUser(IUser entity) { - throw new System.NotImplementedException(); + return entity.AsConcreteMembershipUser(Name); } - public override bool ChangePassword(string username, string oldPassword, string newPassword) + public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { - throw new System.NotImplementedException(); - } + base.Initialize(name, config); - public override string ResetPassword(string username, string answer) + //TODO: need to determine the default member type! + } + + public override string DefaultMemberTypeAlias { - throw new System.NotImplementedException(); - } - - public override void UpdateUser(MembershipUser user) - { - throw new System.NotImplementedException(); - } - - public override bool ValidateUser(string username, string password) - { - throw new System.NotImplementedException(); - } - - public override bool UnlockUser(string userName) - { - throw new System.NotImplementedException(); - } - - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - throw new System.NotImplementedException(); - } - - public override MembershipUser GetUser(string username, bool userIsOnline) - { - throw new System.NotImplementedException(); - } - - public override string GetUserNameByEmail(string email) - { - throw new System.NotImplementedException(); - } - - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - throw new System.NotImplementedException(); - } - - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) - { - throw new System.NotImplementedException(); - } - - public override int GetNumberOfUsersOnline() - { - throw new System.NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new System.NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new System.NotImplementedException(); - } - - public override bool EnablePasswordRetrieval - { - get { throw new System.NotImplementedException(); } - } - - public override bool EnablePasswordReset - { - get { throw new System.NotImplementedException(); } - } - - public override bool RequiresQuestionAndAnswer - { - 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(); } + get { return _defaultMemberTypeAlias; } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 89fac8613b..658ff1b993 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -315,6 +315,7 @@ + diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 51011c5636..3d0f2f3fe7 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -599,7 +599,7 @@ namespace umbraco.BusinessLogic SqlHelper.CreateParameter("@noconsole", noConsole), SqlHelper.CreateParameter("@id", id)); } - + /// /// Gets the ID from the user with the specified login name and password /// diff --git a/src/umbraco.providers/UsersMembershipProvider.cs b/src/umbraco.providers/UsersMembershipProvider.cs index 905fa76b7f..423a48707b 100644 --- a/src/umbraco.providers/UsersMembershipProvider.cs +++ b/src/umbraco.providers/UsersMembershipProvider.cs @@ -112,7 +112,7 @@ namespace umbraco.providers /// protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { - throw new Exception("The method or operation is not implemented."); + throw new NotImplementedException(); } /// @@ -286,7 +286,8 @@ namespace umbraco.providers /// public override int GetNumberOfUsersOnline() { - throw new Exception("The method or operation is not implemented."); + var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); + return Log.Instance.GetLogItems(LogTypes.Login, fromDate).Count; } /// @@ -333,7 +334,12 @@ namespace umbraco.providers public override MembershipUser GetUser(string username, bool userIsOnline) { var userId = User.getUserId(username); - return (userId != -1) ? ConvertToMembershipUser(new User(userId)) : null; + + 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; } /// @@ -346,7 +352,10 @@ namespace umbraco.providers /// public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { - return ConvertToMembershipUser(new User(Convert.ToInt32(providerUserKey))); + 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); } /// @@ -486,6 +495,7 @@ namespace umbraco.providers #region Helper Methods + /// /// Encodes the password. /// diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index c1a4155a77..fed4a5cd5d 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -46,7 +46,8 @@ namespace umbraco.providers.members } #region Fields - + + private string _defaultMemberTypeAlias = "Member"; private string _providerName = Member.UmbracoMemberProviderName; #endregion @@ -117,9 +118,9 @@ namespace umbraco.providers.members // test for membertype (if not specified, choose the first member type available) if (config["defaultMemberTypeAlias"] != null) - DefaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; else if (MemberType.GetAll.Length == 1) - DefaultMemberTypeAlias = MemberType.GetAll[0].Alias; + _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; else throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); @@ -230,6 +231,11 @@ namespace umbraco.providers.members return true; } + public override string DefaultMemberTypeAlias + { + get { return _defaultMemberTypeAlias; } + } + /// /// Adds a new membership user to the data source. ///