Implement password config storage for members (#10170)
* 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 * Implements members Password config in db, fixes members cookie auth to not interfere with the back office cookie auth, fixes Startup sequence, fixes startup pipeline * commits change to Startup * Rename migration from `MemberTableColumns2` to `AddPasswordConfigToMemberTable` * Fix test * Fix tests, but adding default passwordConfig to members Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
@@ -2,10 +2,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
@@ -26,32 +29,67 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
public class MemberRepository : ContentRepositoryBase<int, IMember, MemberRepository>, IMemberRepository
|
||||
{
|
||||
private readonly MemberPasswordConfigurationSettings _passwordConfiguration;
|
||||
private readonly IMemberTypeRepository _memberTypeRepository;
|
||||
private readonly ITagRepository _tagRepository;
|
||||
private readonly IPasswordHasher _passwordHasher;
|
||||
private readonly IJsonSerializer _serializer;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IMemberGroupRepository _memberGroupRepository;
|
||||
private readonly IRepositoryCachePolicy<IMember, string> _memberByUsernameCachePolicy;
|
||||
private bool _passwordConfigInitialized;
|
||||
private string _passwordConfigJson;
|
||||
|
||||
public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<MemberRepository> logger,
|
||||
IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
|
||||
public MemberRepository(
|
||||
IScopeAccessor scopeAccessor,
|
||||
AppCaches cache,
|
||||
ILogger<MemberRepository> logger,
|
||||
IMemberTypeRepository memberTypeRepository,
|
||||
IMemberGroupRepository memberGroupRepository,
|
||||
ITagRepository tagRepository,
|
||||
ILanguageRepository languageRepository,
|
||||
IRelationRepository relationRepository,
|
||||
IRelationTypeRepository relationTypeRepository,
|
||||
IPasswordHasher passwordHasher,
|
||||
Lazy<PropertyEditorCollection> propertyEditors,
|
||||
DataValueReferenceFactoryCollection dataValueReferenceFactories,
|
||||
IDataTypeService dataTypeService,
|
||||
IJsonSerializer serializer,
|
||||
IEventAggregator eventAggregator)
|
||||
IEventAggregator eventAggregator,
|
||||
IOptions<MemberPasswordConfigurationSettings> passwordConfiguration)
|
||||
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories, dataTypeService, eventAggregator)
|
||||
{
|
||||
_memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository));
|
||||
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
|
||||
_passwordHasher = passwordHasher;
|
||||
_serializer = serializer;
|
||||
_jsonSerializer = serializer;
|
||||
_memberGroupRepository = memberGroupRepository;
|
||||
|
||||
_passwordConfiguration = passwordConfiguration.Value;
|
||||
_memberByUsernameCachePolicy = new DefaultRepositoryCachePolicy<IMember, string>(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a serialized dictionary of the password configuration that is stored against the member in the database
|
||||
/// </summary>
|
||||
private string DefaultPasswordConfigJson
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_passwordConfigInitialized)
|
||||
{
|
||||
return _passwordConfigJson;
|
||||
}
|
||||
|
||||
var passwordConfig = new PersistedPasswordSettings
|
||||
{
|
||||
HashAlgorithm = _passwordConfiguration.HashAlgorithmType
|
||||
};
|
||||
|
||||
_passwordConfigJson = passwordConfig == null ? null : _jsonSerializer.Serialize(passwordConfig);
|
||||
_passwordConfigInitialized = true;
|
||||
return _passwordConfigJson;
|
||||
}
|
||||
}
|
||||
|
||||
protected override MemberRepository This => this;
|
||||
|
||||
public override int RecycleBinId => throw new NotSupportedException();
|
||||
@@ -262,17 +300,20 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
// create the dto
|
||||
var dto = ContentBaseFactory.BuildDto(entity);
|
||||
MemberDto memberDto = ContentBaseFactory.BuildDto(entity);
|
||||
|
||||
// check if we have a user config else use the default
|
||||
memberDto.PasswordConfig = entity.PasswordConfiguration ?? DefaultPasswordConfigJson;
|
||||
|
||||
// derive path and level from parent
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
NodeDto parent = GetParentNodeDto(entity.ParentId);
|
||||
var level = parent.Level + 1;
|
||||
|
||||
// get sort order
|
||||
var sortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
|
||||
// persist the node dto
|
||||
var nodeDto = dto.ContentDto.NodeDto;
|
||||
NodeDto nodeDto = memberDto.ContentDto.NodeDto;
|
||||
nodeDto.Path = parent.Path;
|
||||
nodeDto.Level = Convert.ToInt16(level);
|
||||
nodeDto.SortOrder = sortOrder;
|
||||
@@ -304,36 +345,36 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
entity.Level = level;
|
||||
|
||||
// persist the content dto
|
||||
var contentDto = dto.ContentDto;
|
||||
var contentDto = memberDto.ContentDto;
|
||||
contentDto.NodeId = nodeDto.NodeId;
|
||||
Database.Insert(contentDto);
|
||||
|
||||
// persist the content version dto
|
||||
// assumes a new version id and version date (modified date) has been set
|
||||
var contentVersionDto = dto.ContentVersionDto;
|
||||
var contentVersionDto = memberDto.ContentVersionDto;
|
||||
contentVersionDto.NodeId = nodeDto.NodeId;
|
||||
contentVersionDto.Current = true;
|
||||
Database.Insert(contentVersionDto);
|
||||
entity.VersionId = contentVersionDto.Id;
|
||||
|
||||
// persist the member dto
|
||||
dto.NodeId = nodeDto.NodeId;
|
||||
memberDto.NodeId = nodeDto.NodeId;
|
||||
|
||||
// if the password is empty, generate one with the special prefix
|
||||
// this will hash the guid with a salt so should be nicely random
|
||||
if (entity.RawPasswordValue.IsNullOrWhiteSpace())
|
||||
{
|
||||
|
||||
dto.Password = Cms.Core.Constants.Security.EmptyPasswordPrefix + _passwordHasher.HashPassword(Guid.NewGuid().ToString("N"));
|
||||
entity.RawPasswordValue = dto.Password;
|
||||
memberDto.Password = Cms.Core.Constants.Security.EmptyPasswordPrefix + _passwordHasher.HashPassword(Guid.NewGuid().ToString("N"));
|
||||
entity.RawPasswordValue = memberDto.Password;
|
||||
}
|
||||
|
||||
Database.Insert(dto);
|
||||
Database.Insert(memberDto);
|
||||
|
||||
// persist the property data
|
||||
InsertPropertyValues(entity, 0, out _, out _);
|
||||
|
||||
SetEntityTags(entity, _tagRepository, _serializer);
|
||||
SetEntityTags(entity, _tagRepository, _jsonSerializer);
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
@@ -368,17 +409,17 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
// create the dto
|
||||
MemberDto dto = ContentBaseFactory.BuildDto(entity);
|
||||
MemberDto memberDto = ContentBaseFactory.BuildDto(entity);
|
||||
|
||||
// update the node dto
|
||||
NodeDto nodeDto = dto.ContentDto.NodeDto;
|
||||
NodeDto nodeDto = memberDto.ContentDto.NodeDto;
|
||||
Database.Update(nodeDto);
|
||||
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
Database.Update(memberDto.ContentDto);
|
||||
|
||||
// update the content version dto
|
||||
Database.Update(dto.ContentVersionDto);
|
||||
Database.Update(memberDto.ContentVersionDto);
|
||||
|
||||
// update the member dto
|
||||
// but only the changed columns, 'cos we cannot update password if empty
|
||||
@@ -399,6 +440,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
changedCols.Add("LoginName");
|
||||
}
|
||||
|
||||
// this can occur from an upgrade
|
||||
if (memberDto.PasswordConfig.IsNullOrWhiteSpace())
|
||||
{
|
||||
memberDto.PasswordConfig = DefaultPasswordConfigJson;
|
||||
changedCols.Add("passwordConfig");
|
||||
}
|
||||
|
||||
// do NOT update the password if it has not changed or if it is null or empty
|
||||
if (entity.IsPropertyDirty("RawPasswordValue") && !string.IsNullOrWhiteSpace(entity.RawPasswordValue))
|
||||
{
|
||||
@@ -407,33 +455,37 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
// If the security stamp hasn't already updated we need to force it
|
||||
if (entity.IsPropertyDirty("SecurityStamp") == false)
|
||||
{
|
||||
dto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
|
||||
memberDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
|
||||
changedCols.Add("securityStampToken");
|
||||
}
|
||||
|
||||
// check if we have a user config else use the default
|
||||
memberDto.PasswordConfig = entity.PasswordConfiguration ?? DefaultPasswordConfigJson;
|
||||
changedCols.Add("passwordConfig");
|
||||
}
|
||||
|
||||
// If userlogin or the email has changed then need to reset security stamp
|
||||
if (changedCols.Contains("Email") || changedCols.Contains("LoginName"))
|
||||
{
|
||||
dto.EmailConfirmedDate = null;
|
||||
memberDto.EmailConfirmedDate = null;
|
||||
changedCols.Add("emailConfirmedDate");
|
||||
|
||||
// If the security stamp hasn't already updated we need to force it
|
||||
if (entity.IsPropertyDirty("SecurityStamp") == false)
|
||||
{
|
||||
dto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
|
||||
memberDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
|
||||
changedCols.Add("securityStampToken");
|
||||
}
|
||||
}
|
||||
|
||||
if (changedCols.Count > 0)
|
||||
{
|
||||
Database.Update(dto, changedCols);
|
||||
Database.Update(memberDto, changedCols);
|
||||
}
|
||||
|
||||
ReplacePropertyValues(entity, entity.VersionId, 0, out _, out _);
|
||||
|
||||
SetEntityTags(entity, _tagRepository, _serializer);
|
||||
SetEntityTags(entity, _tagRepository, _jsonSerializer);
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user