From 3792cafb9f70b92f6f06c6fa980aa039e6316737 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 22 Apr 2021 21:21:43 +1000 Subject: [PATCH] Published members cleanup (#10159) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Reducing and removing published member cache * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry * cannot inject IPublishedMemberCache and cannot have IPublishedMember * splits out files, fixes build * fix tests * removes membership provider classes * removes membership provider classes * updates the identity map definition * reverts commented out lines * reverts commented out lines Co-authored-by: Bjarke Berg --- src/Umbraco.Core/Constants-Conventions.cs | 8 +- src/Umbraco.Core/Constants-Security.cs | 2 - .../PublishedContent/IPublishedContent.cs | 3 +- .../IPublishedMemberCache.cs | 22 +- .../PublishedContentExtensionsForModels.cs | 2 +- .../MemberPickerValueConverter.cs | 40 +- .../MultiNodeTreePickerValueConverter.cs | 21 +- .../Examine/ExamineExtensions.cs | 5 +- .../Security/IMemberManager.cs | 8 + .../Security/IMemberUserStore.cs | 13 + .../Security/IdentityMapDefinition.cs | 17 +- .../Security/MemberIdentityUser.cs | 31 +- .../Security/MemberUserStore.cs | 21 +- .../CacheKeys.cs | 17 +- .../MemberCache.cs | 135 +--- .../PublishedMember.cs | 41 +- .../PublishedSnapshotService.cs | 2 +- .../Security/MemberManagerTests.cs | 4 +- .../Security/MemberUserStoreTests.cs | 4 +- .../Security/MemberSignInManagerTests.cs | 2 +- .../PublishedContentCacheTests.cs | 4 +- .../PublishedMember.cs | 7 +- .../PublishedMemberCache.cs | 114 +-- .../XmlPublishedSnapshotService.cs | 4 +- .../TestControllerActivatorBase.cs | 2 +- .../TestHelpers/TestObjects-Mocks.cs | 2 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 11 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../UmbracoBuilder.MembersIdentity.cs | 2 +- .../Security/MemberManager.cs | 8 +- src/Umbraco.Web/Composing/Current.cs | 1 - .../Membership/UmbracoMembershipMember.cs | 113 --- src/Umbraco.Web/Security/MembershipHelper.cs | 136 ---- .../Security/MembershipProviderBase.cs | 682 ------------------ .../Security/MembershipProviderExtensions.cs | 94 --- .../Providers/MembersMembershipProvider.cs | 158 ---- .../Providers/UmbracoMembershipProvider.cs | 621 ---------------- .../Security/UmbracoMembershipProviderBase.cs | 92 --- src/Umbraco.Web/Umbraco.Web.csproj | 9 +- src/Umbraco.Web/UmbracoHelper.cs | 70 +- 40 files changed, 179 insertions(+), 2350 deletions(-) rename src/Umbraco.Core/{PublishedCache => Models/PublishedContent}/IPublishedMemberCache.cs (54%) create mode 100644 src/Umbraco.Infrastructure/Security/IMemberUserStore.cs rename src/{Umbraco.Core/PublishedCache => Umbraco.Tests/LegacyXmlPublishedCache}/PublishedMember.cs (96%) delete mode 100644 src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs delete mode 100644 src/Umbraco.Web/Security/MembershipHelper.cs delete mode 100644 src/Umbraco.Web/Security/MembershipProviderBase.cs delete mode 100644 src/Umbraco.Web/Security/MembershipProviderExtensions.cs delete mode 100644 src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs delete mode 100644 src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs delete mode 100644 src/Umbraco.Web/Security/UmbracoMembershipProviderBase.cs diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 92b1113ad0..20d790b1d5 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core { public static partial class Constants { @@ -130,10 +130,6 @@ /// public static readonly string InternalRolePrefix = "__umbracoRole"; - public static readonly string UmbracoMemberProviderName = "UmbracoMembershipProvider"; - - public static readonly string UmbracoRoleProviderName = "UmbracoRoleProvider"; - /// /// Property alias for the Comments on a Member /// @@ -277,7 +273,7 @@ /// Developers should not manually use these relation types since they will all be cleared whenever an entity /// (content, media or member) is saved since they are auto-populated based on property values. /// - public static string[] AutomaticRelationTypes = new[] { RelatedMediaAlias, RelatedDocumentAlias }; + public static string[] AutomaticRelationTypes { get; } = new[] { RelatedMediaAlias, RelatedDocumentAlias }; //TODO: return a list of built in types so we can use that to prevent deletion in the uI } diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index ba0f1e0a37..0506a66ad2 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -4,8 +4,6 @@ namespace Umbraco.Cms.Core { public static class Security { - public const string UserMembershipProviderName = "UsersMembershipProvider"; - /// /// Gets the identifier of the 'super' user. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 5d4eb2f74f..75caa212c5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; namespace Umbraco.Cms.Core.Models.PublishedContent { + /// /// /// Represents a published content item. diff --git a/src/Umbraco.Core/PublishedCache/IPublishedMemberCache.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedMemberCache.cs similarity index 54% rename from src/Umbraco.Core/PublishedCache/IPublishedMemberCache.cs rename to src/Umbraco.Core/Models/PublishedContent/IPublishedMemberCache.cs index fcc1d00103..bafe04f3e4 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedMemberCache.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedMemberCache.cs @@ -1,23 +1,17 @@ -using System.Xml.XPath; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Security; namespace Umbraco.Cms.Core.PublishedCache { - // TODO: Kill this, why do we want this at all? - // See https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/11487 - public interface IPublishedMemberCache : IXPathNavigable + public interface IPublishedMemberCache { - IPublishedContent GetByProviderKey(object key); - IPublishedContent GetById(int memberId); - IPublishedContent GetByUsername(string username); - IPublishedContent GetByEmail(string email); - IPublishedContent GetByMember(IMember member); - - XPathNavigator CreateNavigator(bool preview); - - // if the node does not exist, return null - XPathNavigator CreateNodeNavigator(int id, bool preview); + /// + /// Get an from an + /// + /// + /// + IPublishedContent Get(IMember member); /// /// Gets a content type identified by its unique identifier. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index 2990c5969f..c258d4e50b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Extensions diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs index cd8f203cb9..2f5f7063c8 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs @@ -1,6 +1,8 @@ -using System; +using System; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; @@ -9,19 +11,22 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MemberPickerValueConverter : PropertyValueConverterBase { + private readonly IMemberService _memberService; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public MemberPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IUmbracoContextAccessor umbracoContextAccessor) + public MemberPickerValueConverter( + IMemberService memberService, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IUmbracoContextAccessor umbracoContextAccessor) { - _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); + _memberService = memberService; + _publishedSnapshotAccessor = publishedSnapshotAccessor; _umbracoContextAccessor = umbracoContextAccessor; } public override bool IsConverter(IPublishedPropertyType propertyType) - { - return propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberPicker); - } + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberPicker); public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; @@ -45,24 +50,43 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { if (source == null) + { return null; + } if (_umbracoContextAccessor.UmbracoContext != null) { IPublishedContent member; if (source is int id) { - member = _publishedSnapshotAccessor.PublishedSnapshot.Members.GetById(id); + IMember m = _memberService.GetById(id); + if (m == null) + { + return null; + } + member = _publishedSnapshotAccessor.PublishedSnapshot?.Members.Get(m); if (member != null) + { return member; + } } else { var sourceUdi = source as GuidUdi; if (sourceUdi == null) return null; - member = _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(sourceUdi.Guid); + + IMember m = _memberService.GetByKey(sourceUdi.Guid); + if (m == null) + { + return null; + } + + member = _publishedSnapshotAccessor.PublishedSnapshot?.Members.Get(m); + if (member != null) + { return member; + } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index bcaa89b97e..1c97fccac4 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; @@ -19,6 +20,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IMemberService _memberService; private static readonly List PropertiesToExclude = new List { @@ -26,10 +28,14 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) }; - public MultiNodeTreePickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IUmbracoContextAccessor umbracoContextAccessor) + public MultiNodeTreePickerValueConverter( + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IUmbracoContextAccessor umbracoContextAccessor, + IMemberService memberService) { _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); _umbracoContextAccessor = umbracoContextAccessor; + _memberService = memberService; } public override bool IsConverter(IPublishedPropertyType propertyType) @@ -96,7 +102,16 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, id => _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(guidUdi.Guid)); break; case Constants.UdiEntityType.Member: - multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, id => _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(guidUdi.Guid)); + multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, id => + { + IMember m = _memberService.GetByKey(guidUdi.Guid); + if (m == null) + { + return null; + } + IPublishedContent member = _publishedSnapshotAccessor.PublishedSnapshot.Members.Get(m); + return member; + }); break; } diff --git a/src/Umbraco.Infrastructure/Examine/ExamineExtensions.cs b/src/Umbraco.Infrastructure/Examine/ExamineExtensions.cs index b2103f6e7d..d0f65fe6a4 100644 --- a/src/Umbraco.Infrastructure/Examine/ExamineExtensions.cs +++ b/src/Umbraco.Infrastructure/Examine/ExamineExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Examine; using Umbraco.Cms.Core.Models.PublishedContent; @@ -75,8 +75,7 @@ namespace Umbraco.Extensions content = snapshot.Media.GetById(contentId); break; case IndexTypes.Member: - content = snapshot.Members.GetById(contentId); - break; + throw new NotSupportedException("Cannot convert search results to member instances"); default: continue; } diff --git a/src/Umbraco.Infrastructure/Security/IMemberManager.cs b/src/Umbraco.Infrastructure/Security/IMemberManager.cs index 1fc035d876..f851b205d5 100644 --- a/src/Umbraco.Infrastructure/Security/IMemberManager.cs +++ b/src/Umbraco.Infrastructure/Security/IMemberManager.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.Security { @@ -8,6 +9,13 @@ namespace Umbraco.Cms.Core.Security /// public interface IMemberManager : IUmbracoUserManager { + /// + /// Returns the instance for the specified + /// + /// + /// + IPublishedContent AsPublishedMember(MemberIdentityUser user); + /// /// Returns the currently logged in member if there is one, else returns null /// diff --git a/src/Umbraco.Infrastructure/Security/IMemberUserStore.cs b/src/Umbraco.Infrastructure/Security/IMemberUserStore.cs new file mode 100644 index 0000000000..578b2f2860 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/IMemberUserStore.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Identity; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace Umbraco.Cms.Core.Security +{ + /// + /// A custom user store that uses Umbraco member data + /// + public interface IMemberUserStore : IUserStore + { + IPublishedContent GetPublishedMember(MemberIdentityUser user); + } +} diff --git a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs index b51c434dc6..0d0b9fc156 100644 --- a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs @@ -63,15 +63,10 @@ namespace Umbraco.Cms.Core.Security }); } - // Umbraco.Code.MapAll -Id -Groups -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -TwoFactorEnabled + // Umbraco.Code.MapAll -Id -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -TwoFactorEnabled -ConcurrencyStamp -NormalizedEmail -NormalizedUserName -Roles private void Map(IUser source, BackOfficeIdentityUser target) { - // well, the ctor has been fixed - /* - // these two are already set in ctor but BackOfficeIdentityUser ctor is CompletelyBroken - target.Id = source.Id; - target.Groups = source.Groups.ToArray(); - */ + // NOTE: Groups/Roles are set in the BackOfficeIdentityUser ctor target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches); target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService, _appCaches); @@ -90,14 +85,6 @@ namespace Umbraco.Cms.Core.Security target.IsApproved = source.IsApproved; target.SecurityStamp = source.SecurityStamp; target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null; - - // this was in AutoMapper but does not have a setter anyways - //target.AllowedSections = source.AllowedSections.ToArray(), - - // these were marked as ignored for AutoMapper but don't have a setter anyways - //target.Logins =; - //target.Claims =; - //target.Roles =; } // TODO: We need to validate this mapping is OK, we need to get Umbraco.Code working diff --git a/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs b/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs index f49d21203d..459417b289 100644 --- a/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs +++ b/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Identity; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Extensions; @@ -12,8 +11,7 @@ namespace Umbraco.Cms.Core.Security /// public class MemberIdentityUser : UmbracoIdentityUser { - private string _comments; - private IReadOnlyCollection _groups; + private string _comments; // Custom comparer for enumerables private static readonly DelegateEqualityComparer> s_groupsComparer = new DelegateEqualityComparer>( @@ -73,33 +71,6 @@ namespace Umbraco.Cms.Core.Security // No change tracking because the persisted value is readonly public Guid Key { get; set; } - /// - /// Gets or sets the user groups - /// - public IReadOnlyCollection Groups - { - get => _groups; - set - { - _groups = value.Where(x => x.Alias != null).ToArray(); - - var roles = new List>(); - foreach (IdentityUserRole identityUserRole in _groups.Select(x => new IdentityUserRole - { - RoleId = x.Alias, - UserId = Id - })) - { - roles.Add(identityUserRole); - } - - // now reset the collection - Roles = roles; - - BeingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), s_groupsComparer); - } - } - /// /// Gets or sets the alias of the member type /// diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index a449255273..5329c64a0e 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -9,21 +9,25 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Security { + /// /// A custom user store that uses Umbraco member data /// - public class MemberUserStore : UmbracoUserStore + public class MemberUserStore : UmbracoUserStore, IMemberUserStore { private const string genericIdentityErrorCode = "IdentityErrorUserStore"; private readonly IMemberService _memberService; private readonly IUmbracoMapper _mapper; private readonly IScopeProvider _scopeProvider; + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; /// /// Initializes a new instance of the class for the members identity store @@ -36,12 +40,14 @@ namespace Umbraco.Cms.Core.Security IMemberService memberService, IUmbracoMapper mapper, IScopeProvider scopeProvider, - IdentityErrorDescriber describer) - : base(describer) + IdentityErrorDescriber describer, + IPublishedSnapshotAccessor publishedSnapshotAccessor) + : base(describer) { _memberService = memberService ?? throw new ArgumentNullException(nameof(memberService)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); + _publishedSnapshotAccessor = publishedSnapshotAccessor; } /// @@ -603,5 +609,14 @@ namespace Umbraco.Cms.Core.Security return anythingChanged; } + public IPublishedContent GetPublishedMember(MemberIdentityUser user) + { + IMember member = _memberService.GetByKey(user.Key); + if (member == null) + { + return null; + } + return _publishedSnapshotAccessor.PublishedSnapshot?.Members.Get(member); + } } } diff --git a/src/Umbraco.PublishedCache.NuCache/CacheKeys.cs b/src/Umbraco.PublishedCache.NuCache/CacheKeys.cs index df129d7214..3d8f14afd3 100644 --- a/src/Umbraco.PublishedCache.NuCache/CacheKeys.cs +++ b/src/Umbraco.PublishedCache.NuCache/CacheKeys.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.CompilerServices; namespace Umbraco.Cms.Infrastructure.PublishedCache @@ -60,20 +60,5 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache { return "NuCache.ContentCache.ContentByRoute[" + DraftOrPub(previewing) + route + LangId(culture) + "]"; } - - //public static string ContentCacheRouteByContentStartsWith() - //{ - // return "NuCache.ContentCache.RouteByContent["; - //} - - //public static string ContentCacheContentByRouteStartsWith() - //{ - // return "NuCache.ContentCache.ContentByRoute["; - //} - - public static string MemberCacheMember(string name, bool previewing, object p) - { - return "NuCache.MemberCache." + name + "[" + DraftOrPub(previewing) + p + "]"; - } } } diff --git a/src/Umbraco.PublishedCache.NuCache/MemberCache.cs b/src/Umbraco.PublishedCache.NuCache/MemberCache.cs index f3ea2aad21..98e510966a 100644 --- a/src/Umbraco.PublishedCache.NuCache/MemberCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/MemberCache.cs @@ -1,148 +1,39 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.XPath; -using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Xml.XPath; -using Umbraco.Cms.Infrastructure.PublishedCache.Navigable; -using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.PublishedCache { - internal class MemberCache : IPublishedMemberCache, INavigableData + internal class MemberCache : IPublishedMemberCache { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - - public readonly IVariationContextAccessor VariationContextAccessor; - private readonly IEntityXmlSerializer _entitySerializer; + private readonly IVariationContextAccessor _variationContextAccessor; private readonly IPublishedModelFactory _publishedModelFactory; - private readonly IAppCache _snapshotCache; - private readonly IMemberService _memberService; private readonly PublishedContentTypeCache _contentTypeCache; private readonly bool _previewDefault; - public MemberCache(bool previewDefault, IAppCache snapshotCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IEntityXmlSerializer entitySerializer, + public MemberCache( + bool previewDefault, + PublishedContentTypeCache contentTypeCache, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, IPublishedModelFactory publishedModelFactory) { - _snapshotCache = snapshotCache; _publishedSnapshotAccessor = publishedSnapshotAccessor; - VariationContextAccessor = variationContextAccessor; - _entitySerializer = entitySerializer; + _variationContextAccessor = variationContextAccessor; _publishedModelFactory = publishedModelFactory; - _memberService = memberService; _previewDefault = previewDefault; _contentTypeCache = contentTypeCache; } - private T GetCacheItem(string cacheKey, Func getCacheItem) - where T : class - { - var cache = _snapshotCache; - return cache == null - ? getCacheItem() - : cache.GetCacheItem(cacheKey, getCacheItem); - } - - public IPublishedContent GetById(bool preview, int memberId) - { - return GetById(memberId); - } - - public IPublishedContent /*IPublishedMember*/ GetById(int memberId) - { - return GetCacheItem(CacheKeys.MemberCacheMember("ById", _previewDefault, memberId), () => - { - var member = _memberService.GetById(memberId); - return member == null - ? null - : PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor, _publishedModelFactory); - }); - } - - private IPublishedContent /*IPublishedMember*/ GetById(IMember member, bool previewing) - { - return GetCacheItem(CacheKeys.MemberCacheMember("ById", _previewDefault, member.Id), () => - PublishedMember.Create(member, GetContentType(member.ContentTypeId), previewing, _publishedSnapshotAccessor, VariationContextAccessor, _publishedModelFactory)); - } - - public IPublishedContent /*IPublishedMember*/ GetByProviderKey(object key) - { - return GetCacheItem(CacheKeys.MemberCacheMember("ByProviderKey", _previewDefault, key), () => - { - var member = _memberService.GetByProviderKey(key); - return member == null ? null : GetById(member, _previewDefault); - }); - } - - public IPublishedContent /*IPublishedMember*/ GetByUsername(string username) - { - return GetCacheItem(CacheKeys.MemberCacheMember("ByUsername", _previewDefault, username), () => - { - var member = _memberService.GetByUsername(username); - return member == null ? null : GetById(member, _previewDefault); - }); - } - - public IPublishedContent /*IPublishedMember*/ GetByEmail(string email) - { - return GetCacheItem(CacheKeys.MemberCacheMember("ByEmail", _previewDefault, email), () => - { - var member = _memberService.GetByEmail(email); - return member == null ? null : GetById(member, _previewDefault); - }); - } - - public IPublishedContent /*IPublishedMember*/ GetByMember(IMember member) - { - return PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor, _publishedModelFactory); - } - - public IEnumerable GetAtRoot(bool preview) - { - // because members are flat (not a tree) everything is at root - // because we're loading everything... let's just not cache? - var members = _memberService.GetAllMembers(); - return members.Select(m => PublishedMember.Create(m, GetContentType(m.ContentTypeId), preview, _publishedSnapshotAccessor, VariationContextAccessor, _publishedModelFactory)); - } - - public XPathNavigator CreateNavigator() - { - var source = new Source(this, false); - var navigator = new NavigableNavigator(source); - return navigator; - } - - public XPathNavigator CreateNavigator(bool preview) - { - return CreateNavigator(); - } - - public XPathNavigator CreateNodeNavigator(int id, bool preview) - { - var result = _memberService.GetById(id); - if (result == null) return null; - - var s = _entitySerializer.Serialize(result); - var n = s.GetXmlNode(); - return n.CreateNavigator(); - } - #region Content types - public IPublishedContentType GetContentType(int id) - { - return _contentTypeCache.Get(PublishedItemType.Member, id); - } + public IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Member, id); - public IPublishedContentType GetContentType(string alias) - { - return _contentTypeCache.Get(PublishedItemType.Member, alias); - } + public IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Member, alias); + + public IPublishedContent Get(IMember member) + => PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, _variationContextAccessor, _publishedModelFactory); #endregion } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs b/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs index f717b90da2..4796f32295 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedMember.cs @@ -12,10 +12,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache // note // the whole PublishedMember thing should be refactored because as soon as a member // is wrapped on in a model, the inner IMember and all associated properties are lost - internal class PublishedMember : PublishedContent //, IPublishedMember + internal class PublishedMember : PublishedContent { - private readonly IMember _member; - private PublishedMember( IMember member, ContentNode contentNode, @@ -25,7 +23,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache IPublishedModelFactory publishedModelFactory) : base(contentNode, contentData, publishedSnapshotAccessor, variationContextAccessor, publishedModelFactory) { - _member = member; + Member = member; } public static IPublishedContent Create( @@ -45,12 +43,19 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache WriterId = member.CreatorId, // what else? Properties = GetPropertyValues(contentType, member) }; - var n = new ContentNode(member.Id, member.Key, + var n = new ContentNode( + member.Id, + member.Key, contentType, - member.Level, member.Path, member.SortOrder, + member.Level, + member.Path, + member.SortOrder, member.ParentId, - member.CreateDate, member.CreatorId); - return new PublishedMember(member, n, d, publishedSnapshotAccessor, variationContextAccessor, publishedModelFactory).CreateModel(publishedModelFactory); + member.CreateDate, + member.CreatorId); + + return new PublishedMember(member, n, d, publishedSnapshotAccessor, variationContextAccessor, publishedModelFactory) + .CreateModel(publishedModelFactory); } private static Dictionary GetPropertyValues(IPublishedContentType contentType, IMember member) @@ -99,25 +104,25 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache #region IPublishedMember - public IMember Member => _member; + public IMember Member { get; } - public string Email => _member.Email; + public string Email => Member.Email; - public string UserName => _member.Username; + public string UserName => Member.Username; - public string Comments => _member.Comments; + public string Comments => Member.Comments; - public bool IsApproved => _member.IsApproved; + public bool IsApproved => Member.IsApproved; - public bool IsLockedOut => _member.IsLockedOut; + public bool IsLockedOut => Member.IsLockedOut; - public DateTime LastLockoutDate => _member.LastLockoutDate; + public DateTime LastLockoutDate => Member.LastLockoutDate; - public DateTime CreationDate => _member.CreateDate; + public DateTime CreationDate => Member.CreateDate; - public DateTime LastLoginDate => _member.LastLoginDate; + public DateTime LastLoginDate => Member.LastLoginDate; - public DateTime LastPasswordChangedDate => _member.LastPasswordChangeDate; + public DateTime LastPasswordChangedDate => Member.LastPasswordChangeDate; #endregion } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index c507f5af68..916fb2da5e 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -1092,7 +1092,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache { ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainCache, Options.Create(_globalSettings), _variationContextAccessor), MediaCache = new MediaCache(previewDefault, mediaSnap, _variationContextAccessor), - MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, _publishedSnapshotAccessor, _variationContextAccessor, _entitySerializer, _publishedModelFactory), + MemberCache = new MemberCache(previewDefault, memberTypeCache, _publishedSnapshotAccessor, _variationContextAccessor, _publishedModelFactory), DomainCache = domainCache, SnapshotCache = snapshotCache, ElementsCache = elementsCache diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs index d263997aab..f773b83e5a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Net; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; @@ -40,7 +41,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security _mockMemberService.Object, new UmbracoMapper(new MapDefinitionCollection(new List()), scopeProvider), scopeProvider, - new IdentityErrorDescriber()); + new IdentityErrorDescriber(), + Mock.Of()); _mockIdentityOptions = new Mock>(); var idOptions = new IdentityOptions { Lockout = { AllowedForNewUsers = false } }; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs index 5da4d18bff..1740123612 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; @@ -35,7 +36,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security _mockMemberService.Object, new UmbracoMapper(new MapDefinitionCollection(new List()), mockScopeProvider.Object), mockScopeProvider.Object, - new IdentityErrorDescriber()); + new IdentityErrorDescriber(), + Mock.Of()); } [Test] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs index 752f4783ad..747148b3ee 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs @@ -60,7 +60,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Security private static Mock MockMemberManager() => new Mock( Mock.Of(), - Mock.Of>(), + Mock.Of(), Options.Create(new IdentityOptions()), Mock.Of>(), Enumerable.Empty>(), diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs index c52bd461e2..bec5bbbb92 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Xml; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -67,7 +67,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var publishedShapshot = new PublishedSnapshot( new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, ContentTypesCache, null, VariationContextAccessor, null), new PublishedMediaCache(xmlStore, Mock.Of(), Mock.Of(), appCache, ContentTypesCache, Factory.GetRequiredService(), umbracoContextAccessor, VariationContextAccessor), - new PublishedMemberCache(null, appCache, Mock.Of(), ContentTypesCache, Mock.Of(), VariationContextAccessor), + new PublishedMemberCache(ContentTypesCache, VariationContextAccessor), domainCache); var publishedSnapshotService = new Mock(); publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedShapshot); diff --git a/src/Umbraco.Core/PublishedCache/PublishedMember.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMember.cs similarity index 96% rename from src/Umbraco.Core/PublishedCache/PublishedMember.cs rename to src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMember.cs index 3fb0c80f6a..a885af1475 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMember.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Tests.LegacyXmlPublishedCache { /// /// Exposes a member object as IPublishedContent @@ -18,18 +18,15 @@ namespace Umbraco.Cms.Core.PublishedCache private readonly IMembershipUser _membershipUser; private readonly IPublishedProperty[] _properties; private readonly IPublishedContentType _publishedMemberType; - private readonly IUserService _userService; public PublishedMember( IMember member, IPublishedContentType publishedMemberType, - IUserService userService, IVariationContextAccessor variationContextAccessor) : base(variationContextAccessor) { _member = member ?? throw new ArgumentNullException(nameof(member)); _membershipUser = member; _publishedMemberType = publishedMemberType ?? throw new ArgumentNullException(nameof(publishedMemberType)); - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); // RawValueProperty is used for two things here // - for the 'map properties' thing that we should really get rid of @@ -150,6 +147,8 @@ namespace Umbraco.Cms.Core.PublishedCache public override int Level => _member.Level; + public DateTime LastPasswordChangedDate => throw new NotImplementedException(); + #endregion } } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs index 09c6d458e9..1faa0ee948 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs @@ -1,136 +1,34 @@ -using System.Text; -using System.Xml.XPath; -using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; using Umbraco.Extensions; using Umbraco.Web.Composing; -using Umbraco.Web.Security; namespace Umbraco.Tests.LegacyXmlPublishedCache { class PublishedMemberCache : IPublishedMemberCache { - private readonly IMemberService _memberService; - private readonly IAppCache _requestCache; - private readonly XmlStore _xmlStore; private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IUserService _userService; private readonly IVariationContextAccessor _variationContextAccessor; - public PublishedMemberCache(XmlStore xmlStore, IAppCache requestCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache, IUserService userService, IVariationContextAccessor variationContextAccessor) + public PublishedMemberCache(PublishedContentTypeCache contentTypeCache, IVariationContextAccessor variationContextAccessor) { - _requestCache = requestCache; - _memberService = memberService; - _xmlStore = xmlStore; _contentTypeCache = contentTypeCache; - _userService = userService; _variationContextAccessor = variationContextAccessor; } - public IPublishedContent GetByProviderKey(object key) - { - return _requestCache.GetCacheItem( - GetCacheKey("GetByProviderKey", key), () => - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - - var result = _memberService.GetByProviderKey(key); - if (result == null) return null; - var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _userService, _variationContextAccessor).CreateModel(Current.PublishedModelFactory); - }); - } - - public IPublishedContent GetById(int memberId) - { - return _requestCache.GetCacheItem( - GetCacheKey("GetById", memberId), () => - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - - var result = _memberService.GetById(memberId); - if (result == null) return null; - var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _userService, _variationContextAccessor).CreateModel(Current.PublishedModelFactory); - }); - } - - public IPublishedContent GetByUsername(string username) - { - return _requestCache.GetCacheItem( - GetCacheKey("GetByUsername", username), () => - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - - var result = _memberService.GetByUsername(username); - if (result == null) return null; - var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _userService, _variationContextAccessor).CreateModel(Current.PublishedModelFactory); - }); - } - - public IPublishedContent GetByEmail(string email) - { - return _requestCache.GetCacheItem( - GetCacheKey("GetByEmail", email), () => - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - - var result = _memberService.GetByEmail(email); - if (result == null) return null; - var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _userService, _variationContextAccessor).CreateModel(Current.PublishedModelFactory); - }); - } - - public IPublishedContent GetByMember(IMember member) + public IPublishedContent Get(IMember member) { var type = _contentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId); - return new PublishedMember(member, type, _userService, _variationContextAccessor).CreateModel(Current.PublishedModelFactory); - } - - public XPathNavigator CreateNavigator() - { - var doc = _xmlStore.GetMemberXml(); - return doc.CreateNavigator(); - } - - public XPathNavigator CreateNavigator(bool preview) - { - return CreateNavigator(); - } - - public XPathNavigator CreateNodeNavigator(int id, bool preview) - { - var n = _xmlStore.GetMemberXmlNode(id); - return n?.CreateNavigator(); - } - - private static string GetCacheKey(string key, params object[] additional) - { - var sb = new StringBuilder($"{typeof (MembershipHelper).Name}-{key}"); - foreach (var s in additional) - { - sb.Append("-"); - sb.Append(s); - } - return sb.ToString(); + return new PublishedMember(member, type, _variationContextAccessor) + .CreateModel(Current.PublishedModelFactory); } #region Content types - public IPublishedContentType GetContentType(int id) - { - return _contentTypeCache.Get(PublishedItemType.Member, id); - } + public IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Member, id); - public IPublishedContentType GetContentType(string alias) - { - return _contentTypeCache.Get(PublishedItemType.Member, alias); - } + public IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Member, alias); #endregion } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 45f8582eff..580a4078f8 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -27,7 +27,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; private readonly PublishedContentTypeCache _contentTypeCache; private readonly IDomainService _domainService; - private readonly IMemberService _memberService; private readonly IMediaService _mediaService; private readonly IUserService _userService; private readonly IAppCache _requestCache; @@ -104,7 +103,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache documentRepository, mediaRepository, memberRepository, entitySerializer, hostingEnvironment, hostingLifetime, shortStringHelper); _domainService = serviceContext.DomainService; - _memberService = serviceContext.MemberService; _mediaService = serviceContext.MediaService; _userService = serviceContext.UserService; _defaultCultureAccessor = defaultCultureAccessor; @@ -134,7 +132,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return new PublishedSnapshot( new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache, _variationContextAccessor, previewToken), new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache, _entitySerializer, _umbracoContextAccessor, _variationContextAccessor), - new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache, _userService, _variationContextAccessor), + new PublishedMemberCache(_contentTypeCache, _variationContextAccessor), domainCache); } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 4f03c341e4..6e7da0a558 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net.Http; using System.Security.Claims; diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 4c8c38420b..94b4f5500b 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Data.Common; diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 3fc1670fad..a33b3f6c10 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Reflection; using System.Web.Routing; -using System.Web.Security; using System.Xml.Linq; using Examine; using Microsoft.Extensions.Configuration; @@ -55,7 +54,6 @@ using Umbraco.Cms.Infrastructure.Media; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; -using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Cms.Infrastructure.Serialization; using Umbraco.Cms.Tests.Common; @@ -67,7 +65,6 @@ using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web.Hosting; using Umbraco.Web.Security; -using Umbraco.Web.Security.Providers; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Umbraco.Tests.Testing @@ -112,9 +109,9 @@ namespace Umbraco.Tests.Testing protected UmbracoTestAttribute Options { get; private set; } - protected static bool FirstTestInSession = true; + protected static bool FirstTestInSession { get; set; } = true; - protected bool FirstTestInFixture = true; + protected bool FirstTestInFixture { get; set; } = true; internal TestObjects TestObjects { get; private set; } @@ -233,10 +230,6 @@ namespace Umbraco.Tests.Testing var memberService = Mock.Of(); var memberTypeService = Mock.Of(); - var membershipProvider = new MembersMembershipProvider(memberService, memberTypeService, Mock.Of(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); - var membershipHelper = new MembershipHelper(Mock.Of(), Mock.Of(), membershipProvider, Mock.Of(), memberService, memberTypeService, Mock.Of(), AppCaches.Disabled, loggerFactory, ShortStringHelper, Mock.Of()); - - services.AddUnique(membershipHelper); TestObjects = new TestObjects(); Compose(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 32617b4d8b..4138ae6bdf 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -142,6 +142,7 @@ + diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs index b04141d2b0..be312a920f 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs @@ -33,7 +33,6 @@ namespace Umbraco.Extensions // then we'll probably have to change this and make it more flexible like how we do for Users. Which means booting up // identity here with the basics and registering all of our own custom services. // Since we are using the defaults in v8 (and below) for members, I think using the default for members now is OK! - // TODO: We may need to use services.AddIdentityCore instead if this is doing too much services.AddIdentity() .AddDefaultTokenProviders() @@ -47,6 +46,7 @@ namespace Umbraco.Extensions services.ConfigureOptions(); + services.AddScoped(x => (IMemberUserStore)x.GetRequiredService>()); services.AddScoped, MemberPasswordHasher>(); services.ConfigureOptions(); diff --git a/src/Umbraco.Web.Common/Security/MemberManager.cs b/src/Umbraco.Web.Common/Security/MemberManager.cs index ce09f02b63..9bc8b284c7 100644 --- a/src/Umbraco.Web.Common/Security/MemberManager.cs +++ b/src/Umbraco.Web.Common/Security/MemberManager.cs @@ -11,19 +11,22 @@ using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using System.Threading.Tasks; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Web.Common.Security { public class MemberManager : UmbracoUserManager, IMemberManager { + private readonly IMemberUserStore _store; private readonly IPublicAccessService _publicAccessService; private readonly IHttpContextAccessor _httpContextAccessor; private MemberIdentityUser _currentMember; public MemberManager( IIpResolver ipResolver, - IUserStore store, + IMemberUserStore store, IOptions optionsAccessor, IPasswordHasher passwordHasher, IEnumerable> userValidators, @@ -37,6 +40,7 @@ namespace Umbraco.Cms.Web.Common.Security : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, errors, services, logger, passwordConfiguration) { + _store = store; _publicAccessService = publicAccessService; _httpContextAccessor = httpContextAccessor; } @@ -229,5 +233,7 @@ namespace Umbraco.Cms.Web.Common.Security } return result; } + + public IPublishedContent AsPublishedMember(MemberIdentityUser user) => _store.GetPublishedMember(user); } } diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 3f5a5e44cb..0863d843fa 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -184,7 +184,6 @@ namespace Umbraco.Web.Composing public static IUmbracoVersion UmbracoVersion => Factory.GetRequiredService(); public static IPublishedUrlProvider PublishedUrlProvider => Factory.GetRequiredService(); public static IMenuItemCollectionFactory MenuItemCollectionFactory => Factory.GetRequiredService(); - public static MembershipHelper MembershipHelper => Factory.GetRequiredService(); public static IUmbracoApplicationLifetime UmbracoApplicationLifetime => Factory.GetRequiredService(); public static IPublishedContentQuery PublishedContentQuery => Factory.GetRequiredService(); diff --git a/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs deleted file mode 100644 index 0d5e169cec..0000000000 --- a/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Web.Security; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Web.Models.Membership -{ - - internal class UmbracoMembershipMember : MembershipUser - { - private readonly IMembershipUser _member; - private readonly string _userName; - private readonly object _providerUserKey; - 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; - - //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; - //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(); - _providerName = providerName; - _providerUserKey = member.Key; - _comment = member.Comments; - _isApproved = member.IsApproved; - _isLockedOut = member.IsLockedOut; - _creationDate = member.CreateDate.ToUniversalTime(); - _lastLoginDate = member.LastLoginDate.ToUniversalTime(); - _lastPasswordChangedDate = member.LastPasswordChangeDate.ToUniversalTime(); - _lastLockoutDate = member.LastLockoutDate.ToUniversalTime(); - } - - internal IMembershipUser Member - { - get { return _member; } - } - - public override string UserName - { - get { return _userName; } - } - - public override object ProviderUserKey - { - get { return _providerUserKey; } - } - - public override string Email - { - get { return _email; } - set { _email = value; } - } - - public override string PasswordQuestion => string.Empty; - - public override string Comment - { - get { return _comment; } - set { _comment = value; } - } - - public override bool IsApproved - { - get { return _isApproved; } - set { _isApproved = value; } - } - - public override bool IsLockedOut - { - get { return _isLockedOut; } - } - - public override DateTime LastLockoutDate - { - get { return _lastLockoutDate; } - } - - public override DateTime CreationDate - { - get { return _creationDate; } - } - - public override DateTime LastLoginDate - { - get { return _lastLoginDate; } - set { _lastLoginDate = value; } - } - - public override DateTime LastPasswordChangedDate - { - get { return _lastPasswordChangedDate; } - } - - public override string ProviderName - { - get { return _providerName; } - } - } -} diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs deleted file mode 100644 index 61ed6c9fdd..0000000000 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Web.Security; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Extensions; -using Umbraco.Web.Security.Providers; - -namespace Umbraco.Web.Security -{ - // MIGRATED TO NETCORE - public class MembershipHelper - { - private readonly MembersMembershipProvider _membershipProvider; - private readonly RoleProvider _roleProvider; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IMemberService _memberService; - private readonly IMemberTypeService _memberTypeService; - private readonly IPublicAccessService _publicAccessService; - private readonly AppCaches _appCaches; - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private readonly IShortStringHelper _shortStringHelper; - private readonly IEntityService _entityService; - - #region Constructors - - public MembershipHelper - ( - IHttpContextAccessor httpContextAccessor, - IPublishedMemberCache memberCache, - MembersMembershipProvider membershipProvider, - RoleProvider roleProvider, - IMemberService memberService, - IMemberTypeService memberTypeService, - IPublicAccessService publicAccessService, - AppCaches appCaches, - ILoggerFactory loggerFactory, - IShortStringHelper shortStringHelper, - IEntityService entityService - ) - { - MemberCache = memberCache; - _httpContextAccessor = httpContextAccessor; - _memberService = memberService; - _memberTypeService = memberTypeService; - _publicAccessService = publicAccessService; - _appCaches = appCaches; - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); - _shortStringHelper = shortStringHelper; - - _membershipProvider = membershipProvider ?? throw new ArgumentNullException(nameof(membershipProvider)); - _roleProvider = roleProvider ?? throw new ArgumentNullException(nameof(roleProvider)); - _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); - } - - #endregion - - protected IPublishedMemberCache MemberCache { get; } - - #region Querying for front-end - - public virtual IPublishedContent GetByProviderKey(object key) - { - return MemberCache.GetByProviderKey(key); - } - - public virtual IEnumerable GetByProviderKeys(IEnumerable keys) - { - return keys?.Select(GetByProviderKey).WhereNotNull() ?? Enumerable.Empty(); - } - - public virtual IPublishedContent GetById(int memberId) - { - return MemberCache.GetById(memberId); - } - - public virtual IEnumerable GetByIds(IEnumerable memberIds) - { - return memberIds?.Select(GetById).WhereNotNull() ?? Enumerable.Empty(); - } - - public virtual IPublishedContent GetById(Guid memberId) - { - return GetByProviderKey(memberId); - } - - public virtual IEnumerable GetByIds(IEnumerable memberIds) - { - return GetByProviderKeys(memberIds.OfType()); - } - - public virtual IPublishedContent GetByUsername(string username) - { - return MemberCache.GetByUsername(username); - } - - public virtual IPublishedContent GetByEmail(string email) - { - return MemberCache.GetByEmail(email); - } - - public virtual IPublishedContent Get(Udi udi) - { - var guidUdi = udi as GuidUdi; - if (guidUdi == null) - return null; - - var umbracoType = UdiEntityTypeHelper.ToUmbracoObjectType(udi.EntityType); - - switch (umbracoType) - { - case UmbracoObjectTypes.Member: - // TODO: need to implement Get(guid)! - var memberAttempt = _entityService.GetId(guidUdi.Guid, umbracoType); - if (memberAttempt.Success) - return GetById(memberAttempt.Result); - break; - } - - return null; - } - - #endregion - - } -} diff --git a/src/Umbraco.Web/Security/MembershipProviderBase.cs b/src/Umbraco.Web/Security/MembershipProviderBase.cs deleted file mode 100644 index b0ddac250e..0000000000 --- a/src/Umbraco.Web/Security/MembershipProviderBase.cs +++ /dev/null @@ -1,682 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.ComponentModel.DataAnnotations; -using System.Configuration.Provider; -using System.Text; -using System.Text.RegularExpressions; -using System.Web.Security; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Extensions; -using Umbraco.Web.Composing; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Web.Security -{ - //TODO: Delete - should not be used - /// - /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. - /// - [Obsolete("We are now using ASP.NET Core Identity instead of membership providers")] - public abstract class MembershipProviderBase : MembershipProvider - { - private readonly IHostingEnvironment _hostingEnvironment; - - protected MembershipProviderBase(IHostingEnvironment hostingEnvironment) - { - _hostingEnvironment = hostingEnvironment; - } - - /// - /// Providers can override this setting, default is 10 - /// - public virtual int DefaultMinPasswordLength - { - get { return 10; } - } - - /// - /// Providers can override this setting, default is 0 - /// - public virtual int DefaultMinNonAlphanumericChars - { - get { return 0; } - } - - /// - /// Providers can override this setting, default is false to use better security - /// - public virtual bool DefaultUseLegacyEncoding - { - get { return false; } - } - - /// - /// Returns the raw password value for a given user - /// - /// - /// - /// - /// By default this will return an invalid attempt, inheritors will need to override this to support it - /// - protected virtual Attempt GetRawPassword(string username) - { - return Attempt.Fail(); - } - - 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 _requiresUniqueEmail; - - public bool UseLegacyEncoding { get; private set; } - - #region Properties - - public string CustomHashAlgorithmType { get; private set; } - - /// - /// 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; } - } - - /// - /// Always returns false, question/answer is not supported - /// - public override bool RequiresQuestionAndAnswer => false; - - /// - /// 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 name of the application using the custom membership provider. - /// - /// - /// The name of the application using the custom membership provider. - public override string ApplicationName - { - get - { - return _applicationName; - } - set - { - if (string.IsNullOrEmpty(value)) - throw new ProviderException("ApplicationName cannot be empty."); - - if (value.Length > 0x100) - throw new ProviderException("Provider application name too long."); - - _applicationName = value; - } - } - - #endregion - - /// - /// 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) - { - // Initialize base provider class - base.Initialize(name, config); - - _enablePasswordRetrieval = config.GetValue("enablePasswordRetrieval", false); - _enablePasswordReset = config.GetValue("enablePasswordReset", true); - _requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true); - _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); - _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); - _minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80); - _minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", DefaultMinNonAlphanumericChars, true, 0x80); - _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; - - _applicationName = config["applicationName"]; - if (string.IsNullOrEmpty(_applicationName)) - _applicationName = GetDefaultAppName(_hostingEnvironment); - - //by default we will continue using the legacy encoding. - UseLegacyEncoding = config.GetValue("useLegacyEncoding", DefaultUseLegacyEncoding); - - // make sure password format is Hashed by default. - string str = config["passwordFormat"] ?? "Hashed"; - - switch (str.ToLower()) - { - case "clear": - _passwordFormat = MembershipPasswordFormat.Clear; - break; - - case "encrypted": - _passwordFormat = MembershipPasswordFormat.Encrypted; - break; - - case "hashed": - _passwordFormat = MembershipPasswordFormat.Hashed; - break; - - default: - throw new ProviderException("Provider bad password format"); - } - - if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) - { - var ex = new ProviderException("Provider can not retrieve a hashed password"); - Current.Logger.LogError(ex, "Cannot specify a Hashed password format with the enabledPasswordRetrieval option set to true"); - throw ex; - } - - CustomHashAlgorithmType = config.GetValue("hashAlgorithmType", string.Empty); - } - - /// - /// Override this method to ensure the password is valid before raising the event - /// - /// - protected override void OnValidatingPassword(ValidatePasswordEventArgs e) - { - var attempt = IsPasswordValid(e.Password, MinRequiredNonAlphanumericCharacters, PasswordStrengthRegularExpression, MinRequiredPasswordLength); - if (attempt.Success == false) - { - e.Cancel = true; - return; - } - - base.OnValidatingPassword(e); - } - - protected internal enum PasswordValidityError - { - Ok, - Length, - AlphanumericChars, - Strength - } - - /// - /// Processes a request to update the password for a membership user. - /// - /// The user to update the password for. - /// Required to change a user password if the user is not new and AllowManuallyChangingPassword is false - /// 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) - { - string rawPasswordValue = string.Empty; - - var args = new ValidatePasswordEventArgs(username, newPassword, false); - OnValidatingPassword(args); - - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); - } - - //Special cases to allow changing password without validating existing credentials - // * the member is new and doesn't have a password set - // * during installation to set the admin password - var installing = Current.RuntimeState.Level == RuntimeLevel.Install; - if (rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix) - || (installing && oldPassword == "default")) - { - return PerformChangePassword(username, oldPassword, newPassword); - } - - if (!oldPassword.IsNullOrWhiteSpace()) - { - if (ValidateUser(username, oldPassword) == false) return false; - } - - return PerformChangePassword(username, oldPassword, newPassword); - } - - /// - /// 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 abstract bool PerformChangePassword(string username, string oldPassword, string newPassword); - - /// - /// 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. - /// - /// - /// Performs the basic validation before passing off to PerformChangePasswordQuestionAndAnswer - /// - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - if (RequiresQuestionAndAnswer == false) - { - throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); - } - - if (!password.IsNullOrWhiteSpace()) - { - if (ValidateUser(username, password) == false) - { - return false; - } - } - - return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); - } - - /// - /// 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 abstract bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer); - - /// - /// 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. - /// - /// - /// Ensures the ValidatingPassword event is executed before executing PerformCreateUser and performs basic membership provider validation of values. - /// - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - var valStatus = ValidateNewUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey); - if (valStatus != MembershipCreateStatus.Success) - { - status = valStatus; - return null; - } - - return PerformCreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Performs the validation of the information for creating a new user - /// - /// - /// - /// - /// - /// - /// - /// - /// - protected MembershipCreateStatus ValidateNewUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey) - { - var args = new ValidatePasswordEventArgs(username, password, true); - OnValidatingPassword(args); - if (args.Cancel) - { - return MembershipCreateStatus.InvalidPassword; - } - - // Validate password - var passwordValidAttempt = IsPasswordValid(password, MinRequiredNonAlphanumericCharacters, PasswordStrengthRegularExpression, MinRequiredPasswordLength); - if (passwordValidAttempt.Success == false) - { - return MembershipCreateStatus.InvalidPassword; - } - - // Validate email - if (IsEmailValid(email) == false) - { - return MembershipCreateStatus.InvalidEmail; - } - - // Make sure username isn't all whitespace - if (string.IsNullOrWhiteSpace(username.Trim())) - { - return MembershipCreateStatus.InvalidUserName; - } - - // Check password question - if (string.IsNullOrWhiteSpace(passwordQuestion) && RequiresQuestionAndAnswer) - { - return MembershipCreateStatus.InvalidQuestion; - } - - // Check password answer - if (string.IsNullOrWhiteSpace(passwordAnswer) && RequiresQuestionAndAnswer) - { - return MembershipCreateStatus.InvalidAnswer; - } - - return MembershipCreateStatus.Success; - } - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected abstract MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); - - /// - /// Gets the members password if password retrieval is enabled - /// - /// - /// - /// - public override string GetPassword(string username, string answer) - { - if (EnablePasswordRetrieval == false) - throw new ProviderException("Password Retrieval Not Enabled."); - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - throw new ProviderException("Cannot retrieve Hashed passwords."); - - return PerformGetPassword(username, answer); - } - - /// - /// Gets the members password if password retrieval is enabled - /// - /// - /// - /// - protected abstract string PerformGetPassword(string username, string answer); - - public override string ResetPassword(string username, string answer) - { - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - { - throw args.FailureInformation; - } - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } - - return PerformResetPassword(username, answer, newPassword); - } - - protected abstract string PerformResetPassword(string username, string answer, string generatedPassword); - - protected internal static Attempt IsPasswordValid(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength) - { - if (minRequiredNonAlphanumericChars > 0) - { - var nonAlphaNumeric = Regex.Replace(password, "[a-zA-Z0-9]", "", RegexOptions.Multiline | RegexOptions.IgnoreCase); - if (nonAlphaNumeric.Length < minRequiredNonAlphanumericChars) - { - return Attempt.Fail(PasswordValidityError.AlphanumericChars); - } - } - - if (string.IsNullOrEmpty(strengthRegex) == false) - { - if (Regex.IsMatch(password, strengthRegex, RegexOptions.Compiled) == false) - { - return Attempt.Fail(PasswordValidityError.Strength); - } - - } - - if (password.Length < minLength) - { - return Attempt.Fail(PasswordValidityError.Length); - } - - return Attempt.Succeed(PasswordValidityError.Ok); - } - - /// - /// Gets the name of the default app. - /// - /// - internal static string GetDefaultAppName(IHostingEnvironment hostingEnvironment) - { - try - { - string applicationVirtualPath = hostingEnvironment.ApplicationVirtualPath; - if (string.IsNullOrEmpty(applicationVirtualPath)) - { - return "/"; - } - return applicationVirtualPath; - } - catch - { - return "/"; - } - } - - internal static int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) - { - int num; - string s = config[valueName]; - if (s == null) - { - return defaultValue; - } - if (!int.TryParse(s, out num)) - { - 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 && (num <= 0)) - { - throw new ProviderException("Value must be positive integer"); - } - if ((maxValueAllowed > 0) && (num > maxValueAllowed)) - { - throw new ProviderException("Value too big"); - } - return num; - } - - internal static bool IsEmailValid(string email) - { - return new EmailAddressAttribute().IsValid(email); - } - - protected internal string DecryptPassword(string pass) - { - //if we are doing it the old way - - if (UseLegacyEncoding) - { - return LegacyUnEncodePassword(pass); - } - - //This is the correct way to implement this (as per the sql membership provider) - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Clear: - return pass; - case MembershipPasswordFormat.Hashed: - throw new ProviderException("Provider can not decrypt hashed password"); - case MembershipPasswordFormat.Encrypted: - default: - var bytes = DecryptPassword(Convert.FromBase64String(pass)); - return bytes == null ? null : Encoding.Unicode.GetString(bytes, 16, bytes.Length - 16); - } - } - - /// - /// Unencode password. - /// - /// The encoded password. - /// The unencoded password. - protected string LegacyUnEncodePassword(string encodedPassword) - { - string password = encodedPassword; - switch (PasswordFormat) - { - case MembershipPasswordFormat.Clear: - break; - case MembershipPasswordFormat.Encrypted: - password = Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password))); - break; - case MembershipPasswordFormat.Hashed: - throw new ProviderException("Cannot unencode a hashed password."); - default: - throw new ProviderException("Unsupported password format."); - } - return password; - } - - public override string ToString() - { - var result = base.ToString(); - var sb = new StringBuilder(result); - sb.AppendLine("Name =" + Name); - sb.AppendLine("_applicationName =" + _applicationName); - sb.AppendLine("_enablePasswordReset=" + _enablePasswordReset); - sb.AppendLine("_enablePasswordRetrieval=" + _enablePasswordRetrieval); - sb.AppendLine("_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts); - sb.AppendLine("_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters); - sb.AppendLine("_minRequiredPasswordLength=" + _minRequiredPasswordLength); - sb.AppendLine("_passwordAttemptWindow=" + _passwordAttemptWindow); - sb.AppendLine("_passwordFormat=" + _passwordFormat); - sb.AppendLine("_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression); - sb.AppendLine("_requiresQuestionAndAnswer=" + RequiresQuestionAndAnswer); - sb.AppendLine("_requiresUniqueEmail=" + _requiresUniqueEmail); - return sb.ToString(); - } - - } -} diff --git a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs deleted file mode 100644 index caf27dddf2..0000000000 --- a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Security.Principal; -using System.Threading; -using System.Web; -using System.Web.Security; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Extensions; -using Umbraco.Web.Composing; -using Umbraco.Web.Models.Membership; -using Umbraco.Web.Security.Providers; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Web.Security -{ - public static class MembershipProviderExtensions - { - - internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName) - { - var membershipMember = new UmbracoMembershipMember(member, providerName); - return membershipMember; - } - - - /// - /// Method to get the Umbraco Members membership provider based on its alias - /// - /// - public static MembersMembershipProvider GetMembersMembershipProvider() - { - if (Membership.Providers[Constants.Conventions.Member.UmbracoMemberProviderName] == null) - { - throw new InvalidOperationException("No membership provider found with name " + Constants.Conventions.Member.UmbracoMemberProviderName); - } - return (MembersMembershipProvider)Membership.Providers[Constants.Conventions.Member.UmbracoMemberProviderName]; - } - - /// - /// Returns the currently logged in MembershipUser and flags them as being online - use sparingly (i.e. login) - /// - /// - /// - public static MembershipUser GetCurrentUserOnline(this MembershipProvider membershipProvider) - { - var username = membershipProvider.GetCurrentUserName(); - return username.IsNullOrWhiteSpace() - ? null - : membershipProvider.GetUser(username, true); - } - - /// - /// Returns the currently logged in MembershipUser - /// - /// - /// - internal static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider) - { - var username = membershipProvider.GetCurrentUserName(); - return username.IsNullOrWhiteSpace() - ? null - : membershipProvider.GetUser(username, false); - } - - /// - /// Just returns the current user's login name (just a wrapper). - /// - /// - /// - internal static string GetCurrentUserName(this MembershipProvider membershipProvider) - { - if (Current.HostingEnvironment.IsHosted) - { - HttpContext current = HttpContext.Current; - if (current != null && current.User != null && current.User.Identity != null) - return current.User.Identity.Name; - } - IPrincipal currentPrincipal = Thread.CurrentPrincipal; - if (currentPrincipal == null || currentPrincipal.Identity == null) - return string.Empty; - else - return currentPrincipal.Identity.Name; - } - - /// - /// Returns true if the provider specified is a built-in Umbraco membership provider - /// - /// - /// - public static bool IsUmbracoMembershipProvider(this MembershipProvider membershipProvider) - { - return (membershipProvider is UmbracoMembershipProviderBase); - } - } -} diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs deleted file mode 100644 index 97ab5f9adc..0000000000 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.Configuration.Provider; -using System.Web.Security; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Net; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; -using Umbraco.Web.Composing; -using Constants = Umbraco.Cms.Core.Constants; - -namespace Umbraco.Web.Security.Providers -{ - //TODO: Delete: should not be used - [Obsolete("We are now using ASP.NET Core Identity instead of membership providers")] - /// - /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) - /// - public class MembersMembershipProvider : UmbracoMembershipProvider - { - public MembersMembershipProvider() - : this(Current.Services.MemberService, Current.Services.MemberTypeService, Current.UmbracoVersion, Current.HostingEnvironment, Current.IpResolver) - { } - - public MembersMembershipProvider(IMembershipMemberService memberService, IMemberTypeService memberTypeService, IUmbracoVersion umbracoVersion, IHostingEnvironment hostingEnvironment, IIpResolver ipResolver) - : base(memberService, umbracoVersion, hostingEnvironment, ipResolver) - { - LockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; - LastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; - FailedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; - ApprovedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; - CommentPropertyTypeAlias = Constants.Conventions.Member.Comments; - LastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; - LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; - _memberTypeService = memberTypeService; - } - - private readonly IMemberTypeService _memberTypeService; - private string _defaultMemberTypeAlias = "Member"; - private volatile bool _hasDefaultMember; - private static readonly object Locker = new object(); - - public override string ProviderName => "MembersMembershipProvider"; - - protected override MembershipUser ConvertToMembershipUser(IMember entity) - { - return entity.AsConcreteMembershipUser(Name); - } - - public string LockPropertyTypeAlias { get; } - public string LastLockedOutPropertyTypeAlias { get; } - public string FailedPasswordAttemptsPropertyTypeAlias { get; } - public string ApprovedPropertyTypeAlias { get; } - public string CommentPropertyTypeAlias { get; } - public string LastLoginPropertyTypeAlias { get; } - public string LastPasswordChangedPropertyTypeAlias { get; } - - public override void Initialize(string name, 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"]; - if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) - { - throw new ProviderException("No default member type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); - } - _hasDefaultMember = true; - } - - // these need to be lazy else we get a stack overflow since we cannot access Membership.HashAlgorithmType without initializing the providers - - _passwordConfig = new Lazy(() => new MembershipProviderPasswordConfiguration( - MinRequiredPasswordLength, - MinRequiredNonAlphanumericCharacters > 0, - false, false, false, UseLegacyEncoding, - CustomHashAlgorithmType.IsNullOrWhiteSpace() ? Membership.HashAlgorithmType : CustomHashAlgorithmType, - MaxInvalidPasswordAttempts)); - - _passwordSecurity = new Lazy(() => new LegacyPasswordSecurity()); - - } - - protected override Attempt GetRawPassword(string username) - { - var found = MemberService.GetByUsername(username); - if (found == null) return Attempt.Fail(); - return Attempt.Succeed(found.RawPasswordValue); - } - - public override string DefaultMemberTypeAlias - { - get - { - if (_hasDefaultMember == false) - { - lock (Locker) - { - if (_hasDefaultMember == false) - { - _defaultMemberTypeAlias = _memberTypeService.GetDefault(); - if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) - { - throw new ProviderException("No default member type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); - } - _hasDefaultMember = true; - } - } - } - return _defaultMemberTypeAlias; - } - } - - private Lazy _passwordSecurity; - private Lazy _passwordConfig; - - public override LegacyPasswordSecurity PasswordSecurity => _passwordSecurity.Value; - public IPasswordConfiguration PasswordConfiguration => _passwordConfig.Value; - - [Obsolete("We are now using ASP.NET Core Identity instead of membership providers")] - private class MembershipProviderPasswordConfiguration : IPasswordConfiguration - { - public MembershipProviderPasswordConfiguration(int requiredLength, bool requireNonLetterOrDigit, bool requireDigit, bool requireLowercase, bool requireUppercase, bool useLegacyEncoding, string hashAlgorithmType, int maxFailedAccessAttemptsBeforeLockout) - { - RequiredLength = requiredLength; - RequireNonLetterOrDigit = requireNonLetterOrDigit; - RequireDigit = requireDigit; - RequireLowercase = requireLowercase; - RequireUppercase = requireUppercase; - UseLegacyEncoding = useLegacyEncoding; - HashAlgorithmType = hashAlgorithmType ?? throw new ArgumentNullException(nameof(hashAlgorithmType)); - MaxFailedAccessAttemptsBeforeLockout = maxFailedAccessAttemptsBeforeLockout; - } - - public int RequiredLength { get; } - - public bool RequireNonLetterOrDigit { get; } - - public bool RequireDigit { get; } - - public bool RequireLowercase { get; } - - public bool RequireUppercase { get; } - - public bool UseLegacyEncoding { get; } - - public string HashAlgorithmType { get; } - - public int MaxFailedAccessAttemptsBeforeLockout { get; } - } - } -} diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs deleted file mode 100644 index 55bfe06a07..0000000000 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ /dev/null @@ -1,621 +0,0 @@ -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 Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Net; -using Umbraco.Cms.Core.Persistence.Querying; -using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Security.Providers -{ - //TODO: Delete - should not be used - [Obsolete("We are now using ASP.NET Core Identity instead of membership providers")] - /// - /// Abstract Membership Provider that users any implementation of IMembershipMemberService{TEntity} service - /// - public abstract class UmbracoMembershipProvider : UmbracoMembershipProviderBase - where T : IMembershipMemberService - where TEntity : class, IMembershipUser - { - private readonly IUmbracoVersion _umbracoVersion; - private readonly IIpResolver _ipResolver; - - protected IMembershipMemberService MemberService { get; private set; } - - protected UmbracoMembershipProvider(IMembershipMemberService memberService, IUmbracoVersion umbracoVersion, IHostingEnvironment hostingEnvironment, IIpResolver ipResolver) - : base(hostingEnvironment) - { - _umbracoVersion = umbracoVersion; - _ipResolver = ipResolver; - 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); - } - - /// - /// 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 compatibility 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 = PasswordSecurity.HashNewPassword(Membership.HashAlgorithmType, newPassword, out salt); - - m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(Membership.HashAlgorithmType, 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) - { - throw new NotSupportedException("Password question/answer is not supported"); - } - - /// - /// 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; - Current.Logger.LogWarning("Cannot create member as username already exists: {Username}", username); - return null; - } - - // See if the email is unique - if (MemberService.GetByEmail(email) != null && RequiresUniqueEmail) - { - status = MembershipCreateStatus.DuplicateEmail; - Current.Logger.LogWarning("Cannot create member as a member with the same email address exists: {Email}", email); - return null; - } - - string salt; - var encodedPassword = PasswordSecurity.HashNewPassword(Membership.HashAlgorithmType, password, out salt); - - var member = MemberService.CreateWithIdentity( - username, - email, - PasswordSecurity.FormatPasswordForStorage(Membership.HashAlgorithmType, encodedPassword, salt), - memberTypeAlias, - 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) - { - long totalRecords2; - var byEmail = MemberService.FindByEmail(emailToMatch, pageIndex, pageSize, out totalRecords2, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = Convert.ToInt32(totalRecords2); - - 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) - { - long totalRecords2; - var byEmail = MemberService.FindByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords2, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = Convert.ToInt32(totalRecords2); - - 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(); - - long totalRecords2; - var pagedMembers = MemberService.GetAll(pageIndex, pageSize, out totalRecords2); - totalRecords = Convert.ToInt32(totalRecords2); - - 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() - { - throw new NotImplementedException(); - } - - /// - /// 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 decodedPassword = DecryptPassword(m.RawPasswordValue); - - return decodedPassword; - } - - internal string EncryptString(string str) - { - if (str.IsNullOrWhiteSpace()) - { - return ""; - } - 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) - { - // when upgrading from 7.2 to 7.3 trying to save will throw - if (_umbracoVersion.Version >= new Version(7, 3, 0, 0)) - { - var now = DateTime.Now; - // update the database data directly instead of a full member save which requires DB locks - MemberService.SetLastLogin(username, now); - member.LastLoginDate = now; - member.UpdateDate = now; - } - - } - - 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.GetByProviderKey(providerUserKey); - if (member == null) - { - return null; - } - - if (userIsOnline) - { - member.LastLoginDate = DateTime.Now; - member.UpdateDate = DateTime.Now; - //don't raise events for this! It just sets the member dates, if we do raise events this will - // cause all distributed cache to execute - which will clear out some caches we don't want. - // http://issues.umbraco.org/issue/U4-3451 - MemberService.Save(member, false); - } - - 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."); - } - - string salt; - var encodedPassword = PasswordSecurity.HashNewPassword(Membership.HashAlgorithmType, generatedPassword, out salt); - m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(Membership.HashAlgorithmType, encodedPassword, salt); - m.LastPasswordChangeDate = DateTime.Now; - MemberService.Save(m); - - return generatedPassword; - } - - internal virtual bool PerformUnlockUser(string username, out TEntity member) - { - 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; - } - - /// - /// 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) - { - TEntity member; - var result = PerformUnlockUser(username, out member); - return result; - } - - /// - /// 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)); - } - - if (RequiresUniqueEmail && user.Email.Trim().IsNullOrWhiteSpace() == false) - { - long totalRecs; - var byEmail = MemberService.FindByEmail(user.Email.Trim(), 0, int.MaxValue, out totalRecs, StringPropertyMatchType.Exact); - if (byEmail.Count(x => x.Id != m.Id) > 0) - { - throw new ProviderException(string.Format("A member with the email '{0}' already exists", user.Email)); - } - } - 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); - } - - - - internal virtual ValidateUserResult PerformValidateUser(string username, string password) - { - var member = MemberService.GetByUsername(username); - - if (member == null) - { - Current.Logger.LogInformation("Login attempt failed for username {Username} from IP address {IpAddress}, the user does not exist", username, _ipResolver.GetCurrentRequestIpAddress()); - - return new ValidateUserResult - { - Authenticated = false - }; - } - - if (member.IsApproved == false) - { - Current.Logger.LogInformation("Login attempt failed for username {Username} from IP address {IpAddress}, the user is not approved", username, _ipResolver.GetCurrentRequestIpAddress()); - - return new ValidateUserResult - { - Member = member, - Authenticated = false - }; - } - if (member.IsLockedOut) - { - Current.Logger.LogInformation("Login attempt failed for username {Username} from IP address {IpAddress}, the user is locked", username, _ipResolver.GetCurrentRequestIpAddress()); - - return new ValidateUserResult - { - Member = member, - Authenticated = false - }; - } - - var authenticated = PasswordSecurity.VerifyPassword(Membership.HashAlgorithmType, password, member.RawPasswordValue); - - var requiresFullSave = false; - - 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; - - Current.Logger.LogInformation("Login attempt failed for username {Username} from IP address {IpAddress}, the user is now locked out, max invalid password attempts exceeded", username, _ipResolver.GetCurrentRequestIpAddress()); - } - else - { - Current.Logger.LogInformation("Login attempt failed for username {Username} from IP address {IpAddress}", username, _ipResolver.GetCurrentRequestIpAddress()); - } - - requiresFullSave = true; - } - else - { - if (member.FailedPasswordAttempts > 0) - { - //we have successfully logged in, reset the AccessFailedCount - member.FailedPasswordAttempts = 0; - requiresFullSave = true; - } - - member.LastLoginDate = DateTime.Now; - - Current.Logger.LogInformation("Login attempt succeeded for username {Username} from IP address {IpAddress}", username, _ipResolver.GetCurrentRequestIpAddress()); - } - - // don't raise events for this! It just sets the member dates, if we do raise events this will - // cause all distributed cache to execute - which will clear out some caches we don't want. - // http://issues.umbraco.org/issue/U4-3451 - // TODO: In v8 we aren't going to have an overload to disable events, so we'll need to make a different method - // for this type of thing (i.e. UpdateLastLogin or similar). - - if (requiresFullSave) - { - // when upgrading from 7.2 to 7.3 trying to save will throw - if (_umbracoVersion.Version >= new Version(7, 3, 0, 0)) - { - // We need to raise event to ensure caches are updated. (e.g. the cache that uses username as key). - // Even that this is a heavy operation, because indexes are updates, we consider that okay, as it - // is still cheap to do a successful login. - MemberService.Save(member, true); - } - } - else - { - // set the last login date without full save (fast, no locks). - // We do not update caches. This is to the best of our knowledge okay, as this info are only stored - // because it is required by the membership provider. - // If we one day have to revisit this, we will most likely need to spilt the events in membership info - // saved and umbraco info saved. We don't want to update indexes etc when it is just membership info that is saved - MemberService.SetLastLogin(member.Username, member.LastLoginDate); - } - - return new ValidateUserResult - { - Authenticated = authenticated, - Member = 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) - { - var result = PerformValidateUser(username, password); - return result.Authenticated; - } - - internal class ValidateUserResult - { - public TEntity Member { get; set; } - public bool Authenticated { get; set; } - } - } -} diff --git a/src/Umbraco.Web/Security/UmbracoMembershipProviderBase.cs b/src/Umbraco.Web/Security/UmbracoMembershipProviderBase.cs deleted file mode 100644 index ef6d7eb0e4..0000000000 --- a/src/Umbraco.Web/Security/UmbracoMembershipProviderBase.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Text; -using System.Web.Security; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Security; - -namespace Umbraco.Web.Security -{ - /// - /// A base membership provider class for umbraco providers - /// - public abstract class UmbracoMembershipProviderBase : MembershipProviderBase - { - protected UmbracoMembershipProviderBase(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) - { - } - - public abstract LegacyPasswordSecurity PasswordSecurity { get; } - public abstract string DefaultMemberTypeAlias { get; } - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Adds a new membership user to the data source. - /// - /// The member type alias to use when creating the member - /// 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 MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - //do the base validation first - var valStatus = ValidateNewUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey); - if (valStatus != MembershipCreateStatus.Success) - { - status = valStatus; - return null; - } - - return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Adds a new membership user to the data source. - /// - /// The member type alias to use when creating the member - /// 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 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(); - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7e2e333c3d..31b04c18f2 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -139,16 +139,12 @@ - - - - @@ -172,10 +168,7 @@ - - - @@ -250,4 +243,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 849d378b4c..194d9ab91d 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -27,7 +27,6 @@ namespace Umbraco.Web { private readonly IPublishedContentQuery _publishedContentQuery; private readonly IUmbracoComponentRenderer _componentRenderer; - private readonly MembershipHelper _membershipHelper; private readonly ICultureDictionaryFactory _cultureDictionaryFactory; private IPublishedContent _currentPage; @@ -47,12 +46,10 @@ namespace Umbraco.Web public UmbracoHelper(IPublishedContent currentPage, ICultureDictionaryFactory cultureDictionary, IUmbracoComponentRenderer componentRenderer, - IPublishedContentQuery publishedContentQuery, - MembershipHelper membershipHelper) + IPublishedContentQuery publishedContentQuery) { _cultureDictionaryFactory = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary)); _componentRenderer = componentRenderer ?? throw new ArgumentNullException(nameof(componentRenderer)); - _membershipHelper = membershipHelper ?? throw new ArgumentNullException(nameof(membershipHelper)); _publishedContentQuery = publishedContentQuery ?? throw new ArgumentNullException(nameof(publishedContentQuery)); _currentPage = currentPage; } @@ -205,71 +202,6 @@ namespace Umbraco.Web #endregion - #region Members - - public IPublishedContent Member(Udi id) - { - var guidUdi = id as GuidUdi; - return guidUdi == null ? null : Member(guidUdi.Guid); - } - - public IPublishedContent Member(Guid id) - { - return _membershipHelper.GetById(id); - } - - public IPublishedContent Member(int id) - { - return _membershipHelper.GetById(id); - } - - public IPublishedContent Member(string id) - { - var asInt = id.TryConvertTo(); - return asInt ? _membershipHelper.GetById(asInt.Result) : _membershipHelper.GetByProviderKey(id); - } - - public IEnumerable Members(IEnumerable ids) - { - return _membershipHelper.GetByIds(ids); - } - - public IEnumerable Members(IEnumerable ids) - { - return ids.Select(Member).WhereNotNull(); - } - - public IEnumerable Members(IEnumerable ids) - { - return _membershipHelper.GetByIds(ids); - } - - public IEnumerable Members(IEnumerable ids) - { - return ids.Select(Member).WhereNotNull(); - } - public IEnumerable Members(params int[] ids) - { - return ids.Select(Member).WhereNotNull(); - } - - public IEnumerable Members(params string[] ids) - { - return ids.Select(Member).WhereNotNull(); - } - - public IEnumerable Members(params Guid[] ids) - { - return _membershipHelper.GetByIds(ids); - } - - public IEnumerable Members(params Udi[] ids) - { - return ids.Select(Member).WhereNotNull(); - } - - #endregion - #region Content ///