Compare commits
75
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf64f39c33 | ||
|
|
6653b92bb3 | ||
|
|
0a8819a503 | ||
|
|
8a9c3d0c2e | ||
|
|
512361f504 | ||
|
|
5053c97e02 | ||
|
|
ecf3650f53 | ||
|
|
2cdbc6b54d | ||
|
|
0c787973ca | ||
|
|
69da581260 | ||
|
|
152ad9684c | ||
|
|
8caf2a0e62 | ||
|
|
6df67cd93e | ||
|
|
7dce742b96 | ||
|
|
af07790528 | ||
|
|
c8d1d16257 | ||
|
|
f30d6f4cad | ||
|
|
2893dcb8b8 | ||
|
|
6ff38582d6 | ||
|
|
ce43c979b5 | ||
|
|
90899c68f8 | ||
|
|
9b9a3d9197 | ||
|
|
c7725953f3 | ||
|
|
0d0a10d861 | ||
|
|
d224071dda | ||
|
|
7df0040127 | ||
|
|
74efd7fbda | ||
|
|
b8ef38d287 | ||
|
|
6dd5ae910a | ||
|
|
4cd9fcc36d | ||
|
|
9ba78497a0 | ||
|
|
1431a0a076 | ||
|
|
e5d7f53a1e | ||
|
|
5b4f9a5100 | ||
|
|
1a866a3b1e | ||
|
|
da8f8091fa | ||
|
|
d285f626a0 | ||
|
|
14b1046c17 | ||
|
|
d1a90ad0ff | ||
|
|
ba317b24dc | ||
|
|
c5c01146b5 | ||
|
|
67efe92858 | ||
|
|
2c006ea99f | ||
|
|
6fb8724886 | ||
|
|
3f0e2f9054 | ||
|
|
ec09c9b09f | ||
|
|
aa1fd298cd | ||
|
|
205edf57b7 | ||
|
|
cea1ed9771 | ||
|
|
2f7766a972 | ||
|
|
9531550e86 | ||
|
|
f24dad1f30 | ||
|
|
3f0e7ab315 | ||
|
|
bcccbd3c73 | ||
|
|
b59935c799 | ||
|
|
79dccc87b1 | ||
|
|
40f2a881ab | ||
|
|
0560bef48c | ||
|
|
57684a8672 | ||
|
|
785570761a | ||
|
|
cf2252f967 | ||
|
|
f1fbeb8ad3 | ||
|
|
55ddc8cc49 | ||
|
|
8a552a10d9 | ||
|
|
cf56731e42 | ||
|
|
fba4126a62 | ||
|
|
75bee025ce | ||
|
|
f0f17b3cf8 | ||
|
|
9c73429da7 | ||
|
|
9c41d335ea | ||
|
|
17df4eb607 | ||
|
|
5b4b948543 | ||
|
|
e7a21bcc18 | ||
|
|
8572d7bbd9 | ||
|
|
c95e560c54 |
@@ -3,7 +3,7 @@
|
||||
namespace Umbraco.Core.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The password configuration for back office users
|
||||
/// The password configuration for members
|
||||
/// </summary>
|
||||
public class MemberPasswordConfiguration : PasswordConfiguration, IMemberPasswordConfiguration
|
||||
{
|
||||
|
||||
@@ -41,6 +41,9 @@ namespace Umbraco.Core
|
||||
|
||||
public const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__";
|
||||
|
||||
public const string DefaultMemberTypeAlias = "Member";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used for external identity providers for their authentication type
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Core.Models.Mapping
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class MemberMapDefinition : IMapDefinition
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void DefineMaps(UmbracoMapper mapper) => mapper.Define<MemberSave, IMember>(Map);
|
||||
|
||||
private static void Map(MemberSave source, IMember target, MapperContext context)
|
||||
{
|
||||
target.IsApproved = source.IsApproved;
|
||||
target.Name = source.Name;
|
||||
target.Email = source.Email;
|
||||
target.Key = source.Key;
|
||||
target.Username = source.Username;
|
||||
target.Comments = source.Comments;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
target.Email = source.Email;
|
||||
|
||||
// TODO: ensure all properties are mapped as required
|
||||
//target.Id = source.Id;
|
||||
//target.ParentId = -1;
|
||||
//target.Path = "-1," + source.Id;
|
||||
|
||||
//TODO: add groups as required
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Security.Claims;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A custom user identity for the Umbraco backoffice
|
||||
/// </summary>
|
||||
|
||||
@@ -1,29 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
public interface IMembershipRoleService<out T>
|
||||
public interface IMembershipRoleService<out T>
|
||||
where T : class, IMembershipUser
|
||||
{
|
||||
void AddRole(string roleName);
|
||||
IEnumerable<string> GetAllRoles();
|
||||
|
||||
IEnumerable<IMemberGroup> GetAllRoles();
|
||||
|
||||
IEnumerable<string> GetAllRoles(int memberId);
|
||||
|
||||
IEnumerable<string> GetAllRoles(string username);
|
||||
|
||||
IEnumerable<int> GetAllRolesIds();
|
||||
|
||||
IEnumerable<int> GetAllRolesIds(int memberId);
|
||||
|
||||
IEnumerable<int> GetAllRolesIds(string username);
|
||||
|
||||
IEnumerable<T> GetMembersInRole(string roleName);
|
||||
|
||||
IEnumerable<T> FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
|
||||
|
||||
bool DeleteRole(string roleName, bool throwIfBeingUsed);
|
||||
|
||||
void AssignRole(string username, string roleName);
|
||||
|
||||
void AssignRoles(string[] usernames, string[] roleNames);
|
||||
|
||||
void DissociateRole(string username, string roleName);
|
||||
|
||||
void DissociateRoles(string[] usernames, string[] roleNames);
|
||||
|
||||
void AssignRole(int memberId, string roleName);
|
||||
|
||||
void AssignRoles(int[] memberIds, string[] roleNames);
|
||||
|
||||
void DissociateRole(int memberId, string roleName);
|
||||
|
||||
void DissociateRoles(int[] memberIds, string[] roleNames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Infrastructure.Services.Implement;
|
||||
|
||||
namespace Umbraco.Web.Cache
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
using Umbraco.Infrastructure.Services.Implement;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Core.Compose
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models.Mapping;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.Models.Mapping;
|
||||
|
||||
namespace Umbraco.Infrastructure.DependencyInjection
|
||||
@@ -30,6 +32,7 @@ namespace Umbraco.Infrastructure.DependencyInjection
|
||||
.Add<TagMapDefinition>()
|
||||
.Add<TemplateMapDefinition>()
|
||||
.Add<UserMapDefinition>()
|
||||
.Add<MemberMapDefinition>()
|
||||
.Add<LanguageMapDefinition>()
|
||||
.Add<IdentityMapDefinition>();
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Packaging;
|
||||
using Umbraco.Core.Routing;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Infrastructure.Services.Implement;
|
||||
|
||||
namespace Umbraco.Infrastructure.DependencyInjection
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.Install.Models;
|
||||
|
||||
namespace Umbraco.Web.Install.InstallSteps
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -336,7 +336,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
protected override void PersistUpdatedItem(IMember entity)
|
||||
{
|
||||
{
|
||||
// update
|
||||
entity.UpdatingEntity();
|
||||
|
||||
@@ -347,7 +347,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty("ParentId"))
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
NodeDto parent = GetParentNodeDto(entity.ParentId);
|
||||
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
@@ -355,10 +355,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
// create the dto
|
||||
var dto = ContentBaseFactory.BuildDto(entity);
|
||||
MemberDto dto = ContentBaseFactory.BuildDto(entity);
|
||||
|
||||
// update the node dto
|
||||
var nodeDto = dto.ContentDto.NodeDto;
|
||||
NodeDto nodeDto = dto.ContentDto.NodeDto;
|
||||
Database.Update(nodeDto);
|
||||
|
||||
// update the content dto
|
||||
@@ -409,7 +409,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
//get the group id
|
||||
var grpQry = Query<IMemberGroup>().Where(group => group.Name.Equals(roleName));
|
||||
var memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault();
|
||||
if (memberGroup == null) return Enumerable.Empty<IMember>();
|
||||
if (memberGroup == null)
|
||||
return Enumerable.Empty<IMember>();
|
||||
|
||||
// get the members by username
|
||||
var query = Query<IMember>();
|
||||
@@ -464,7 +465,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var grpQry = Query<IMemberGroup>().Where(group => group.Name.Equals(groupName));
|
||||
var memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault();
|
||||
if (memberGroup == null) return Enumerable.Empty<IMember>();
|
||||
if (memberGroup == null)
|
||||
return Enumerable.Empty<IMember>();
|
||||
|
||||
var subQuery = Sql().Select("Member").From<Member2MemberGroupDto>().Where<Member2MemberGroupDto>(dto => dto.MemberGroup == memberGroup.Id);
|
||||
|
||||
@@ -539,7 +541,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s
|
||||
.Select<ContentVersionDto>(x => x.Id)
|
||||
.From<ContentVersionDto>()
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
|
||||
@@ -614,7 +616,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Member) cached;
|
||||
content[i] = (Member)cached;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -656,7 +658,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
// get properties - indexed by version id
|
||||
var versionId = dto.ContentVersionDto.Id;
|
||||
var temp = new TempContent<Member>(dto.ContentDto.NodeId,versionId, 0, memberType);
|
||||
var temp = new TempContent<Member>(dto.ContentDto.NodeId, versionId, 0, memberType);
|
||||
var properties = GetPropertyCollections(new List<TempContent<Member>> { temp });
|
||||
member.Properties = properties[versionId];
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Infrastructure.Services.Implement;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
@@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="UserClaimsPrincipalFactory{TUser}" for the back office/>
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Reflection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
public class BackOfficeIdentityBuilder : IdentityBuilder
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Umbraco back office specific <see cref="IdentityErrorDescriber"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Identity options specifically for the back office identity implementation
|
||||
|
||||
@@ -2,12 +2,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// The identity user used for the back office
|
||||
@@ -93,7 +93,9 @@ namespace Umbraco.Core.Security
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password config
|
||||
/// </summary>
|
||||
public string PasswordConfig
|
||||
{
|
||||
get => _passwordConfig;
|
||||
@@ -186,13 +188,13 @@ namespace Umbraco.Core.Security
|
||||
{
|
||||
get
|
||||
{
|
||||
var isLocked = LockoutEnd.HasValue && LockoutEnd.Value.ToLocalTime() >= DateTime.Now;
|
||||
bool isLocked = LockoutEnd.HasValue && LockoutEnd.Value.ToLocalTime() >= DateTime.Now;
|
||||
return isLocked;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the IUser IsApproved
|
||||
/// Gets or sets a value indicating whether the IUser IsApproved
|
||||
/// </summary>
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -16,7 +17,7 @@ using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
// TODO: Make this into a base class that can be re-used
|
||||
|
||||
@@ -437,7 +438,7 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the roles (user groups) for this user
|
||||
/// Gets a list of role names the specified user belongs to.
|
||||
/// </summary>
|
||||
public override Task<IList<string>> GetRolesAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// The user manager for the back office
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by the BackOfficeUserManager to check the username/password which allows for developers to more easily
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// The user manager for members
|
||||
/// </summary>
|
||||
public interface IMembersUserManager : IUmbracoUserManager<MembersIdentityUser>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,11 @@ using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Security
|
||||
/// </summary>
|
||||
/// <typeparam name="TUser">The type of user</typeparam>
|
||||
public interface IUmbracoUserManager<TUser> : IDisposable
|
||||
where TUser : BackOfficeIdentityUser
|
||||
where TUser : UmbracoIdentityUser
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the user id of a user
|
||||
@@ -223,12 +223,64 @@ namespace Umbraco.Core.Security
|
||||
/// </returns>
|
||||
Task<IdentityResult> CreateAsync(TUser user);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of role names the specified user belongs to.
|
||||
/// </summary>
|
||||
/// <param name="user">The user whose role names to retrieve.</param>
|
||||
/// <returns>The Task that represents the asynchronous operation, containing a list of role names.</returns>
|
||||
Task<IList<string>> GetRolesAsync(TUser user);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified user from the named roles.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to remove from the named roles.</param>
|
||||
/// <param name="roles">The name of the roles to remove the user from.</param>
|
||||
/// <returns>The Task that represents the asynchronous operation, containing the IdentityResult of the operation.</returns>
|
||||
Task<IdentityResult> RemoveFromRolesAsync(TUser user, IEnumerable<string> roles);
|
||||
|
||||
/// <summary>
|
||||
/// Add the specified user to the named roles
|
||||
/// </summary>
|
||||
/// <param name="user">The user to add to the named roles</param>
|
||||
/// <param name="roles">The name of the roles to add the user to.</param>
|
||||
/// <returns>The Task that represents the asynchronous operation, containing the IdentityResult of the operation</returns>
|
||||
Task<IdentityResult> AddToRolesAsync(TUser user, IEnumerable<string> roles);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the specified <paramref name="user"/> in the backing store with a password,
|
||||
/// as an asynchronous operation.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create.</param>
|
||||
/// <param name="password">The password to add to the user.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
|
||||
/// of the operation.
|
||||
/// </returns>
|
||||
Task<IdentityResult> CreateAsync(TUser user, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Generate a password for a user based on the current password validator
|
||||
/// </summary>
|
||||
/// <returns>A generated password</returns>
|
||||
string GeneratePassword();
|
||||
|
||||
/// <summary>
|
||||
/// Hashes a password for a null user based on the default password hasher
|
||||
/// </summary>
|
||||
/// <param name="password">The password to hash</param>
|
||||
/// <returns>The hashed password</returns>
|
||||
string HashPassword(string password);
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate the password without an identity user
|
||||
/// Validation code is based on the default ValidatePasswordAsync code
|
||||
/// Should return <see cref="IdentityResult.Success"/> if validation is successful
|
||||
/// </summary>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <returns>A <see cref="IdentityResult"/> representing whether validation was successful.</returns>
|
||||
|
||||
Task<IdentityResult> ValidatePasswordAsync(string password);
|
||||
|
||||
/// <summary>
|
||||
/// Generates an email confirmation token for the specified user.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// An IUserStore interface part to implement if the store supports validating user session Ids
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
public static class IdentityExtensions
|
||||
{
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
public class IdentityMapDefinition : IMapDefinition
|
||||
{
|
||||
@@ -37,6 +37,20 @@ namespace Umbraco.Core.Security
|
||||
target.ResetDirtyProperties(true);
|
||||
target.EnableChangeTracking();
|
||||
});
|
||||
|
||||
mapper.Define<IMember, MembersIdentityUser>(
|
||||
(source, context) =>
|
||||
{
|
||||
var target = new MembersIdentityUser(source.Id);
|
||||
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,9 +90,24 @@ namespace Umbraco.Core.Security
|
||||
//target.Roles =;
|
||||
}
|
||||
|
||||
private static string GetPasswordHash(string storedPass)
|
||||
private void Map(IMember source, MembersIdentityUser target)
|
||||
{
|
||||
return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
|
||||
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.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null;
|
||||
|
||||
// NB: same comments re AutoMapper as per BackOfficeUser
|
||||
}
|
||||
|
||||
private static string GetPasswordHash(string storedPass) => storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom user store that uses Umbraco member data
|
||||
/// </summary>
|
||||
public class MemberRolesUserStore : RoleStoreBase<IdentityRole<string>, string, IdentityUserRole<string>, IdentityRoleClaim<string>>
|
||||
{
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly IMemberGroupService _memberGroupService;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
|
||||
public MemberRolesUserStore(IMemberService memberService, IMemberGroupService memberGroupService, IScopeProvider scopeProvider, IdentityErrorDescriber describer)
|
||||
: base(describer)
|
||||
{
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
|
||||
_memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService));
|
||||
_scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IQueryable<IdentityRole<string>> Roles { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityResult> CreateAsync(IdentityRole<string> role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityResult> UpdateAsync(IdentityRole<string> role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityResult> DeleteAsync(IdentityRole<string> role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityRole<string>> FindByIdAsync(string id, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityRole<string>> FindByNameAsync(string normalizedName, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IList<Claim>> GetClaimsAsync(IdentityRole<string> role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task AddClaimAsync(IdentityRole<string> role, Claim claim, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task RemoveClaimAsync(IdentityRole<string> role, Claim claim, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
public class MembersIdentityBuilder : IdentityBuilder
|
||||
{
|
||||
public MembersIdentityBuilder(IServiceCollection services) : base(typeof(MembersIdentityUser), services)
|
||||
{
|
||||
}
|
||||
|
||||
public MembersIdentityBuilder(Type role, IServiceCollection services) : base(typeof(MembersIdentityUser), role, services)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a token provider for the <seealso cref="MembersIdentityUser"/>.
|
||||
/// </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<MembersIdentityOptions>(options =>
|
||||
{
|
||||
options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider);
|
||||
});
|
||||
Services.AddTransient(provider);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Identity options specifically for the Umbraco members identity implementation
|
||||
/// </summary>
|
||||
public class MembersIdentityOptions : IdentityOptions
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// The identity user used for the member
|
||||
/// </summary>
|
||||
public class MembersIdentityUser : UmbracoIdentityUser
|
||||
{
|
||||
private string _name;
|
||||
private string _passwordConfig;
|
||||
private IReadOnlyCollection<IReadOnlyUserGroup> _groups;
|
||||
|
||||
// Custom comparer for enumerables
|
||||
private static readonly DelegateEqualityComparer<IReadOnlyCollection<IReadOnlyUserGroup>> s_groupsComparer = new DelegateEqualityComparer<IReadOnlyCollection<IReadOnlyUserGroup>>(
|
||||
(groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)),
|
||||
groups => groups.GetHashCode());
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MembersIdentityUser"/> class.
|
||||
/// </summary>
|
||||
public MembersIdentityUser(int userId)
|
||||
{
|
||||
// use the property setters - they do more than just setting a field
|
||||
Id = UserIdToString(userId);
|
||||
}
|
||||
|
||||
public MembersIdentityUser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to construct a new instance without an identity
|
||||
/// </summary>
|
||||
public static MembersIdentityUser CreateNew(string username, string email, string memberTypeAlias, string name = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(username));
|
||||
}
|
||||
|
||||
var user = new MembersIdentityUser();
|
||||
user.DisableChangeTracking();
|
||||
user.UserName = username;
|
||||
user.Email = email;
|
||||
user.MemberTypeAlias = memberTypeAlias;
|
||||
user.Id = null;
|
||||
user.HasIdentity = false;
|
||||
user._name = name;
|
||||
user.EnableChangeTracking();
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the member's real name
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password config
|
||||
/// </summary>
|
||||
public string PasswordConfig
|
||||
{
|
||||
get => _passwordConfig;
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfig));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user groups
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<IReadOnlyUserGroup> Groups
|
||||
{
|
||||
get => _groups;
|
||||
set
|
||||
{
|
||||
_groups = value.Where(x => x.Alias != null).ToArray();
|
||||
|
||||
var roles = new List<IdentityUserRole<string>>();
|
||||
foreach (IdentityUserRole<string> identityUserRole in _groups.Select(x => new IdentityUserRole<string>
|
||||
{
|
||||
RoleId = x.Alias,
|
||||
UserId = Id
|
||||
}))
|
||||
{
|
||||
roles.Add(identityUserRole);
|
||||
}
|
||||
|
||||
// now reset the collection
|
||||
Roles = roles;
|
||||
|
||||
BeingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), s_groupsComparer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the member is locked out
|
||||
/// </summary>
|
||||
public bool IsLockedOut
|
||||
{
|
||||
get
|
||||
{
|
||||
bool isLocked = LockoutEnd.HasValue && LockoutEnd.Value.ToLocalTime() >= DateTime.Now;
|
||||
return isLocked;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the member is approved
|
||||
/// </summary>
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias of the member type
|
||||
/// </summary>
|
||||
public string MemberTypeAlias { get; set; }
|
||||
|
||||
private static string UserIdToString(int userId) => string.Intern(userId.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,700 @@
|
||||
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;
|
||||
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom user store that uses Umbraco member data
|
||||
/// </summary>
|
||||
public class MembersUserStore : UserStoreBase<MembersIdentityUser, IdentityRole<string>, string, IdentityUserClaim<string>, IdentityUserRole<string>, IdentityUserLogin<string>, IdentityUserToken<string>, IdentityRoleClaim<string>>
|
||||
{
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly UmbracoMapper _mapper;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MembersUserStore"/> class for the members identity store
|
||||
/// </summary>
|
||||
/// <param name="memberService">The member service</param>
|
||||
/// <param name="mapper">The mapper for properties</param>
|
||||
/// <param name="scopeProvider">The scope provider</param>
|
||||
/// <param name="describer">The error describer</param>
|
||||
public MembersUserStore(IMemberService memberService, UmbracoMapper mapper, IScopeProvider scopeProvider, IdentityErrorDescriber describer)
|
||||
: base(describer)
|
||||
{
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
|
||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||
_scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not supported in Umbraco
|
||||
/// </summary>
|
||||
/// <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 = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
// create member
|
||||
IMember memberEntity = _memberService.CreateMember(
|
||||
user.UserName,
|
||||
user.Email,
|
||||
user.Name.IsNullOrWhiteSpace() ? user.UserName : user.Name,
|
||||
user.MemberTypeAlias.IsNullOrWhiteSpace() ? Constants.Security.DefaultMemberTypeAlias : user.MemberTypeAlias);
|
||||
|
||||
UpdateMemberProperties(memberEntity, user);
|
||||
|
||||
// create the member
|
||||
_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.
|
||||
// var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MembersIdentityUser.Logins));
|
||||
// TODO: confirm re externallogins implementation
|
||||
//if (isLoginsPropertyDirty)
|
||||
//{
|
||||
// _externalLoginService.Save(
|
||||
// user.Id,
|
||||
// user.Logins.Select(x => new ExternalLogin(
|
||||
// x.LoginProvider,
|
||||
// x.ProviderKey,
|
||||
// x.UserData)));
|
||||
//}
|
||||
|
||||
return Task.FromResult(IdentityResult.Success);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityResult> UpdateAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
Attempt<int> asInt = user.Id.TryConvertTo<int>();
|
||||
if (asInt == false)
|
||||
{
|
||||
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
|
||||
}
|
||||
|
||||
using (IScope scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
IMember found = _memberService.GetById(asInt.Result);
|
||||
if (found != null)
|
||||
{
|
||||
// we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it.
|
||||
var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MembersIdentityUser.Logins));
|
||||
|
||||
if (UpdateMemberProperties(found, user))
|
||||
{
|
||||
_memberService.Save(found);
|
||||
}
|
||||
|
||||
// TODO: when to implement external login service?
|
||||
|
||||
//if (isLoginsPropertyDirty)
|
||||
//{
|
||||
// _externalLoginService.Save(
|
||||
// found.Id,
|
||||
// user.Logins.Select(x => new ExternalLogin(
|
||||
// x.LoginProvider,
|
||||
// x.ProviderKey,
|
||||
// x.UserData)));
|
||||
//}
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return Task.FromResult(IdentityResult.Success);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IdentityResult> DeleteAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
IMember found = _memberService.GetById(UserIdToInt(user.Id));
|
||||
if (found != null)
|
||||
{
|
||||
_memberService.Delete(found);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
return Task.FromResult((MembersIdentityUser)null);
|
||||
}
|
||||
|
||||
return Task.FromResult(AssignLoginsCallback(_mapper.Map<MembersIdentityUser>(user)));
|
||||
}
|
||||
|
||||
/// <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 false;
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
var logins = new List<IIdentityUserLogin>();
|
||||
|
||||
// TODO: external login needed?
|
||||
//_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
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task AddToRoleAsync(MembersIdentityUser user, string role, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (role == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(role))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(role));
|
||||
}
|
||||
|
||||
IdentityUserRole<string> userRole = user.Roles.SingleOrDefault(r => r.RoleId == role);
|
||||
|
||||
if (userRole == null)
|
||||
{
|
||||
_memberService.AssignRole(user.UserName, role);
|
||||
user.AddRole(role);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task RemoveFromRoleAsync(MembersIdentityUser user, string role, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (role == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(role))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(role));
|
||||
}
|
||||
|
||||
IdentityUserRole<string> userRole = user.Roles.SingleOrDefault(r => r.RoleId == role);
|
||||
|
||||
if (userRole != null)
|
||||
{
|
||||
_memberService.DissociateRole(user.UserName, userRole.RoleId);
|
||||
user.Roles.Remove(userRole);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of role names the specified user belongs to.
|
||||
/// </summary>
|
||||
public override Task<IList<string>> GetRolesAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
IEnumerable<string> currentRoles = _memberService.GetAllRoles(user.UserName);
|
||||
ICollection<IdentityUserRole<string>> roles = currentRoles.Select(role => new IdentityUserRole<string>
|
||||
{
|
||||
RoleId = role,
|
||||
UserId = user.Id
|
||||
}).ToList();
|
||||
|
||||
user.Roles = roles;
|
||||
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.GetAllRoles().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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
/// <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();
|
||||
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// No-op lookup normalizer to maintain compatibility with ASP.NET Identity 2
|
||||
/// </summary>
|
||||
public class BackOfficeLookupNormalizer : ILookupNormalizer
|
||||
public class NoOpLookupNormalizer : ILookupNormalizer
|
||||
{
|
||||
// TODO: Do we need this?
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Event args used when signing out
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -7,11 +8,11 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class for Umbraco User Managers for back office users or front-end members
|
||||
/// </summary>
|
||||
@@ -33,12 +34,11 @@ namespace Umbraco.Core.Security
|
||||
IPasswordHasher<TUser> passwordHasher,
|
||||
IEnumerable<IUserValidator<TUser>> userValidators,
|
||||
IEnumerable<IPasswordValidator<TUser>> passwordValidators,
|
||||
ILookupNormalizer keyNormalizer,
|
||||
IdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
ILogger<UserManager<TUser>> logger,
|
||||
IOptions<TPasswordConfig> passwordConfiguration)
|
||||
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, new NoOpLookupNormalizer(), errors, services, logger)
|
||||
{
|
||||
IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver));
|
||||
PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration));
|
||||
@@ -49,7 +49,7 @@ namespace Umbraco.Core.Security
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool SupportsQueryableUsers => false; // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Developers will need to override this to support custom 2 factor auth
|
||||
/// </summary>
|
||||
@@ -73,8 +73,8 @@ namespace Umbraco.Core.Security
|
||||
/// Used to validate a user's session
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id</param>
|
||||
/// <param name="sessionId">The sesion id</param>
|
||||
/// <returns>True if the sesion is valid, else false</returns>
|
||||
/// <param name="sessionId">The session id</param>
|
||||
/// <returns>True if the session is valid, else false</returns>
|
||||
public virtual async Task<bool> ValidateSessionIdAsync(string userId, string sessionId)
|
||||
{
|
||||
var userSessionStore = Store as IUserSessionStore<TUser>;
|
||||
@@ -102,13 +102,58 @@ namespace Umbraco.Core.Security
|
||||
/// <returns>The generated password</returns>
|
||||
public string GeneratePassword()
|
||||
{
|
||||
if (_passwordGenerator == null)
|
||||
_passwordGenerator ??= new PasswordGenerator(PasswordConfiguration);
|
||||
|
||||
string password = _passwordGenerator.GeneratePassword();
|
||||
return password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a hashed password based on the default password hasher
|
||||
/// No existing identity user is required and this does not validate the password
|
||||
/// </summary>
|
||||
/// <param name="password">The password to hash</param>
|
||||
/// <returns>The hashed password</returns>
|
||||
public string HashPassword(string password)
|
||||
{
|
||||
IPasswordHasher<TUser> passwordHasher = GetDefaultPasswordHasher(PasswordConfiguration);
|
||||
|
||||
string hashedPassword = passwordHasher.HashPassword(null, password);
|
||||
return hashedPassword;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate the password without an identity user
|
||||
/// Validation code is based on the default ValidatePasswordAsync code
|
||||
/// Should return <see cref="IdentityResult.Success"/> if validation is successful
|
||||
/// </summary>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <returns>A <see cref="IdentityResult"/> representing whether validation was successful.</returns>
|
||||
public async Task<IdentityResult> ValidatePasswordAsync(string password)
|
||||
{
|
||||
var errors = new List<IdentityError>();
|
||||
var isValid = true;
|
||||
foreach (IPasswordValidator<TUser> v in PasswordValidators)
|
||||
{
|
||||
_passwordGenerator = new PasswordGenerator(PasswordConfiguration);
|
||||
IdentityResult result = await v.ValidateAsync(this, null, password);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
if (result.Errors.Any())
|
||||
{
|
||||
errors.AddRange(result.Errors);
|
||||
}
|
||||
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
var password = _passwordGenerator.GeneratePassword();
|
||||
return password;
|
||||
if (!isValid)
|
||||
{
|
||||
Logger.LogWarning(14, "Password validation failed: {errors}.", string.Join(";", errors.Select(e => e.Code)));
|
||||
return IdentityResult.Failed(errors.ToArray());
|
||||
}
|
||||
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -2,7 +2,7 @@ using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
namespace Umbraco.Infrastructure.Security
|
||||
{
|
||||
public class UserInviteEventArgs : IdentityAuditEventArgs
|
||||
{
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
namespace Umbraco.Infrastructure.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the MemberService.
|
||||
@@ -109,19 +110,20 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <param name="email">Email of the Member to create</param>
|
||||
/// <param name="name">Name of the Member to create</param>
|
||||
/// <param name="memberTypeAlias">Alias of the MemberType the Member should be based on</param>
|
||||
/// <exception cref="ArgumentException">Thrown when a member type for the given alias isn't found</exception>
|
||||
/// <returns><see cref="IMember"/></returns>
|
||||
public IMember CreateMember(string username, string email, string name, string memberTypeAlias)
|
||||
{
|
||||
var memberType = GetMemberType(memberTypeAlias);
|
||||
IMemberType memberType = GetMemberType(memberTypeAlias);
|
||||
if (memberType == null)
|
||||
{
|
||||
throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias));
|
||||
}
|
||||
|
||||
var member = new Member(name, email.ToLower().Trim(), username, memberType);
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
CreateMember(scope, member, 0, false);
|
||||
scope.Complete();
|
||||
}
|
||||
using IScope scope = ScopeProvider.CreateScope();
|
||||
CreateMember(scope, member, 0, false);
|
||||
scope.Complete();
|
||||
|
||||
return member;
|
||||
}
|
||||
@@ -312,7 +314,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
// if saving is cancelled, media remains without an identity
|
||||
var saveEventArgs = new SaveEventArgs<IMember>(member);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_memberRepository.Save(member);
|
||||
|
||||
@@ -321,7 +325,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
if (withIdentity == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Audit(AuditType.New, member.CreatorId, member.Id, $"Member '{member.Name}' was created with Id {member.Id}");
|
||||
}
|
||||
@@ -801,11 +807,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <inheritdoc />
|
||||
public void Save(IMember member, bool raiseEvents = true)
|
||||
{
|
||||
//trimming username and email to make sure we have no trailing space
|
||||
// trimming username and email to make sure we have no trailing space
|
||||
member.Username = member.Username.Trim();
|
||||
member.Email = member.Email.Trim();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
using (IScope scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IMember>(member);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
@@ -828,6 +834,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
Audit(AuditType.Save, 0, member.Id);
|
||||
|
||||
scope.Complete();
|
||||
@@ -924,18 +931,28 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAllRoles()
|
||||
/// <summary>
|
||||
/// Returns a list of all member roles
|
||||
/// </summary>
|
||||
/// <returns>A list of member roles</returns>
|
||||
|
||||
public IEnumerable<IMemberGroup> GetAllRoles()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
return _memberGroupRepository.GetMany().Select(x => x.Name).Distinct();
|
||||
return _memberGroupRepository.GetMany().Select(x=>x).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all member roles for a given member ID
|
||||
/// </summary>
|
||||
/// <param name="memberId"></param>
|
||||
/// <returns>A list of member roles</returns>
|
||||
public IEnumerable<string> GetAllRoles(int memberId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
var result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
|
||||
@@ -945,17 +962,17 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
public IEnumerable<string> GetAllRoles(string username)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
var result = _memberGroupRepository.GetMemberGroupsForMember(username);
|
||||
IEnumerable<IMemberGroup> result = _memberGroupRepository.GetMemberGroupsForMember(username);
|
||||
return result.Select(x => x.Name).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetAllRolesIds()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
return _memberGroupRepository.GetMany().Select(x => x.Id).Distinct();
|
||||
@@ -964,27 +981,27 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
public IEnumerable<int> GetAllRolesIds(int memberId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
var result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
|
||||
IEnumerable<IMemberGroup> result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
|
||||
return result.Select(x => x.Id).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetAllRolesIds(string username)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
var result = _memberGroupRepository.GetMemberGroupsForMember(username);
|
||||
IEnumerable<IMemberGroup> result = _memberGroupRepository.GetMemberGroupsForMember(username);
|
||||
return result.Select(x => x.Id).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IMember> GetMembersInRole(string roleName)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
return _memberRepository.GetByMemberGroup(roleName);
|
||||
@@ -993,7 +1010,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
public IEnumerable<IMember> FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
return _memberRepository.FindMembersInRole(roleName, usernameToMatch, matchType);
|
||||
@@ -1002,71 +1019,66 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
public bool DeleteRole(string roleName, bool throwIfBeingUsed)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
using (IScope scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MemberTree);
|
||||
|
||||
if (throwIfBeingUsed)
|
||||
{
|
||||
// get members in role
|
||||
var membersInRole = _memberRepository.GetByMemberGroup(roleName);
|
||||
IEnumerable<IMember> membersInRole = _memberRepository.GetByMemberGroup(roleName);
|
||||
if (membersInRole.Any())
|
||||
{
|
||||
throw new InvalidOperationException("The role " + roleName + " is currently assigned to members");
|
||||
}
|
||||
}
|
||||
|
||||
var query = Query<IMemberGroup>().Where(g => g.Name == roleName);
|
||||
var found = _memberGroupRepository.Get(query).ToArray();
|
||||
IQuery<IMemberGroup> query = Query<IMemberGroup>().Where(g => g.Name == roleName);
|
||||
IMemberGroup[] found = _memberGroupRepository.Get(query).ToArray();
|
||||
|
||||
foreach (var memberGroup in found)
|
||||
foreach (IMemberGroup memberGroup in found)
|
||||
{
|
||||
_memberGroupService.Delete(memberGroup);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
return found.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void AssignRole(string username, string roleName)
|
||||
{
|
||||
AssignRoles(new[] { username }, new[] { roleName });
|
||||
}
|
||||
public void AssignRole(string username, string roleName) => AssignRoles(new[] { username }, new[] { roleName });
|
||||
|
||||
public void AssignRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
using (IScope scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MemberTree);
|
||||
var ids = _memberGroupRepository.GetMemberIds(usernames);
|
||||
int[] ids = _memberGroupRepository.GetMemberIds(usernames);
|
||||
_memberGroupRepository.AssignRoles(ids, roleNames);
|
||||
scope.Events.Dispatch(AssignedRoles, this, new RolesEventArgs(ids, roleNames), nameof(AssignedRoles));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void DissociateRole(string username, string roleName)
|
||||
{
|
||||
DissociateRoles(new[] { username }, new[] { roleName });
|
||||
}
|
||||
public void DissociateRole(string username, string roleName) => DissociateRoles(new[] { username }, new[] { roleName });
|
||||
|
||||
public void DissociateRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
using (IScope scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MemberTree);
|
||||
var ids = _memberGroupRepository.GetMemberIds(usernames);
|
||||
int[] ids = _memberGroupRepository.GetMemberIds(usernames);
|
||||
_memberGroupRepository.DissociateRoles(ids, roleNames);
|
||||
scope.Events.Dispatch(RemovedRoles, this, new RolesEventArgs(ids, roleNames), nameof(RemovedRoles));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void AssignRole(int memberId, string roleName)
|
||||
{
|
||||
AssignRoles(new[] { memberId }, new[] { roleName });
|
||||
}
|
||||
public void AssignRole(int memberId, string roleName) => AssignRoles(new[] { memberId }, new[] { roleName });
|
||||
|
||||
public void AssignRoles(int[] memberIds, string[] roleNames)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
using (IScope scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MemberTree);
|
||||
_memberGroupRepository.AssignRoles(memberIds, roleNames);
|
||||
@@ -1075,14 +1087,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
public void DissociateRole(int memberId, string roleName)
|
||||
{
|
||||
DissociateRoles(new[] { memberId }, new[] { roleName });
|
||||
}
|
||||
public void DissociateRole(int memberId, string roleName) => DissociateRoles(new[] { memberId }, new[] { roleName });
|
||||
|
||||
public void DissociateRoles(int[] memberIds, string[] roleNames)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
using (IScope scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MemberTree);
|
||||
_memberGroupRepository.DissociateRoles(memberIds, roleNames);
|
||||
@@ -1095,10 +1104,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void Audit(AuditType type, int userId, int objectId, string message = null)
|
||||
{
|
||||
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Member), message));
|
||||
}
|
||||
private void Audit(AuditType type, int userId, int objectId, string message = null) => _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Member), message));
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1153,12 +1159,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// </remarks>
|
||||
public MemberExportModel ExportMember(Guid key)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IMember>().Where(x => x.Key == key);
|
||||
var member = _memberRepository.Get(query).FirstOrDefault();
|
||||
IQuery<IMember> query = Query<IMember>().Where(x => x.Key == key);
|
||||
IMember member = _memberRepository.Get(query).FirstOrDefault();
|
||||
|
||||
if (member == null) return null;
|
||||
if (member == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var model = new MemberExportModel
|
||||
{
|
||||
@@ -1182,11 +1191,14 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
private static IEnumerable<MemberExportProperty> GetPropertyExportItems(IMember member)
|
||||
{
|
||||
if (member == null) throw new ArgumentNullException(nameof(member));
|
||||
if (member == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
|
||||
var exportProperties = new List<MemberExportProperty>();
|
||||
|
||||
foreach (var property in member.Properties)
|
||||
foreach (IProperty property in member.Properties)
|
||||
{
|
||||
var propertyExportModel = new MemberExportProperty
|
||||
{
|
||||
@@ -1214,15 +1226,14 @@ namespace Umbraco.Core.Services.Implement
|
||||
public void DeleteMembersOfType(int memberTypeId)
|
||||
{
|
||||
// note: no tree to manage here
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
using (IScope scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MemberTree);
|
||||
|
||||
// TODO: What about content that has the contenttype as part of its composition?
|
||||
var query = Query<IMember>().Where(x => x.ContentTypeId == memberTypeId);
|
||||
IQuery<IMember> query = Query<IMember>().Where(x => x.ContentTypeId == memberTypeId);
|
||||
|
||||
var members = _memberRepository.Get(query).ToArray();
|
||||
IMember[] members = _memberRepository.Get(query).ToArray();
|
||||
var deleteEventArgs = new DeleteEventArgs<IMember>(members);
|
||||
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
|
||||
@@ -1231,43 +1242,58 @@ namespace Umbraco.Core.Services.Implement
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var member in members)
|
||||
foreach (IMember member in members)
|
||||
{
|
||||
// delete media
|
||||
// triggers the deleted event (and handles the files)
|
||||
DeleteLocked(scope, member);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private IMemberType GetMemberType(IScope scope, string memberTypeAlias)
|
||||
{
|
||||
if (memberTypeAlias == null) throw new ArgumentNullException(nameof(memberTypeAlias));
|
||||
if (string.IsNullOrWhiteSpace(memberTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias));
|
||||
if (memberTypeAlias == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(memberTypeAlias));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(memberTypeAlias))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias));
|
||||
}
|
||||
|
||||
scope.ReadLock(Constants.Locks.MemberTypes);
|
||||
|
||||
var memberType = _memberTypeRepository.Get(memberTypeAlias);
|
||||
IMemberType memberType = _memberTypeRepository.Get(memberTypeAlias);
|
||||
|
||||
if (memberType == null)
|
||||
{
|
||||
throw new Exception($"No MemberType matching the passed in Alias: '{memberTypeAlias}' was found"); // causes rollback
|
||||
}
|
||||
|
||||
return memberType;
|
||||
}
|
||||
|
||||
private IMemberType GetMemberType(string memberTypeAlias)
|
||||
{
|
||||
if (memberTypeAlias == null) throw new ArgumentNullException(nameof(memberTypeAlias));
|
||||
if (string.IsNullOrWhiteSpace(memberTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias));
|
||||
if (memberTypeAlias == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(memberTypeAlias));
|
||||
}
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
if (string.IsNullOrWhiteSpace(memberTypeAlias))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias));
|
||||
}
|
||||
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return GetMemberType(scope, memberTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.Common.Security;
|
||||
|
||||
namespace Umbraco.Tests.Integration.TestServerTest
|
||||
|
||||
@@ -158,6 +158,7 @@ namespace Umbraco.Tests.Integration.TestServerTest
|
||||
.AddBackOfficeCore()
|
||||
.AddBackOfficeAuthentication()
|
||||
.AddBackOfficeIdentity()
|
||||
.AddMembersIdentity()
|
||||
.AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme)
|
||||
.AddPreviewSupport()
|
||||
.AddMvcAndRazor(mvcBuilding: mvcBuilder =>
|
||||
|
||||
@@ -229,6 +229,8 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
}
|
||||
|
||||
services.AddSignalR();
|
||||
builder.AddMembersIdentity();
|
||||
|
||||
services.AddMvc();
|
||||
|
||||
CustomTestSetup(builder);
|
||||
|
||||
@@ -183,10 +183,10 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
{
|
||||
MemberService.AddRole("MyTestRole");
|
||||
|
||||
IEnumerable<string> found = MemberService.GetAllRoles();
|
||||
IEnumerable<IMemberGroup> found = MemberService.GetAllRoles();
|
||||
|
||||
Assert.AreEqual(1, found.Count());
|
||||
Assert.AreEqual("MyTestRole", found.Single());
|
||||
Assert.AreEqual("MyTestRole", found.Single().Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -195,10 +195,10 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
MemberService.AddRole("MyTestRole");
|
||||
MemberService.AddRole("MyTestRole");
|
||||
|
||||
IEnumerable<string> found = MemberService.GetAllRoles();
|
||||
IEnumerable<IMemberGroup> found = MemberService.GetAllRoles();
|
||||
|
||||
Assert.AreEqual(1, found.Count());
|
||||
Assert.AreEqual("MyTestRole", found.Single());
|
||||
Assert.AreEqual("MyTestRole", found.Single().Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -208,7 +208,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
MemberService.AddRole("MyTestRole2");
|
||||
MemberService.AddRole("MyTestRole3");
|
||||
|
||||
IEnumerable<string> found = MemberService.GetAllRoles();
|
||||
IEnumerable<IMemberGroup> found = MemberService.GetAllRoles();
|
||||
|
||||
Assert.AreEqual(3, found.Count());
|
||||
}
|
||||
@@ -292,7 +292,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
|
||||
MemberService.DeleteRole("MyTestRole1", false);
|
||||
|
||||
IEnumerable<string> memberRoles = MemberService.GetAllRoles();
|
||||
IEnumerable<IMemberGroup> memberRoles = MemberService.GetAllRoles();
|
||||
|
||||
Assert.AreEqual(0, memberRoles.Count());
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Tests.Integration.Testing;
|
||||
using Umbraco.Web.BackOffice.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice
|
||||
{
|
||||
[TestFixture]
|
||||
public class MembersServiceCollectionExtensionsTests : UmbracoIntegrationTest
|
||||
{
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.Services.AddMembersIdentity();
|
||||
|
||||
[Test]
|
||||
public void AddMembersIdentity_ExpectMembersUserStoreResolvable()
|
||||
{
|
||||
IUserStore<MembersIdentityUser> userStore = Services.GetService<IUserStore<MembersIdentityUser>>();
|
||||
|
||||
Assert.IsNotNull(userStore);
|
||||
Assert.AreEqual(typeof(MembersUserStore), userStore.GetType());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddMembersIdentity_ExpectMembersUserManagerResolvable()
|
||||
{
|
||||
IMembersUserManager userManager = Services.GetService<IMembersUserManager>();
|
||||
|
||||
Assert.NotNull(userManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Tests.Integration.Testing;
|
||||
using Umbraco.Web.BackOffice.DependencyInjection;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.BackOffice.Routing;
|
||||
using Umbraco.Web.Common.Install;
|
||||
@@ -51,8 +52,10 @@ namespace Umbraco.Tests.UnitTests.AutoFixture
|
||||
.Customize(new ConstructorCustomization(typeof(UsersController), new GreedyConstructorQuery()))
|
||||
.Customize(new ConstructorCustomization(typeof(InstallController), new GreedyConstructorQuery()))
|
||||
.Customize(new ConstructorCustomization(typeof(PreviewController), new GreedyConstructorQuery()))
|
||||
.Customize(new ConstructorCustomization(typeof(MemberController), new GreedyConstructorQuery()))
|
||||
.Customize(new ConstructorCustomization(typeof(BackOfficeController), new GreedyConstructorQuery()))
|
||||
.Customize(new ConstructorCustomization(typeof(BackOfficeUserManager), new GreedyConstructorQuery()));
|
||||
.Customize(new ConstructorCustomization(typeof(BackOfficeUserManager), new GreedyConstructorQuery()))
|
||||
.Customize(new ConstructorCustomization(typeof(MembersUserManager), new GreedyConstructorQuery()));
|
||||
|
||||
fixture.Customize(new AutoMoqCustomization());
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
{
|
||||
|
||||
+2
-1
@@ -14,8 +14,9 @@ using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.BackOffice
|
||||
{
|
||||
[TestFixture]
|
||||
public class BackOfficeClaimsPrincipalFactoryTests
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice
|
||||
{
|
||||
public class BackOfficeLookupNormalizerTests
|
||||
{
|
||||
[Test]
|
||||
[TestCase(null)]
|
||||
[TestCase("")]
|
||||
[TestCase(" ")]
|
||||
public void NormalizeName_When_Name_Null_Or_Whitespace_Expect_Same_Returned(string name)
|
||||
{
|
||||
var sut = new BackOfficeLookupNormalizer();
|
||||
|
||||
var normalizedName = sut.NormalizeName(name);
|
||||
|
||||
Assert.AreEqual(name, normalizedName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NormalizeName_Expect_Input_Returned()
|
||||
{
|
||||
var name = Guid.NewGuid().ToString();
|
||||
var sut = new BackOfficeLookupNormalizer();
|
||||
|
||||
var normalizedName = sut.NormalizeName(name);
|
||||
|
||||
Assert.AreEqual(name, normalizedName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)]
|
||||
[TestCase("")]
|
||||
[TestCase(" ")]
|
||||
public void NormalizeEmail_When_Name_Null_Or_Whitespace_Expect_Same_Returned(string email)
|
||||
{
|
||||
var sut = new BackOfficeLookupNormalizer();
|
||||
|
||||
var normalizedEmail = sut.NormalizeEmail(email);
|
||||
|
||||
Assert.AreEqual(email, normalizedEmail);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NormalizeEmail_Expect_Input_Returned()
|
||||
{
|
||||
var email = $"{Guid.NewGuid()}@umbraco";
|
||||
var sut = new BackOfficeLookupNormalizer();
|
||||
|
||||
var normalizedEmail = sut.NormalizeEmail(email);
|
||||
|
||||
Assert.AreEqual(email, normalizedEmail);
|
||||
}
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
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.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.Common.Security;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Security
|
||||
{
|
||||
[TestFixture]
|
||||
public class MemberIdentityUserManagerTests
|
||||
{
|
||||
private Mock<IUserStore<MembersIdentityUser>> _mockMemberStore;
|
||||
private Mock<IOptions<MembersIdentityOptions>> _mockIdentityOptions;
|
||||
private Mock<IPasswordHasher<MembersIdentityUser>> _mockPasswordHasher;
|
||||
private Mock<IUserValidator<MembersIdentityUser>> _mockUserValidators;
|
||||
private Mock<IEnumerable<IPasswordValidator<MembersIdentityUser>>> _mockPasswordValidators;
|
||||
private Mock<ILookupNormalizer> _mockNormalizer;
|
||||
private IdentityErrorDescriber _mockErrorDescriber;
|
||||
private Mock<IServiceProvider> _mockServiceProviders;
|
||||
private Mock<ILogger<UserManager<MembersIdentityUser>>> _mockLogger;
|
||||
private Mock<IOptions<MemberPasswordConfigurationSettings>> _mockPasswordConfiguration;
|
||||
|
||||
public MembersUserManager CreateSut()
|
||||
{
|
||||
_mockMemberStore = new Mock<IUserStore<MembersIdentityUser>>();
|
||||
_mockIdentityOptions = new Mock<IOptions<MembersIdentityOptions>>();
|
||||
|
||||
var idOptions = new MembersIdentityOptions { Lockout = { AllowedForNewUsers = false } };
|
||||
_mockIdentityOptions.Setup(o => o.Value).Returns(idOptions);
|
||||
_mockPasswordHasher = new Mock<IPasswordHasher<MembersIdentityUser>>();
|
||||
|
||||
var userValidators = new List<IUserValidator<MembersIdentityUser>>();
|
||||
_mockUserValidators = new Mock<IUserValidator<MembersIdentityUser>>();
|
||||
var validator = new Mock<IUserValidator<MembersIdentityUser>>();
|
||||
userValidators.Add(validator.Object);
|
||||
|
||||
_mockPasswordValidators = new Mock<IEnumerable<IPasswordValidator<MembersIdentityUser>>>();
|
||||
_mockNormalizer = new Mock<ILookupNormalizer>();
|
||||
_mockErrorDescriber = new IdentityErrorDescriber();
|
||||
_mockServiceProviders = new Mock<IServiceProvider>();
|
||||
_mockLogger = new Mock<ILogger<UserManager<MembersIdentityUser>>>();
|
||||
_mockPasswordConfiguration = new Mock<IOptions<MemberPasswordConfigurationSettings>>();
|
||||
_mockPasswordConfiguration.Setup(x => x.Value).Returns(() =>
|
||||
new MemberPasswordConfigurationSettings()
|
||||
{
|
||||
|
||||
});
|
||||
|
||||
var pwdValidators = new List<PasswordValidator<MembersIdentityUser>>
|
||||
{
|
||||
new PasswordValidator<MembersIdentityUser>()
|
||||
};
|
||||
|
||||
var userManager = new MembersUserManager(
|
||||
new Mock<IIpResolver>().Object,
|
||||
_mockMemberStore.Object,
|
||||
_mockIdentityOptions.Object,
|
||||
_mockPasswordHasher.Object,
|
||||
userValidators,
|
||||
pwdValidators,
|
||||
new BackOfficeIdentityErrorDescriber(),
|
||||
_mockServiceProviders.Object,
|
||||
new Mock<IHttpContextAccessor>().Object,
|
||||
new Mock<ILogger<UserManager<MembersIdentityUser>>>().Object,
|
||||
_mockPasswordConfiguration.Object);
|
||||
|
||||
validator.Setup(v => v.ValidateAsync(
|
||||
userManager,
|
||||
It.IsAny<MembersIdentityUser>()))
|
||||
.Returns(Task.FromResult(IdentityResult.Success)).Verifiable();
|
||||
|
||||
return userManager;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GivenICreateUser_AndTheIdentityResultFailed_ThenIShouldGetAFailedResultAsync()
|
||||
{
|
||||
//arrange
|
||||
MembersUserManager sut = CreateSut();
|
||||
MembersIdentityUser fakeUser = new MembersIdentityUser()
|
||||
{
|
||||
PasswordConfig = "testConfig"
|
||||
};
|
||||
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
|
||||
MembersUserManager sut = CreateSut();
|
||||
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
|
||||
MembersUserManager sut = CreateSut();
|
||||
MembersIdentityUser fakeUser = new MembersIdentityUser()
|
||||
{
|
||||
PasswordConfig = "testConfig"
|
||||
};
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Security
|
||||
{
|
||||
[TestFixture]
|
||||
public class MemberIdentityUserStoreTests
|
||||
{
|
||||
private Mock<IMemberService> _mockMemberService;
|
||||
|
||||
public MembersUserStore CreateSut()
|
||||
{
|
||||
_mockMemberService = new Mock<IMemberService>();
|
||||
return new MembersUserStore(
|
||||
_mockMemberService.Object,
|
||||
new UmbracoMapper(new MapDefinitionCollection(new List<IMapDefinition>())),
|
||||
new Mock<IScopeProvider>().Object,
|
||||
new IdentityErrorDescriber());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GivenICreateUser_AndTheUserIsNull_ThenIShouldGetAFailedResultAsync()
|
||||
{
|
||||
// arrange
|
||||
MembersUserStore sut = CreateSut();
|
||||
CancellationToken fakeCancellationToken = new CancellationToken(){};
|
||||
|
||||
// act
|
||||
Action actual = () => sut.CreateAsync(null, fakeCancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.That(actual, Throws.ArgumentNullException);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task GivenICreateANewUser_AndTheUserIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync()
|
||||
{
|
||||
// arrange
|
||||
MembersUserStore sut = CreateSut();
|
||||
var fakeUser = new MembersIdentityUser() { };
|
||||
var fakeCancellationToken = new CancellationToken() { };
|
||||
|
||||
IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77);
|
||||
IMember mockMember = Mock.Of<IMember>(m =>
|
||||
m.Name == "fakeName" &&
|
||||
m.Email == "fakeemail@umbraco.com" &&
|
||||
m.Username == "fakeUsername" &&
|
||||
m.RawPasswordValue == "fakePassword" &&
|
||||
m.ContentTypeAlias == fakeMemberType.Alias &&
|
||||
m.HasIdentity == true);
|
||||
|
||||
bool raiseEvents = false;
|
||||
|
||||
_mockMemberService.Setup(x => x.CreateMember(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(mockMember);
|
||||
_mockMemberService.Setup(x => x.Save(mockMember, raiseEvents));
|
||||
|
||||
// act
|
||||
IdentityResult identityResult = await sut.CreateAsync(fakeUser, fakeCancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(identityResult.Succeeded);
|
||||
Assert.IsTrue(!identityResult.Errors.Any());
|
||||
}
|
||||
|
||||
//GetPasswordHashAsync
|
||||
//GetUserIdAsync
|
||||
}
|
||||
}
|
||||
+23
-23
@@ -3,19 +3,17 @@
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Security
|
||||
{
|
||||
public class NopLookupNormalizerTests
|
||||
public class NoOpLookupNormalizerTests
|
||||
{
|
||||
[Test]
|
||||
[TestCase(null)]
|
||||
[TestCase("")]
|
||||
[TestCase(" ")]
|
||||
public void NormalizeName_When_Name_Null_Or_Whitespace_Expect_Same_Returned(string name)
|
||||
public void NormalizeName_Expect_Input_Returned()
|
||||
{
|
||||
var sut = new BackOfficeLookupNormalizer();
|
||||
var name = Guid.NewGuid().ToString();
|
||||
var sut = new NoOpLookupNormalizer();
|
||||
|
||||
var normalizedName = sut.NormalizeName(name);
|
||||
|
||||
@@ -23,10 +21,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NormalizeName_Expect_Input_Returned()
|
||||
public void NormalizeEmail_Expect_Input_Returned()
|
||||
{
|
||||
var name = Guid.NewGuid().ToString();
|
||||
var sut = new BackOfficeLookupNormalizer();
|
||||
var email = $"{Guid.NewGuid()}@umbraco";
|
||||
var sut = new NoOpLookupNormalizer();
|
||||
|
||||
var normalizedEmail = sut.NormalizeEmail(email);
|
||||
|
||||
Assert.AreEqual(email, normalizedEmail);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)]
|
||||
[TestCase("")]
|
||||
[TestCase(" ")]
|
||||
public void NormalizeName_When_Name_Null_Or_Whitespace_Expect_Same_Returned(string name)
|
||||
{
|
||||
var sut = new NoOpLookupNormalizer();
|
||||
|
||||
var normalizedName = sut.NormalizeName(name);
|
||||
|
||||
@@ -39,18 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
[TestCase(" ")]
|
||||
public void NormalizeEmail_When_Name_Null_Or_Whitespace_Expect_Same_Returned(string email)
|
||||
{
|
||||
var sut = new BackOfficeLookupNormalizer();
|
||||
|
||||
var normalizedEmail = sut.NormalizeEmail(email);
|
||||
|
||||
Assert.AreEqual(email, normalizedEmail);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NormalizeEmail_Expect_Input_Returned()
|
||||
{
|
||||
var email = $"{Guid.NewGuid()}@umbraco";
|
||||
var sut = new BackOfficeLookupNormalizer();
|
||||
var sut = new NoOpLookupNormalizer();
|
||||
|
||||
var normalizedEmail = sut.NormalizeEmail(email);
|
||||
|
||||
+564
@@ -0,0 +1,564 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Common;
|
||||
using AutoFixture.NUnit3;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Tests.Common.Builders;
|
||||
using Umbraco.Tests.UnitTests.AutoFixture;
|
||||
using Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.BackOffice.Mapping;
|
||||
using Umbraco.Web.Common.ActionsResults;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.Mapping;
|
||||
using Umbraco.Web.PropertyEditors;
|
||||
using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[TestFixture]
|
||||
public class MemberControllerUnitTests
|
||||
{
|
||||
private UmbracoMapper _mapper;
|
||||
|
||||
[Test]
|
||||
[AutoMoqData]
|
||||
public void PostSaveMember_WhenMemberIsNull_ExpectFailureResponse(
|
||||
MemberController sut)
|
||||
{
|
||||
// arrange
|
||||
// act
|
||||
ArgumentNullException exception = Assert.ThrowsAsync<ArgumentNullException>(() => sut.PostSave(null));
|
||||
|
||||
// assert
|
||||
Assert.That(exception.Message, Is.EqualTo("Value cannot be null. (Parameter 'The member content item was null')"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoMoqData]
|
||||
public void PostSaveMember_WhenModelStateIsNotValid_ExpectFailureResponse(
|
||||
[Frozen] IMembersUserManager umbracoMembersUserManager,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
sut.ModelState.AddModelError("key", "Invalid model state");
|
||||
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.CreateAsync(It.IsAny<MembersIdentityUser>(), It.IsAny<string>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.ValidatePasswordAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
|
||||
var value = new MemberDisplay();
|
||||
string reason = "Validation failed";
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = sut.PostSave(fakeMemberData).Result;
|
||||
var validation = result.Result as ValidationErrorResult;
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result.Result);
|
||||
Assert.IsNull(result.Value);
|
||||
Assert.AreEqual(StatusCodes.Status400BadRequest, validation?.StatusCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoMoqData]
|
||||
public async Task PostSaveMember_SaveNew_NoCustomField_WhenAllIsSetupCorrectly_ExpectSuccessResponse(
|
||||
[Frozen] IMembersUserManager umbracoMembersUserManager,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.CreateAsync(It.IsAny<MembersIdentityUser>(), It.IsAny<string>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.ValidatePasswordAsync(It.IsAny<string>()))
|
||||
.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>()))
|
||||
.Returns(() => null)
|
||||
.Returns(() => member);
|
||||
Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny<string>())).Returns(() => member);
|
||||
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = await sut.PostSave(fakeMemberData);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(result.Result);
|
||||
Assert.IsNotNull(result.Value);
|
||||
AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoMoqData]
|
||||
public async Task PostSaveMember_SaveNew_CustomField_WhenAllIsSetupCorrectly_ExpectSuccessResponse(
|
||||
[Frozen] IMembersUserManager umbracoMembersUserManager,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.CreateAsync(It.IsAny<MembersIdentityUser>(), It.IsAny<string>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.ValidatePasswordAsync(It.IsAny<string>()))
|
||||
.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>()))
|
||||
.Returns(() => null)
|
||||
.Returns(() => member);
|
||||
Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny<string>())).Returns(() => member);
|
||||
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = await sut.PostSave(fakeMemberData);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(result.Result);
|
||||
Assert.IsNotNull(result.Value);
|
||||
AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
[AutoMoqData]
|
||||
public async Task PostSaveMember_SaveExisting_WhenAllIsSetupCorrectly_ExpectSuccessResponse(
|
||||
[Frozen] IMembersUserManager umbracoMembersUserManager,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.Save);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.FindByIdAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => new MembersIdentityUser());
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.ValidatePasswordAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
|
||||
string password = "fakepassword9aw89rnyco3938cyr^%&*()i8Y";
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.HashPassword(It.IsAny<string>()))
|
||||
.Returns(password);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.UpdateAsync(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).Setup(x => x.GetByUsername(It.IsAny<string>())).Returns(() => member);
|
||||
Mock.Get(memberService).SetupSequence(
|
||||
x => x.GetByEmail(It.IsAny<string>()))
|
||||
.Returns(() => null)
|
||||
.Returns(() => member);
|
||||
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = await sut.PostSave(fakeMemberData);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(result.Result);
|
||||
Assert.IsNotNull(result.Value);
|
||||
AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoMoqData]
|
||||
public void PostSaveMember_SaveNew_WhenMemberEmailAlreadyExists_ExpectFailResponse(
|
||||
[Frozen] IMembersUserManager umbracoMembersUserManager,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(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(umbracoMembersUserManager)
|
||||
.Setup(x => x.ValidatePasswordAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
|
||||
Mock.Get(memberService).SetupSequence(
|
||||
x => x.GetByEmail(It.IsAny<string>()))
|
||||
.Returns(() => member);
|
||||
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
string reason = "Validation failed";
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = sut.PostSave(fakeMemberData).Result;
|
||||
var validation = result.Result as ValidationErrorResult;
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result.Result);
|
||||
Assert.IsNull(result.Value);
|
||||
Assert.AreEqual(StatusCodes.Status400BadRequest, validation?.StatusCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoMoqData]
|
||||
public async Task PostSaveMember_SaveExistingMember_WithNoRoles_Add1Role_ExpectSuccessResponse(
|
||||
[Frozen] IMembersUserManager umbracoMembersUserManager,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
{
|
||||
// arrange
|
||||
string password = "fakepassword9aw89rnyco3938cyr^%&*()i8Y";
|
||||
var roleName = "anyrole";
|
||||
IMember member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.Save);
|
||||
fakeMemberData.Groups = new List<string>()
|
||||
{
|
||||
roleName
|
||||
};
|
||||
var membersIdentityUser = new MembersIdentityUser();
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.FindByIdAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => membersIdentityUser);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.ValidatePasswordAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.HashPassword(It.IsAny<string>()))
|
||||
.Returns(password);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.UpdateAsync(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).Setup(x => x.GetByUsername(It.IsAny<string>())).Returns(() => member);
|
||||
|
||||
Mock.Get(memberService).SetupSequence(
|
||||
x => x.GetByEmail(It.IsAny<string>()))
|
||||
.Returns(() => null)
|
||||
.Returns(() => member);
|
||||
Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny<string>())).Returns(() => member);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = await sut.PostSave(fakeMemberData);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(result.Result);
|
||||
Assert.IsNotNull(result.Value);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Verify(u => u.GetRolesAsync(membersIdentityUser));
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Verify(u => u.AddToRolesAsync(membersIdentityUser, new[] { roleName }));
|
||||
Mock.Get(memberService)
|
||||
.Verify(m => m.Save(It.IsAny<Member>(), true));
|
||||
AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create member controller to test
|
||||
/// </summary>
|
||||
/// <param name="memberService">Member service</param>
|
||||
/// <param name="memberTypeService">Member type service</param>
|
||||
/// <param name="memberGroupService">Member group service</param>
|
||||
/// <param name="membersUserManager">Members user manager</param>
|
||||
/// <param name="dataTypeService">Data type service</param>
|
||||
/// <param name="backOfficeSecurityAccessor">Back office security accessor</param>
|
||||
/// <returns>A member controller for the tests</returns>
|
||||
private MemberController CreateSut(
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IMembersUserManager membersUserManager,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
var mockShortStringHelper = new MockShortStringHelper();
|
||||
|
||||
var textService = new Mock<ILocalizedTextService>();
|
||||
var contentTypeBaseServiceProvider = new Mock<IContentTypeBaseServiceProvider>();
|
||||
contentTypeBaseServiceProvider.Setup(x => x.GetContentTypeOf(It.IsAny<IContentBase>())).Returns(new ContentType(mockShortStringHelper, 123));
|
||||
var contentAppFactories = new Mock<List<IContentAppFactory>>();
|
||||
var mockContentAppFactoryCollection = new Mock<ILogger<ContentAppFactoryCollection>>();
|
||||
var hybridBackOfficeSecurityAccessor = new HybridBackofficeSecurityAccessor(new DictionaryAppCache());
|
||||
var contentAppFactoryCollection = new ContentAppFactoryCollection(
|
||||
contentAppFactories.Object,
|
||||
mockContentAppFactoryCollection.Object,
|
||||
hybridBackOfficeSecurityAccessor);
|
||||
var mockUserService = new Mock<IUserService>();
|
||||
var commonMapper = new CommonMapper(
|
||||
mockUserService.Object,
|
||||
contentTypeBaseServiceProvider.Object,
|
||||
contentAppFactoryCollection,
|
||||
textService.Object);
|
||||
var mockCultureDictionary = new Mock<ICultureDictionary>();
|
||||
|
||||
var mockPasswordConfig = new Mock<IOptions<MemberPasswordConfigurationSettings>>();
|
||||
mockPasswordConfig.Setup(x => x.Value).Returns(() => new MemberPasswordConfigurationSettings());
|
||||
IDataEditor dataEditor = Mock.Of<IDataEditor>(
|
||||
x => x.Type == EditorType.PropertyValue
|
||||
&& x.Alias == Constants.PropertyEditors.Aliases.Label);
|
||||
Mock.Get(dataEditor).Setup(x => x.GetValueEditor()).Returns(new TextOnlyValueEditor(Mock.Of<IDataTypeService>(), Mock.Of<ILocalizationService>(), new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox, "Test Textbox", "textbox"), textService.Object, Mock.Of<IShortStringHelper>(), Mock.Of<IJsonSerializer>()));
|
||||
|
||||
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(new[] { dataEditor }));
|
||||
|
||||
IMapDefinition memberMapDefinition = new MemberMapDefinition(
|
||||
commonMapper,
|
||||
new CommonTreeNodeMapper(Mock.Of<LinkGenerator>()),
|
||||
new MemberTabsAndPropertiesMapper(
|
||||
mockCultureDictionary.Object,
|
||||
backOfficeSecurityAccessor,
|
||||
textService.Object,
|
||||
memberTypeService,
|
||||
memberService,
|
||||
memberGroupService,
|
||||
mockPasswordConfig.Object,
|
||||
contentTypeBaseServiceProvider.Object,
|
||||
propertyEditorCollection),
|
||||
new HttpContextAccessor());
|
||||
|
||||
var map = new MapDefinitionCollection(new List<IMapDefinition>()
|
||||
{
|
||||
new global::Umbraco.Core.Models.Mapping.MemberMapDefinition(),
|
||||
memberMapDefinition,
|
||||
new ContentTypeMapDefinition(
|
||||
commonMapper,
|
||||
propertyEditorCollection,
|
||||
dataTypeService,
|
||||
new Mock<IFileService>().Object,
|
||||
new Mock<IContentTypeService>().Object,
|
||||
new Mock<IMediaTypeService>().Object,
|
||||
memberTypeService,
|
||||
new Mock<ILoggerFactory>().Object,
|
||||
mockShortStringHelper,
|
||||
new Mock<IOptions<GlobalSettings>>().Object,
|
||||
new Mock<IHostingEnvironment>().Object)
|
||||
});
|
||||
_mapper = new UmbracoMapper(map);
|
||||
|
||||
return new MemberController(
|
||||
new DefaultCultureDictionary(
|
||||
new Mock<ILocalizationService>().Object,
|
||||
new HttpRequestAppCache(() => null)),
|
||||
new LoggerFactory(),
|
||||
mockShortStringHelper,
|
||||
new DefaultEventMessagesFactory(
|
||||
new Mock<IEventMessagesAccessor>().Object),
|
||||
textService.Object,
|
||||
propertyEditorCollection,
|
||||
_mapper,
|
||||
memberService,
|
||||
memberTypeService,
|
||||
membersUserManager,
|
||||
dataTypeService,
|
||||
backOfficeSecurityAccessor,
|
||||
new ConfigurationEditorJsonSerializer());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Setup all standard member data for test
|
||||
/// </summary>
|
||||
private Member SetupMemberTestData(
|
||||
out MemberSave fakeMemberData,
|
||||
out MemberDisplay memberDisplay,
|
||||
ContentSaveAction contentAction)
|
||||
{
|
||||
// arrange
|
||||
MemberType memberType = MemberTypeBuilder.CreateSimpleMemberType();
|
||||
Member member = MemberBuilder.CreateSimpleMember(memberType, "Test Member", "test@example.com", "123", "test");
|
||||
int memberId = 123;
|
||||
member.Id = memberId;
|
||||
|
||||
//TODO: replace with builder for MemberSave and MemberDisplay
|
||||
fakeMemberData = new MemberSave()
|
||||
{
|
||||
Id = memberId,
|
||||
SortOrder = member.SortOrder,
|
||||
ContentTypeId = memberType.Id,
|
||||
Key = member.Key,
|
||||
Password = new ChangingPasswordModel()
|
||||
{
|
||||
Id = 456,
|
||||
NewPassword = member.RawPasswordValue,
|
||||
OldPassword = null
|
||||
},
|
||||
Name = member.Name,
|
||||
Email = member.Email,
|
||||
Username = member.Username,
|
||||
PersistedContent = member,
|
||||
PropertyCollectionDto = new ContentPropertyCollectionDto()
|
||||
{
|
||||
},
|
||||
Groups = new List<string>(),
|
||||
//Alias = "fakeAlias",
|
||||
ContentTypeAlias = member.ContentTypeAlias,
|
||||
Action = contentAction,
|
||||
Icon = "icon-document",
|
||||
Path = member.Path
|
||||
};
|
||||
|
||||
memberDisplay = new MemberDisplay()
|
||||
{
|
||||
Id = memberId,
|
||||
SortOrder = member.SortOrder,
|
||||
ContentTypeId = memberType.Id,
|
||||
Key = member.Key,
|
||||
Name = member.Name,
|
||||
Email = member.Email,
|
||||
Username = member.Username,
|
||||
//Alias = "fakeAlias",
|
||||
ContentTypeAlias = member.ContentTypeAlias,
|
||||
ContentType = new ContentTypeBasic(),
|
||||
ContentTypeName = member.ContentType.Name,
|
||||
Icon = fakeMemberData.Icon,
|
||||
Path = member.Path,
|
||||
Tabs = new List<Tab<ContentPropertyDisplay>>()
|
||||
{
|
||||
new Tab<ContentPropertyDisplay>()
|
||||
{
|
||||
Alias = "test",
|
||||
Id = 77,
|
||||
Properties = new List<ContentPropertyDisplay>()
|
||||
{
|
||||
new ContentPropertyDisplay()
|
||||
{
|
||||
Alias = "_umb_id",
|
||||
View = "idwithguid",
|
||||
Value = new []
|
||||
{
|
||||
"123",
|
||||
"guid"
|
||||
}
|
||||
},
|
||||
new ContentPropertyDisplay()
|
||||
{
|
||||
Alias = "_umb_doctype"
|
||||
},
|
||||
new ContentPropertyDisplay()
|
||||
{
|
||||
Alias = "_umb_login"
|
||||
},
|
||||
new ContentPropertyDisplay()
|
||||
{
|
||||
Alias= "_umb_email"
|
||||
},
|
||||
new ContentPropertyDisplay()
|
||||
{
|
||||
Alias = "_umb_password"
|
||||
},
|
||||
new ContentPropertyDisplay()
|
||||
{
|
||||
Alias = "_umb_membergroup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check all member properties are equal
|
||||
/// </summary>
|
||||
/// <param name="memberDisplay"></param>
|
||||
/// <param name="resultValue"></param>
|
||||
private void AssertMemberDisplayPropertiesAreEqual(MemberDisplay memberDisplay, MemberDisplay resultValue)
|
||||
{
|
||||
Assert.AreNotSame(memberDisplay, resultValue);
|
||||
Assert.AreEqual(memberDisplay.Id, resultValue.Id);
|
||||
Assert.AreEqual(memberDisplay.Alias, resultValue.Alias);
|
||||
Assert.AreEqual(memberDisplay.Username, resultValue.Username);
|
||||
Assert.AreEqual(memberDisplay.Email, resultValue.Email);
|
||||
Assert.AreEqual(memberDisplay.AdditionalData, resultValue.AdditionalData);
|
||||
Assert.AreEqual(memberDisplay.ContentApps, resultValue.ContentApps);
|
||||
Assert.AreEqual(memberDisplay.ContentType.Alias, resultValue.ContentType.Alias);
|
||||
Assert.AreEqual(memberDisplay.ContentTypeAlias, resultValue.ContentTypeAlias);
|
||||
Assert.AreEqual(memberDisplay.ContentTypeName, resultValue.ContentTypeName);
|
||||
Assert.AreEqual(memberDisplay.ContentTypeId, resultValue.ContentTypeId);
|
||||
Assert.AreEqual(memberDisplay.Icon, resultValue.Icon);
|
||||
Assert.AreEqual(memberDisplay.Errors, resultValue.Errors);
|
||||
Assert.AreEqual(memberDisplay.Key, resultValue.Key);
|
||||
Assert.AreEqual(memberDisplay.Name, resultValue.Name);
|
||||
Assert.AreEqual(memberDisplay.Path, resultValue.Path);
|
||||
Assert.AreEqual(memberDisplay.SortOrder, resultValue.SortOrder);
|
||||
Assert.AreEqual(memberDisplay.Trashed, resultValue.Trashed);
|
||||
Assert.AreEqual(memberDisplay.TreeNodeUrl, resultValue.TreeNodeUrl);
|
||||
|
||||
//TODO: can we check create/update dates when saving?
|
||||
//Assert.AreEqual(memberDisplay.CreateDate, resultValue.CreateDate);
|
||||
//Assert.AreEqual(memberDisplay.UpdateDate, resultValue.UpdateDate);
|
||||
|
||||
//TODO: check all properties
|
||||
Assert.AreEqual(memberDisplay.Properties.Count(), resultValue.Properties.Count());
|
||||
Assert.AreNotSame(memberDisplay.Properties, resultValue.Properties);
|
||||
for (var index = 0; index < resultValue.Properties.Count(); index++)
|
||||
{
|
||||
Assert.AreNotSame(memberDisplay.Properties.GetItemByIndex(index), resultValue.Properties.GetItemByIndex(index));
|
||||
//Assert.AreEqual(memberDisplay.Properties.GetItemByIndex(index), resultValue.Properties.GetItemByIndex(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Tests.UnitTests.AutoFixture;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using Umbraco.Core.Models.Security;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
@@ -24,6 +24,7 @@ using Umbraco.Core.Serialization;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.WebAssets;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.BackOffice.ActionResults;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
@@ -70,10 +70,18 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// </summary>
|
||||
protected ILocalizedTextService LocalizedTextService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles if the content for the specified ID isn't found
|
||||
/// </summary>
|
||||
/// <param name="id">The content ID to find</param>
|
||||
/// <param name="throwException">Whether to throw an exception</param>
|
||||
/// <returns>The error response</returns>
|
||||
protected NotFoundObjectResult HandleContentNotFound(object id)
|
||||
{
|
||||
ModelState.AddModelError("id", $"content with id: {id} was not found");
|
||||
var errorResponse = NotFound(ModelState);
|
||||
NotFoundObjectResult errorResponse = NotFound(ModelState);
|
||||
|
||||
|
||||
return errorResponse;
|
||||
}
|
||||
|
||||
@@ -90,7 +98,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
where TSaved : IContentSave<TPersisted>
|
||||
{
|
||||
// map the property values
|
||||
foreach (var propertyDto in dto.Properties)
|
||||
foreach (ContentPropertyDto propertyDto in dto.Properties)
|
||||
{
|
||||
// get the property editor
|
||||
if (propertyDto.PropertyEditor == null)
|
||||
@@ -101,42 +109,53 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
// get the value editor
|
||||
// nothing to save/map if it is readonly
|
||||
var valueEditor = propertyDto.PropertyEditor.GetValueEditor();
|
||||
if (valueEditor.IsReadOnly) continue;
|
||||
IDataValueEditor valueEditor = propertyDto.PropertyEditor.GetValueEditor();
|
||||
if (valueEditor.IsReadOnly)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the property
|
||||
var property = contentItem.PersistedContent.Properties[propertyDto.Alias];
|
||||
IProperty property = contentItem.PersistedContent.Properties[propertyDto.Alias];
|
||||
|
||||
// prepare files, if any matching property and culture
|
||||
var files = contentItem.UploadedFiles
|
||||
ContentPropertyFile[] files = contentItem.UploadedFiles
|
||||
.Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture && x.Segment == propertyDto.Segment)
|
||||
.ToArray();
|
||||
|
||||
foreach (var file in files)
|
||||
foreach (ContentPropertyFile file in files)
|
||||
{
|
||||
file.FileName = file.FileName.ToSafeFileName(ShortStringHelper);
|
||||
}
|
||||
|
||||
// create the property data for the property editor
|
||||
var data = new ContentPropertyData(propertyDto.Value, propertyDto.DataType.Configuration)
|
||||
{
|
||||
ContentKey = contentItem.PersistedContent.Key,
|
||||
PropertyTypeKey = property.PropertyType.Key,
|
||||
Files = files
|
||||
Files = files
|
||||
};
|
||||
|
||||
// let the editor convert the value that was received, deal with files, etc
|
||||
var value = valueEditor.FromEditor(data, getPropertyValue(contentItem, property));
|
||||
object value = valueEditor.FromEditor(data, getPropertyValue(contentItem, property));
|
||||
|
||||
// set the value - tags are special
|
||||
var tagAttribute = propertyDto.PropertyEditor.GetTagAttribute();
|
||||
TagsPropertyEditorAttribute tagAttribute = propertyDto.PropertyEditor.GetTagAttribute();
|
||||
if (tagAttribute != null)
|
||||
{
|
||||
var tagConfiguration = ConfigurationEditor.ConfigurationAs<TagConfiguration>(propertyDto.DataType.Configuration);
|
||||
if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = tagAttribute.Delimiter;
|
||||
TagConfiguration tagConfiguration = ConfigurationEditor.ConfigurationAs<TagConfiguration>(propertyDto.DataType.Configuration);
|
||||
if (tagConfiguration.Delimiter == default)
|
||||
{
|
||||
tagConfiguration.Delimiter = tagAttribute.Delimiter;
|
||||
}
|
||||
|
||||
var tagCulture = property.PropertyType.VariesByCulture() ? culture : null;
|
||||
property.SetTagsValue(_serializer, value, tagConfiguration, tagCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
savePropertyValue(contentItem, property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,38 +172,45 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// </remarks>
|
||||
protected TPersisted GetObjectFromRequest<TPersisted>(Func<TPersisted> getFromService)
|
||||
{
|
||||
//checks if the request contains the key and the item is not null, if that is the case, return it from the request, otherwise return
|
||||
// checks if the request contains the key and the item is not null, if that is the case, return it from the request, otherwise return
|
||||
// it from the callback
|
||||
return HttpContext.Items.ContainsKey(typeof(TPersisted).ToString()) && HttpContext.Items[typeof(TPersisted).ToString()] != null
|
||||
? (TPersisted) HttpContext.Items[typeof (TPersisted).ToString()]
|
||||
? (TPersisted)HttpContext.Items[typeof(TPersisted).ToString()]
|
||||
: getFromService();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the action passed in means we need to create something new
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool IsCreatingAction(ContentSaveAction action)
|
||||
{
|
||||
return (action.ToString().EndsWith("New"));
|
||||
}
|
||||
/// <param name="action">The content action</param>
|
||||
/// <returns>Returns true if this is a creating action</returns>
|
||||
internal static bool IsCreatingAction(ContentSaveAction action) => action.ToString().EndsWith("New");
|
||||
|
||||
protected void AddCancelMessage(INotificationModel display,
|
||||
string header = "speechBubbles/operationCancelledHeader",
|
||||
string message = "speechBubbles/operationCancelledText",
|
||||
bool localizeHeader = true,
|
||||
/// <summary>
|
||||
/// Adds a cancelled message to the display
|
||||
/// </summary>
|
||||
/// <param name="display"></param>
|
||||
/// <param name="header"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="localizeHeader"></param>
|
||||
/// <param name="localizeMessage"></param>
|
||||
/// <param name="headerParams"></param>
|
||||
/// <param name="messageParams"></param>
|
||||
protected void AddCancelMessage(INotificationModel display, string header = "speechBubbles/operationCancelledHeader", string message = "speechBubbles/operationCancelledText", bool localizeHeader = true,
|
||||
bool localizeMessage = true,
|
||||
string[] headerParams = null,
|
||||
string[] messageParams = null)
|
||||
{
|
||||
//if there's already a default event message, don't add our default one
|
||||
var msgs = EventMessages;
|
||||
if (msgs != null && msgs.GetOrDefault().GetAll().Any(x => x.IsDefaultEventMessage)) return;
|
||||
// if there's already a default event message, don't add our default one
|
||||
IEventMessagesFactory messages = EventMessages;
|
||||
if (messages != null && messages.GetOrDefault().GetAll().Any(x => x.IsDefaultEventMessage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
display.AddWarningNotification(
|
||||
localizeHeader ? LocalizedTextService.Localize(header, headerParams) : header,
|
||||
localizeMessage ? LocalizedTextService.Localize(message, messageParams): message);
|
||||
localizeMessage ? LocalizedTextService.Localize(message, messageParams) : message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
using Umbraco.Web.Common.ActionsResults;
|
||||
|
||||
@@ -8,23 +8,24 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Mapping;
|
||||
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;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Infrastructure.Services.Implement;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.ModelBinders;
|
||||
using Umbraco.Web.Common.ActionsResults;
|
||||
@@ -45,46 +46,72 @@ 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 IMembersUserManager _memberManager;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemberController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="cultureDictionary">The culture dictionary</param>
|
||||
/// <param name="loggerFactory">The logger factory</param>
|
||||
/// <param name="shortStringHelper">The string helper</param>
|
||||
/// <param name="eventMessages">The event messages factory</param>
|
||||
/// <param name="localizedTextService">The entry point for localizing key services</param>
|
||||
/// <param name="propertyEditors">The property editors</param>
|
||||
/// <param name="umbracoMapper">The mapper</param>
|
||||
/// <param name="memberService">The member service</param>
|
||||
/// <param name="memberTypeService">The member type service</param>
|
||||
/// <param name="memberManager">The member manager</param>
|
||||
/// <param name="dataTypeService">The data-type service</param>
|
||||
/// <param name="backOfficeSecurityAccessor">The back office security accessor</param>
|
||||
/// <param name="jsonSerializer">The JSON serializer</param>
|
||||
public MemberController(
|
||||
ICultureDictionary cultureDictionary,
|
||||
ILoggerFactory loggerFactory,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IEventMessagesFactory eventMessages,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IOptions<MemberPasswordConfigurationSettings> passwordConfig,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
LegacyPasswordSecurity passwordSecurity,
|
||||
UmbracoMapper umbracoMapper,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMembersUserManager memberManager,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
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;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The paginated list of members
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">The page number to display</param>
|
||||
/// <param name="pageSize">The size of the page</param>
|
||||
/// <param name="orderBy">The ordering of the member list</param>
|
||||
/// <param name="orderDirection">The direction of the member list</param>
|
||||
/// <param name="orderBySystemField">The system field to order by</param>
|
||||
/// <param name="filter">The current filter for the list</param>
|
||||
/// <param name="memberTypeAlias">The member type</param>
|
||||
/// <returns>The paged result of members</returns>
|
||||
public PagedResult<MemberBasic> GetPagedResults(
|
||||
int pageNumber = 1,
|
||||
int pageSize = 100,
|
||||
@@ -100,8 +127,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 +143,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;
|
||||
}
|
||||
@@ -118,15 +151,22 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// Returns a display node with a list view to render members
|
||||
/// </summary>
|
||||
/// <param name="listName"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="listName">The member type to list</param>
|
||||
/// <returns>The member list for display</returns>
|
||||
public MemberListDisplay GetListNodeDisplay(string listName)
|
||||
{
|
||||
var foundType = _memberTypeService.Get(listName);
|
||||
IMemberType 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
|
||||
@@ -147,138 +187,120 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// Gets the content json for the member
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="key">The Guid key of the member</param>
|
||||
/// <returns>The member for display</returns>
|
||||
[OutgoingEditorModelEvent]
|
||||
public MemberDisplay GetByKey(Guid key)
|
||||
{
|
||||
var foundMember = _memberService.GetByKey(key);
|
||||
IMember foundMember = _memberService.GetByKey(key);
|
||||
if (foundMember == null)
|
||||
{
|
||||
HandleContentNotFound(key);
|
||||
}
|
||||
|
||||
return _umbracoMapper.Map<MemberDisplay>(foundMember);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty content item for the
|
||||
/// </summary>
|
||||
/// <param name="contentTypeAlias"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="contentTypeAlias">The content type</param>
|
||||
/// <returns>The empty member for display</returns>
|
||||
[OutgoingEditorModelEvent]
|
||||
public ActionResult<MemberDisplay> GetEmpty(string contentTypeAlias = null)
|
||||
{
|
||||
IMember emptyContent;
|
||||
if (contentTypeAlias == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var contentType = _memberTypeService.Get(contentTypeAlias);
|
||||
IMemberType contentType = _memberTypeService.Get(contentTypeAlias);
|
||||
if (contentType == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var passwordGenerator = new PasswordGenerator(_passwordConfig);
|
||||
string newPassword = _memberManager.GeneratePassword();
|
||||
|
||||
emptyContent = new Member(contentType);
|
||||
emptyContent.AdditionalData["NewPassword"] = passwordGenerator.GeneratePassword();
|
||||
IMember emptyContent = new Member(contentType);
|
||||
emptyContent.AdditionalData["NewPassword"] = newPassword;
|
||||
return _umbracoMapper.Map<MemberDisplay>(emptyContent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves member
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <param name="contentItem">The content item to save as a member</param>
|
||||
/// <returns>The resulting member display object</returns>
|
||||
[FileUploadCleanupFilter]
|
||||
[OutgoingEditorModelEvent]
|
||||
[MemberSaveValidation]
|
||||
public async Task<ActionResult<MemberDisplay>> PostSave(
|
||||
[ModelBinder(typeof(MemberBinder))]
|
||||
MemberSave contentItem)
|
||||
public async Task<ActionResult<MemberDisplay>> PostSave([ModelBinder(typeof(MemberBinder))] MemberSave contentItem)
|
||||
{
|
||||
if (contentItem == null)
|
||||
{
|
||||
throw new ArgumentNullException("The member content item was null");
|
||||
}
|
||||
|
||||
//If we've reached here it means:
|
||||
// If we've reached here it means:
|
||||
// * Our model has been bound
|
||||
// * and validated
|
||||
// * any file attachments have been saved to their temporary location for us to use
|
||||
// * we have a reference to the DTO object and the persisted object
|
||||
// * Permissions are valid
|
||||
|
||||
//map the properties to the persisted entity
|
||||
// map the properties to the persisted entity
|
||||
MapPropertyValues(contentItem);
|
||||
|
||||
await ValidateMemberDataAsync(contentItem);
|
||||
|
||||
//Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors
|
||||
// Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
var forDisplay = _umbracoMapper.Map<MemberDisplay>(contentItem.PersistedContent);
|
||||
MemberDisplay forDisplay = _umbracoMapper.Map<MemberDisplay>(contentItem.PersistedContent);
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
return new ValidationErrorResult(forDisplay);
|
||||
}
|
||||
|
||||
//We're gonna look up the current roles now because the below code can cause
|
||||
// events to be raised and developers could be manually adding roles to members in
|
||||
// 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();
|
||||
|
||||
//Depending on the action we need to first do a create or update using the membership provider
|
||||
// Depending on the action we need to first do a create or update using the membership manager
|
||||
// this ensures that passwords are formatted correctly and also performs the validation on the provider itself.
|
||||
|
||||
switch (contentItem.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
UpdateMemberData(contentItem);
|
||||
Task<ActionResult<bool>> updateSuccessful = UpdateMemberAsync(contentItem);
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
contentItem.PersistedContent = CreateMemberData(contentItem);
|
||||
Task<ActionResult<bool>> createSuccessful = CreateMemberAsync(contentItem);
|
||||
break;
|
||||
default:
|
||||
//we don't support anything else for members
|
||||
// we don't support anything else for members
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
//TODO: There's 3 things saved here and we should do this all in one transaction, which we can do here by wrapping in a scope
|
||||
// TODO: There's 3 things saved here and we should do this all in one transaction, which we can do here by wrapping in a scope
|
||||
// but it would be nicer to have this taken care of within the Save method itself
|
||||
|
||||
//create/save the IMember
|
||||
_memberService.Save(contentItem.PersistedContent);
|
||||
// return the updated model
|
||||
MemberDisplay display = _umbracoMapper.Map<MemberDisplay>(contentItem.PersistedContent);
|
||||
|
||||
//Now let's do the role provider stuff - now that we've saved the content item (that is important since
|
||||
// if we are changing the username, it must be persisted before looking up the member roles).
|
||||
if (rolesToRemove.Any())
|
||||
{
|
||||
_memberService.DissociateRoles(new[] { contentItem.PersistedContent.Username }, rolesToRemove);
|
||||
}
|
||||
//find the ones to add and add them
|
||||
var toAdd = contentItem.Groups.Except(currRoles).ToArray();
|
||||
if (toAdd.Any())
|
||||
{
|
||||
//add the ones submitted
|
||||
_memberService.AssignRoles(new[] { contentItem.PersistedContent.Username }, toAdd);
|
||||
}
|
||||
|
||||
//return the updated model
|
||||
var display = _umbracoMapper.Map<MemberDisplay>(contentItem.PersistedContent);
|
||||
|
||||
//lastly, if it is not valid, add the model state to the outgoing object and throw a 403
|
||||
// lastly, if it is not valid, add the model state to the outgoing object and throw a 403
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
display.Errors = ModelState.ToErrorDictionary();
|
||||
return new ValidationErrorResult(display, StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
var localizedTextService = _localizedTextService;
|
||||
//put the correct messages in
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -288,81 +310,121 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// Maps the property values to the persisted entity
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <param name="contentItem">The member content item to map properties from</param>
|
||||
private void MapPropertyValues(MemberSave contentItem)
|
||||
{
|
||||
UpdateName(contentItem);
|
||||
// Don't update the name if it is empty
|
||||
if (contentItem.Name.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
contentItem.PersistedContent.Name = contentItem.Name;
|
||||
}
|
||||
|
||||
//map the custom properties - this will already be set for new entities in our member binder
|
||||
// map the custom properties - this will already be set for new entities in our member binder
|
||||
contentItem.PersistedContent.Email = contentItem.Email;
|
||||
contentItem.PersistedContent.Username = contentItem.Username;
|
||||
|
||||
//use the base method to map the rest of the properties
|
||||
base.MapPropertyValuesForPersistence<IMember, MemberSave>(
|
||||
// use the base method to map the rest of the properties
|
||||
MapPropertyValuesForPersistence<IMember, MemberSave>(
|
||||
contentItem,
|
||||
contentItem.PropertyCollectionDto,
|
||||
(save, property) => property.GetValue(), //get prop val
|
||||
(save, property, v) => property.SetValue(v), //set prop val
|
||||
(save, property) => property.GetValue(), // get prop val
|
||||
(save, property, v) => property.SetValue(v), // set prop val
|
||||
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 identity manager
|
||||
/// </summary>
|
||||
/// <param name="contentItem">Member content data</param>
|
||||
/// <returns>The identity result of the created member</returns>
|
||||
private async Task<ActionResult<bool>> CreateMemberAsync(MemberSave contentItem)
|
||||
{
|
||||
throw new NotImplementedException("Members have not been migrated to netcore");
|
||||
IMemberType memberType = _memberTypeService.Get(contentItem.ContentTypeAlias);
|
||||
if (memberType == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No member type found with alias {contentItem.ContentTypeAlias}");
|
||||
}
|
||||
|
||||
// TODO: all member password processing and creation needs to be done with a new aspnet identity MemberUserManager that hasn't been created yet.
|
||||
var identityMember = MembersIdentityUser.CreateNew(
|
||||
contentItem.Username,
|
||||
contentItem.Email,
|
||||
memberType.Alias,
|
||||
contentItem.Name);
|
||||
|
||||
//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)
|
||||
//{
|
||||
// CreatorId = _backofficeSecurityAccessor.BackofficeSecurity.CurrentUser.Id,
|
||||
// RawPasswordValue = _passwordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword),
|
||||
// Comments = contentItem.Comments,
|
||||
// IsApproved = contentItem.IsApproved
|
||||
//};
|
||||
IdentityResult created = await _memberManager.CreateAsync(identityMember, contentItem.Password.NewPassword);
|
||||
|
||||
//return member;
|
||||
if (created.Succeeded == false)
|
||||
{
|
||||
return new ValidationErrorResult(created.Errors.ToErrorMessage());
|
||||
}
|
||||
|
||||
// now re-look up the member, which will now exist
|
||||
IMember member = _memberService.GetByEmail(contentItem.Email);
|
||||
|
||||
// map the save info over onto the user
|
||||
member = _umbracoMapper.Map<MemberSave, IMember>(contentItem, member);
|
||||
|
||||
int creatorId = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id;
|
||||
member.CreatorId = creatorId;
|
||||
|
||||
// assign the mapped property values that are not part of the identity properties
|
||||
string[] builtInAliases = ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Select(x => x.Key).ToArray();
|
||||
foreach (ContentPropertyBasic property in contentItem.Properties)
|
||||
{
|
||||
if (builtInAliases.Contains(property.Alias) == false)
|
||||
{
|
||||
member.Properties[property.Alias].SetValue(property.Value);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: do we need to resave the key?
|
||||
//contentItem.PersistedContent.Key = contentItem.Key;
|
||||
|
||||
// now the member has been saved via identity, resave the member with mapped content properties
|
||||
_memberService.Save(member);
|
||||
contentItem.PersistedContent = member;
|
||||
|
||||
await AddOrUpdateRoles(contentItem, identityMember);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the member security data
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></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)
|
||||
/// </summary>
|
||||
/// <param name="contentItem">The member to save</param>
|
||||
private async Task<ActionResult<bool>> UpdateMemberAsync(MemberSave contentItem)
|
||||
{
|
||||
contentItem.PersistedContent.WriterId = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id;
|
||||
contentItem.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.
|
||||
// There's only 3 special ones we need to deal with that are part of the MemberSave instance: Comments, IsApproved, IsLockedOut
|
||||
// but we will take care of this in a generic way below so that it works for all props.
|
||||
if (!_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasAccessToSensitiveData())
|
||||
if (!_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasAccessToSensitiveData())
|
||||
{
|
||||
var memberType = _memberTypeService.Get(contentItem.PersistedContent.ContentTypeId);
|
||||
IMemberType memberType = _memberTypeService.Get(contentItem.PersistedContent.ContentTypeId);
|
||||
var sensitiveProperties = memberType
|
||||
.PropertyTypes.Where(x => memberType.IsSensitiveProperty(x.Alias))
|
||||
.ToList();
|
||||
|
||||
foreach (var sensitiveProperty in sensitiveProperties)
|
||||
foreach (IPropertyType sensitiveProperty in sensitiveProperties)
|
||||
{
|
||||
var destProp = contentItem.Properties.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias);
|
||||
ContentPropertyBasic destProp = contentItem.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);
|
||||
// if found, change the value of the contentItem model to the persisted value so it remains unchanged
|
||||
object origValue = contentItem.PersistedContent.GetValue(sensitiveProperty.Alias);
|
||||
destProp.Value = origValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isLockedOut = contentItem.IsLockedOut;
|
||||
bool isLockedOut = contentItem.IsLockedOut;
|
||||
|
||||
//if they were locked but now they are trying to be unlocked
|
||||
// if they were locked but now they are trying to be unlocked
|
||||
if (contentItem.PersistedContent.IsLockedOut && isLockedOut == false)
|
||||
{
|
||||
contentItem.PersistedContent.IsLockedOut = false;
|
||||
@@ -370,90 +432,153 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
else if (!contentItem.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
|
||||
// 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
|
||||
ModelState.AddModelError("custom", "An admin cannot lock a user");
|
||||
}
|
||||
|
||||
//no password changes then exit ?
|
||||
if (contentItem.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);
|
||||
}
|
||||
|
||||
private static void UpdateName(MemberSave memberSave)
|
||||
{
|
||||
//Don't update the name if it is empty
|
||||
if (memberSave.Name.IsNullOrWhiteSpace() == false)
|
||||
MembersIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id.ToString());
|
||||
if (identityMember == null)
|
||||
{
|
||||
memberSave.PersistedContent.Name = memberSave.Name;
|
||||
return new ValidationErrorResult("Member was not found");
|
||||
}
|
||||
|
||||
if (contentItem.Password != null)
|
||||
{
|
||||
IdentityResult validatePassword = await _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword);
|
||||
if (validatePassword.Succeeded == false)
|
||||
{
|
||||
return new ValidationErrorResult(validatePassword.Errors.ToErrorMessage());
|
||||
}
|
||||
|
||||
string newPassword = _memberManager.HashPassword(contentItem.Password.NewPassword);
|
||||
identityMember.PasswordHash = newPassword;
|
||||
contentItem.PersistedContent.RawPasswordValue = identityMember.PasswordHash;
|
||||
if (identityMember.LastPasswordChangeDateUtc != null)
|
||||
{
|
||||
contentItem.PersistedContent.LastPasswordChangeDate = DateTime.UtcNow;
|
||||
identityMember.LastPasswordChangeDateUtc = contentItem.PersistedContent.LastPasswordChangeDate;
|
||||
}
|
||||
}
|
||||
|
||||
IdentityResult updatedResult = await _memberManager.UpdateAsync(identityMember);
|
||||
|
||||
if (updatedResult.Succeeded == false)
|
||||
{
|
||||
return new ValidationErrorResult(updatedResult.Errors.ToErrorMessage());
|
||||
}
|
||||
|
||||
_memberService.Save(contentItem.PersistedContent);
|
||||
|
||||
await AddOrUpdateRoles(contentItem, identityMember);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: This logic should be pulled into the service layer
|
||||
private async Task<bool> ValidateMemberDataAsync(MemberSave contentItem)
|
||||
{
|
||||
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;
|
||||
// }
|
||||
IdentityResult validPassword = await _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword);
|
||||
if (!validPassword.Succeeded)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Invalid password: " + MapErrors(validPassword.Errors), new[] { "value" }),
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var byUsername = _memberService.GetByUsername(contentItem.Username);
|
||||
IMember byUsername = _memberService.GetByUsername(contentItem.Username);
|
||||
if (byUsername != null && byUsername.Key != contentItem.Key)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Username is already in use", new[] { "value" }),
|
||||
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
new ValidationResult("Username is already in use", new[] { "value" }),
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login");
|
||||
return false;
|
||||
}
|
||||
|
||||
var byEmail = _memberService.GetByEmail(contentItem.Email);
|
||||
IMember byEmail = _memberService.GetByEmail(contentItem.Email);
|
||||
if (byEmail != null && byEmail.Key != contentItem.Key)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Email address is already in use", new[] { "value" }),
|
||||
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
new ValidationResult("Email address is already in use", new[] { "value" }),
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string MapErrors(IEnumerable<IdentityError> result)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
IEnumerable<IdentityError> identityErrors = result.ToList();
|
||||
foreach (IdentityError error in identityErrors)
|
||||
{
|
||||
string errorString = $"{error.Description}";
|
||||
sb.AppendLine(errorString);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add or update the identity roles
|
||||
/// </summary>
|
||||
/// <param name="contentItem">The member content item</param>
|
||||
/// <param name="identityMember">The member as an identity user</param>
|
||||
private async Task AddOrUpdateRoles(MemberSave contentItem, MembersIdentityUser identityMember)
|
||||
{
|
||||
// We're gonna look up the current roles now because the below code can cause
|
||||
// events to be raised and developers could be manually adding roles to members in
|
||||
// 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.
|
||||
IEnumerable<string> currentRoles = await _memberManager.GetRolesAsync(identityMember);
|
||||
|
||||
// find the ones to remove and remove them
|
||||
IEnumerable<string> roles = currentRoles.ToList();
|
||||
string[] rolesToRemove = roles.Except(contentItem.Groups).ToArray();
|
||||
|
||||
// Now let's do the role provider stuff - now that we've saved the content item (that is important since
|
||||
// if we are changing the username, it must be persisted before looking up the member roles).
|
||||
if (rolesToRemove.Any())
|
||||
{
|
||||
IdentityResult rolesIdentityResult = await _memberManager.RemoveFromRolesAsync(identityMember, rolesToRemove);
|
||||
}
|
||||
|
||||
// find the ones to add and add them
|
||||
string[] toAdd = contentItem.Groups.Except(roles).ToArray();
|
||||
if (toAdd.Any())
|
||||
{
|
||||
// add the ones submitted
|
||||
IdentityResult identityResult = await _memberManager.AddToRolesAsync(identityMember, toAdd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permanently deletes a member
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="key">Guid of the member to delete</param>
|
||||
/// <returns>The result of the deletion</returns>
|
||||
///
|
||||
[HttpPost]
|
||||
public IActionResult DeleteByKey(Guid key)
|
||||
{
|
||||
var foundMember = _memberService.GetByKey(key);
|
||||
//TODO: move to MembersUserStore
|
||||
IMember foundMember = _memberService.GetByKey(key);
|
||||
if (foundMember == null)
|
||||
{
|
||||
return HandleContentNotFound(key);
|
||||
}
|
||||
|
||||
_memberService.Delete(foundMember);
|
||||
|
||||
return Ok();
|
||||
@@ -467,25 +592,27 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult ExportMemberData(Guid key)
|
||||
{
|
||||
var currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
|
||||
if (currentUser.HasAccessToSensitiveData() == false)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var member = ((MemberService)_memberService).ExportMember(key);
|
||||
if (member is null) throw new NullReferenceException("No member found with key " + 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);
|
||||
|
||||
var fileName = $"{member.Name}_{member.Email}.txt";
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.BackOffice.ActionResults;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.ModelBinders;
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.Actions;
|
||||
using Umbraco.Web.BackOffice.Authorization;
|
||||
@@ -37,6 +38,15 @@ namespace Umbraco.Web.BackOffice.DependencyInjection
|
||||
services.ConfigureOptions<ConfigureBackOfficeSecurityStampValidatorOptions>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the services required for using Members Identity
|
||||
/// </summary>
|
||||
public static void AddMembersIdentity(this IServiceCollection services) =>
|
||||
services.BuildMembersIdentity()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddUserStore<MembersUserStore>()
|
||||
.AddMembersUserManager<IMembersUserManager, MembersUserManager>();
|
||||
|
||||
private static BackOfficeIdentityBuilder BuildUmbracoBackOfficeIdentity(this IServiceCollection services)
|
||||
{
|
||||
// Borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityServiceCollectionExtensions.cs#L33
|
||||
@@ -57,7 +67,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection
|
||||
services.TryAddScoped<IUserClaimsPrincipalFactory<BackOfficeIdentityUser>, UserClaimsPrincipalFactory<BackOfficeIdentityUser>>();
|
||||
|
||||
// CUSTOM:
|
||||
services.TryAddScoped<BackOfficeLookupNormalizer>();
|
||||
services.TryAddScoped<NoOpLookupNormalizer>();
|
||||
services.TryAddScoped<BackOfficeIdentityErrorDescriber>();
|
||||
services.TryAddScoped<IIpResolver, AspNetCoreIpResolver>();
|
||||
services.TryAddSingleton<IBackOfficeExternalLoginProviders, BackOfficeExternalLoginProviders>();
|
||||
@@ -69,12 +79,21 @@ namespace Umbraco.Web.BackOffice.DependencyInjection
|
||||
* To validate the container the following registrations are required (dependencies of UserManager<T>)
|
||||
* Perhaps we shouldn't be registering UserManager<T> at all and only registering/depending the UmbracoBackOffice prefixed types.
|
||||
*/
|
||||
services.TryAddScoped<ILookupNormalizer, BackOfficeLookupNormalizer>();
|
||||
services.TryAddScoped<ILookupNormalizer, NoOpLookupNormalizer>();
|
||||
services.TryAddScoped<IdentityErrorDescriber, BackOfficeIdentityErrorDescriber>();
|
||||
|
||||
return new BackOfficeIdentityBuilder(services);
|
||||
}
|
||||
|
||||
private static MembersIdentityBuilder BuildMembersIdentity(this IServiceCollection services)
|
||||
{
|
||||
// Services used by Umbraco members identity
|
||||
services.TryAddScoped<IUserValidator<MembersIdentityUser>, UserValidator<MembersIdentityUser>>();
|
||||
services.TryAddScoped<IPasswordValidator<MembersIdentityUser>, PasswordValidator<MembersIdentityUser>>();
|
||||
services.TryAddScoped<IPasswordHasher<MembersIdentityUser>, PasswordHasher<MembersIdentityUser>>();
|
||||
return new MembersIdentityBuilder(services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add authorization handlers and policies
|
||||
/// </summary>
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection
|
||||
.AddBackOfficeCore()
|
||||
.AddBackOfficeAuthentication()
|
||||
.AddBackOfficeIdentity()
|
||||
.AddMembersIdentity()
|
||||
.AddBackOfficeAuthorizationPolicies()
|
||||
.AddUmbracoProfiler()
|
||||
.AddMvcAndRazor()
|
||||
@@ -94,6 +95,16 @@ namespace Umbraco.Web.BackOffice.DependencyInjection
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Identity support for Umbraco members
|
||||
/// </summary>
|
||||
public static IUmbracoBuilder AddMembersIdentity(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddMembersIdentity();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Umbraco back office authorization policies
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
@@ -24,6 +25,19 @@ namespace Umbraco.Extensions
|
||||
return identityBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="UserManager{TUser}"/> for the <seealso cref="MembersIdentityUser"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">The usermanager interface</typeparam>
|
||||
/// <typeparam name="TUserManager">The usermanager type</typeparam>
|
||||
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
||||
public static IdentityBuilder AddMembersUserManager<TInterface, TUserManager>(this IdentityBuilder identityBuilder)
|
||||
where TUserManager : UserManager<MembersIdentityUser>, TInterface
|
||||
{
|
||||
identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TUserManager));
|
||||
return identityBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="SignInManager{TUser}"/> implementation for <seealso cref="BackOfficeIdentityUser"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -17,6 +17,7 @@ using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
using Umbraco.Web.Common.Security;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -93,14 +93,11 @@ namespace Umbraco.Web.BackOffice.Mapping
|
||||
target.Path = $"-1,{source.Id}";
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.MemberGroup, source.Key);
|
||||
}
|
||||
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(IMember source, ContentPropertyCollectionDto target, MapperContext context)
|
||||
{
|
||||
target.Properties = context.MapEnumerable<IProperty, ContentPropertyDto>(source.Properties);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.Common.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -13,6 +13,7 @@ using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using SecurityConstants = Umbraco.Core.Constants.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.Models;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
|
||||
@@ -55,12 +55,14 @@ namespace Umbraco.Web.BackOffice.Trees
|
||||
/// <summary>
|
||||
/// Gets an individual tree node
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
public ActionResult<TreeNode> GetTreeNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings)
|
||||
public ActionResult<TreeNode> GetTreeNode([FromRoute]string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings)
|
||||
{
|
||||
var node = GetSingleTreeNode(id, queryStrings);
|
||||
ActionResult<TreeNode> node = GetSingleTreeNode(id, queryStrings);
|
||||
|
||||
if (!(node.Result is null))
|
||||
{
|
||||
return node.Result;
|
||||
}
|
||||
|
||||
//add the tree alias to the node since it is standalone (has no root for which this normally belongs)
|
||||
node.Value.AdditionalData["treeAlias"] = TreeAlias;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Core;
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
@@ -28,13 +29,12 @@ namespace Umbraco.Web.Common.Security
|
||||
IPasswordHasher<BackOfficeIdentityUser> passwordHasher,
|
||||
IEnumerable<IUserValidator<BackOfficeIdentityUser>> userValidators,
|
||||
IEnumerable<IPasswordValidator<BackOfficeIdentityUser>> passwordValidators,
|
||||
BackOfficeLookupNormalizer keyNormalizer,
|
||||
BackOfficeIdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILogger<UserManager<BackOfficeIdentityUser>> logger,
|
||||
IOptions<UserPasswordConfigurationSettings> passwordConfiguration)
|
||||
: base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger, passwordConfiguration)
|
||||
: base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, errors, services, logger, passwordConfiguration)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ namespace Umbraco.Web.Common.Security
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<IdentityResult> SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset? lockoutEnd)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
public class MembersUserManager : UmbracoUserManager<MembersIdentityUser, MemberPasswordConfigurationSettings>, IMembersUserManager
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public MembersUserManager(
|
||||
IIpResolver ipResolver,
|
||||
IUserStore<MembersIdentityUser> store,
|
||||
IOptions<MembersIdentityOptions> optionsAccessor,
|
||||
IPasswordHasher<MembersIdentityUser> passwordHasher,
|
||||
IEnumerable<IUserValidator<MembersIdentityUser>> userValidators,
|
||||
IEnumerable<IPasswordValidator<MembersIdentityUser>> passwordValidators,
|
||||
BackOfficeIdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILogger<UserManager<MembersIdentityUser>> logger,
|
||||
IOptions<MemberPasswordConfigurationSettings> passwordConfiguration)
|
||||
: base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, errors, services, logger, passwordConfiguration)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
private string GetCurrentUserId(IPrincipal currentUser)
|
||||
{
|
||||
UmbracoBackOfficeIdentity umbIdentity = currentUser?.GetUmbracoIdentity();
|
||||
var currentUserId = umbIdentity?.GetUserId<string>() ?? Core.Constants.Security.SuperUserIdAsString;
|
||||
return currentUserId;
|
||||
}
|
||||
|
||||
private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, string affectedUserId, string affectedUsername)
|
||||
{
|
||||
var currentUserId = GetCurrentUserId(currentUser);
|
||||
var ip = IpResolver.GetCurrentRequestIpAddress();
|
||||
return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername);
|
||||
}
|
||||
|
||||
//TODO: have removed all other member audit events - can revisit if we need member auditing on a user level in future
|
||||
|
||||
public void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, string userId) => throw new NotImplementedException();
|
||||
|
||||
public void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, string userId) => throw new NotImplementedException();
|
||||
|
||||
public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, string userId) => throw new NotImplementedException();
|
||||
|
||||
public UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser) => throw new NotImplementedException();
|
||||
|
||||
public bool HasSendingUserInviteEventHandler { get; }
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
@@ -11,7 +11,6 @@ using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Models.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Security.Providers;
|
||||
@@ -19,6 +18,15 @@ using Umbraco.Web.Security.Providers;
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
// MIGRATED TO NETCORE
|
||||
// TODO: Analyse all - much can be moved/removed since most methods will occur on the manager via identity implementation
|
||||
|
||||
/// <summary>
|
||||
/// Helper class containing logic relating to the built-in Umbraco members macros and controllers for:
|
||||
/// - Registration
|
||||
/// - Updating
|
||||
/// - Logging in
|
||||
/// - Current status
|
||||
/// </summary>
|
||||
public class MembershipHelper
|
||||
{
|
||||
private readonly MembersMembershipProvider _membershipProvider;
|
||||
@@ -118,7 +126,7 @@ namespace Umbraco.Web.Security
|
||||
var pathsWithAccess = HasAccess(pathsWithProtection, Roles.Provider);
|
||||
|
||||
var result = new Dictionary<string, bool>();
|
||||
foreach(var path in paths)
|
||||
foreach (var path in paths)
|
||||
{
|
||||
pathsWithAccess.TryGetValue(path, out var hasAccess);
|
||||
// if it's not found it's false anyways
|
||||
@@ -144,7 +152,8 @@ namespace Umbraco.Web.Security
|
||||
string[] userRoles = null;
|
||||
string[] getUserRoles(string username)
|
||||
{
|
||||
if (userRoles != null) return userRoles;
|
||||
if (userRoles != null)
|
||||
return userRoles;
|
||||
userRoles = roleProvider.GetRolesForUser(username).ToArray();
|
||||
return userRoles;
|
||||
}
|
||||
@@ -185,7 +194,8 @@ namespace Umbraco.Web.Security
|
||||
var provider = _membershipProvider;
|
||||
var membershipUser = provider.GetCurrentUser();
|
||||
//NOTE: This should never happen since they are logged in
|
||||
if (membershipUser == null) throw new InvalidOperationException("Could not find member with username " + _httpContextAccessor.GetRequiredHttpContext().User.Identity.Name);
|
||||
if (membershipUser == null)
|
||||
throw new InvalidOperationException("Could not find member with username " + _httpContextAccessor.GetRequiredHttpContext().User.Identity.Name);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -257,7 +267,8 @@ namespace Umbraco.Web.Security
|
||||
null, null,
|
||||
true, null, out status);
|
||||
|
||||
if (status != MembershipCreateStatus.Success) return null;
|
||||
if (status != MembershipCreateStatus.Success)
|
||||
return null;
|
||||
|
||||
var member = _memberService.GetByUsername(membershipUser.UserName);
|
||||
member.Name = model.Name;
|
||||
@@ -367,7 +378,8 @@ namespace Umbraco.Web.Security
|
||||
public virtual IPublishedContent Get(Udi udi)
|
||||
{
|
||||
var guidUdi = udi as GuidUdi;
|
||||
if (guidUdi == null) return null;
|
||||
if (guidUdi == null)
|
||||
return null;
|
||||
|
||||
var umbracoType = UdiEntityTypeHelper.ToUmbracoObjectType(udi.EntityType);
|
||||
|
||||
@@ -702,27 +714,32 @@ namespace Umbraco.Web.Security
|
||||
|
||||
if (email != null)
|
||||
{
|
||||
if (member.Email != email) update = true;
|
||||
if (member.Email != email)
|
||||
update = true;
|
||||
member.Email = email;
|
||||
}
|
||||
if (isApproved.HasValue)
|
||||
{
|
||||
if (member.IsApproved != isApproved.Value) update = true;
|
||||
if (member.IsApproved != isApproved.Value)
|
||||
update = true;
|
||||
member.IsApproved = isApproved.Value;
|
||||
}
|
||||
if (lastLoginDate.HasValue)
|
||||
{
|
||||
if (member.LastLoginDate != lastLoginDate.Value) update = true;
|
||||
if (member.LastLoginDate != lastLoginDate.Value)
|
||||
update = true;
|
||||
member.LastLoginDate = lastLoginDate.Value;
|
||||
}
|
||||
if (lastActivityDate.HasValue)
|
||||
{
|
||||
if (member.LastActivityDate != lastActivityDate.Value) update = true;
|
||||
if (member.LastActivityDate != lastActivityDate.Value)
|
||||
update = true;
|
||||
member.LastActivityDate = lastActivityDate.Value;
|
||||
}
|
||||
if (comment != null)
|
||||
{
|
||||
if (member.Comment != comment) update = true;
|
||||
if (member.Comment != comment)
|
||||
update = true;
|
||||
member.Comment = comment;
|
||||
}
|
||||
|
||||
@@ -741,8 +758,8 @@ namespace Umbraco.Web.Security
|
||||
{
|
||||
var provider = _membershipProvider;
|
||||
|
||||
var username = provider.GetCurrentUserName();
|
||||
// The result of this is cached by the MemberRepository
|
||||
var username = provider.GetCurrentUserName();
|
||||
// The result of this is cached by the MemberRepository
|
||||
var member = _memberService.GetByUsername(username);
|
||||
return member;
|
||||
}
|
||||
@@ -763,8 +780,10 @@ namespace Umbraco.Web.Security
|
||||
|
||||
// YES! It is completely insane how many options you have to take into account based on the membership provider. yikes!
|
||||
|
||||
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
|
||||
if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider));
|
||||
if (passwordModel == null)
|
||||
throw new ArgumentNullException(nameof(passwordModel));
|
||||
if (membershipProvider == null)
|
||||
throw new ArgumentNullException(nameof(membershipProvider));
|
||||
var userId = -1;
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Configuration.Provider;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
using System.Web.Configuration;
|
||||
using System.Web.Security;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
//TODO: Delete - should not be used
|
||||
/// <summary>
|
||||
/// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing.
|
||||
/// </summary>
|
||||
[Obsolete("We are now using ASP.NET Core Identity instead of membership providers")]
|
||||
public abstract class MembershipProviderBase : MembershipProvider
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration.Provider;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
@@ -7,13 +7,14 @@ using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Composing;
|
||||
using System;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Web.Security.Providers
|
||||
{
|
||||
//TODO: Delete: should not be used
|
||||
[Obsolete("We are now using ASP.NET Core Identity instead of membership providers")]
|
||||
/// <summary>
|
||||
/// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS)
|
||||
/// </summary>
|
||||
@@ -120,6 +121,7 @@ namespace Umbraco.Web.Security.Providers
|
||||
public override LegacyPasswordSecurity PasswordSecurity => _passwordSecurity.Value;
|
||||
public IPasswordConfiguration PasswordConfiguration => _passwordConfig.Value;
|
||||
|
||||
[Obsolete("We are now using ASP.NET Core Identity instead of membership providers")]
|
||||
private class MembershipProviderPasswordConfiguration : IPasswordConfiguration
|
||||
{
|
||||
public MembershipProviderPasswordConfiguration(int requiredLength, bool requireNonLetterOrDigit, bool requireDigit, bool requireLowercase, bool requireUppercase, bool useLegacyEncoding, string hashAlgorithmType, int maxFailedAccessAttemptsBeforeLockout)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Collections.Specialized;
|
||||
using System;
|
||||
using System.Configuration.Provider;
|
||||
using System.Linq;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -10,9 +9,12 @@ using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web.Security.Providers
|
||||
{
|
||||
//TODO: Delete: should not be used
|
||||
[Obsolete("We are now using ASP.NET Core Identity instead of membership providers")]
|
||||
public class MembersRoleProvider : RoleProvider
|
||||
{
|
||||
private readonly IMembershipRoleService<IMember> _roleService;
|
||||
private string _applicationName;
|
||||
|
||||
public MembersRoleProvider(IMembershipRoleService<IMember> roleService)
|
||||
{
|
||||
@@ -24,8 +26,6 @@ namespace Umbraco.Web.Security.Providers
|
||||
{
|
||||
}
|
||||
|
||||
private string _applicationName;
|
||||
|
||||
public override bool IsUserInRole(string username, string roleName)
|
||||
{
|
||||
return GetRolesForUser(username).Any(x => x == roleName);
|
||||
@@ -46,10 +46,12 @@ namespace Umbraco.Web.Security.Providers
|
||||
return _roleService.DeleteRole(roleName, throwOnPopulatedRole);
|
||||
}
|
||||
|
||||
public override bool RoleExists(string roleName)
|
||||
{
|
||||
return _roleService.GetAllRoles().Any(x => x == roleName);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns true if the specified member role name exists
|
||||
/// </summary>
|
||||
/// <param name="roleName">Member role name</param>
|
||||
/// <returns>True if member role exists, otherwise false</returns>
|
||||
public override bool RoleExists(string roleName) => _roleService.GetAllRoles().Any(x => x.Name == roleName);
|
||||
|
||||
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
@@ -66,10 +68,11 @@ namespace Umbraco.Web.Security.Providers
|
||||
return _roleService.GetMembersInRole(roleName).Select(x => x.Username).ToArray();
|
||||
}
|
||||
|
||||
public override string[] GetAllRoles()
|
||||
{
|
||||
return _roleService.GetAllRoles().ToArray();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets all the member roles
|
||||
/// </summary>
|
||||
/// <returns>A list of member roles</returns>
|
||||
public override string[] GetAllRoles() => _roleService.GetAllRoles().Select(x => x.Name).ToArray();
|
||||
|
||||
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
|
||||
{
|
||||
@@ -87,6 +90,7 @@ namespace Umbraco.Web.Security.Providers
|
||||
{
|
||||
return _applicationName;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration.Provider;
|
||||
using System.Linq;
|
||||
@@ -17,8 +17,8 @@ using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web.Security.Providers
|
||||
{
|
||||
|
||||
|
||||
//TODO: Delete - should not be used
|
||||
[Obsolete("We are now using ASP.NET Core Identity instead of membership providers")]
|
||||
/// <summary>
|
||||
/// Abstract Membership Provider that users any implementation of IMembershipMemberService{TEntity} service
|
||||
/// </summary>
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Web.Security.Providers
|
||||
protected IMembershipMemberService<TEntity> MemberService { get; private set; }
|
||||
|
||||
protected UmbracoMembershipProvider(IMembershipMemberService<TEntity> memberService, IUmbracoVersion umbracoVersion, IHostingEnvironment hostingEnvironment, IIpResolver ipResolver)
|
||||
:base(hostingEnvironment)
|
||||
: base(hostingEnvironment)
|
||||
{
|
||||
_umbracoVersion = umbracoVersion;
|
||||
_ipResolver = ipResolver;
|
||||
@@ -55,9 +55,11 @@ namespace Umbraco.Web.Security.Providers
|
||||
/// <exception cref="T:System.ArgumentException">The name of the provider has a length of zero.</exception>
|
||||
public override void Initialize(string name, NameValueCollection config)
|
||||
{
|
||||
if (config == null) { throw new ArgumentNullException("config"); }
|
||||
if (config == null)
|
||||
{ throw new ArgumentNullException("config"); }
|
||||
|
||||
if (string.IsNullOrEmpty(name)) name = ProviderName;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = ProviderName;
|
||||
|
||||
// Initialize base provider class
|
||||
base.Initialize(name, config);
|
||||
@@ -80,7 +82,8 @@ namespace Umbraco.Web.Security.Providers
|
||||
|
||||
// in order to support updating passwords from the umbraco core, we can't validate the old password
|
||||
var m = MemberService.GetByUsername(username);
|
||||
if (m == null) return false;
|
||||
if (m == null)
|
||||
return false;
|
||||
|
||||
string salt;
|
||||
var encodedPassword = PasswordSecurity.HashNewPassword(Membership.HashAlgorithmType, newPassword, out salt);
|
||||
@@ -174,7 +177,8 @@ namespace Umbraco.Web.Security.Providers
|
||||
public override bool DeleteUser(string username, bool deleteAllRelatedData)
|
||||
{
|
||||
var member = MemberService.GetByUsername(username);
|
||||
if (member == null) return false;
|
||||
if (member == null)
|
||||
return false;
|
||||
|
||||
MemberService.Delete(member);
|
||||
return true;
|
||||
@@ -423,7 +427,8 @@ namespace Umbraco.Web.Security.Providers
|
||||
}
|
||||
|
||||
// Non need to update
|
||||
if (member.IsLockedOut == false) return true;
|
||||
if (member.IsLockedOut == false)
|
||||
return true;
|
||||
|
||||
member.IsLockedOut = false;
|
||||
member.FailedPasswordAttempts = 0;
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=2DA32DA040A7D74599ABE288C7224CF0/Severity/@EntryValue">HINT</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=37A0B37A0ABAA34AA5CB32A93653C4FE/@KeyIndexDefined">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">Default</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Umbraco/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unpublish/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unpublishing/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
|
||||
Reference in New Issue
Block a user