Added store functionality based on backoffice user (to be revisited as a lot of it may be shareable) and also added a new members service method for membergroups.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
@@ -9,6 +10,7 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
void AddRole(string roleName);
|
||||
IEnumerable<string> GetAllRoles();
|
||||
IEnumerable<IMemberGroup> GetAllRolesTyped();
|
||||
IEnumerable<string> GetAllRoles(int memberId);
|
||||
IEnumerable<string> GetAllRoles(string username);
|
||||
IEnumerable<int> GetAllRolesIds();
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by the MembersUserManager to check the username/password which allows for developers to more easily
|
||||
/// set the logic for this procedure.
|
||||
/// </summary>
|
||||
public interface IMembersUserPasswordChecker
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks a password for a member
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: what should our implementation be for members?
|
||||
/// </remarks>
|
||||
Task<MembersUserPasswordCheckerResult> CheckPasswordAsync(MembersIdentityUser user, string password);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
@@ -14,18 +19,8 @@ namespace Umbraco.Infrastructure.Security
|
||||
/// <summary>
|
||||
/// A custom user store that uses Umbraco member data
|
||||
/// </summary>
|
||||
public class MembersUserStore : DisposableObjectSlim,
|
||||
//IUserStore<UmbracoMembersIdentityUser>,
|
||||
IUserPasswordStore<MembersIdentityUser>
|
||||
//IUserEmailStore<UmbracoMembersIdentityUser>
|
||||
//IUserLoginStore<UmbracoMembersIdentityUser>
|
||||
//IUserRoleStore<UmbracoMembersIdentityUser>,
|
||||
//IUserSecurityStampStore<UmbracoMembersIdentityUser>
|
||||
//IUserLockoutStore<UmbracoMembersIdentityUser>
|
||||
//IUserTwoFactorStore<UmbracoMembersIdentityUser>
|
||||
//IUserSessionStore<UmbracoMembersIdentityUser>
|
||||
public class MembersUserStore : UserStoreBase<MembersIdentityUser, IdentityRole<string>, string, IdentityUserClaim<string>, IdentityUserRole<string>, IdentityUserLogin<string>, IdentityUserToken<string>, IdentityRoleClaim<string>>
|
||||
{
|
||||
private readonly bool _disposed = false;
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly UmbracoMapper _mapper;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
@@ -36,20 +31,31 @@ namespace Umbraco.Infrastructure.Security
|
||||
/// <param name="memberService">The member service</param>
|
||||
/// <param name="mapper">The mapper for properties</param>
|
||||
/// <param name="scopeProvider">The scope provider</param>
|
||||
public MembersUserStore(IMemberService memberService, UmbracoMapper mapper, IScopeProvider scopeProvider)
|
||||
/// <param name="describer">The error describer</param>
|
||||
///
|
||||
public MembersUserStore(IMemberService memberService, UmbracoMapper mapper, IScopeProvider scopeProvider, IdentityErrorDescriber describer)
|
||||
: base(describer)
|
||||
{
|
||||
_memberService = memberService;
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
|
||||
_mapper = mapper;
|
||||
_scopeProvider = scopeProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the member as an identity user
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <param name="user">The identity user for a member</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>The identity result</returns>
|
||||
public Task<IdentityResult> CreateAsync(MembersIdentityUser user, CancellationToken cancellationToken)
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override IQueryable<MembersIdentityUser> Users => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetNormalizedUserNameAsync(MembersIdentityUser user, CancellationToken cancellationToken) => GetUserNameAsync(user, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SetNormalizedUserNameAsync(MembersIdentityUser user, string normalizedName, CancellationToken cancellationToken) => SetUserNameAsync(user, normalizedName, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityResult> CreateAsync(MembersIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
@@ -67,18 +73,24 @@ namespace Umbraco.Infrastructure.Security
|
||||
user.Name.IsNullOrWhiteSpace() ? user.UserName : user.Name,
|
||||
user.MemberTypeAlias.IsNullOrWhiteSpace() ? Constants.Security.DefaultMemberTypeAlias : user.MemberTypeAlias);
|
||||
|
||||
// [from backofficeuser] we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it.
|
||||
var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MembersIdentityUser.Logins));
|
||||
|
||||
UpdateMemberProperties(memberEntity, user);
|
||||
|
||||
// TODO: do we want to accept empty passwords here - if third-party for example?
|
||||
// In other method if so?
|
||||
_memberService.Save(memberEntity);
|
||||
|
||||
if (!memberEntity.HasIdentity)
|
||||
{
|
||||
throw new DataException("Could not create the member, check logs for details");
|
||||
}
|
||||
|
||||
// re-assign id
|
||||
user.Id = UserIdToString(memberEntity.Id);
|
||||
|
||||
// [from backofficeuser] we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it.
|
||||
bool isLoginsPropertyDirty = memberEntity.IsPropertyDirty(nameof(MembersIdentityUser.Logins));
|
||||
|
||||
//TODO: confirm re externallogins implementation
|
||||
//if (isLoginsPropertyDirty)
|
||||
//{
|
||||
// _externalLoginService.Save(
|
||||
@@ -89,153 +101,13 @@ namespace Umbraco.Infrastructure.Security
|
||||
// x.UserData)));
|
||||
//}
|
||||
|
||||
if (!memberEntity.HasIdentity)
|
||||
{
|
||||
throw new DataException("Could not create the member, check logs for details");
|
||||
}
|
||||
|
||||
return Task.FromResult(IdentityResult.Success);
|
||||
|
||||
// TODO: confirm and implement
|
||||
//if (memberUser.LoginsChanged)
|
||||
//{
|
||||
// var logins = await GetLoginsAsync(memberUser);
|
||||
// _externalLoginStore.SaveUserLogins(member.Id, logins);
|
||||
//}
|
||||
|
||||
// TODO: confirm and implement
|
||||
//if (memberUser.RolesChanged)
|
||||
//{
|
||||
//IMembershipRoleService<IMember> memberRoleService = _memberService;
|
||||
|
||||
//var persistedRoles = memberRoleService.GetAllRoles(member.Id).ToArray();
|
||||
//var userRoles = memberUser.Roles.Select(x => x.RoleName).ToArray();
|
||||
|
||||
//var keep = persistedRoles.Intersect(userRoles).ToArray();
|
||||
//var remove = persistedRoles.Except(keep).ToArray();
|
||||
//var add = userRoles.Except(persistedRoles).ToArray();
|
||||
|
||||
//memberRoleService.DissociateRoles(new[] { member.Id }, remove);
|
||||
//memberRoleService.AssignRoles(new[] { member.Id }, add);
|
||||
//}
|
||||
// TODO: confirm re roles implementations
|
||||
}
|
||||
|
||||
private bool UpdateMemberProperties(IMember member, MembersIdentityUser memberIdentityUser)
|
||||
{
|
||||
var anythingChanged = false;
|
||||
|
||||
// don't assign anything if nothing has changed as this will trigger the track changes of the model
|
||||
if (memberIdentityUser.IsPropertyDirty(nameof(MembersIdentityUser.Name)) &&
|
||||
member.Name != memberIdentityUser.Name && memberIdentityUser.Name.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.Name = memberIdentityUser.Name;
|
||||
}
|
||||
|
||||
if (memberIdentityUser.IsPropertyDirty(nameof(MembersIdentityUser.Email)) &&
|
||||
member.Email != memberIdentityUser.Email && memberIdentityUser.Email.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.Email = memberIdentityUser.Email;
|
||||
}
|
||||
|
||||
if (member.IsLockedOut != memberIdentityUser.IsLockedOut)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.IsLockedOut = memberIdentityUser.IsLockedOut;
|
||||
|
||||
if (member.IsLockedOut)
|
||||
{
|
||||
// need to set the last lockout date
|
||||
member.LastLockoutDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
if (memberIdentityUser.IsPropertyDirty(nameof(MembersIdentityUser.UserName)) &&
|
||||
member.Username != memberIdentityUser.UserName && memberIdentityUser.UserName.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.Username = memberIdentityUser.UserName;
|
||||
}
|
||||
|
||||
if (memberIdentityUser.IsPropertyDirty(nameof(MembersIdentityUser.PasswordHash))
|
||||
&& member.RawPasswordValue != memberIdentityUser.PasswordHash && memberIdentityUser.PasswordHash.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.RawPasswordValue = memberIdentityUser.PasswordHash;
|
||||
member.PasswordConfiguration = memberIdentityUser.PasswordConfig;
|
||||
}
|
||||
|
||||
// TODO: Roles
|
||||
// [Comment] Same comment as per BackOfficeUserStore: Fix this for Groups too
|
||||
//if (identityUser.IsPropertyDirty(nameof(UmbracoMembersIdentityUser.Roles)) || identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Groups)))
|
||||
//{
|
||||
// var userGroupAliases = member.Groups.Select(x => x.Alias).ToArray();
|
||||
|
||||
// var identityUserRoles = identityUser.Roles.Select(x => x.RoleId).ToArray();
|
||||
// var identityUserGroups = identityUser.Groups.Select(x => x.Alias).ToArray();
|
||||
|
||||
// var combinedAliases = identityUserRoles.Union(identityUserGroups).ToArray();
|
||||
|
||||
// if (userGroupAliases.ContainsAll(combinedAliases) == false
|
||||
// || combinedAliases.ContainsAll(userGroupAliases) == false)
|
||||
// {
|
||||
// anythingChanged = true;
|
||||
|
||||
// //clear out the current groups (need to ToArray since we are modifying the iterator)
|
||||
// member.ClearGroups();
|
||||
|
||||
// //go lookup all these groups
|
||||
// var groups = _userService.GetUserGroupsByAlias(combinedAliases).Select(x => x.ToReadOnlyGroup()).ToArray();
|
||||
|
||||
// //use all of the ones assigned and add them
|
||||
// foreach (var group in groups)
|
||||
// {
|
||||
// member.AddGroup(group);
|
||||
// }
|
||||
|
||||
// //re-assign
|
||||
// identityUser.Groups = groups;
|
||||
// }
|
||||
//}
|
||||
|
||||
memberIdentityUser.ResetDirtyProperties(false);
|
||||
return anythingChanged;
|
||||
}
|
||||
|
||||
public Task<IdentityResult> DeleteAsync(MembersIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<MembersIdentityUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<MembersIdentityUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: confirm logic
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
IMember member = _memberService.GetByUsername(normalizedUserName);
|
||||
if (member == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MembersIdentityUser result = _mapper.Map<MembersIdentityUser>(member);
|
||||
|
||||
return await Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<string> GetNormalizedUserNameAsync(MembersIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<string> GetUserIdAsync(MembersIdentityUser user, CancellationToken cancellationToken)
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityResult> UpdateAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
@@ -244,70 +116,10 @@ namespace Umbraco.Infrastructure.Security
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
return Task.FromResult(user.Id.ToString());
|
||||
}
|
||||
|
||||
public Task<string> GetUserNameAsync(MembersIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: unit tests for and implement all bodies
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
return Task.FromResult(user.UserName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the normalized user name
|
||||
/// </summary>
|
||||
/// <param name="user">The member identity user</param>
|
||||
/// <param name="normalizedName">The normalized member name</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>A task once complete</returns>
|
||||
public Task SetNormalizedUserNameAsync(MembersIdentityUser user, string normalizedName, CancellationToken cancellationToken) => SetUserNameAsync(user, normalizedName, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the user name as an async operation
|
||||
/// </summary>
|
||||
/// <param name="user">The member identity user</param>
|
||||
/// <param name="userName">The member user name</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>A task once complete</returns>
|
||||
public Task SetUserNameAsync(MembersIdentityUser user, string userName, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
user.UserName = userName;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the user asynchronously
|
||||
/// </summary>
|
||||
/// <param name="member">The member identity user</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>An identity result task</returns>
|
||||
public Task<IdentityResult> UpdateAsync(MembersIdentityUser member, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (member == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
|
||||
Attempt<int> asInt = member.Id.TryConvertTo<int>();
|
||||
Attempt<int> asInt = user.Id.TryConvertTo<int>();
|
||||
if (asInt == false)
|
||||
{
|
||||
throw new InvalidOperationException("The member id must be an integer to work with the Umbraco");
|
||||
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
|
||||
}
|
||||
|
||||
using (IScope scope = _scopeProvider.CreateScope())
|
||||
@@ -316,18 +128,20 @@ namespace Umbraco.Infrastructure.Security
|
||||
if (found != null)
|
||||
{
|
||||
// we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it.
|
||||
// var isLoginsPropertyDirty = member.IsPropertyDirty(nameof(UmbracoMembersIdentityUser.Logins));
|
||||
var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MembersIdentityUser.Logins));
|
||||
|
||||
if (UpdateMemberProperties(found, member))
|
||||
if (UpdateMemberProperties(found, user))
|
||||
{
|
||||
_memberService.Save(found);
|
||||
}
|
||||
|
||||
// TODO: when to implement external login service?
|
||||
|
||||
//if (isLoginsPropertyDirty)
|
||||
//{
|
||||
// _externalLoginService.Save(
|
||||
// found.Id,
|
||||
// member.Logins.Select(x => new ExternalLogin(
|
||||
// user.Logins.Select(x => new ExternalLogin(
|
||||
// x.LoginProvider,
|
||||
// x.ProviderKey,
|
||||
// x.UserData)));
|
||||
@@ -340,24 +154,8 @@ namespace Umbraco.Infrastructure.Security
|
||||
return Task.FromResult(IdentityResult.Success);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
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">The identity member user</param>
|
||||
/// <param name="passwordHash">The password hash</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <exception cref="ArgumentException">Throws if the properties are null</exception>
|
||||
/// <returns>Returns asynchronously</returns>
|
||||
public Task SetPasswordHashAsync(MembersIdentityUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken))
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityResult> DeleteAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
@@ -366,50 +164,271 @@ namespace Umbraco.Infrastructure.Security
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (passwordHash == null)
|
||||
IMember found = _memberService.GetById(UserIdToInt(user.Id));
|
||||
if (found != null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(passwordHash));
|
||||
_memberService.Delete(found);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(passwordHash))
|
||||
// TODO: when to implement external login service?
|
||||
//_externalLoginService.DeleteUserLogins(UserIdToInt(user.Id));
|
||||
|
||||
return Task.FromResult(IdentityResult.Success);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<MembersIdentityUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default) => FindUserAsync(userId, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<MembersIdentityUser> FindUserAsync(string userId, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
IMember user = _memberService.GetById(UserIdToInt(userId));
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty.", nameof(passwordHash));
|
||||
return Task.FromResult((MembersIdentityUser)null);
|
||||
}
|
||||
|
||||
user.PasswordHash = passwordHash;
|
||||
return Task.FromResult(AssignLoginsCallback(_mapper.Map<MembersIdentityUser>(user)));
|
||||
}
|
||||
|
||||
// Clear this so that it's reset at the repository level
|
||||
user.PasswordConfig = null;
|
||||
/// <inheritdoc />
|
||||
public override Task<MembersIdentityUser> FindByNameAsync(string userName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
IMember user = _memberService.GetByUsername(userName);
|
||||
if (user == null)
|
||||
{
|
||||
return Task.FromResult((MembersIdentityUser)null);
|
||||
}
|
||||
|
||||
MembersIdentityUser result = AssignLoginsCallback(_mapper.Map<MembersIdentityUser>(user));
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task SetPasswordHashAsync(MembersIdentityUser user, string passwordHash, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await base.SetPasswordHashAsync(user, passwordHash, cancellationToken);
|
||||
|
||||
user.PasswordConfig = null; // Clear this so that it's reset at the repository level
|
||||
user.LastPasswordChangeDateUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<bool> HasPasswordAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// This checks if it's null
|
||||
var result = await base.HasPasswordAsync(user, cancellationToken);
|
||||
if (result)
|
||||
{
|
||||
// we also want to check empty
|
||||
return string.IsNullOrEmpty(user.PasswordHash) == false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<MembersIdentityUser> FindByEmailAsync(string email, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
IMember member = _memberService.GetByEmail(email);
|
||||
MembersIdentityUser result = member == null
|
||||
? null
|
||||
: _mapper.Map<MembersIdentityUser>(member);
|
||||
|
||||
return Task.FromResult(AssignLoginsCallback(result));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetNormalizedEmailAsync(MembersIdentityUser user, CancellationToken cancellationToken)
|
||||
=> GetEmailAsync(user, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SetNormalizedEmailAsync(MembersIdentityUser user, string normalizedEmail, CancellationToken cancellationToken)
|
||||
=> SetEmailAsync(user, normalizedEmail, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task AddLoginAsync(MembersIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (login == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(login));
|
||||
}
|
||||
|
||||
ICollection<IIdentityUserLogin> logins = user.Logins;
|
||||
var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id.ToString());
|
||||
IdentityUserLogin userLogin = instance;
|
||||
logins.Add(userLogin);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task RemoveLoginAsync(MembersIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
IIdentityUserLogin userLogin = user.Logins.SingleOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey);
|
||||
if (userLogin != null)
|
||||
{
|
||||
user.Logins.Remove(userLogin);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IList<UserLoginInfo>> GetLoginsAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
return Task.FromResult((IList<UserLoginInfo>)user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.LoginProvider)).ToList());
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<IdentityUserLogin<string>> FindUserLoginAsync(string userId, string loginProvider, string providerKey, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
MembersIdentityUser user = await FindUserAsync(userId, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IList<UserLoginInfo> logins = await GetLoginsAsync(user, cancellationToken);
|
||||
UserLoginInfo found = logins.FirstOrDefault(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider);
|
||||
if (found == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new IdentityUserLogin<string>
|
||||
{
|
||||
LoginProvider = found.LoginProvider,
|
||||
ProviderKey = found.ProviderKey,
|
||||
ProviderDisplayName = found.ProviderDisplayName, // TODO: We don't store this value so it will be null
|
||||
UserId = user.Id
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<IdentityUserLogin<string>> FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
// TODO: external login needed?
|
||||
var logins = new List<IIdentityUserLogin>(); //_externalLoginService.Find(loginProvider, providerKey).ToList();
|
||||
if (logins.Count == 0)
|
||||
{
|
||||
return Task.FromResult((IdentityUserLogin<string>)null);
|
||||
}
|
||||
|
||||
IIdentityUserLogin found = logins[0];
|
||||
return Task.FromResult(new IdentityUserLogin<string>
|
||||
{
|
||||
LoginProvider = found.LoginProvider,
|
||||
ProviderKey = found.ProviderKey,
|
||||
ProviderDisplayName = null, // TODO: We don't store this value so it will be null
|
||||
UserId = found.UserId
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a user to a role (user group)
|
||||
/// </summary>
|
||||
public override Task AddToRoleAsync(MembersIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (normalizedRoleName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(normalizedRoleName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalizedRoleName))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName));
|
||||
}
|
||||
|
||||
IdentityUserRole<string> userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName);
|
||||
|
||||
if (userRole == null)
|
||||
{
|
||||
user.AddRole(normalizedRoleName);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user password hash
|
||||
/// Removes the role (user group) for the user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <returns/>
|
||||
public Task<string> GetPasswordHashAsync(MembersIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public override Task RemoveFromRoleAsync(MembersIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
return Task.FromResult(user.PasswordHash);
|
||||
if (normalizedRoleName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(normalizedRoleName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalizedRoleName))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName));
|
||||
}
|
||||
|
||||
IdentityUserRole<string> userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName);
|
||||
|
||||
if (userRole != null)
|
||||
{
|
||||
user.Roles.Remove(userRole);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a user has a password set
|
||||
/// Returns the roles (user groups) for this user
|
||||
/// </summary>
|
||||
/// <param name="user">The identity user</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>True if the user has a password</returns>
|
||||
public Task<bool> HasPasswordAsync(MembersIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public override Task<IList<string>> GetRolesAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
@@ -418,8 +437,268 @@ namespace Umbraco.Infrastructure.Security
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
return Task.FromResult(string.IsNullOrEmpty(user.PasswordHash) == false);
|
||||
return Task.FromResult((IList<string>)user.Roles.Select(x => x.RoleId).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a user is in the role
|
||||
/// </summary>
|
||||
public override Task<bool> IsInRoleAsync(MembersIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(normalizedRoleName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists all users of a given role.
|
||||
/// </summary>
|
||||
public override Task<IList<MembersIdentityUser>> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (normalizedRoleName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(normalizedRoleName));
|
||||
}
|
||||
|
||||
IEnumerable<IMember> members = _memberService.GetMembersByMemberType(normalizedRoleName);
|
||||
|
||||
IList<MembersIdentityUser> membersIdentityUsers = members.Select(x => _mapper.Map<MembersIdentityUser>(x)).ToList();
|
||||
|
||||
return Task.FromResult(membersIdentityUsers);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task<IdentityRole<string>> FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken)
|
||||
{
|
||||
IMemberGroup group = _memberService.GetAllRolesTyped().SingleOrDefault(x => x.Name == normalizedRoleName);
|
||||
if (group == null)
|
||||
{
|
||||
return Task.FromResult((IdentityRole<string>)null);
|
||||
}
|
||||
|
||||
return Task.FromResult(new IdentityRole<string>(group.Name)
|
||||
{
|
||||
//TODO: what should the alias be?
|
||||
Id = @group.Id.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<IdentityUserRole<string>> FindUserRoleAsync(string userId, string roleId, CancellationToken cancellationToken)
|
||||
{
|
||||
MembersIdentityUser user = await FindUserAsync(userId, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IdentityUserRole<string> found = user.Roles.FirstOrDefault(x => x.RoleId.InvariantEquals(roleId));
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetSecurityStampAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
// the stamp cannot be null, so if it is currently null then we'll just return a hash of the password
|
||||
return Task.FromResult(user.SecurityStamp.IsNullOrWhiteSpace()
|
||||
? user.PasswordHash.GenerateHash()
|
||||
: user.SecurityStamp);
|
||||
}
|
||||
|
||||
// TODO: share all possible between backoffice user
|
||||
|
||||
private MembersIdentityUser AssignLoginsCallback(MembersIdentityUser user)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
//TODO: when to
|
||||
//user.SetLoginsCallback(new Lazy<IEnumerable<IIdentityUserLogin>>(() => _externalLoginService.GetAll(UserIdToInt(user.Id))));
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private bool UpdateMemberProperties(IMember member, MembersIdentityUser identityUserMember)
|
||||
{
|
||||
var anythingChanged = false;
|
||||
|
||||
// don't assign anything if nothing has changed as this will trigger the track changes of the model
|
||||
if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.LastLoginDateUtc))
|
||||
|| (member.LastLoginDate != default && identityUserMember.LastLoginDateUtc.HasValue == false)
|
||||
|| (identityUserMember.LastLoginDateUtc.HasValue && member.LastLoginDate.ToUniversalTime() != identityUserMember.LastLoginDateUtc.Value))
|
||||
{
|
||||
anythingChanged = true;
|
||||
|
||||
// if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
|
||||
DateTime dt = identityUserMember.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUserMember.LastLoginDateUtc.Value.ToLocalTime();
|
||||
member.LastLoginDate = dt;
|
||||
}
|
||||
|
||||
if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.LastPasswordChangeDateUtc))
|
||||
|| (member.LastPasswordChangeDate != default && identityUserMember.LastPasswordChangeDateUtc.HasValue == false)
|
||||
|| (identityUserMember.LastPasswordChangeDateUtc.HasValue && member.LastPasswordChangeDate.ToUniversalTime() != identityUserMember.LastPasswordChangeDateUtc.Value))
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.LastPasswordChangeDate = identityUserMember.LastPasswordChangeDateUtc.Value.ToLocalTime();
|
||||
}
|
||||
|
||||
//if (identityUser.IsPropertyDirty(nameof(MembersIdentityUser.EmailConfirmed))
|
||||
// || (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default && identityUser.EmailConfirmed == false)
|
||||
// || ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default) && identityUser.EmailConfirmed))
|
||||
//{
|
||||
// anythingChanged = true;
|
||||
// user.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null;
|
||||
//}
|
||||
|
||||
if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Name))
|
||||
&& member.Name != identityUserMember.Name && identityUserMember.Name.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.Name = identityUserMember.Name;
|
||||
}
|
||||
|
||||
if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Email))
|
||||
&& member.Email != identityUserMember.Email && identityUserMember.Email.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.Email = identityUserMember.Email;
|
||||
}
|
||||
|
||||
if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.AccessFailedCount))
|
||||
&& member.FailedPasswordAttempts != identityUserMember.AccessFailedCount)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.FailedPasswordAttempts = identityUserMember.AccessFailedCount;
|
||||
}
|
||||
|
||||
if (member.IsLockedOut != identityUserMember.IsLockedOut)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.IsLockedOut = identityUserMember.IsLockedOut;
|
||||
|
||||
if (member.IsLockedOut)
|
||||
{
|
||||
// need to set the last lockout date
|
||||
member.LastLockoutDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.UserName))
|
||||
&& member.Username != identityUserMember.UserName && identityUserMember.UserName.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.Username = identityUserMember.UserName;
|
||||
}
|
||||
|
||||
if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.PasswordHash))
|
||||
&& member.RawPasswordValue != identityUserMember.PasswordHash && identityUserMember.PasswordHash.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
member.RawPasswordValue = identityUserMember.PasswordHash;
|
||||
member.PasswordConfiguration = identityUserMember.PasswordConfig;
|
||||
}
|
||||
|
||||
//if (user.SecurityStamp != identityUser.SecurityStamp)
|
||||
//{
|
||||
// anythingChanged = true;
|
||||
// user.SecurityStamp = identityUser.SecurityStamp;
|
||||
//}
|
||||
|
||||
// TODO: Fix this for Groups too (as per backoffice comment)
|
||||
if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Roles)) || identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Groups)))
|
||||
{
|
||||
}
|
||||
|
||||
// reset all changes
|
||||
identityUserMember.ResetDirtyProperties(false);
|
||||
|
||||
return anythingChanged;
|
||||
}
|
||||
|
||||
private static int UserIdToInt(string userId)
|
||||
{
|
||||
Attempt<int> attempt = userId.TryConvertTo<int>();
|
||||
if (attempt.Success)
|
||||
{
|
||||
return attempt.Result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Unable to convert user ID to int", attempt.Exception);
|
||||
}
|
||||
|
||||
private static string UserIdToString(int userId) => string.Intern(userId.ToString());
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override Task<IList<Claim>> GetClaimsAsync(MembersIdentityUser user, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override Task AddClaimsAsync(MembersIdentityUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override Task ReplaceClaimAsync(MembersIdentityUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override Task RemoveClaimsAsync(MembersIdentityUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override Task<IList<MembersIdentityUser>> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
// TODO: We should support these
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
protected override Task<IdentityUserToken<string>> FindTokenAsync(MembersIdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
protected override Task AddUserTokenAsync(IdentityUserToken<string> token) => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
protected override Task RemoveUserTokenAsync(IdentityUserToken<string> token) => throw new NotImplementedException();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,6 +938,20 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a strongly typed list of all member groups
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
|
||||
public IEnumerable<IMemberGroup> GetAllRolesTyped()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
return _memberGroupRepository.GetMany().Select(x=>x).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAllRoles(int memberId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -208,6 +208,19 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
|
||||
Assert.AreEqual(3, found.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_All_Roles_Typed()
|
||||
{
|
||||
MemberService.AddRole("MyTestRole1");
|
||||
MemberService.AddRole("MyTestRole2");
|
||||
MemberService.AddRole("MyTestRole3");
|
||||
|
||||
var found = MemberService.GetAllRolesTyped();
|
||||
|
||||
Assert.AreEqual(3, found.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_All_Roles_IDs()
|
||||
{
|
||||
|
||||
@@ -26,7 +26,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Members
|
||||
return new MembersUserStore(
|
||||
_mockMemberService.Object,
|
||||
new UmbracoMapper(new MapDefinitionCollection(new List<IMapDefinition>())),
|
||||
new Mock<IScopeProvider>().Object);
|
||||
new Mock<IScopeProvider>().Object,
|
||||
new IdentityErrorDescriber());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -81,7 +81,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(umbracoMembersUserManager, memberTypeService, memberMapDefinition, backOfficeSecurityAccessor, backOfficeSecurity, out UmbracoMapper mapper, out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
Member member = SetupMemberTestData(memberMapDefinition, out UmbracoMapper mapper, out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.CreateAsync(It.IsAny<MembersIdentityUser>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias");
|
||||
Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity);
|
||||
|
||||
Mock.Get(memberService).SetupSequence(
|
||||
x => x.GetByEmail(It.IsAny<string>()))
|
||||
@@ -110,7 +115,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(umbracoMembersUserManager, memberTypeService, memberMapDefinition, backOfficeSecurityAccessor, backOfficeSecurity, out UmbracoMapper mapper, out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.Save);
|
||||
Member member = SetupMemberTestData(memberMapDefinition, out UmbracoMapper mapper, out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.FindByIdAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => new MembersIdentityUser());
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.UpdateAsync(new MembersIdentityUser()));
|
||||
Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias");
|
||||
Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity);
|
||||
|
||||
Mock.Get(memberService).SetupSequence(
|
||||
x => x.GetByEmail(It.IsAny<string>()))
|
||||
@@ -139,7 +151,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(umbracoMembersUserManager, memberTypeService, memberMapDefinition, backOfficeSecurityAccessor, backOfficeSecurity, out UmbracoMapper mapper, out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
Member member = SetupMemberTestData(memberMapDefinition, out UmbracoMapper mapper, out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.CreateAsync(It.IsAny<MembersIdentityUser>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias");
|
||||
Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity);
|
||||
|
||||
Mock.Get(memberService).SetupSequence(
|
||||
x => x.GetByEmail(It.IsAny<string>()))
|
||||
@@ -158,22 +175,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// Setup all standard member data for test
|
||||
/// </summary>
|
||||
private Member SetupMemberTestData(IMembersUserManager umbracoMembersUserManager,
|
||||
IMemberTypeService memberTypeService,
|
||||
private Member SetupMemberTestData(
|
||||
MapDefinitionCollection memberMapDefinition,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity,
|
||||
out UmbracoMapper mapper,
|
||||
out MemberSave fakeMemberData,
|
||||
out MemberDisplay memberDisplay,
|
||||
ContentSaveAction contentAction)
|
||||
{
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.CreateAsync(It.IsAny<MembersIdentityUser>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias");
|
||||
Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity);
|
||||
|
||||
var memberType = new MemberType(new DefaultShortStringHelper(new DefaultShortStringHelperConfig()), int.MinValue);
|
||||
IMemberType testContentType = memberType;
|
||||
|
||||
|
||||
@@ -242,11 +242,10 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
throw new ArgumentNullException(nameof(contentItem));
|
||||
}
|
||||
|
||||
// TODO: this causes an issue when trying to correct an invalid model
|
||||
//if (ModelState.IsValid == false)
|
||||
//{
|
||||
// throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
|
||||
//}
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
|
||||
}
|
||||
|
||||
// If we've reached here it means:
|
||||
// * Our model has been bound
|
||||
@@ -380,6 +379,18 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
contentItem.Email,
|
||||
memberType.Alias,
|
||||
contentItem.Name);
|
||||
|
||||
//if (contentItem.Password != null && !contentItem.Password.NewPassword.IsNullOrWhiteSpace())
|
||||
//{
|
||||
// // TODO: should we show the password rules?
|
||||
// Task<bool> isPasswordValid = _memberManager.ValidatePasswordAsync(identityMember, contentItem.Password.NewPassword);
|
||||
// if (isPasswordValid.Result == false)
|
||||
// {
|
||||
// ModelState.AddPropertyError(
|
||||
// new ValidationResult($"Invalid password", new[] { "value" }),
|
||||
// $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password");
|
||||
// }
|
||||
//}
|
||||
|
||||
IdentityResult created = await _memberManager.CreateAsync(identityMember, contentItem.Password.NewPassword);
|
||||
|
||||
@@ -413,7 +424,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <param name="contentItem">The member to save</param>
|
||||
private async Task UpdateMemberDataAsync(MemberSave contentItem)
|
||||
{
|
||||
MembersIdentityUser identityMember = await _memberManager.FindByIdAsync(((int)contentItem.Id).ToString());
|
||||
MembersIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id.ToString());
|
||||
if (identityMember == null)
|
||||
{
|
||||
}
|
||||
@@ -497,18 +508,6 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
new ValidationResult("Email address is already in use", new[] { "value" }),
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email");
|
||||
}
|
||||
|
||||
if (contentItem.Password != null && !contentItem.Password.NewPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
//TODO: should we validate the password here, in advance? or when saving the identity user
|
||||
//Task<List<IdentityResult>> result = _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword);
|
||||
//if (result.Result.Exists(x => x.Succeeded == false))
|
||||
//{
|
||||
// ModelState.AddPropertyError(
|
||||
// new ValidationResult($"Invalid password: {MapErrors(result.Result)}", new[] { "value" }),
|
||||
// $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password");
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
private string MapErrors(List<IdentityResult> result)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Umbraco.Web.BackOffice.Trees
|
||||
/// <returns></returns>
|
||||
public ActionResult<TreeNode> GetTreeNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings)
|
||||
{
|
||||
//TODO: this is currently throwing an exception
|
||||
//TODO: this is currently throwing an exception when loading a member
|
||||
var node = GetSingleTreeNode(id, queryStrings);
|
||||
|
||||
//add the tree alias to the node since it is standalone (has no root for which this normally belongs)
|
||||
|
||||
@@ -40,50 +40,7 @@ namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default members user password checker
|
||||
/// </summary>
|
||||
public IMembersUserPasswordChecker MembersUserPasswordChecker { get; set; }
|
||||
// TODO: as per backoffice: This isn't a good way to set this, it needs to be injected
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <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(MembersIdentityUser user, string password)
|
||||
{
|
||||
if (MembersUserPasswordChecker != null)
|
||||
{
|
||||
MembersUserPasswordCheckerResult result = await MembersUserPasswordChecker.CheckPasswordAsync(user, password);
|
||||
|
||||
if (user.HasIdentity == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the result indicates to not fallback to the default, then return true if the credentials are valid
|
||||
if (result != MembersUserPasswordCheckerResult.FallbackToDefaultChecker)
|
||||
{
|
||||
return result == MembersUserPasswordCheckerResult.ValidCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
// use the default behavior
|
||||
return await base.CheckPasswordAsync(user, password);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user