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 <mail@bergmania.dk>
This commit is contained in:
Shannon Deminick
2021-04-22 21:21:43 +10:00
committed by GitHub
parent c991e83088
commit 3792cafb9f
40 changed files with 179 additions and 2350 deletions

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Core
namespace Umbraco.Cms.Core
{
public static partial class Constants
{
@@ -130,10 +130,6 @@
/// </summary>
public static readonly string InternalRolePrefix = "__umbracoRole";
public static readonly string UmbracoMemberProviderName = "UmbracoMembershipProvider";
public static readonly string UmbracoRoleProviderName = "UmbracoRoleProvider";
/// <summary>
/// Property alias for the Comments on a Member
/// </summary>
@@ -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.
/// </remarks>
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
}

View File

@@ -4,8 +4,6 @@ namespace Umbraco.Cms.Core
{
public static class Security
{
public const string UserMembershipProviderName = "UsersMembershipProvider";
/// <summary>
/// Gets the identifier of the 'super' user.
/// </summary>

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
namespace Umbraco.Cms.Core.Models.PublishedContent
{
/// <inheritdoc />
/// <summary>
/// Represents a published content item.

View File

@@ -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);
/// <summary>
/// Get an <see cref="IPublishedContent"/> from an <see cref="IMember"/>
/// </summary>
/// <param name="member"></param>
/// <returns></returns>
IPublishedContent Get(IMember member);
/// <summary>
/// Gets a content type identified by its unique identifier.

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Extensions

View File

@@ -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;
}
}
}

View File

@@ -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<string> PropertiesToExclude = new List<string>
{
@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
/// </summary>
public interface IMemberManager : IUmbracoUserManager<MemberIdentityUser>
{
/// <summary>
/// Returns the <see cref="IPublishedContent"/> instance for the specified <see cref="MemberIdentityUser"/>
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
IPublishedContent AsPublishedMember(MemberIdentityUser user);
/// <summary>
/// Returns the currently logged in member if there is one, else returns null
/// </summary>

View File

@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Identity;
using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Cms.Core.Security
{
/// <summary>
/// A custom user store that uses Umbraco member data
/// </summary>
public interface IMemberUserStore : IUserStore<MemberIdentityUser>
{
IPublishedContent GetPublishedMember(MemberIdentityUser user);
}
}

View File

@@ -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

View File

@@ -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
/// </summary>
public class MemberIdentityUser : UmbracoIdentityUser
{
private string _comments;
private IReadOnlyCollection<IReadOnlyUserGroup> _groups;
private string _comments;
// Custom comparer for enumerables
private static readonly DelegateEqualityComparer<IReadOnlyCollection<IReadOnlyUserGroup>> s_groupsComparer = new DelegateEqualityComparer<IReadOnlyCollection<IReadOnlyUserGroup>>(
@@ -73,33 +71,6 @@ namespace Umbraco.Cms.Core.Security
// No change tracking because the persisted value is readonly
public Guid Key { get; set; }
/// <summary>
/// Gets or sets the user groups
/// </summary>
public IReadOnlyCollection<IReadOnlyUserGroup> Groups
{
get => _groups;
set
{
_groups = value.Where(x => x.Alias != null).ToArray();
var roles = new List<IdentityUserRole<string>>();
foreach (IdentityUserRole<string> identityUserRole in _groups.Select(x => new IdentityUserRole<string>
{
RoleId = x.Alias,
UserId = Id
}))
{
roles.Add(identityUserRole);
}
// now reset the collection
Roles = roles;
BeingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), s_groupsComparer);
}
}
/// <summary>
/// Gets or sets the alias of the member type
/// </summary>

View File

@@ -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
{
/// <summary>
/// A custom user store that uses Umbraco member data
/// </summary>
public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdentityRole>
public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdentityRole>, IMemberUserStore
{
private const string genericIdentityErrorCode = "IdentityErrorUserStore";
private readonly IMemberService _memberService;
private readonly IUmbracoMapper _mapper;
private readonly IScopeProvider _scopeProvider;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="MemberUserStore"/> 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;
}
/// <inheritdoc />
@@ -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);
}
}
}

