Username passed into roles. Added initial roles store functionality. Updated user roles functionality to persist the member group.

This commit is contained in:
Emma Garland
2021-02-01 17:43:11 +00:00
parent 8caf2a0e62
commit 152ad9684c
10 changed files with 133 additions and 133 deletions

View File

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

View File

@@ -32,13 +32,12 @@ namespace Umbraco.Infrastructure.Security
/// <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;
_scopeProvider = scopeProvider;
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
}
/// <summary>
@@ -55,7 +54,7 @@ namespace Umbraco.Infrastructure.Security
public override Task SetNormalizedUserNameAsync(MembersIdentityUser user, string normalizedName, CancellationToken cancellationToken) => SetUserNameAsync(user, normalizedName, cancellationToken);
/// <inheritdoc />
public override Task<IdentityResult> CreateAsync(MembersIdentityUser user, CancellationToken cancellationToken)
public override Task<IdentityResult> CreateAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -338,8 +337,10 @@ namespace Umbraco.Infrastructure.Security
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var logins = new List<IIdentityUserLogin>();
// TODO: external login needed?
var logins = new List<IIdentityUserLogin>(); //_externalLoginService.Find(loginProvider, providerKey).ToList();
//_externalLoginService.Find(loginProvider, providerKey).ToList();
if (logins.Count == 0)
{
return Task.FromResult((IdentityUserLogin<string>)null);
@@ -379,99 +380,13 @@ namespace Umbraco.Infrastructure.Security
if (userRole == null)
{
_memberService.AssignRole(user.UserName, role);
user.AddRole(role);
}
return Task.CompletedTask;
}
/// <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>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The Task that represents the asynchronous operation, containing the IdentityResult of the operation</returns>
public Task AddToRolesAsync(MembersIdentityUser user, IEnumerable<string> roles, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
IEnumerable<string> enumerable = roles as string[] ?? roles.ToArray();
foreach (string role in enumerable)
{
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)
{
user.AddRole(role);
}
}
_memberService.AssignRoles(new[] { user.UserName }, enumerable.ToArray());
return Task.CompletedTask;
}
/// <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>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>The Task that represents the asynchronous operation, containing the IdentityResult of the operation.</returns>
public Task RemoveFromRolesAsync(MembersIdentityUser user, IEnumerable<string> roles, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
IEnumerable<string> enumerable = roles as string[] ?? roles.ToArray();
foreach (string role in enumerable)
{
if (string.IsNullOrWhiteSpace(role))
{
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(role));
}
//TODO: is the role ID the role string passed in?
IdentityUserRole<string> userRole = user.Roles.SingleOrDefault(r => r.RoleId == role);
if (userRole != null)
{
user.Roles.Remove(userRole);
}
}
//TODO: confirm that when updating the identity member, we're also calling the service to update in the DB via repository
_memberService.DissociateRoles(new[] { user.UserName }, enumerable.ToArray());
return Task.CompletedTask;
}
//TODO: should we call the single remove from the multiple remove? or have it only in one place?
/// <inheritdoc/>
public override Task RemoveFromRoleAsync(MembersIdentityUser user, string role, CancellationToken cancellationToken = default)
{
@@ -492,13 +407,11 @@ namespace Umbraco.Infrastructure.Security
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(role));
}
//TODO: is the role ID the role string passed in?
IdentityUserRole<string> userRole = user.Roles.SingleOrDefault(r => r.RoleId == role);
if (userRole != null)
{
//TODO: when updating the identity member, we're also calling the service to update in the DB via repository
_memberService.DissociateRole(userRole.UserId, userRole.RoleId);
_memberService.DissociateRole(user.UserName, userRole.RoleId);
user.Roles.Remove(userRole);
}
@@ -517,8 +430,14 @@ namespace Umbraco.Infrastructure.Security
throw new ArgumentNullException(nameof(user));
}
//TODO: should we have tests for the store?
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());
}

View File

@@ -54,7 +54,8 @@ namespace Umbraco.Tests.UnitTests.AutoFixture
.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());

View File

@@ -527,7 +527,6 @@ namespace Umbraco.Web.BackOffice.Controllers
// their handlers. If we don't look this up now there's a chance we'll just end up
// removing the roles they've assigned.
IEnumerable<string> currentRoles = await _memberManager.GetRolesAsync(identityMember);
//IEnumerable<string> currentRoles = _memberService.GetAllRoles(contentItem.PersistedContent.Username);
// find the ones to remove and remove them
IEnumerable<string> roles = currentRoles.ToList();
@@ -538,7 +537,6 @@ namespace Umbraco.Web.BackOffice.Controllers
if (rolesToRemove.Any())
{
await _memberManager.RemoveFromRolesAsync(identityMember, rolesToRemove);
//_memberService.DissociateRoles(new[] { contentItem.PersistedContent.Username }, rolesToRemove);
}
// find the ones to add and add them

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;

View File

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

View File

@@ -1,25 +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("Will be replaced by UmbracoMemberUserManager")]
[Obsolete("We are now using ASP.NET Core Identity instead of membership providers")]
public abstract class MembershipProviderBase : MembershipProvider
{
private readonly IHostingEnvironment _hostingEnvironment;

View File

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

View File

@@ -1,3 +1,4 @@
using System;
using System.Configuration.Provider;
using System.Linq;
using System.Web.Security;
@@ -8,6 +9,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")]
public class MembersRoleProvider : RoleProvider
{
private readonly IMembershipRoleService<IMember> _roleService;

View File

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