Added more layers to have a vertical slice through Umbraco for CreateMember via backoffice.
Still lots to implement and test, not complete and needs early review.
This commit is contained in:
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Members;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -37,6 +38,20 @@ namespace Umbraco.Core.BackOffice
|
||||
target.ResetDirtyProperties(true);
|
||||
target.EnableChangeTracking();
|
||||
});
|
||||
|
||||
mapper.Define<IMember, UmbracoMembersIdentityUser>(
|
||||
(source, context) =>
|
||||
{
|
||||
var target = new UmbracoMembersIdentityUser();
|
||||
//target.DisableChangeTracking();
|
||||
return target;
|
||||
},
|
||||
(source, target, context) =>
|
||||
{
|
||||
Map(source, target);
|
||||
//target.ResetDirtyProperties(true);
|
||||
//target.EnableChangeTracking();
|
||||
});
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Id -Groups -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -TwoFactorEnabled
|
||||
@@ -76,6 +91,22 @@ namespace Umbraco.Core.BackOffice
|
||||
//target.Roles =;
|
||||
}
|
||||
|
||||
private void Map(IMember source, UmbracoMembersIdentityUser target)
|
||||
{
|
||||
target.Email = source.Email;
|
||||
target.UserName = source.Username;
|
||||
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime();
|
||||
//target.LastLoginDateUtc = source.LastLoginDate.ToUniversalTime();
|
||||
//target.EmailConfirmed = source.EmailConfirmedDate.HasValue;
|
||||
target.Name = source.Name;
|
||||
//target.AccessFailedCount = source.FailedPasswordAttempts;
|
||||
target.PasswordHash = GetPasswordHash(source.RawPasswordValue);
|
||||
target.PasswordConfig = source.PasswordConfiguration;
|
||||
target.IsApproved = source.IsApproved;
|
||||
//target.SecurityStamp = source.SecurityStamp;
|
||||
//target.LockoutEndDateUtc = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null;
|
||||
}
|
||||
|
||||
private static string GetPasswordHash(string storedPass)
|
||||
{
|
||||
return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Members
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by the UmbracoMembersUserManager to check the username/password which allows for developers to more easily
|
||||
/// set the logic for this procedure.
|
||||
/// </summary>
|
||||
public interface IUmbracoMembersUserPasswordChecker
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks a password for a member
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
Task<UmbracoMembersUserPasswordCheckerResult> CheckPasswordAsync(UmbracoMembersIdentityUser member, string password);
|
||||
}
|
||||
}
|
||||
@@ -12,23 +12,59 @@ namespace Umbraco.Core.Members
|
||||
//TODO: use of identity classes
|
||||
//: IdentityUser<int, IIdentityUserLogin, IdentityUserRole<string>, IdentityUserClaim<int>>,
|
||||
{
|
||||
public int Id;
|
||||
public string Name;
|
||||
public string Email;
|
||||
public string UserName;
|
||||
public string MemberTypeAlias;
|
||||
public bool IsLockedOut;
|
||||
private bool _hasIdentity;
|
||||
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string MemberTypeAlias { get; set; }
|
||||
public bool IsLockedOut { get; set; }
|
||||
|
||||
public string RawPasswordValue { get; set; }
|
||||
public DateTime LastPasswordChangeDateUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if an Id has been set on this object
|
||||
/// This will be false if the object is new and not persisted to the database
|
||||
/// </summary>
|
||||
public bool HasIdentity => _hasIdentity;
|
||||
|
||||
//TODO: track
|
||||
public string PasswordHash { get; set; }
|
||||
|
||||
//TODO: config
|
||||
public string PasswordConfig { get; set; }
|
||||
|
||||
string Comment;
|
||||
bool IsApproved;
|
||||
internal bool IsApproved;
|
||||
DateTime LastLockoutDate;
|
||||
DateTime CreationDate;
|
||||
DateTime LastLoginDate;
|
||||
DateTime LastActivityDate;
|
||||
DateTime LastPasswordChangedDate;
|
||||
|
||||
//TODO: needed?
|
||||
//public bool LoginsChanged;
|
||||
//public bool RolesChanged;
|
||||
|
||||
|
||||
public static UmbracoMembersIdentityUser CreateNew(string username, string email, string name = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username));
|
||||
|
||||
//no groups/roles yet
|
||||
var member = new UmbracoMembersIdentityUser
|
||||
{
|
||||
UserName = username,
|
||||
Email = email,
|
||||
Name = name,
|
||||
Id = 0, //TODO
|
||||
_hasIdentity = false
|
||||
};
|
||||
|
||||
//TODO: do we use this?
|
||||
//member.EnableChangeTracking();
|
||||
return member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Core.Members
|
||||
{
|
||||
/// <summary>
|
||||
/// The result returned from the IUmbracoMembersUserPasswordChecker
|
||||
/// </summary>
|
||||
public enum UmbracoMembersUserPasswordCheckerResult
|
||||
{
|
||||
ValidCredentials,
|
||||
InvalidCredentials,
|
||||
FallbackToDefaultChecker
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Core.Members;
|
||||
|
||||
namespace Umbraco.Infrastructure.Members
|
||||
@@ -7,8 +9,46 @@ namespace Umbraco.Infrastructure.Members
|
||||
{
|
||||
}
|
||||
|
||||
public interface IUmbracoMembersUserManager<TUser> : IDisposable
|
||||
where TUser : UmbracoMembersIdentityUser
|
||||
public interface IUmbracoMembersUserManager<TUser> : IDisposable where TUser : UmbracoMembersIdentityUser
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the specified <paramref name="memberUser"/> in the backing store with no password,
|
||||
/// as an asynchronous operation.
|
||||
/// </summary>
|
||||
/// <param name="memberUser">The member to create.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
|
||||
/// of the operation.
|
||||
/// </returns>
|
||||
Task<IdentityResult> CreateAsync(TUser memberUser);
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to generate a password for a user based on the current password validator
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GeneratePassword();
|
||||
|
||||
/// <summary>
|
||||
/// Adds the <paramref name="password"/> to the specified <paramref name="memberUser"/> only if the user
|
||||
/// does not already have a password.
|
||||
/// </summary>
|
||||
/// <param name="memberUser">The member whose password should be set.</param>
|
||||
/// <param name="password">The password to set.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
|
||||
/// of the operation.
|
||||
/// </returns>
|
||||
Task<IdentityResult> AddPasswordAsync(TUser memberUser, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flag indicating whether the given <paramref name="password"/> is valid for the
|
||||
/// specified <paramref name="memberUser"/>.
|
||||
/// </summary>
|
||||
/// <param name="memberUser">The user whose password should be validated.</param>
|
||||
/// <param name="password">The password to validate</param>
|
||||
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing true if
|
||||
/// the specified <paramref name="password" /> matches the one store for the <paramref name="memberUser"/>,
|
||||
/// otherwise false.</returns>
|
||||
Task<bool> CheckPasswordAsync(TUser memberUser, string password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Core.Members;
|
||||
|
||||
namespace Umbraco.Infrastructure.Members
|
||||
{
|
||||
public class UmbracoMembersIdentityBuilder : IdentityBuilder
|
||||
{
|
||||
|
||||
public UmbracoMembersIdentityBuilder(IServiceCollection services) : base(typeof(UmbracoMembersIdentityUser), services)
|
||||
{
|
||||
}
|
||||
|
||||
public UmbracoMembersIdentityBuilder(Type role, IServiceCollection services) : base(typeof(UmbracoMembersIdentityUser), role, services)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a token provider for the <seealso cref="UmbracoMembersIdentityUser"/>.
|
||||
/// </summary>
|
||||
/// <param name="providerName">The name of the provider to add.</param>
|
||||
/// <param name="provider">The type of the <see cref="IUserTwoFactorTokenProvider{UmbracoMembersIdentityUser}"/> to add.</param>
|
||||
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
||||
public override IdentityBuilder AddTokenProvider(string providerName, Type provider)
|
||||
{
|
||||
if (!typeof(IUserTwoFactorTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo()))
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid Type for TokenProvider: {provider.FullName}");
|
||||
}
|
||||
Services.Configure<UmbracoMembersIdentityOptions>(options =>
|
||||
{
|
||||
options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider);
|
||||
});
|
||||
Services.AddTransient(provider);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Infrastructure.Members
|
||||
{
|
||||
/// <summary>
|
||||
/// Identity options specifically for the Umbraco members identity implementation
|
||||
/// </summary>
|
||||
public class UmbracoMembersIdentityOptions : IdentityOptions
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,16 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Members;
|
||||
using Umbraco.Core.Security;
|
||||
using System.Threading;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
|
||||
namespace Umbraco.Infrastructure.Members
|
||||
{
|
||||
@@ -14,15 +23,16 @@ namespace Umbraco.Infrastructure.Members
|
||||
{
|
||||
public UmbracoMembersUserManager(
|
||||
IUserStore<UmbracoMembersIdentityUser> store,
|
||||
IOptions<IdentityOptions> optionsAccessor,
|
||||
IOptions<UmbracoMembersIdentityOptions> optionsAccessor,
|
||||
IPasswordHasher<UmbracoMembersIdentityUser> passwordHasher,
|
||||
IEnumerable<IUserValidator<UmbracoMembersIdentityUser>> userValidators,
|
||||
IEnumerable<IPasswordValidator<UmbracoMembersIdentityUser>> passwordValidators,
|
||||
ILookupNormalizer keyNormalizer,
|
||||
IdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
ILogger<UserManager<UmbracoMembersIdentityUser>> logger) :
|
||||
base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
ILogger<UserManager<UmbracoMembersIdentityUser>> logger,
|
||||
IOptions<MemberPasswordConfigurationSettings> passwordConfiguration) :
|
||||
base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger, passwordConfiguration)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -30,6 +40,10 @@ namespace Umbraco.Infrastructure.Members
|
||||
public class UmbracoMembersUserManager<T> : UserManager<T>
|
||||
where T : UmbracoMembersIdentityUser
|
||||
{
|
||||
public IPasswordConfiguration PasswordConfiguration { get; protected set; }
|
||||
|
||||
private PasswordGenerator _passwordGenerator;
|
||||
|
||||
public UmbracoMembersUserManager(
|
||||
IUserStore<T> store,
|
||||
IOptions<IdentityOptions> optionsAccessor,
|
||||
@@ -39,9 +53,154 @@ namespace Umbraco.Infrastructure.Members
|
||||
ILookupNormalizer keyNormalizer,
|
||||
IdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
ILogger<UserManager<T>> logger) :
|
||||
ILogger<UserManager<T>> logger,
|
||||
IOptions<MemberPasswordConfigurationSettings> passwordConfiguration) :
|
||||
base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
{
|
||||
PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the underlying options property with our own strongly typed version
|
||||
/// </summary>
|
||||
public new UmbracoMembersIdentityOptions Options
|
||||
{
|
||||
get => (UmbracoMembersIdentityOptions)base.Options;
|
||||
set => base.Options = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the default Umbraco member user password checker
|
||||
/// </summary>
|
||||
public IUmbracoMembersUserPasswordChecker UmbracoMembersUserPasswordChecker { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [TODO: from BackOfficeUserManager duplicated, could be shared]
|
||||
/// Override to determine how to hash the password
|
||||
/// </summary>
|
||||
/// <param name="memberUser"></param>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <param name="validatePassword"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This method is called anytime the password needs to be hashed for storage (i.e. including when reset password is used)
|
||||
/// </remarks>
|
||||
protected override async Task<IdentityResult> UpdatePasswordHash(T memberUser, string newPassword, bool validatePassword)
|
||||
{
|
||||
memberUser.LastPasswordChangeDateUtc = DateTime.UtcNow;
|
||||
|
||||
if (validatePassword)
|
||||
{
|
||||
IdentityResult validate = await ValidatePasswordAsync(memberUser, newPassword);
|
||||
if (!validate.Succeeded)
|
||||
{
|
||||
return validate;
|
||||
}
|
||||
}
|
||||
|
||||
var passwordStore = Store as IUserPasswordStore<T>;
|
||||
if (passwordStore == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserPasswordStore<>));
|
||||
|
||||
var hash = newPassword != null ? PasswordHasher.HashPassword(memberUser, newPassword) : null;
|
||||
await passwordStore.SetPasswordHashAsync(memberUser, hash, CancellationToken);
|
||||
await UpdateSecurityStampInternal(memberUser);
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
///TODO: duplicated code
|
||||
/// <summary>
|
||||
/// Logic used to validate a username and password
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// By default this uses the standard ASP.Net Identity approach which is:
|
||||
/// * Get password store
|
||||
/// * Call VerifyPasswordAsync with the password store + user + password
|
||||
/// * Uses the PasswordHasher.VerifyHashedPassword to compare the stored password
|
||||
///
|
||||
/// In some cases people want simple custom control over the username/password check, for simplicity
|
||||
/// sake, developers would like the users to simply validate against an LDAP directory but the user
|
||||
/// data remains stored inside of Umbraco.
|
||||
/// See: http://issues.umbraco.org/issue/U4-7032 for the use cases.
|
||||
///
|
||||
/// We've allowed this check to be overridden with a simple callback so that developers don't actually
|
||||
/// have to implement/override this class.
|
||||
/// </remarks>
|
||||
public override async Task<bool> CheckPasswordAsync(T member, string password)
|
||||
{
|
||||
if (UmbracoMembersUserPasswordChecker != null)
|
||||
{
|
||||
UmbracoMembersUserPasswordCheckerResult result = await UmbracoMembersUserPasswordChecker.CheckPasswordAsync(member, password);
|
||||
|
||||
if (member.HasIdentity == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//if the result indicates to not fallback to the default, then return true if the credentials are valid
|
||||
if (result != UmbracoMembersUserPasswordCheckerResult.FallbackToDefaultChecker)
|
||||
{
|
||||
return result == UmbracoMembersUserPasswordCheckerResult.ValidCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
//we cannot proceed if the user passed in does not have an identity
|
||||
if (member.HasIdentity == false)
|
||||
return false;
|
||||
|
||||
//use the default behavior
|
||||
return await base.CheckPasswordAsync(member, password);
|
||||
}
|
||||
|
||||
///[TODO: from BackOfficeUserManager duplicated, could be shared]
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decided to not expose it
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
private async Task UpdateSecurityStampInternal(T user)
|
||||
{
|
||||
if (SupportsUserSecurityStamp == false) return;
|
||||
await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp(), CancellationToken.None);
|
||||
}
|
||||
|
||||
///[TODO: from BackOfficeUserManager duplicated, could be shared]
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decided to not expose it
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IUserSecurityStampStore<T> GetSecurityStore()
|
||||
{
|
||||
var store = Store as IUserSecurityStampStore<T>;
|
||||
if (store == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>));
|
||||
return store;
|
||||
}
|
||||
|
||||
///[TODO: from BackOfficeUserManager duplicated, could be shared]
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decided to not expose it
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string NewSecurityStamp()
|
||||
{
|
||||
return Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
///[TODO: from BackOfficeUserManager duplicated, could be shared]
|
||||
/// <summary>
|
||||
/// Helper method to generate a password for a member based on the current password validator
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GeneratePassword()
|
||||
{
|
||||
if (_passwordGenerator == null)
|
||||
{
|
||||
_passwordGenerator = new PasswordGenerator(PasswordConfiguration);
|
||||
}
|
||||
string password = _passwordGenerator.GeneratePassword();
|
||||
return password;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Members;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Infrastructure.Members
|
||||
@@ -19,8 +15,8 @@ namespace Umbraco.Infrastructure.Members
|
||||
/// A custom user store that uses Umbraco member data
|
||||
/// </summary>
|
||||
public class UmbracoMembersUserStore : DisposableObjectSlim,
|
||||
IUserStore<UmbracoMembersIdentityUser>
|
||||
//IUserPasswordStore<UmbracoMembersIdentityUser>
|
||||
IUserStore<UmbracoMembersIdentityUser>,
|
||||
IUserPasswordStore<UmbracoMembersIdentityUser>
|
||||
//IUserEmailStore<UmbracoMembersIdentityUser>
|
||||
//IUserLoginStore<UmbracoMembersIdentityUser>
|
||||
//IUserRoleStore<UmbracoMembersIdentityUser>,
|
||||
@@ -30,32 +26,37 @@ namespace Umbraco.Infrastructure.Members
|
||||
//IUserSessionStore<UmbracoMembersIdentityUser>
|
||||
{
|
||||
private bool _disposed = false;
|
||||
private IMemberService _memberService;
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly UmbracoMapper _mapper;
|
||||
|
||||
public UmbracoMembersUserStore(IMemberService memberService)
|
||||
public UmbracoMembersUserStore(IMemberService memberService, UmbracoMapper mapper)
|
||||
{
|
||||
_memberService = memberService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
|
||||
public Task<IdentityResult> CreateAsync(UmbracoMembersIdentityUser memberUser, CancellationToken cancellationToken)
|
||||
{
|
||||
//TODO: cancellationToken.ThrowIfCancellationRequested();
|
||||
//TODO: ThrowIfDisposed();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (memberUser == null) throw new ArgumentNullException(nameof(memberUser));
|
||||
|
||||
|
||||
// [Comments from Identity package and BackOfficeUser]
|
||||
// [Comments from Identity package and BackOfficeUser - do we need this?]
|
||||
// the password must be 'something' it could be empty if authenticating
|
||||
// with an external provider so we'll just generate one and prefix it, the
|
||||
// prefix will help us determine if the password hasn't actually been specified yet.
|
||||
//this will hash the guid with a salt so should be nicely random
|
||||
|
||||
var aspHasher = new PasswordHasher<UmbracoMembersIdentityUser>();
|
||||
var emptyPasswordValue =
|
||||
Constants.Security.EmptyPasswordPrefix +
|
||||
aspHasher.HashPassword(memberUser, Guid.NewGuid().ToString("N"));
|
||||
|
||||
if (memberUser.RawPasswordValue.IsNullOrWhiteSpace())
|
||||
{
|
||||
memberUser.RawPasswordValue = emptyPasswordValue;
|
||||
}
|
||||
|
||||
//create member
|
||||
//TODO: are we keeping this method, e.g. the Member Service?
|
||||
IMember memberEntity = _memberService.CreateMember(
|
||||
@@ -65,8 +66,7 @@ namespace Umbraco.Infrastructure.Members
|
||||
memberUser.MemberTypeAlias.IsNullOrWhiteSpace() ?
|
||||
Constants.Security.DefaultMemberTypeAlias : memberUser.MemberTypeAlias);
|
||||
|
||||
|
||||
UpdateMemberProperties(memberEntity, memberUser);
|
||||
bool anythingChanged = UpdateMemberProperties(memberEntity, memberUser);
|
||||
|
||||
_memberService.Save(memberEntity);
|
||||
|
||||
@@ -115,7 +115,6 @@ namespace Umbraco.Infrastructure.Members
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
private bool UpdateMemberProperties(IMember member, UmbracoMembersIdentityUser memberIdentityUser)
|
||||
{
|
||||
//[Comments as per BackOfficeUserStore & identity package]
|
||||
@@ -191,13 +190,15 @@ namespace Umbraco.Infrastructure.Members
|
||||
}
|
||||
|
||||
//TODO: PasswordHash and PasswordConfig
|
||||
//if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.PasswordHash))
|
||||
// && member.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false)
|
||||
//{
|
||||
// anythingChanged = true;
|
||||
// member.RawPasswordValue = identityUser.PasswordHash;
|
||||
// member.PasswordConfiguration = identityUser.PasswordConfig;
|
||||
//}
|
||||
if (
|
||||
//member.IsPropertyDirty(nameof(BackOfficeIdentityUser.PasswordHash))&&
|
||||
member.RawPasswordValue != memberIdentityUser.PasswordHash
|
||||
&& memberIdentityUser.PasswordHash.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.RawPasswordValue = memberIdentityUser.PasswordHash;
|
||||
member.PasswordConfiguration = memberIdentityUser.PasswordConfig;
|
||||
}
|
||||
|
||||
//TODO: SecurityStamp
|
||||
//if (member.SecurityStamp != identityUser.SecurityStamp)
|
||||
@@ -245,7 +246,6 @@ namespace Umbraco.Infrastructure.Members
|
||||
return anythingChanged;
|
||||
}
|
||||
|
||||
|
||||
public Task<IdentityResult> DeleteAsync(UmbracoMembersIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -256,9 +256,20 @@ namespace Umbraco.Infrastructure.Members
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<UmbracoMembersIdentityUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
|
||||
public async Task<UmbracoMembersIdentityUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
//TODO: confirm logic
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
var member = _memberService.GetByUsername(normalizedUserName);
|
||||
if (member == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = _mapper.Map<UmbracoMembersIdentityUser>(member);
|
||||
|
||||
return await Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<string> GetNormalizedUserNameAsync(UmbracoMembersIdentityUser user, CancellationToken cancellationToken)
|
||||
@@ -268,22 +279,36 @@ namespace Umbraco.Infrastructure.Members
|
||||
|
||||
public Task<string> GetUserIdAsync(UmbracoMembersIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
|
||||
return Task.FromResult(user.Id.ToString());
|
||||
}
|
||||
|
||||
public Task<string> GetUserNameAsync(UmbracoMembersIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
//TODO: unit tests for and implement all bodies
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
|
||||
return Task.FromResult(user.UserName);
|
||||
}
|
||||
|
||||
public Task SetNormalizedUserNameAsync(UmbracoMembersIdentityUser user, string normalizedName, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return SetUserNameAsync(user, normalizedName, cancellationToken); throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SetUserNameAsync(UmbracoMembersIdentityUser user, string userName, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
|
||||
user.UserName = userName;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IdentityResult> UpdateAsync(UmbracoMembersIdentityUser user, CancellationToken cancellationToken)
|
||||
@@ -296,5 +321,57 @@ namespace Umbraco.Infrastructure.Members
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
|
||||
///TODO: All from BackOfficeUserStore - same. Can we share?
|
||||
/// <summary>
|
||||
/// Set the user password hash
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="passwordHash"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task SetPasswordHashAsync(UmbracoMembersIdentityUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (passwordHash == null) throw new ArgumentNullException(nameof(passwordHash));
|
||||
if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentException("Value can't be empty.", nameof(passwordHash));
|
||||
|
||||
user.PasswordHash = passwordHash;
|
||||
user.PasswordConfig = null; // Clear this so that it's reset at the repository level
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user password hash
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<string> GetPasswordHashAsync(UmbracoMembersIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
|
||||
return Task.FromResult(user.PasswordHash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a user has a password set
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<bool> HasPasswordAsync(UmbracoMembersIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
|
||||
return Task.FromResult(string.IsNullOrEmpty(user.PasswordHash) == false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ namespace Umbraco.Tests.Integration.TestServerTest
|
||||
.WithRuntimeMinifier()
|
||||
.WithBackOffice()
|
||||
.WithBackOfficeIdentity()
|
||||
.WithUmbracoMembersIdentity()
|
||||
.WithPreview()
|
||||
//.WithMiniProfiler() // we don't want this running in tests
|
||||
.WithMvcAndRazor(mvcBuilding: mvcBuilder =>
|
||||
|
||||
@@ -37,6 +37,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice
|
||||
Assert.NotNull(userManager);
|
||||
}
|
||||
|
||||
protected override Action<IServiceCollection> CustomTestSetup => (services) => services.AddUmbracoBackOfficeIdentity();
|
||||
protected override Action<IServiceCollection> CustomTestSetup => (services) => services.AddUmbracoMembersIdentity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Members;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Members;
|
||||
using Umbraco.Tests.Integration.Testing;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice
|
||||
{
|
||||
[TestFixture]
|
||||
public class UmbracoMembersServiceCollectionExtensionsTests : UmbracoIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public void AddUmbracoMembersIdentity_ExpectUmbracoMembersUserStoreResolvable()
|
||||
{
|
||||
var userStore = Services.GetService<IUserStore<UmbracoMembersIdentityUser>>();
|
||||
|
||||
Assert.IsNotNull(userStore);
|
||||
Assert.AreEqual(typeof(UmbracoMembersUserStore), userStore.GetType());
|
||||
}
|
||||
|
||||
//[Test]
|
||||
//public void AddUmbracoMembersIdentity_ExpectUmbracoMembersClaimsPrincipalFactoryResolvable()
|
||||
//{
|
||||
// var principalFactory = Services.GetService<IUserClaimsPrincipalFactory<UmbracoMembersIdentityUser>>();
|
||||
|
||||
// Assert.IsNotNull(principalFactory);
|
||||
// Assert.AreEqual(typeof(UmbracoMembersClaimsPrincipalFactory<UmbracoMembersIdentityUser>), principalFactory.GetType());
|
||||
//}
|
||||
|
||||
[Test]
|
||||
public void AddUmbracoMembersIdentity_ExpectUmbracomMembersUserManagerResolvable()
|
||||
{
|
||||
var userManager = Services.GetService<IUmbracoMembersUserManager>();
|
||||
|
||||
Assert.NotNull(userManager);
|
||||
}
|
||||
|
||||
protected override Action<IServiceCollection> CustomTestSetup => (services) => services.AddUmbracoMembersIdentity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Members;
|
||||
using Umbraco.Infrastructure.Members;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Members
|
||||
{
|
||||
[TestFixture]
|
||||
public class UmbracoMemberIdentityUserManagerTests
|
||||
{
|
||||
private Mock<IUserStore<UmbracoMembersIdentityUser>> _mockMemberStore;
|
||||
private Mock<IOptions<UmbracoMembersIdentityOptions>> _mockIdentityOptions;
|
||||
private Mock<IPasswordHasher<UmbracoMembersIdentityUser>> _mockPasswordHasher;
|
||||
private Mock<IUserValidator<UmbracoMembersIdentityUser>> _mockUserValidators;
|
||||
private Mock<IEnumerable<IPasswordValidator<UmbracoMembersIdentityUser>>> _mockPasswordValidators;
|
||||
private Mock<ILookupNormalizer> _mockNormalizer;
|
||||
private IdentityErrorDescriber _mockErrorDescriber;
|
||||
private Mock<IServiceProvider> _mockServiceProviders;
|
||||
private Mock<ILogger<UserManager<UmbracoMembersIdentityUser>>> _mockLogger;
|
||||
|
||||
public UmbracoMembersUserManager<UmbracoMembersIdentityUser> CreateSut()
|
||||
{
|
||||
_mockMemberStore = new Mock<IUserStore<UmbracoMembersIdentityUser>>();
|
||||
_mockIdentityOptions = new Mock<IOptions<UmbracoMembersIdentityOptions>>();
|
||||
|
||||
var idOptions = new UmbracoMembersIdentityOptions { Lockout = { AllowedForNewUsers = false } };
|
||||
_mockIdentityOptions.Setup(o => o.Value).Returns(idOptions);
|
||||
_mockPasswordHasher = new Mock<IPasswordHasher<UmbracoMembersIdentityUser>>();
|
||||
|
||||
var userValidators = new List<IUserValidator<UmbracoMembersIdentityUser>>();
|
||||
_mockUserValidators = new Mock<IUserValidator<UmbracoMembersIdentityUser>>();
|
||||
var validator = new Mock<IUserValidator<UmbracoMembersIdentityUser>>();
|
||||
userValidators.Add(validator.Object);
|
||||
|
||||
_mockPasswordValidators = new Mock<IEnumerable<IPasswordValidator<UmbracoMembersIdentityUser>>>();
|
||||
_mockNormalizer = new Mock<ILookupNormalizer>();
|
||||
_mockErrorDescriber = new IdentityErrorDescriber();
|
||||
_mockServiceProviders = new Mock<IServiceProvider>();
|
||||
_mockLogger = new Mock<ILogger<UserManager<UmbracoMembersIdentityUser>>>();
|
||||
|
||||
var pwdValidators = new List<PasswordValidator<UmbracoMembersIdentityUser>>
|
||||
{
|
||||
new PasswordValidator<UmbracoMembersIdentityUser>()
|
||||
};
|
||||
|
||||
var userManager = new UmbracoMembersUserManager<UmbracoMembersIdentityUser>(
|
||||
_mockMemberStore.Object,
|
||||
_mockIdentityOptions.Object,
|
||||
_mockPasswordHasher.Object,
|
||||
userValidators,
|
||||
pwdValidators,
|
||||
new UpperInvariantLookupNormalizer(),
|
||||
new IdentityErrorDescriber(),
|
||||
_mockServiceProviders.Object,
|
||||
new Mock<ILogger<UserManager<UmbracoMembersIdentityUser>>>().Object,
|
||||
new Mock<IOptions<MemberPasswordConfigurationSettings>>().Object);
|
||||
|
||||
validator.Setup(v => v.ValidateAsync(
|
||||
userManager,
|
||||
It.IsAny<UmbracoMembersIdentityUser>()))
|
||||
.Returns(Task.FromResult(IdentityResult.Success)).Verifiable();
|
||||
|
||||
return userManager;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GivenICreateUser_AndTheIdentityResultFailed_ThenIShouldGetAFailedResultAsync()
|
||||
{
|
||||
//arrange
|
||||
UmbracoMembersUserManager<UmbracoMembersIdentityUser> sut = CreateSut();
|
||||
UmbracoMembersIdentityUser fakeUser = new UmbracoMembersIdentityUser() { };
|
||||
CancellationToken fakeCancellationToken = new CancellationToken() { };
|
||||
IdentityError[] identityErrors =
|
||||
{
|
||||
new IdentityError()
|
||||
{
|
||||
Code = "IdentityError1",
|
||||
Description = "There was an identity error when creating a user"
|
||||
}
|
||||
};
|
||||
|
||||
_mockMemberStore.Setup(x =>
|
||||
x.CreateAsync(fakeUser, fakeCancellationToken))
|
||||
.ReturnsAsync(IdentityResult.Failed(identityErrors));
|
||||
|
||||
//act
|
||||
IdentityResult identityResult = await sut.CreateAsync(fakeUser);
|
||||
|
||||
//assert
|
||||
Assert.IsFalse(identityResult.Succeeded);
|
||||
Assert.IsFalse(!identityResult.Errors.Any());
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task GivenICreateUser_AndTheUserIsNull_ThenIShouldGetAFailedResultAsync()
|
||||
{
|
||||
//arrange
|
||||
UmbracoMembersUserManager<UmbracoMembersIdentityUser> sut = CreateSut();
|
||||
UmbracoMembersIdentityUser fakeUser = new UmbracoMembersIdentityUser() { };
|
||||
CancellationToken fakeCancellationToken = new CancellationToken() { };
|
||||
IdentityError[] identityErrors =
|
||||
{
|
||||
new IdentityError()
|
||||
{
|
||||
Code = "IdentityError1",
|
||||
Description = "There was an identity error when creating a user"
|
||||
}
|
||||
};
|
||||
|
||||
_mockMemberStore.Setup(x =>
|
||||
x.CreateAsync(null, fakeCancellationToken))
|
||||
.ReturnsAsync(IdentityResult.Failed(identityErrors));
|
||||
|
||||
//act
|
||||
var identityResult = new Func<Task<IdentityResult>>(() => sut.CreateAsync(null));
|
||||
|
||||
|
||||
//assert
|
||||
Assert.That(identityResult, Throws.ArgumentNullException);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task GivenICreateANewUser_AndTheUserIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync()
|
||||
{
|
||||
//arrange
|
||||
UmbracoMembersUserManager<UmbracoMembersIdentityUser> sut = CreateSut();
|
||||
UmbracoMembersIdentityUser fakeUser = new UmbracoMembersIdentityUser() { };
|
||||
CancellationToken fakeCancellationToken = new CancellationToken() { };
|
||||
_mockMemberStore.Setup(x =>
|
||||
x.CreateAsync(fakeUser, fakeCancellationToken))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
//act
|
||||
IdentityResult identityResult = await sut.CreateAsync(fakeUser);
|
||||
|
||||
//assert
|
||||
Assert.IsTrue(identityResult.Succeeded);
|
||||
Assert.IsTrue(!identityResult.Errors.Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Castle.Core.Internal;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Members;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -22,7 +24,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Members
|
||||
public UmbracoMembersUserStore CreateSut()
|
||||
{
|
||||
_mockMemberService = new Mock<IMemberService>();
|
||||
return new UmbracoMembersUserStore(_mockMemberService.Object);
|
||||
return new UmbracoMembersUserStore(
|
||||
_mockMemberService.Object,
|
||||
new UmbracoMapper(new MapDefinitionCollection(
|
||||
new Mock<IEnumerable<IMapDefinition>>().Object)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -41,20 +46,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Members
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task GivenICreateUser_AndTheUserIsPopulatedCorrectly_ThenIShouldGetASuccessResult()
|
||||
public async Task GivenICreateANewUser_AndTheUserIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync()
|
||||
{
|
||||
//arrange
|
||||
UmbracoMembersUserStore sut = CreateSut();
|
||||
UmbracoMembersIdentityUser fakeUser = new UmbracoMembersIdentityUser()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
UmbracoMembersIdentityUser fakeUser = new UmbracoMembersIdentityUser() { };
|
||||
CancellationToken fakeCancellationToken = new CancellationToken() { };
|
||||
|
||||
IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77);
|
||||
|
||||
var mockMember = Mock.Of<IMember>(m =>
|
||||
IMember mockMember = Mock.Of<IMember>(m =>
|
||||
m.Name == "fakeName" &&
|
||||
m.Email == "fakeemail@umbraco.com" &&
|
||||
m.Username == "fakeUsername" &&
|
||||
@@ -74,5 +74,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Members
|
||||
Assert.IsTrue(identityResult.Succeeded);
|
||||
Assert.IsTrue(!identityResult.Errors.Any());
|
||||
}
|
||||
|
||||
//FindByNameAsync
|
||||
[Test]
|
||||
public async Task GivenIGetUserNameAsync()
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GivenIFindByNameAsync()
|
||||
{
|
||||
}
|
||||
|
||||
//SetNormalizedUserNameAsync
|
||||
//SetUserNameAsync
|
||||
//HasPasswordAsync
|
||||
//GetPasswordHashAsync
|
||||
//SetPasswordHashAsync
|
||||
//GetUserIdAsync
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -15,8 +16,10 @@ using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Members;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Serialization;
|
||||
@@ -24,6 +27,7 @@ using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Members;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.ModelBinders;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
@@ -31,7 +35,6 @@ using Umbraco.Web.Common.Exceptions;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
@@ -45,12 +48,11 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[OutgoingNoHyphenGuidFormat]
|
||||
public class MemberController : ContentControllerBase
|
||||
{
|
||||
private readonly MemberPasswordConfigurationSettings _passwordConfig;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly LegacyPasswordSecurity _passwordSecurity;
|
||||
private readonly UmbracoMapper _umbracoMapper;
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IUmbracoMembersUserManager _memberManager;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
@@ -62,23 +64,21 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
IShortStringHelper shortStringHelper,
|
||||
IEventMessagesFactory eventMessages,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IOptions<MemberPasswordConfigurationSettings> passwordConfig,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
LegacyPasswordSecurity passwordSecurity,
|
||||
UmbracoMapper umbracoMapper,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IUmbracoMembersUserManager memberManager,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IJsonSerializer jsonSerializer)
|
||||
: base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, jsonSerializer)
|
||||
{
|
||||
_passwordConfig = passwordConfig.Value;
|
||||
_propertyEditors = propertyEditors;
|
||||
_passwordSecurity = passwordSecurity;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_memberService = memberService;
|
||||
_memberTypeService = memberTypeService;
|
||||
_memberManager = memberManager;
|
||||
_dataTypeService = dataTypeService;
|
||||
_localizedTextService = localizedTextService;
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
@@ -100,8 +100,15 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero");
|
||||
}
|
||||
|
||||
var members = _memberService
|
||||
.GetAll((pageNumber - 1), pageSize, out var totalRecords, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter).ToArray();
|
||||
IMember[] members = _memberService.GetAll(
|
||||
pageNumber - 1,
|
||||
pageSize,
|
||||
out var totalRecords,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
orderBySystemField,
|
||||
memberTypeAlias,
|
||||
filter).ToArray();
|
||||
if (totalRecords == 0)
|
||||
{
|
||||
return new PagedResult<MemberBasic>(0, 0, 0);
|
||||
@@ -109,8 +116,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
var pagedResult = new PagedResult<MemberBasic>(totalRecords, pageNumber, pageSize)
|
||||
{
|
||||
Items = members
|
||||
.Select(x => _umbracoMapper.Map<MemberBasic>(x))
|
||||
Items = members.Select(x => _umbracoMapper.Map<MemberBasic>(x))
|
||||
};
|
||||
return pagedResult;
|
||||
}
|
||||
@@ -125,8 +131,15 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
var foundType = _memberTypeService.Get(listName);
|
||||
var name = foundType != null ? foundType.Name : listName;
|
||||
|
||||
var apps = new List<ContentApp>();
|
||||
apps.Add(ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, listName, "member", Core.Constants.DataTypes.DefaultMembersListView));
|
||||
var apps = new List<ContentApp>
|
||||
{
|
||||
ListViewContentAppFactory.CreateContentApp(
|
||||
_dataTypeService,
|
||||
_propertyEditors,
|
||||
listName,
|
||||
Constants.Security.DefaultMemberTypeAlias.ToLower(),
|
||||
Constants.DataTypes.DefaultMembersListView)
|
||||
};
|
||||
apps[0].Active = true;
|
||||
|
||||
var display = new MemberListDisplay
|
||||
@@ -152,7 +165,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
|
||||
public MemberDisplay GetByKey(Guid key)
|
||||
{
|
||||
var foundMember = _memberService.GetByKey(key);
|
||||
IMember foundMember = _memberService.GetByKey(key);
|
||||
if (foundMember == null)
|
||||
{
|
||||
HandleContentNotFound(key);
|
||||
@@ -168,22 +181,21 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
|
||||
public MemberDisplay GetEmpty(string contentTypeAlias = null)
|
||||
{
|
||||
IMember emptyContent;
|
||||
if (contentTypeAlias == null)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
var contentType = _memberTypeService.Get(contentTypeAlias);
|
||||
IMemberType contentType = _memberTypeService.Get(contentTypeAlias);
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
var passwordGenerator = new PasswordGenerator(_passwordConfig);
|
||||
IMember emptyContent = new Member(contentType);
|
||||
|
||||
emptyContent = new Member(contentType);
|
||||
emptyContent.AdditionalData["NewPassword"] = passwordGenerator.GeneratePassword();
|
||||
string newPassword = _memberManager.GeneratePassword();
|
||||
emptyContent.AdditionalData["NewPassword"] = newPassword;
|
||||
return _umbracoMapper.Map<MemberDisplay>(emptyContent);
|
||||
}
|
||||
|
||||
@@ -224,8 +236,10 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
// their handlers. If we don't look this up now there's a chance we'll just end up
|
||||
// removing the roles they've assigned.
|
||||
var currRoles = _memberService.GetAllRoles(contentItem.PersistedContent.Username);
|
||||
|
||||
//find the ones to remove and remove them
|
||||
var rolesToRemove = currRoles.Except(contentItem.Groups).ToArray();
|
||||
IEnumerable<string> roles = currRoles.ToList();
|
||||
var rolesToRemove = roles.Except(contentItem.Groups).ToArray();
|
||||
|
||||
//Depending on the action we need to first do a create or update using the membership provider
|
||||
// this ensures that passwords are formatted correctly and also performs the validation on the provider itself.
|
||||
@@ -235,7 +249,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
UpdateMemberData(contentItem);
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
contentItem.PersistedContent = CreateMemberData(contentItem);
|
||||
contentItem.PersistedContent = await CreateMemberData(contentItem);
|
||||
break;
|
||||
default:
|
||||
//we don't support anything else for members
|
||||
@@ -255,7 +269,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_memberService.DissociateRoles(new[] { contentItem.PersistedContent.Username }, rolesToRemove);
|
||||
}
|
||||
//find the ones to add and add them
|
||||
var toAdd = contentItem.Groups.Except(currRoles).ToArray();
|
||||
string[] toAdd = contentItem.Groups.Except(roles).ToArray();
|
||||
if (toAdd.Any())
|
||||
{
|
||||
//add the ones submitted
|
||||
@@ -263,18 +277,20 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
//return the updated model
|
||||
var display = _umbracoMapper.Map<MemberDisplay>(contentItem.PersistedContent);
|
||||
MemberDisplay display = _umbracoMapper.Map<MemberDisplay>(contentItem.PersistedContent);
|
||||
|
||||
//lastly, if it is not valid, add the model state to the outgoing object and throw a 403
|
||||
HandleInvalidModelState(display);
|
||||
|
||||
var localizedTextService = _localizedTextService;
|
||||
ILocalizedTextService localizedTextService = _localizedTextService;
|
||||
//put the correct messages in
|
||||
switch (contentItem.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
case ContentSaveAction.SaveNew:
|
||||
display.AddSuccessNotification(localizedTextService.Localize("speechBubbles/editMemberSaved"), localizedTextService.Localize("speechBubbles/editMemberSaved"));
|
||||
display.AddSuccessNotification(
|
||||
localizedTextService.Localize("speechBubbles/editMemberSaved"),
|
||||
localizedTextService.Localize("speechBubbles/editMemberSaved"));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -302,36 +318,98 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
null); // member are all invariant
|
||||
}
|
||||
|
||||
private IMember CreateMemberData(MemberSave contentItem)
|
||||
/// <summary>
|
||||
/// Create a member from the supplied member content data
|
||||
/// All member password processing and creation is done via the aspnet identity MemberUserManager
|
||||
/// </summary>
|
||||
/// <param name="memberSave"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<IMember> CreateMemberData(MemberSave memberSave)
|
||||
{
|
||||
throw new NotImplementedException("Members have not been migrated to netcore");
|
||||
if (memberSave == null) throw new ArgumentNullException("memberSave");
|
||||
|
||||
// TODO: all member password processing and creation needs to be done with a new aspnet identity MemberUserManager that hasn't been created yet.
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
|
||||
}
|
||||
//TODO: check if unique
|
||||
|
||||
//var memberType = _memberTypeService.Get(contentItem.ContentTypeAlias);
|
||||
//if (memberType == null)
|
||||
// throw new InvalidOperationException($"No member type found with alias {contentItem.ContentTypeAlias}");
|
||||
//var member = new Member(contentItem.Name, contentItem.Email, contentItem.Username, memberType, true)
|
||||
IMemberType memberType = _memberTypeService.Get(memberSave.ContentTypeAlias);
|
||||
if (memberType == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No member type found with alias {memberSave.ContentTypeAlias}");
|
||||
}
|
||||
|
||||
// Create the member with the UserManager
|
||||
// The 'empty' (special) password format is applied without us having to duplicate that logic
|
||||
UmbracoMembersIdentityUser identityMember = UmbracoMembersIdentityUser.CreateNew(
|
||||
memberSave.Username,
|
||||
memberSave.Email,
|
||||
memberSave.Name);
|
||||
|
||||
//TODO: confirm
|
||||
identityMember.MemberTypeAlias = memberType.Alias;
|
||||
|
||||
IdentityResult created = await _memberManager.CreateAsync(identityMember);
|
||||
if (created.Succeeded == false)
|
||||
{
|
||||
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
|
||||
}
|
||||
|
||||
string resetPassword;
|
||||
string password = _memberManager.GeneratePassword();
|
||||
|
||||
IdentityResult result = await _memberManager.AddPasswordAsync(identityMember, password);
|
||||
if (result.Succeeded == false)
|
||||
{
|
||||
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
|
||||
}
|
||||
|
||||
resetPassword = password;
|
||||
|
||||
//now re-look the member back up which will now exist
|
||||
IMember member = _memberService.GetByEmail(memberSave.Email);
|
||||
|
||||
//TODO: previous implementation
|
||||
//IMember member = new Member(
|
||||
// memberSave.Name,
|
||||
// memberSave.Email,
|
||||
// memberSave.Username,
|
||||
// memberType,
|
||||
// true)
|
||||
//{
|
||||
// CreatorId = _backofficeSecurityAccessor.BackofficeSecurity.CurrentUser.Id,
|
||||
// RawPasswordValue = _passwordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword),
|
||||
// Comments = contentItem.Comments,
|
||||
// IsApproved = contentItem.IsApproved
|
||||
// CreatorId = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id,
|
||||
// RawPasswordValue = _memberManager.GeneratePassword(),
|
||||
// Comments = memberSave.Comments,
|
||||
// IsApproved = memberSave.IsApproved
|
||||
//};
|
||||
|
||||
//return member;
|
||||
|
||||
//since the back office user is creating this member, they will be set to approved
|
||||
member.IsApproved = true;
|
||||
member.CreatorId = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id;
|
||||
member.Comments = memberSave.Comments;
|
||||
member.IsApproved = memberSave.IsApproved;
|
||||
|
||||
//map the save info over onto the user
|
||||
member = _umbracoMapper.Map(memberSave, member);
|
||||
|
||||
_memberService.Save(member);
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the member security data
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <param name="memberSave"></param>
|
||||
/// <returns>
|
||||
/// If the password has been reset then this method will return the reset/generated password, otherwise will return null.
|
||||
/// </returns>
|
||||
private void UpdateMemberData(MemberSave contentItem)
|
||||
private void UpdateMemberData(MemberSave memberSave)
|
||||
{
|
||||
contentItem.PersistedContent.WriterId = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id;
|
||||
//TODO: optimise based on new member manager
|
||||
memberSave.PersistedContent.WriterId = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id;
|
||||
|
||||
// If the user doesn't have access to sensitive values, then we need to check if any of the built in member property types
|
||||
// have been marked as sensitive. If that is the case we cannot change these persisted values no matter what value has been posted.
|
||||
@@ -339,32 +417,32 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
// but we will take care of this in a generic way below so that it works for all props.
|
||||
if (!_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasAccessToSensitiveData())
|
||||
{
|
||||
var memberType = _memberTypeService.Get(contentItem.PersistedContent.ContentTypeId);
|
||||
var memberType = _memberTypeService.Get(memberSave.PersistedContent.ContentTypeId);
|
||||
var sensitiveProperties = memberType
|
||||
.PropertyTypes.Where(x => memberType.IsSensitiveProperty(x.Alias))
|
||||
.ToList();
|
||||
|
||||
foreach (var sensitiveProperty in sensitiveProperties)
|
||||
{
|
||||
var destProp = contentItem.Properties.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias);
|
||||
var destProp = memberSave.Properties.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias);
|
||||
if (destProp != null)
|
||||
{
|
||||
//if found, change the value of the contentItem model to the persisted value so it remains unchanged
|
||||
var origValue = contentItem.PersistedContent.GetValue(sensitiveProperty.Alias);
|
||||
var origValue = memberSave.PersistedContent.GetValue(sensitiveProperty.Alias);
|
||||
destProp.Value = origValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isLockedOut = contentItem.IsLockedOut;
|
||||
var isLockedOut = memberSave.IsLockedOut;
|
||||
|
||||
//if they were locked but now they are trying to be unlocked
|
||||
if (contentItem.PersistedContent.IsLockedOut && isLockedOut == false)
|
||||
if (memberSave.PersistedContent.IsLockedOut && isLockedOut == false)
|
||||
{
|
||||
contentItem.PersistedContent.IsLockedOut = false;
|
||||
contentItem.PersistedContent.FailedPasswordAttempts = 0;
|
||||
memberSave.PersistedContent.IsLockedOut = false;
|
||||
memberSave.PersistedContent.FailedPasswordAttempts = 0;
|
||||
}
|
||||
else if (!contentItem.PersistedContent.IsLockedOut && isLockedOut)
|
||||
else if (!memberSave.PersistedContent.IsLockedOut && isLockedOut)
|
||||
{
|
||||
//NOTE: This should not ever happen unless someone is mucking around with the request data.
|
||||
//An admin cannot simply lock a user, they get locked out by password attempts, but an admin can un-approve them
|
||||
@@ -372,13 +450,11 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
//no password changes then exit ?
|
||||
if (contentItem.Password == null)
|
||||
if (memberSave.Password == null)
|
||||
return;
|
||||
|
||||
throw new NotImplementedException("Members have not been migrated to netcore");
|
||||
// TODO: all member password processing and creation needs to be done with a new aspnet identity MemberUserManager that hasn't been created yet.
|
||||
// set the password
|
||||
//contentItem.PersistedContent.RawPasswordValue = _passwordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword);
|
||||
memberSave.PersistedContent.RawPasswordValue = _memberManager.GeneratePassword();
|
||||
}
|
||||
|
||||
private static void UpdateName(MemberSave memberSave)
|
||||
@@ -396,23 +472,23 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
if (contentItem.Name.IsNullOrWhiteSpace())
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Invalid user name", new[] { "value" }),
|
||||
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
new ValidationResult("Invalid user name", new[] { "value" }),
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (contentItem.Password != null && !contentItem.Password.NewPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
//TODO implement when NETCORE members are implemented
|
||||
throw new NotImplementedException("TODO implement when members are implemented");
|
||||
// var validPassword = await _passwordValidator.ValidateAsync(_passwordConfig, contentItem.Password.NewPassword);
|
||||
// if (!validPassword)
|
||||
// {
|
||||
// ModelState.AddPropertyError(
|
||||
// new ValidationResult("Invalid password: " + string.Join(", ", validPassword.Result), new[] { "value" }),
|
||||
// string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
// return false;
|
||||
// }
|
||||
//TODO: implement as per backoffice user
|
||||
//var validPassword = await _memberManager.CheckPasswordAsync(null, contentItem.Password.NewPassword);
|
||||
//if (!validPassword)
|
||||
//{
|
||||
// ModelState.AddPropertyError(
|
||||
// new ValidationResult("Invalid password: TODO", new[] { "value" }),
|
||||
// $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password");
|
||||
// return false;
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
||||
var byUsername = _memberService.GetByUsername(contentItem.Username);
|
||||
@@ -420,7 +496,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Username is already in use", new[] { "value" }),
|
||||
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -429,7 +505,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Email address is already in use", new[] { "value" }),
|
||||
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -470,7 +546,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var member = ((MemberService)_memberService).ExportMember(key);
|
||||
MemberExportModel member = ((MemberService)_memberService).ExportMember(key);
|
||||
if (member is null) throw new NullReferenceException("No member found with key " + key);
|
||||
|
||||
var json = _jsonSerializer.Serialize(member);
|
||||
@@ -479,9 +555,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
// Set custom header so umbRequestHelper.downloadFile can save the correct filename
|
||||
HttpContext.Response.Headers.Add("x-filename", fileName);
|
||||
|
||||
return File( Encoding.UTF8.GetBytes(json), MediaTypeNames.Application.Octet, fileName);
|
||||
return File(Encoding.UTF8.GetBytes(json), MediaTypeNames.Application.Octet, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Umbraco.Extensions
|
||||
/// <typeparam name="TUserManager">The type of the user manager to add.</typeparam>
|
||||
/// <typeparam name="TInterface"></typeparam>
|
||||
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
||||
public static IdentityBuilder AddUserManager<TInterface, TUserManager>(this IdentityBuilder identityBuilder) where TUserManager : UserManager<BackOfficeIdentityUser>, TInterface
|
||||
public static IdentityBuilder AddUserManager<TInterface, TUserManager>(this IdentityBuilder identityBuilder)
|
||||
where TUserManager : UserManager<BackOfficeIdentityUser>, TInterface
|
||||
{
|
||||
identityBuilder.AddUserManager<TUserManager>();
|
||||
identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TUserManager));
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Umbraco.Extensions
|
||||
.WithRuntimeMinifier()
|
||||
.WithBackOffice()
|
||||
.WithBackOfficeIdentity()
|
||||
.WithUmbracoMembersIdentity()
|
||||
.WithMiniProfiler()
|
||||
.WithMvcAndRazor()
|
||||
.WithWebServer()
|
||||
@@ -27,6 +28,9 @@ namespace Umbraco.Extensions
|
||||
public static IUmbracoBuilder WithBackOfficeIdentity(this IUmbracoBuilder builder)
|
||||
=> builder.AddWith(nameof(WithBackOfficeIdentity), () => builder.Services.AddUmbracoBackOfficeIdentity());
|
||||
|
||||
public static IUmbracoBuilder WithUmbracoMembersIdentity(this IUmbracoBuilder builder)
|
||||
=> builder.AddWith(nameof(WithUmbracoMembersIdentity), () => builder.Services.AddUmbracoMembersIdentity());
|
||||
|
||||
public static IUmbracoBuilder WithPreview(this IUmbracoBuilder builder)
|
||||
=> builder.AddWith(nameof(WithPreview), () => builder.Services.AddUmbracoPreview());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Core.Members;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class UmbracoMemberIdentityBuilderExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="UserManager{TUser}"/> for the <seealso cref="UserType"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUserManager">The type of the user manager to add.</typeparam>
|
||||
/// <typeparam name="TInterface"></typeparam>
|
||||
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
||||
public static IdentityBuilder AddUserManager<TInterface, TUserManager>(this IdentityBuilder identityBuilder) where TUserManager : UserManager<UmbracoMembersIdentityUser>, TInterface
|
||||
{
|
||||
identityBuilder.AddUserManager<TUserManager>();
|
||||
identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TUserManager));
|
||||
return identityBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Umbraco.Core.Members;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Infrastructure.Members;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class UmbracoMembersUserServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the services required for using Umbraco Members Identity
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
public static void AddUmbracoMembersIdentity(this IServiceCollection services)
|
||||
{
|
||||
services.BuildUmbracoMembersIdentity()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddUserStore<UmbracoMembersUserStore>()
|
||||
.AddUserManager<IUmbracoMembersUserManager, UmbracoMembersUserManager>();
|
||||
}
|
||||
|
||||
private static UmbracoMembersIdentityBuilder BuildUmbracoMembersIdentity(this IServiceCollection services)
|
||||
{
|
||||
// Services used by Umbraco members identity
|
||||
services.TryAddScoped<IUserValidator<UmbracoMembersIdentityUser>, UserValidator<UmbracoMembersIdentityUser>>();
|
||||
services.TryAddScoped<IPasswordValidator<UmbracoMembersIdentityUser>, PasswordValidator<UmbracoMembersIdentityUser>>();
|
||||
services.TryAddScoped<IPasswordHasher<UmbracoMembersIdentityUser>, PasswordHasher<UmbracoMembersIdentityUser>>();
|
||||
return new UmbracoMembersIdentityBuilder(services);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ namespace Umbraco.Web.BackOffice.Trees
|
||||
var node = GetSingleTreeNode(id, queryStrings);
|
||||
|
||||
//add the tree alias to the node since it is standalone (has no root for which this normally belongs)
|
||||
//TODO: ID is null since new member created
|
||||
node.Value.AdditionalData["treeAlias"] = TreeAlias;
|
||||
return node;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user