View File

@@ -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 + "]";
}
}
}

View File

@@ -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<T>(string cacheKey, Func<T> getCacheItem)
where T : class
{
var cache = _snapshotCache;
return cache == null
? getCacheItem()
: cache.GetCacheItem<T>(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<IPublishedContent> 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
}

View File

@@ -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<string, PropertyData[]> 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
}

View File

@@ -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

View File

@@ -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<IMapDefinition>()), scopeProvider),
scopeProvider,
new IdentityErrorDescriber());
new IdentityErrorDescriber(),
Mock.Of<IPublishedSnapshotAccessor>());
_mockIdentityOptions = new Mock<IOptions<IdentityOptions>>();
var idOptions = new IdentityOptions { Lockout = { AllowedForNewUsers = false } };

View File

@@ -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<IMapDefinition>()), mockScopeProvider.Object),
mockScopeProvider.Object,
new IdentityErrorDescriber());
new IdentityErrorDescriber(),
Mock.Of<IPublishedSnapshotAccessor>());
}
[Test]

View File

@@ -60,7 +60,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Security
private static Mock<MemberManager> MockMemberManager()
=> new Mock<MemberManager>(
Mock.Of<IIpResolver>(),
Mock.Of<IUserStore<MemberIdentityUser>>(),
Mock.Of<IMemberUserStore>(),
Options.Create(new IdentityOptions()),
Mock.Of<IPasswordHasher<MemberIdentityUser>>(),
Enumerable.Empty<IUserValidator<MemberIdentityUser>>(),

View File

@@ -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<IMediaService>(), Mock.Of<IUserService>(), appCache, ContentTypesCache, Factory.GetRequiredService<IEntityXmlSerializer>(), umbracoContextAccessor, VariationContextAccessor),
new PublishedMemberCache(null, appCache, Mock.Of<IMemberService>(), ContentTypesCache, Mock.Of<IUserService>(), VariationContextAccessor),
new PublishedMemberCache(ContentTypesCache, VariationContextAccessor),
domainCache);
var publishedSnapshotService = new Mock<IPublishedSnapshotService>();
publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(publishedShapshot);

View File

@@ -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
{
/// <summary>
/// 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
}
}

View File

@@ -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<IPublishedContent>(
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<IPublishedContent>(
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<IPublishedContent>(
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<IPublishedContent>(
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
}

View File

@@ -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);
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;

View File

@@ -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<IMemberService>();
var memberTypeService = Mock.Of<IMemberTypeService>();
var membershipProvider = new MembersMembershipProvider(memberService, memberTypeService, Mock.Of<IUmbracoVersion>(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver());
var membershipHelper = new MembershipHelper(Mock.Of<IHttpContextAccessor>(), Mock.Of<IPublishedMemberCache>(), membershipProvider, Mock.Of<RoleProvider>(), memberService, memberTypeService, Mock.Of<IPublicAccessService>(), AppCaches.Disabled, loggerFactory, ShortStringHelper, Mock.Of<IEntityService>());
services.AddUnique(membershipHelper);
TestObjects = new TestObjects();
Compose();

View File

@@ -142,6 +142,7 @@
<Compile Include="LegacyXmlPublishedCache\LegacyBackgroundTask\TaskEventArgs.cs" />
<Compile Include="LegacyXmlPublishedCache\LegacyBackgroundTask\ThreadingTaskImmutable.cs" />
<Compile Include="LegacyXmlPublishedCache\PreviewXmlDto.cs" />
<Compile Include="LegacyXmlPublishedCache\PublishedMember.cs" />
<Compile Include="Models\ContentXmlTest.cs" />
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
<Compile Include="Published\ModelTypeTests.cs" />

View File

@@ -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<MemberIdentityUser, UmbracoIdentityRole>()
.AddDefaultTokenProviders()
@@ -47,6 +46,7 @@ namespace Umbraco.Extensions
services.ConfigureOptions<ConfigureMemberIdentityOptions>();
services.AddScoped<IMemberUserStore>(x => (IMemberUserStore)x.GetRequiredService<IUserStore<MemberIdentityUser>>());
services.AddScoped<IPasswordHasher<MemberIdentityUser>, MemberPasswordHasher>();
services.ConfigureOptions<ConfigureSecurityStampOptions>();

View File

@@ -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<MemberIdentityUser, MemberPasswordConfigurationSettings>, IMemberManager
{
private readonly IMemberUserStore _store;
private readonly IPublicAccessService _publicAccessService;
private readonly IHttpContextAccessor _httpContextAccessor;
private MemberIdentityUser _currentMember;
public MemberManager(
IIpResolver ipResolver,
IUserStore<MemberIdentityUser> store,
IMemberUserStore store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<MemberIdentityUser> passwordHasher,
IEnumerable<IUserValidator<MemberIdentityUser>> 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);
}
}

View File

@@ -184,7 +184,6 @@ namespace Umbraco.Web.Composing
public static IUmbracoVersion UmbracoVersion => Factory.GetRequiredService<IUmbracoVersion>();
public static IPublishedUrlProvider PublishedUrlProvider => Factory.GetRequiredService<IPublishedUrlProvider>();
public static IMenuItemCollectionFactory MenuItemCollectionFactory => Factory.GetRequiredService<IMenuItemCollectionFactory>();
public static MembershipHelper MembershipHelper => Factory.GetRequiredService<MembershipHelper>();
public static IUmbracoApplicationLifetime UmbracoApplicationLifetime => Factory.GetRequiredService<IUmbracoApplicationLifetime>();
public static IPublishedContentQuery PublishedContentQuery => Factory.GetRequiredService<IPublishedContentQuery>();

View File

@@ -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; }
}
}
}

View File

@@ -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<MembershipHelper>();
_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<IPublishedContent> GetByProviderKeys(IEnumerable<object> keys)
{
return keys?.Select(GetByProviderKey).WhereNotNull() ?? Enumerable.Empty<IPublishedContent>();
}
public virtual IPublishedContent GetById(int memberId)
{
return MemberCache.GetById(memberId);
}
public virtual IEnumerable<IPublishedContent> GetByIds(IEnumerable<int> memberIds)
{
return memberIds?.Select(GetById).WhereNotNull() ?? Enumerable.Empty<IPublishedContent>();
}
public virtual IPublishedContent GetById(Guid memberId)
{
return GetByProviderKey(memberId);
}
public virtual IEnumerable<IPublishedContent> GetByIds(IEnumerable<Guid> memberIds)
{
return GetByProviderKeys(memberIds.OfType<object>());
}
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
}
}

View File

@@ -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
/// <summary>
/// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing.
/// </summary>
[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;
}
/// <summary>
/// Providers can override this setting, default is 10
/// </summary>
public virtual int DefaultMinPasswordLength
{
get { return 10; }
}
/// <summary>
/// Providers can override this setting, default is 0
/// </summary>
public virtual int DefaultMinNonAlphanumericChars
{
get { return 0; }
}
/// <summary>
/// Providers can override this setting, default is false to use better security
/// </summary>
public virtual bool DefaultUseLegacyEncoding
{
get { return false; }
}
/// <summary>
/// Returns the raw password value for a given user
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
/// <remarks>
/// By default this will return an invalid attempt, inheritors will need to override this to support it
/// </remarks>
protected virtual Attempt<string> GetRawPassword(string username)
{
return Attempt<string>.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; }
/// <summary>
/// Indicates whether the membership provider is configured to allow users to reset their passwords.
/// </summary>
/// <value></value>
/// <returns>true if the membership provider supports password reset; otherwise, false. The default is true.</returns>
public override bool EnablePasswordReset
{
get { return _enablePasswordReset; }
}
/// <summary>
/// Indicates whether the membership provider is configured to allow users to retrieve their passwords.
/// </summary>
/// <value></value>
/// <returns>true if the membership provider is configured to support password retrieval; otherwise, false. The default is false.</returns>
public override bool EnablePasswordRetrieval
{
get { return _enablePasswordRetrieval; }
}
/// <summary>
/// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out.
/// </summary>
/// <value></value>
/// <returns>The number of invalid password or password-answer attempts allowed before the membership user is locked out.</returns>
public override int MaxInvalidPasswordAttempts
{
get { return _maxInvalidPasswordAttempts; }
}
/// <summary>
/// Gets the minimum number of special characters that must be present in a valid password.
/// </summary>
/// <value></value>
/// <returns>The minimum number of special characters that must be present in a valid password.</returns>
public override int MinRequiredNonAlphanumericCharacters
{
get { return _minRequiredNonAlphanumericCharacters; }
}
/// <summary>
/// Gets the minimum length required for a password.
/// </summary>
/// <value></value>
/// <returns>The minimum length required for a password. </returns>
public override int MinRequiredPasswordLength
{
get { return _minRequiredPasswordLength; }
}
/// <summary>
/// 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.
/// </summary>
/// <value></value>
/// <returns>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.</returns>
public override int PasswordAttemptWindow
{
get { return _passwordAttemptWindow; }
}
/// <summary>
/// Gets a value indicating the format for storing passwords in the membership data store.
/// </summary>
/// <value></value>
/// <returns>One of the <see cref="T:System.Web.Security.MembershipPasswordFormat"></see> values indicating the format for storing passwords in the data store.</returns>
public override MembershipPasswordFormat PasswordFormat
{
get { return _passwordFormat; }
}
/// <summary>
/// Gets the regular expression used to evaluate a password.
/// </summary>
/// <value></value>
/// <returns>A regular expression used to evaluate a password.</returns>
public override string PasswordStrengthRegularExpression
{
get { return _passwordStrengthRegularExpression; }
}
/// <summary>
/// Always returns false, question/answer is not supported
/// </summary>
public override bool RequiresQuestionAndAnswer => false;
/// <summary>
/// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name.
/// </summary>
/// <value></value>
/// <returns>true if the membership provider requires a unique e-mail address; otherwise, false. The default is true.</returns>
public override bool RequiresUniqueEmail
{
get { return _requiresUniqueEmail; }
}
/// <summary>
/// The name of the application using the custom membership provider.
/// </summary>
/// <value></value>
/// <returns>The name of the application using the custom membership provider.</returns>
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
/// <summary>
/// Initializes the provider.
/// </summary>
/// <param name="name">The friendly name of the provider.</param>
/// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
/// <exception cref="T:System.ArgumentNullException">The name of the provider is null.</exception>
/// <exception cref="T:System.InvalidOperationException">An attempt is made to call
/// <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"></see> on a provider after the provider
/// has already been initialized.</exception>
/// <exception cref="T:System.ArgumentException">The name of the provider has a length of zero.</exception>
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);
}
/// <summary>
/// Override this method to ensure the password is valid before raising the event
/// </summary>
/// <param name="e"></param>
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
}
/// <summary>
/// Processes a request to update the password for a membership user.
/// </summary>
/// <param name="username">The user to update the password for.</param>
/// <param name="oldPassword">Required to change a user password if the user is not new and AllowManuallyChangingPassword is false</param>
/// <param name="newPassword">The new password for the specified user.</param>
/// <returns>
/// true if the password was updated successfully; otherwise, false.
/// </returns>
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);
}
/// <summary>
/// Processes a request to update the password for a membership user.
/// </summary>
/// <param name="username">The user to update the password for.</param>
/// <param name="oldPassword">This property is ignore for this provider</param>
/// <param name="newPassword">The new password for the specified user.</param>
/// <returns>
/// true if the password was updated successfully; otherwise, false.
/// </returns>
protected abstract bool PerformChangePassword(string username, string oldPassword, string newPassword);
/// <summary>
/// Processes a request to update the password question and answer for a membership user.
/// </summary>
/// <param name="username">The user to change the password question and answer for.</param>
/// <param name="password">The password for the specified user.</param>
/// <param name="newPasswordQuestion">The new password question for the specified user.</param>
/// <param name="newPasswordAnswer">The new password answer for the specified user.</param>
/// <returns>
/// true if the password question and answer are updated successfully; otherwise, false.
/// </returns>
/// <remarks>
/// Performs the basic validation before passing off to PerformChangePasswordQuestionAndAnswer
/// </remarks>
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);
}
/// <summary>
/// Processes a request to update the password question and answer for a membership user.
/// </summary>
/// <param name="username">The user to change the password question and answer for.</param>
/// <param name="password">The password for the specified user.</param>
/// <param name="newPasswordQuestion">The new password question for the specified user.</param>
/// <param name="newPasswordAnswer">The new password answer for the specified user.</param>
/// <returns>
/// true if the password question and answer are updated successfully; otherwise, false.
/// </returns>
protected abstract bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer);
/// <summary>
/// Adds a new membership user to the data source.
/// </summary>
/// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
/// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"></see> enumeration value indicating whether the user was created successfully.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
/// <remarks>
/// Ensures the ValidatingPassword event is executed before executing PerformCreateUser and performs basic membership provider validation of values.
/// </remarks>
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);
}
/// <summary>
/// Performs the validation of the information for creating a new user
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="email"></param>
/// <param name="passwordQuestion"></param>
/// <param name="passwordAnswer"></param>
/// <param name="isApproved"></param>
/// <param name="providerUserKey"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Adds a new membership user to the data source.
/// </summary>
/// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
/// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"></see> enumeration value indicating whether the user was created successfully.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
protected abstract MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status);
/// <summary>
/// Gets the members password if password retrieval is enabled
/// </summary>
/// <param name="username"></param>
/// <param name="answer"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Gets the members password if password retrieval is enabled
/// </summary>
/// <param name="username"></param>
/// <param name="answer"></param>
/// <returns></returns>
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<PasswordValidityError> 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);
}
/// <summary>
/// Gets the name of the default app.
/// </summary>
/// <returns></returns>
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);
}
}
/// <summary>
/// Unencode password.
/// </summary>
/// <param name="encodedPassword">The encoded password.</param>
/// <returns>The unencoded password.</returns>
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();
}
}
}

View File

@@ -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;
}
/// <summary>
/// Method to get the Umbraco Members membership provider based on its alias
/// </summary>
/// <returns></returns>
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];
}
/// <summary>
/// Returns the currently logged in MembershipUser and flags them as being online - use sparingly (i.e. login)
/// </summary>
/// <param name="membershipProvider"></param>
/// <returns></returns>
public static MembershipUser GetCurrentUserOnline(this MembershipProvider membershipProvider)
{
var username = membershipProvider.GetCurrentUserName();
return username.IsNullOrWhiteSpace()
? null
: membershipProvider.GetUser(username, true);
}
/// <summary>
/// Returns the currently logged in MembershipUser
/// </summary>
/// <param name="membershipProvider"></param>
/// <returns></returns>
internal static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider)
{
var username = membershipProvider.GetCurrentUserName();
return username.IsNullOrWhiteSpace()
? null
: membershipProvider.GetUser(username, false);
}
/// <summary>
/// Just returns the current user's login name (just a wrapper).
/// </summary>
/// <param name="membershipProvider"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Returns true if the provider specified is a built-in Umbraco membership provider
/// </summary>
/// <param name="membershipProvider"></param>
/// <returns></returns>
public static bool IsUmbracoMembershipProvider(this MembershipProvider membershipProvider)
{
return (membershipProvider is UmbracoMembershipProviderBase);
}
}
}

View File

@@ -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")]
/// <summary>
/// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS)
/// </summary>
public class MembersMembershipProvider : UmbracoMembershipProvider<IMembershipMemberService, IMember>
{
public MembersMembershipProvider()
: this(Current.Services.MemberService, Current.Services.MemberTypeService, Current.UmbracoVersion, Current.HostingEnvironment, Current.IpResolver)
{ }
public MembersMembershipProvider(IMembershipMemberService<IMember> 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<IPasswordConfiguration>(() => new MembershipProviderPasswordConfiguration(
MinRequiredPasswordLength,
MinRequiredNonAlphanumericCharacters > 0,
false, false, false, UseLegacyEncoding,
CustomHashAlgorithmType.IsNullOrWhiteSpace() ? Membership.HashAlgorithmType : CustomHashAlgorithmType,
MaxInvalidPasswordAttempts));
_passwordSecurity = new Lazy<LegacyPasswordSecurity>(() => new LegacyPasswordSecurity());
}
protected override Attempt<string> GetRawPassword(string username)
{
var found = MemberService.GetByUsername(username);
if (found == null) return Attempt<string>.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<LegacyPasswordSecurity> _passwordSecurity;
private Lazy<IPasswordConfiguration> _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; }
}
}
}

View File

@@ -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")]
/// <summary>
/// Abstract Membership Provider that users any implementation of IMembershipMemberService{TEntity} service
/// </summary>
public abstract class UmbracoMembershipProvider<T, TEntity> : UmbracoMembershipProviderBase
where T : IMembershipMemberService<TEntity>
where TEntity : class, IMembershipUser
{
private readonly IUmbracoVersion _umbracoVersion;
private readonly IIpResolver _ipResolver;
protected IMembershipMemberService<TEntity> MemberService { get; private set; }
protected UmbracoMembershipProvider(IMembershipMemberService<TEntity> 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);
/// <summary>
/// Initializes the provider.
/// </summary>
/// <param name="name">The friendly name of the provider.</param>
/// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
/// <exception cref="T:System.ArgumentNullException">The name of the provider is null.</exception>
/// <exception cref="T:System.InvalidOperationException">An attempt is made to call
/// <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"></see> on a provider after the provider
/// has already been initialized.</exception>
/// <exception cref="T:System.ArgumentException">The name of the provider has a length of zero.</exception>
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);
}
/// <summary>
/// Processes a request to update the password for a membership user.
/// </summary>
/// <param name="username">The user to update the password for.</param>
/// <param name="oldPassword">This property is ignore for this provider</param>
/// <param name="newPassword">The new password for the specified user.</param>
/// <returns>
/// true if the password was updated successfully; otherwise, false.
/// </returns>
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;
}
/// <summary>
/// Processes a request to update the password question and answer for a membership user.
/// </summary>
/// <param name="username">The user to change the password question and answer for.</param>
/// <param name="password">The password for the specified user.</param>
/// <param name="newPasswordQuestion">The new password question for the specified user.</param>
/// <param name="newPasswordAnswer">The new password answer for the specified user.</param>
/// <returns>
/// true if the password question and answer are updated successfully; otherwise, false.
/// </returns>
protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
{
throw new NotSupportedException("Password question/answer is not supported");
}
/// <summary>
/// Adds a new membership user to the data source with the specified member type
/// </summary>
/// <param name="memberTypeAlias">A specific member type to create the member for</param>
/// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
/// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"></see> enumeration value indicating whether the user was created successfully.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
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);
}
/// <summary>
/// Removes a user from the membership data source.
/// </summary>
/// <param name="username">The name of the user to delete.</param>
/// <param name="deleteAllRelatedData">
/// TODO: This setting currently has no effect
/// </param>
/// <returns>
/// true if the user was successfully deleted; otherwise, false.
/// </returns>
public override bool DeleteUser(string username, bool deleteAllRelatedData)
{
var member = MemberService.GetByUsername(username);
if (member == null)
return false;
MemberService.Delete(member);
return true;
}
/// <summary>
/// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match.
/// </summary>
/// <param name="emailToMatch">The e-mail address to search for.</param>
/// <param name="pageIndex">The index of the page of results to return. pageIndex is zero-based.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">The total number of matched users.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUserCollection"></see> collection that contains a page of pageSize<see cref="T:System.Web.Security.MembershipUser"></see> objects beginning at the page specified by pageIndex.
/// </returns>
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;
}
/// <summary>
/// Gets a collection of membership users where the user name contains the specified user name to match.
/// </summary>
/// <param name="usernameToMatch">The user name to search for.</param>
/// <param name="pageIndex">The index of the page of results to return. pageIndex is zero-based.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">The total number of matched users.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUserCollection"></see> collection that contains a page of pageSize<see cref="T:System.Web.Security.MembershipUser"></see> objects beginning at the page specified by pageIndex.
/// </returns>
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;
}
/// <summary>
/// Gets a collection of all the users in the data source in pages of data.
/// </summary>
/// <param name="pageIndex">The index of the page of results to return. pageIndex is zero-based.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">The total number of matched users.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUserCollection"></see> collection that contains a page of pageSize<see cref="T:System.Web.Security.MembershipUser"></see> objects beginning at the page specified by pageIndex.
/// </returns>
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;
}
/// <summary>
/// Gets the number of users currently accessing the application.
/// </summary>
/// <returns>
/// The number of users currently accessing the application.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
public override int GetNumberOfUsersOnline()
{
throw new NotImplementedException();
}
/// <summary>
/// Gets the password for the specified user name from the data source.
/// </summary>
/// <param name="username">The user to retrieve the password for.</param>
/// <param name="answer">The password answer for the user.</param>
/// <returns>
/// The password for the specified user name.
/// </returns>
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);
}
/// <summary>
/// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
/// </summary>
/// <param name="username">The name of the user to get information for.</param>
/// <param name="userIsOnline">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.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the specified user's information from the data source.
/// </returns>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="providerUserKey">The unique identifier for the membership user to get information for.</param>
/// <param name="userIsOnline">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.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the specified user's information from the data source.
/// </returns>
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);
}
/// <summary>
/// Gets the user name associated with the specified e-mail address.
/// </summary>
/// <param name="email">The e-mail address to search for.</param>
/// <returns>
/// The user name associated with the specified e-mail address. If no match is found, return null.
/// </returns>
public override string GetUserNameByEmail(string email)
{
var member = MemberService.GetByEmail(email);
return member == null ? null : member.Username;
}
/// <summary>
/// Resets a user's password to a new, automatically generated password.
/// </summary>
/// <param name="username">The user to reset the password for.</param>
/// <param name="answer">The password answer for the specified user (not used with Umbraco).</param>
/// <param name="generatedPassword"></param>
/// <returns>The new password for the specified user.</returns>
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;
}
/// <summary>
/// Clears a lock so that the membership user can be validated.
/// </summary>
/// <param name="username">The membership user to clear the lock status for.</param>
/// <returns>
/// true if the membership user was successfully unlocked; otherwise, false.
/// </returns>
public override bool UnlockUser(string username)
{
TEntity member;
var result = PerformUnlockUser(username, out member);
return result;
}
/// <summary>
/// Updates e-mail approved status, lock status and comment on a user.
/// </summary>
/// <param name="user">A <see cref="T:System.Web.Security.MembershipUser"></see> object that represents the user to update and the updated information for the user.</param>
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
};
}
/// <summary>
/// Verifies that the specified user name and password exist in the data source.
/// </summary>
/// <param name="username">The name of the user to validate.</param>
/// <param name="password">The password for the specified user.</param>
/// <returns>
/// true if the specified username and password are valid; otherwise, false.
/// </returns>
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; }
}
}
}

View File

@@ -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
{
/// <summary>
/// A base membership provider class for umbraco providers
/// </summary>
public abstract class UmbracoMembershipProviderBase : MembershipProviderBase
{
protected UmbracoMembershipProviderBase(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment)
{
}
public abstract LegacyPasswordSecurity PasswordSecurity { get; }
public abstract string DefaultMemberTypeAlias { get; }
/// <summary>
/// Adds a new membership user to the data source.
/// </summary>
/// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
/// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"></see> enumeration value indicating whether the user was created successfully.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
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);
}
/// <summary>
/// Adds a new membership user to the data source.
/// </summary>
/// <param name="memberTypeAlias">The member type alias to use when creating the member</param>
/// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
/// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"></see> enumeration value indicating whether the user was created successfully.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
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);
}
/// <summary>
/// Adds a new membership user to the data source.
/// </summary>
/// <param name="memberTypeAlias">The member type alias to use when creating the member</param>
/// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
/// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"></see> enumeration value indicating whether the user was created successfully.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
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();
}
}
}

View File

@@ -139,16 +139,12 @@
<Compile Include="HttpContextExtensions.cs" />
<Compile Include="Security\BackOfficeSecurity.cs" />
<Compile Include="HttpContextAccessorExtensions.cs" />
<Compile Include="Models\Membership\UmbracoMembershipMember.cs" />
<Compile Include="AspNet\AspNetBackOfficeInfo.cs" />
<Compile Include="AspNet\AspNetCookieManager.cs" />
<Compile Include="AspNet\AspNetHttpContextAccessor.cs" />
<Compile Include="AspNet\AspNetIpResolver.cs" />
<Compile Include="AspNet\AspNetPasswordHasher.cs" />
<Compile Include="Runtime\AspNetUmbracoBootPermissionChecker.cs" />
<Compile Include="Security\MembershipProviderBase.cs" />
<Compile Include="Security\MembershipProviderExtensions.cs" />
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
<Compile Include="StringExtensions.cs" />
<Compile Include="UmbracoContext.cs" />
<Compile Include="UmbracoContextFactory.cs" />
@@ -172,10 +168,7 @@
<Compile Include="Mvc\UmbracoVirtualNodeRouteHandler.cs" />
<Compile Include="Security\AuthenticationOptionsExtensions.cs" />
<Compile Include="Mvc\ViewDataDictionaryExtensions.cs" />
<Compile Include="Security\MembershipHelper.cs" />
<Compile Include="HttpCookieExtensions.cs" />
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
<Compile Include="Security\Providers\UmbracoMembershipProvider.cs" />
<Compile Include="HttpRequestExtensions.cs" />
<Compile Include="UrlHelperExtensions.cs" />
<Compile Include="UrlHelperRenderExtensions.cs" />
@@ -250,4 +243,4 @@
<!-- <Output TaskParameter="SerializationAssembly" ItemName="SerializationAssembly" />-->
<!-- </SGen>-->
<!-- </Target>-->
</Project>
</Project>

View File

@@ -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<int>();
return asInt ? _membershipHelper.GetById(asInt.Result) : _membershipHelper.GetByProviderKey(id);
}
public IEnumerable<IPublishedContent> Members(IEnumerable<int> ids)
{
return _membershipHelper.GetByIds(ids);
}
public IEnumerable<IPublishedContent> Members(IEnumerable<string> ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(IEnumerable<Guid> ids)
{
return _membershipHelper.GetByIds(ids);
}
public IEnumerable<IPublishedContent> Members(IEnumerable<Udi> ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(params int[] ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(params string[] ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(params Guid[] ids)
{
return _membershipHelper.GetByIds(ids);
}
public IEnumerable<IPublishedContent> Members(params Udi[] ids)
{
return ids.Select(Member).WhereNotNull();
}
#endregion
#region Content
/// <summary>