diff --git a/.gitignore b/.gitignore
index 95295e44c4..1c54100176 100644
--- a/.gitignore
+++ b/.gitignore
@@ -201,4 +201,6 @@ src/Umbraco.Tests/TEMP/
/src/Umbraco.Web.UI/config/umbracoSettings.config
/src/Umbraco.Web.UI.NetCore/Umbraco/models/*
+src/Umbraco.Tests.UnitTests/umbraco/Data/TEMP/
/src/Umbraco.Web.UI.NetCore/appsettings.Local.json
+src/Umbraco.Tests.Integration/DatabaseContextTests.sdf
diff --git a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs
index 92aab36bd4..0d3a5b7536 100644
--- a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs
+++ b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs
@@ -119,7 +119,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
Value = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, member.ContentType.Name),
View = _propertyEditorCollection[Constants.PropertyEditors.Aliases.Label].GetValueEditor().View
},
- GetLoginProperty(_memberTypeService, member, _localizedTextService),
+ GetLoginProperty(member, _localizedTextService),
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email",
@@ -208,7 +208,6 @@ namespace Umbraco.Cms.Core.Models.Mapping
///
/// Returns the login property display field
///
- ///
///
///
///
@@ -218,7 +217,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
/// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not actually natively
/// allow that.
///
- internal static ContentPropertyDisplay GetLoginProperty(IMemberTypeService memberTypeService, IMember member, ILocalizedTextService localizedText)
+ internal static ContentPropertyDisplay GetLoginProperty(IMember member, ILocalizedTextService localizedText)
{
var prop = new ContentPropertyDisplay
{
@@ -234,7 +233,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
internal IDictionary GetMemberGroupValue(string username)
{
- var userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username);
+ IEnumerable userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username);
// create a dictionary of all roles (except internal roles) + "false"
var result = _memberGroupService.GetAll()
@@ -245,11 +244,16 @@ namespace Umbraco.Cms.Core.Models.Mapping
.ToDictionary(x => x, x => false);
// if user has no roles, just return the dictionary
- if (userRoles == null) return result;
+ if (userRoles == null)
+ {
+ return result;
+ }
// else update the dictionary to "true" for the user roles (except internal roles)
foreach (var userRole in userRoles.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false))
+ {
result[userRole] = true;
+ }
return result;
}
diff --git a/src/Umbraco.Core/Services/IMemberGroupService.cs b/src/Umbraco.Core/Services/IMemberGroupService.cs
index e584537ab1..16028ded3f 100644
--- a/src/Umbraco.Core/Services/IMemberGroupService.cs
+++ b/src/Umbraco.Core/Services/IMemberGroupService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
index 406eb08c62..e97add3f5e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
@@ -314,7 +314,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
// persist the member dto
dto.NodeId = nodeDto.NodeId;
- // TODO: password parts of this file need updating
// if the password is empty, generate one with the special prefix
// this will hash the guid with a salt so should be nicely random
if (entity.RawPasswordValue.IsNullOrWhiteSpace())
diff --git a/src/Umbraco.Infrastructure/Security/IMemberManager.cs b/src/Umbraco.Infrastructure/Security/IMemberManager.cs
index 85b4c0c300..b310e9434f 100644
--- a/src/Umbraco.Infrastructure/Security/IMemberManager.cs
+++ b/src/Umbraco.Infrastructure/Security/IMemberManager.cs
@@ -3,7 +3,7 @@ namespace Umbraco.Cms.Core.Security
///
/// The user manager for members
///
- public interface IMemberManager : IUmbracoUserManager
+ public interface IMemberManager : IUmbracoUserManager
{
}
}
diff --git a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
index 30d435f345..0cf724ec20 100644
--- a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
+++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs
@@ -48,10 +48,10 @@ namespace Umbraco.Cms.Core.Security
target.EnableChangeTracking();
});
- mapper.Define(
+ mapper.Define(
(source, context) =>
{
- var target = new MembersIdentityUser(source.Id);
+ var target = new MemberIdentityUser(source.Id);
target.DisableChangeTracking();
return target;
},
@@ -100,7 +100,7 @@ namespace Umbraco.Cms.Core.Security
//target.Roles =;
}
- private void Map(IMember source, MembersIdentityUser target)
+ private void Map(IMember source, MemberIdentityUser target)
{
target.Email = source.Email;
target.UserName = source.Username;
diff --git a/src/Umbraco.Infrastructure/Security/MembersIdentityBuilder.cs b/src/Umbraco.Infrastructure/Security/MemberIdentityBuilder.cs
similarity index 71%
rename from src/Umbraco.Infrastructure/Security/MembersIdentityBuilder.cs
rename to src/Umbraco.Infrastructure/Security/MemberIdentityBuilder.cs
index 726b999b89..4e2e4a39b1 100644
--- a/src/Umbraco.Infrastructure/Security/MembersIdentityBuilder.cs
+++ b/src/Umbraco.Infrastructure/Security/MemberIdentityBuilder.cs
@@ -5,18 +5,18 @@ using Microsoft.Extensions.DependencyInjection;
namespace Umbraco.Cms.Core.Security
{
- public class MembersIdentityBuilder : IdentityBuilder
+ public class MemberIdentityBuilder : IdentityBuilder
{
- public MembersIdentityBuilder(IServiceCollection services) : base(typeof(MembersIdentityUser), services)
+ public MemberIdentityBuilder(IServiceCollection services) : base(typeof(MemberIdentityUser), services)
{
}
- public MembersIdentityBuilder(Type role, IServiceCollection services) : base(typeof(MembersIdentityUser), role, services)
+ public MemberIdentityBuilder(Type role, IServiceCollection services) : base(typeof(MemberIdentityUser), role, services)
{
}
///
- /// Adds a token provider for the .
+ /// Adds a token provider for the .
///
/// The name of the provider to add.
/// The type of the to add.
@@ -27,7 +27,7 @@ namespace Umbraco.Cms.Core.Security
{
throw new InvalidOperationException($"Invalid Type for TokenProvider: {provider.FullName}");
}
- Services.Configure(options =>
+ Services.Configure(options =>
{
options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider);
});
diff --git a/src/Umbraco.Infrastructure/Security/MembersIdentityOptions.cs b/src/Umbraco.Infrastructure/Security/MemberIdentityOptions.cs
similarity index 78%
rename from src/Umbraco.Infrastructure/Security/MembersIdentityOptions.cs
rename to src/Umbraco.Infrastructure/Security/MemberIdentityOptions.cs
index 8f993a1f76..4e05797a04 100644
--- a/src/Umbraco.Infrastructure/Security/MembersIdentityOptions.cs
+++ b/src/Umbraco.Infrastructure/Security/MemberIdentityOptions.cs
@@ -5,7 +5,7 @@ namespace Umbraco.Cms.Core.Security
///
/// Identity options specifically for the Umbraco members identity implementation
///
- public class MembersIdentityOptions : IdentityOptions
+ public class MemberIdentityOptions : IdentityOptions
{
}
}
diff --git a/src/Umbraco.Infrastructure/Security/MembersIdentityUser.cs b/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs
similarity index 90%
rename from src/Umbraco.Infrastructure/Security/MembersIdentityUser.cs
rename to src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs
index 6e3473c3ce..539234ac65 100644
--- a/src/Umbraco.Infrastructure/Security/MembersIdentityUser.cs
+++ b/src/Umbraco.Infrastructure/Security/MemberIdentityUser.cs
@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Core.Security
///
/// The identity user used for the member
///
- public class MembersIdentityUser : UmbracoIdentityUser
+ public class MemberIdentityUser : UmbracoIdentityUser
{
private string _name;
private string _passwordConfig;
@@ -23,29 +23,29 @@ namespace Umbraco.Cms.Core.Security
groups => groups.GetHashCode());
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public MembersIdentityUser(int userId)
+ public MemberIdentityUser(int userId)
{
// use the property setters - they do more than just setting a field
Id = UserIdToString(userId);
}
- public MembersIdentityUser()
+ public MemberIdentityUser()
{
}
///
/// Used to construct a new instance without an identity
///
- public static MembersIdentityUser CreateNew(string username, string email, string memberTypeAlias, string name = null)
+ public static MemberIdentityUser 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();
+ var user = new MemberIdentityUser();
user.DisableChangeTracking();
user.UserName = username;
user.Email = email;
diff --git a/src/Umbraco.Infrastructure/Security/MemberRoleStore.cs b/src/Umbraco.Infrastructure/Security/MemberRoleStore.cs
new file mode 100644
index 0000000000..279735bfa2
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Security/MemberRoleStore.cs
@@ -0,0 +1,272 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models.Identity;
+using Umbraco.Cms.Core.Services;
+
+namespace Umbraco.Cms.Core.Security
+{
+ ///
+ /// A custom user store that uses Umbraco member data
+ ///
+ public class MemberRoleStore : IRoleStore
+ {
+ private readonly IMemberGroupService _memberGroupService;
+ private bool _disposed;
+
+ //TODO: Move into custom error describer.
+ //TODO: How revealing can the error messages be?
+ private readonly IdentityError _intParseError = new IdentityError { Code = "IdentityIdParseError", Description = "Cannot parse ID to int" };
+ private readonly IdentityError _memberGroupNotFoundError = new IdentityError { Code = "IdentityMemberGroupNotFound", Description = "Member group not found" };
+ private const string genericIdentityErrorCode = "IdentityErrorUserStore";
+
+ public MemberRoleStore(IMemberGroupService memberGroupService, IdentityErrorDescriber errorDescriber)
+ {
+ _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService));
+ ErrorDescriber = errorDescriber ?? throw new ArgumentNullException(nameof(errorDescriber));
+ }
+
+ ///
+ /// Gets or sets the for any error that occurred with the current operation.
+ ///
+ public IdentityErrorDescriber ErrorDescriber { get; set; }
+
+ ///
+ public Task CreateAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+
+ var memberGroup = new MemberGroup
+ {
+ Name = role.Name
+ };
+
+ _memberGroupService.Save(memberGroup);
+
+ role.Id = memberGroup.Id.ToString();
+
+ return Task.FromResult(IdentityResult.Success);
+ }
+
+
+ ///
+ public Task UpdateAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+
+ if (!int.TryParse(role.Id, out int roleId))
+ {
+ return Task.FromResult(IdentityResult.Failed(_intParseError));
+ }
+
+ IMemberGroup memberGroup = _memberGroupService.GetById(roleId);
+ if (memberGroup != null)
+ {
+ if (MapToMemberGroup(role, memberGroup))
+ {
+ _memberGroupService.Save(memberGroup);
+ }
+
+ return Task.FromResult(IdentityResult.Success);
+ }
+ else
+ {
+ return Task.FromResult(IdentityResult.Failed(_memberGroupNotFoundError));
+ }
+ }
+
+ ///
+ public Task DeleteAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+
+ if (!int.TryParse(role.Id, out int roleId))
+ {
+ throw new ArgumentException("The Id of the role is not an integer");
+ }
+
+ IMemberGroup memberGroup = _memberGroupService.GetById(roleId);
+ if (memberGroup != null)
+ {
+ _memberGroupService.Delete(memberGroup);
+ }
+ else
+ {
+ return Task.FromResult(IdentityResult.Failed(_memberGroupNotFoundError));
+ }
+
+ return Task.FromResult(IdentityResult.Success);
+ }
+
+ ///
+ public Task GetRoleIdAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+
+ return Task.FromResult(role.Id);
+ }
+
+ ///
+ public Task GetRoleNameAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+
+ return Task.FromResult(role.Name);
+ }
+
+ ///
+ public Task SetRoleNameAsync(UmbracoIdentityRole role, string roleName, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+ role.Name = roleName;
+ return Task.CompletedTask;
+ }
+
+ ///
+ public Task GetNormalizedRoleNameAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
+ => GetRoleNameAsync(role, cancellationToken);
+
+ ///
+ public Task SetNormalizedRoleNameAsync(UmbracoIdentityRole role, string normalizedName, CancellationToken cancellationToken = default)
+ => SetRoleNameAsync(role, normalizedName, cancellationToken);
+
+ ///
+ public Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+
+ if (string.IsNullOrWhiteSpace(roleId))
+ {
+ throw new ArgumentNullException(nameof(roleId));
+ }
+
+ IMemberGroup memberGroup;
+
+ // member group can be found by int or Guid, so try both
+ if (!int.TryParse(roleId, out int id))
+ {
+ if (!Guid.TryParse(roleId, out Guid guid))
+ {
+ throw new ArgumentOutOfRangeException(nameof(roleId), $"{nameof(roleId)} is not a valid Guid");
+ }
+ else
+ {
+ memberGroup = _memberGroupService.GetById(guid);
+ }
+ }
+ else
+ {
+ memberGroup = _memberGroupService.GetById(id);
+ }
+
+ return Task.FromResult(memberGroup == null ? null : MapFromMemberGroup(memberGroup));
+ }
+
+ ///
+ public Task FindByNameAsync(string name, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ IMemberGroup memberGroup = _memberGroupService.GetByName(name);
+ return Task.FromResult(memberGroup == null ? null : MapFromMemberGroup(memberGroup));
+ }
+
+ ///
+ /// Maps a member group to an identity role
+ ///
+ ///
+ ///
+ private UmbracoIdentityRole MapFromMemberGroup(IMemberGroup memberGroup)
+ {
+ var result = new UmbracoIdentityRole
+ {
+ Id = memberGroup.Id.ToString(),
+ Name = memberGroup.Name
+ // TODO: Implement this functionality, requires DB and logic updates
+ //ConcurrencyStamp
+ };
+ return result;
+ }
+
+ ///
+ /// Map an identity role to a member group
+ ///
+ ///
+ ///
+ ///
+ private bool MapToMemberGroup(UmbracoIdentityRole role, IMemberGroup memberGroup)
+ {
+ var anythingChanged = false;
+
+ if (role.IsPropertyDirty(nameof(UmbracoIdentityRole.Name))
+ && !string.IsNullOrEmpty(role.Name) && memberGroup.Name != role.Name)
+ {
+ // TODO: Need to support ConcurrencyStamp and logic
+
+ memberGroup.Name = role.Name;
+ anythingChanged = true;
+ }
+
+ return anythingChanged;
+ }
+
+ ///
+ /// Dispose the store
+ ///
+ public void Dispose() => _disposed = true;
+
+ ///
+ /// Throws if this class has been disposed.
+ ///
+ protected void ThrowIfDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Security/MemberRolesUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberRolesUserStore.cs
deleted file mode 100644
index 65b3cf1ef9..0000000000
--- a/src/Umbraco.Infrastructure/Security/MemberRolesUserStore.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-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.Cms.Core.Scoping;
-using Umbraco.Cms.Core.Services;
-
-namespace Umbraco.Cms.Core.Security
-{
- ///
- /// A custom user store that uses Umbraco member data
- ///
- public class MemberRolesUserStore : RoleStoreBase, string, IdentityUserRole, IdentityRoleClaim>
- {
- 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));
- }
-
- ///
- public override IQueryable> Roles { get; }
-
- ///
- public override Task CreateAsync(IdentityRole role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
-
- ///
- public override Task UpdateAsync(IdentityRole role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
-
- ///
- public override Task DeleteAsync(IdentityRole role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
-
- ///
- public override Task> FindByIdAsync(string id, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
-
- ///
- public override Task> FindByNameAsync(string normalizedName, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
-
- ///
- public override Task> GetClaimsAsync(IdentityRole role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
-
- ///
- public override Task AddClaimAsync(IdentityRole role, Claim claim, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
-
- ///
- public override Task RemoveClaimAsync(IdentityRole role, Claim claim, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
- }
-}
diff --git a/src/Umbraco.Infrastructure/Security/MembersUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs
similarity index 59%
rename from src/Umbraco.Infrastructure/Security/MembersUserStore.cs
rename to src/Umbraco.Infrastructure/Security/MemberUserStore.cs
index 6b4a735988..c0b9a19ef1 100644
--- a/src/Umbraco.Infrastructure/Security/MembersUserStore.cs
+++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs
@@ -19,20 +19,21 @@ namespace Umbraco.Cms.Core.Security
///
/// A custom user store that uses Umbraco member data
///
- public class MembersUserStore : UserStoreBase, string, IdentityUserClaim, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim>
+ public class MemberUserStore : UserStoreBase, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim>
{
+ private const string genericIdentityErrorCode = "IdentityErrorUserStore";
private readonly IMemberService _memberService;
private readonly UmbracoMapper _mapper;
private readonly IScopeProvider _scopeProvider;
///
- /// Initializes a new instance of the class for the members identity store
+ /// Initializes a new instance of the class for the members identity store
///
/// The member service
/// The mapper for properties
/// The scope provider
/// The error describer
- public MembersUserStore(IMemberService memberService, UmbracoMapper mapper, IScopeProvider scopeProvider, IdentityErrorDescriber describer)
+ public MemberUserStore(IMemberService memberService, UmbracoMapper mapper, IScopeProvider scopeProvider, IdentityErrorDescriber describer)
: base(describer)
{
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
@@ -40,183 +41,213 @@ namespace Umbraco.Cms.Core.Security
_scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
}
+ //TODO: why is this not supported?
///
/// Not supported in Umbraco
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public override IQueryable Users => throw new NotImplementedException();
+ public override IQueryable Users => throw new NotImplementedException();
///
- public override Task GetNormalizedUserNameAsync(MembersIdentityUser user, CancellationToken cancellationToken) => GetUserNameAsync(user, cancellationToken);
+ public override Task GetNormalizedUserNameAsync(MemberIdentityUser user, CancellationToken cancellationToken = default) => GetUserNameAsync(user, cancellationToken);
///
- public override Task SetNormalizedUserNameAsync(MembersIdentityUser user, string normalizedName, CancellationToken cancellationToken) => SetUserNameAsync(user, normalizedName, cancellationToken);
+ public override Task SetNormalizedUserNameAsync(MemberIdentityUser user, string normalizedName, CancellationToken cancellationToken = default) => SetUserNameAsync(user, normalizedName, cancellationToken);
///
- public override Task CreateAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
+ public override Task CreateAsync(MemberIdentityUser user, CancellationToken cancellationToken = default)
{
- cancellationToken.ThrowIfCancellationRequested();
- ThrowIfDisposed();
- if (user == null)
+ try
{
- 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);
- }
-
- ///
- public override Task UpdateAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
- {
- cancellationToken.ThrowIfCancellationRequested();
- ThrowIfDisposed();
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
-
- Attempt asInt = user.Id.TryConvertTo();
- 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)
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (user == 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)));
- //}
+ throw new ArgumentNullException(nameof(user));
}
- scope.Complete();
- }
+ // 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);
- return Task.FromResult(IdentityResult.Success);
+ 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);
+ }
+ catch (Exception ex)
+ {
+ return Task.FromResult(IdentityResult.Failed(new IdentityError { Code = genericIdentityErrorCode, Description = ex.Message }));
+ }
}
///
- public override Task DeleteAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
+ public override Task UpdateAsync(MemberIdentityUser user, CancellationToken cancellationToken = default)
{
- cancellationToken.ThrowIfCancellationRequested();
- ThrowIfDisposed();
- if (user == null)
+ try
{
- throw new ArgumentNullException(nameof(user));
- }
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (user == null)
+ {
+ throw new ArgumentNullException(nameof(user));
+ }
- IMember found = _memberService.GetById(UserIdToInt(user.Id));
- if (found != null)
+ Attempt asInt = user.Id.TryConvertTo();
+ if (asInt == false)
+ {
+ //TODO: should this be thrown, or an identity result?
+ throw new InvalidOperationException("The user id must be an integer to work with 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(MemberIdentityUser.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);
+ }
+ }
+ catch (Exception ex)
{
- _memberService.Delete(found);
+ return Task.FromResult(IdentityResult.Failed(new IdentityError { Code = genericIdentityErrorCode, Description = ex.Message }));
}
-
- // TODO: when to implement external login service?
- //_externalLoginService.DeleteUserLogins(UserIdToInt(user.Id));
-
- return Task.FromResult(IdentityResult.Success);
}
///
- public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default) => FindUserAsync(userId, cancellationToken);
+ public override Task DeleteAsync(MemberIdentityUser user, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ 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);
+ }
+ catch (Exception ex)
+ {
+ return Task.FromResult(IdentityResult.Failed(new IdentityError { Code = genericIdentityErrorCode, Description = ex.Message }));
+ }
+ }
///
- protected override Task FindUserAsync(string userId, CancellationToken cancellationToken)
+ public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default) => FindUserAsync(userId, cancellationToken);
+
+ ///
+ protected override Task FindUserAsync(string userId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
+ if (string.IsNullOrWhiteSpace(userId))
+ {
+ throw new ArgumentNullException(nameof(userId));
+ }
+
IMember user = _memberService.GetById(UserIdToInt(userId));
if (user == null)
{
- return Task.FromResult((MembersIdentityUser)null);
+ return Task.FromResult((MemberIdentityUser)null);
}
- return Task.FromResult(AssignLoginsCallback(_mapper.Map(user)));
+ return Task.FromResult(AssignLoginsCallback(_mapper.Map(user)));
}
///
- public override Task FindByNameAsync(string userName, CancellationToken cancellationToken = default)
+ public override Task FindByNameAsync(string userName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
IMember user = _memberService.GetByUsername(userName);
if (user == null)
{
- return Task.FromResult((MembersIdentityUser)null);
+ return Task.FromResult((MemberIdentityUser)null);
}
- MembersIdentityUser result = AssignLoginsCallback(_mapper.Map(user));
+ MemberIdentityUser result = AssignLoginsCallback(_mapper.Map(user));
return Task.FromResult(result);
}
///
- public override async Task SetPasswordHashAsync(MembersIdentityUser user, string passwordHash, CancellationToken cancellationToken = default)
+ public override async Task SetPasswordHashAsync(MemberIdentityUser 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
+ // Clear this so that it's reset at the repository level
+ user.PasswordConfig = null;
user.LastPasswordChangeDateUtc = DateTime.UtcNow;
}
///
- public override async Task HasPasswordAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
+ public override async Task HasPasswordAsync(MemberIdentityUser user, CancellationToken cancellationToken = default)
{
// This checks if it's null
- var result = await base.HasPasswordAsync(user, cancellationToken);
+ bool result = await base.HasPasswordAsync(user, cancellationToken);
if (result)
{
// we also want to check empty
@@ -227,28 +258,28 @@ namespace Umbraco.Cms.Core.Security
}
///
- public override Task FindByEmailAsync(string email, CancellationToken cancellationToken = default)
+ public override Task FindByEmailAsync(string email, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
IMember member = _memberService.GetByEmail(email);
- MembersIdentityUser result = member == null
+ MemberIdentityUser result = member == null
? null
- : _mapper.Map(member);
+ : _mapper.Map(member);
return Task.FromResult(AssignLoginsCallback(result));
}
///
- public override Task GetNormalizedEmailAsync(MembersIdentityUser user, CancellationToken cancellationToken)
+ public override Task GetNormalizedEmailAsync(MemberIdentityUser user, CancellationToken cancellationToken)
=> GetEmailAsync(user, cancellationToken);
///
- public override Task SetNormalizedEmailAsync(MembersIdentityUser user, string normalizedEmail, CancellationToken cancellationToken)
+ public override Task SetNormalizedEmailAsync(MemberIdentityUser user, string normalizedEmail, CancellationToken cancellationToken)
=> SetEmailAsync(user, normalizedEmail, cancellationToken);
///
- public override Task AddLoginAsync(MembersIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default)
+ public override Task AddLoginAsync(MemberIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -262,8 +293,22 @@ namespace Umbraco.Cms.Core.Security
throw new ArgumentNullException(nameof(login));
}
+ if (string.IsNullOrWhiteSpace(login.LoginProvider))
+ {
+ throw new ArgumentNullException(nameof(login.LoginProvider));
+ }
+
+ if (string.IsNullOrWhiteSpace(login.ProviderKey))
+ {
+ throw new ArgumentNullException(nameof(login.ProviderKey));
+ }
+
ICollection logins = user.Logins;
- var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id.ToString());
+ var instance = new IdentityUserLogin(
+ login.LoginProvider,
+ login.ProviderKey,
+ user.Id.ToString());
+
IdentityUserLogin userLogin = instance;
logins.Add(userLogin);
@@ -271,7 +316,7 @@ namespace Umbraco.Cms.Core.Security
}
///
- public override Task RemoveLoginAsync(MembersIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default)
+ public override Task RemoveLoginAsync(MemberIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -280,6 +325,16 @@ namespace Umbraco.Cms.Core.Security
throw new ArgumentNullException(nameof(user));
}
+ if (string.IsNullOrWhiteSpace(loginProvider))
+ {
+ throw new ArgumentNullException(nameof(loginProvider));
+ }
+
+ if (string.IsNullOrWhiteSpace(providerKey))
+ {
+ throw new ArgumentNullException(nameof(providerKey));
+ }
+
IIdentityUserLogin userLogin = user.Logins.SingleOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey);
if (userLogin != null)
{
@@ -290,7 +345,7 @@ namespace Umbraco.Cms.Core.Security
}
///
- public override Task> GetLoginsAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
+ public override Task> GetLoginsAsync(MemberIdentityUser user, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -308,24 +363,35 @@ namespace Umbraco.Cms.Core.Security
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- MembersIdentityUser user = await FindUserAsync(userId, cancellationToken);
+ if (string.IsNullOrWhiteSpace(loginProvider))
+ {
+ throw new ArgumentNullException(nameof(loginProvider));
+ }
+
+ if (string.IsNullOrWhiteSpace(providerKey))
+ {
+ throw new ArgumentNullException(nameof(providerKey));
+ }
+
+ MemberIdentityUser user = await FindUserAsync(userId, cancellationToken);
if (user == null)
{
- return null;
+ return await Task.FromResult((IdentityUserLogin)null);
}
IList logins = await GetLoginsAsync(user, cancellationToken);
UserLoginInfo found = logins.FirstOrDefault(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider);
if (found == null)
{
- return null;
+ return await Task.FromResult((IdentityUserLogin)null);
}
return new IdentityUserLogin
{
LoginProvider = found.LoginProvider,
ProviderKey = found.ProviderKey,
- ProviderDisplayName = found.ProviderDisplayName, // TODO: We don't store this value so it will be null
+ // TODO: We don't store this value so it will be null
+ ProviderDisplayName = found.ProviderDisplayName,
UserId = user.Id
};
}
@@ -336,9 +402,19 @@ namespace Umbraco.Cms.Core.Security
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
+ if (string.IsNullOrWhiteSpace(loginProvider))
+ {
+ throw new ArgumentNullException(nameof(loginProvider));
+ }
+
+ if (string.IsNullOrWhiteSpace(providerKey))
+ {
+ throw new ArgumentNullException(nameof(providerKey));
+ }
+
var logins = new List();
- // TODO: external login needed?
+ // TODO: external login needed
//_externalLoginService.Find(loginProvider, providerKey).ToList();
if (logins.Count == 0)
{
@@ -350,15 +426,20 @@ namespace Umbraco.Cms.Core.Security
{
LoginProvider = found.LoginProvider,
ProviderKey = found.ProviderKey,
- ProviderDisplayName = null, // TODO: We don't store this value so it will be null
+ // TODO: We don't store this value so it will be null
+ ProviderDisplayName = null,
UserId = found.UserId
});
}
///
- public override Task AddToRoleAsync(MembersIdentityUser user, string role, CancellationToken cancellationToken = default)
+ public override Task AddToRoleAsync(MemberIdentityUser user, string role, CancellationToken cancellationToken = default)
{
- cancellationToken.ThrowIfCancellationRequested();
+ if (cancellationToken != null)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+
ThrowIfDisposed();
if (user == null)
{
@@ -387,7 +468,7 @@ namespace Umbraco.Cms.Core.Security
}
///
- public override Task RemoveFromRoleAsync(MembersIdentityUser user, string role, CancellationToken cancellationToken = default)
+ public override Task RemoveFromRoleAsync(MemberIdentityUser user, string role, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -420,7 +501,7 @@ namespace Umbraco.Cms.Core.Security
///
/// Gets a list of role names the specified user belongs to.
///
- public override Task> GetRolesAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
+ public override Task> GetRolesAsync(MemberIdentityUser user, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -443,7 +524,7 @@ namespace Umbraco.Cms.Core.Security
///
/// Returns true if a user is in the role
///
- public override Task IsInRoleAsync(MembersIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
+ public override Task IsInRoleAsync(MemberIdentityUser user, string roleName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -452,48 +533,59 @@ namespace Umbraco.Cms.Core.Security
throw new ArgumentNullException(nameof(user));
}
- return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(normalizedRoleName));
+ if (string.IsNullOrWhiteSpace(roleName))
+ {
+ throw new ArgumentNullException(nameof(roleName));
+ }
+
+ return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(roleName));
}
///
/// Lists all users of a given role.
///
- public override Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default)
+ public override Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- if (normalizedRoleName == null)
+
+ if (string.IsNullOrWhiteSpace(roleName))
{
- throw new ArgumentNullException(nameof(normalizedRoleName));
+ throw new ArgumentNullException(nameof(roleName));
}
- IEnumerable members = _memberService.GetMembersByMemberType(normalizedRoleName);
+ IEnumerable members = _memberService.GetMembersByMemberType(roleName);
- IList membersIdentityUsers = members.Select(x => _mapper.Map(x)).ToList();
+ IList membersIdentityUsers = members.Select(x => _mapper.Map(x)).ToList();
return Task.FromResult(membersIdentityUsers);
}
///
- protected override Task> FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken)
+ protected override Task FindRoleAsync(string roleName, CancellationToken cancellationToken)
{
- IMemberGroup group = _memberService.GetAllRoles().SingleOrDefault(x => x.Name == normalizedRoleName);
- if (group == null)
+ if (string.IsNullOrWhiteSpace(roleName))
{
- return Task.FromResult((IdentityRole)null);
+ throw new ArgumentNullException(nameof(roleName));
}
- return Task.FromResult(new IdentityRole(group.Name)
+ IMemberGroup group = _memberService.GetAllRoles().SingleOrDefault(x => x.Name == roleName);
+ if (group == null)
+ {
+ return Task.FromResult((IdentityRole)null);
+ }
+
+ return Task.FromResult(new IdentityRole(group.Name)
{
//TODO: what should the alias be?
- Id = @group.Id.ToString()
+ Id = group.Id.ToString()
});
}
///
protected override async Task> FindUserRoleAsync(string userId, string roleId, CancellationToken cancellationToken)
{
- MembersIdentityUser user = await FindUserAsync(userId, cancellationToken);
+ MemberIdentityUser user = await FindUserAsync(userId, cancellationToken);
if (user == null)
{
return null;
@@ -504,7 +596,7 @@ namespace Umbraco.Cms.Core.Security
}
///
- public override Task GetSecurityStampAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
+ public override Task GetSecurityStampAsync(MemberIdentityUser user, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -519,23 +611,23 @@ namespace Umbraco.Cms.Core.Security
: user.SecurityStamp);
}
- private MembersIdentityUser AssignLoginsCallback(MembersIdentityUser user)
+ private MemberIdentityUser AssignLoginsCallback(MemberIdentityUser user)
{
if (user != null)
{
- //TODO: when to
+ //TODO: implement
//user.SetLoginsCallback(new Lazy>(() => _externalLoginService.GetAll(UserIdToInt(user.Id))));
}
return user;
}
- private bool UpdateMemberProperties(IMember member, MembersIdentityUser identityUserMember)
+ private bool UpdateMemberProperties(IMember member, MemberIdentityUser 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))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.LastLoginDateUtc))
|| (member.LastLoginDate != default && identityUserMember.LastLoginDateUtc.HasValue == false)
|| (identityUserMember.LastLoginDateUtc.HasValue && member.LastLoginDate.ToUniversalTime() != identityUserMember.LastLoginDateUtc.Value))
{
@@ -546,7 +638,7 @@ namespace Umbraco.Cms.Core.Security
member.LastLoginDate = dt;
}
- if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.LastPasswordChangeDateUtc))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.LastPasswordChangeDateUtc))
|| (member.LastPasswordChangeDate != default && identityUserMember.LastPasswordChangeDateUtc.HasValue == false)
|| (identityUserMember.LastPasswordChangeDateUtc.HasValue && member.LastPasswordChangeDate.ToUniversalTime() != identityUserMember.LastPasswordChangeDateUtc.Value))
{
@@ -554,7 +646,7 @@ namespace Umbraco.Cms.Core.Security
member.LastPasswordChangeDate = identityUserMember.LastPasswordChangeDateUtc.Value.ToLocalTime();
}
- if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.EmailConfirmed))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.EmailConfirmed))
|| (member.EmailConfirmedDate.HasValue && member.EmailConfirmedDate.Value != default && identityUserMember.EmailConfirmed == false)
|| ((member.EmailConfirmedDate.HasValue == false || member.EmailConfirmedDate.Value == default) && identityUserMember.EmailConfirmed))
{
@@ -562,21 +654,21 @@ namespace Umbraco.Cms.Core.Security
member.EmailConfirmedDate = identityUserMember.EmailConfirmed ? (DateTime?)DateTime.Now : null;
}
- if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Name))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.Name))
&& member.Name != identityUserMember.Name && identityUserMember.Name.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
member.Name = identityUserMember.Name;
}
- if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Email))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.Email))
&& member.Email != identityUserMember.Email && identityUserMember.Email.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
member.Email = identityUserMember.Email;
}
- if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.AccessFailedCount))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.AccessFailedCount))
&& member.FailedPasswordAttempts != identityUserMember.AccessFailedCount)
{
anythingChanged = true;
@@ -601,14 +693,14 @@ namespace Umbraco.Cms.Core.Security
member.IsApproved = identityUserMember.IsApproved;
}
- if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.UserName))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.UserName))
&& member.Username != identityUserMember.UserName && identityUserMember.UserName.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
member.Username = identityUserMember.UserName;
}
- if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.PasswordHash))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.PasswordHash))
&& member.RawPasswordValue != identityUserMember.PasswordHash && identityUserMember.PasswordHash.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
@@ -623,7 +715,7 @@ namespace Umbraco.Cms.Core.Security
}
// TODO: Fix this for Groups too (as per backoffice comment)
- if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Roles)) || identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Groups)))
+ if (identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.Roles)) || identityUserMember.IsPropertyDirty(nameof(MemberIdentityUser.Groups)))
{
}
@@ -651,42 +743,42 @@ namespace Umbraco.Cms.Core.Security
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public override Task> GetClaimsAsync(MembersIdentityUser user, CancellationToken cancellationToken = default) => throw new NotImplementedException();
+ public override Task> GetClaimsAsync(MemberIdentityUser user, CancellationToken cancellationToken = default) => throw new NotImplementedException();
///
/// Not supported in Umbraco
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public override Task AddClaimsAsync(MembersIdentityUser user, IEnumerable claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
+ public override Task AddClaimsAsync(MemberIdentityUser user, IEnumerable claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
///
/// Not supported in Umbraco
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public override Task ReplaceClaimAsync(MembersIdentityUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default) => throw new NotImplementedException();
+ public override Task ReplaceClaimAsync(MemberIdentityUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default) => throw new NotImplementedException();
///
/// Not supported in Umbraco
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public override Task RemoveClaimsAsync(MembersIdentityUser user, IEnumerable claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
+ public override Task RemoveClaimsAsync(MemberIdentityUser user, IEnumerable claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
///
/// Not supported in Umbraco
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default) => throw new NotImplementedException();
+ public override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default) => throw new NotImplementedException();
///
/// Not supported in Umbraco
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
- protected override Task> FindTokenAsync(MembersIdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) => throw new NotImplementedException();
+ protected override Task> FindTokenAsync(MemberIdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) => throw new NotImplementedException();
///
/// Not supported in Umbraco
diff --git a/src/Umbraco.Infrastructure/Security/UmbracoIdentityRole.cs b/src/Umbraco.Infrastructure/Security/UmbracoIdentityRole.cs
new file mode 100644
index 0000000000..9d06dcd037
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Security/UmbracoIdentityRole.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using Microsoft.AspNetCore.Identity;
+using Umbraco.Cms.Core.Models.Entities;
+
+namespace Umbraco.Cms.Core.Models.Identity
+{
+ public class UmbracoIdentityRole : IdentityRole, IRememberBeingDirty
+ {
+ private string _id;
+ private string _name;
+
+ public event PropertyChangedEventHandler PropertyChanged
+ {
+ add
+ {
+ BeingDirty.PropertyChanged += value;
+ }
+
+ remove
+ {
+ BeingDirty.PropertyChanged -= value;
+ }
+ }
+
+ ///
+ public override string Id
+ {
+ get => _id;
+ set
+ {
+ _id = value;
+ HasIdentity = true;
+ }
+ }
+
+ ///
+ public override string Name
+ {
+ get => _name;
+ set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
+ }
+
+ ///
+ public override string NormalizedName { get => base.Name; set => base.Name = value; }
+
+ ///
+ /// Gets or sets a value indicating whether returns an Id has been set on this object this will be false if the object is new and not persisted to the database
+ ///
+ public bool HasIdentity { get; protected set; }
+
+ // TODO: We should support this and it's logic
+ public override string ConcurrencyStamp { get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; }
+
+ ///
+ /// Gets the for change tracking
+ ///
+ protected BeingDirty BeingDirty { get; } = new BeingDirty();
+
+ ///
+ public bool IsDirty() => BeingDirty.IsDirty();
+
+ ///
+ public bool IsPropertyDirty(string propName) => BeingDirty.IsPropertyDirty(propName);
+
+ ///
+ public IEnumerable GetDirtyProperties() => BeingDirty.GetDirtyProperties();
+
+ ///
+ public void ResetDirtyProperties() => BeingDirty.ResetDirtyProperties();
+
+ ///
+ public bool WasDirty() => BeingDirty.WasDirty();
+
+ ///
+ public bool WasPropertyDirty(string propertyName) => BeingDirty.WasPropertyDirty(propertyName);
+
+ ///
+ public void ResetWereDirtyProperties() => BeingDirty.ResetWereDirtyProperties();
+
+ ///
+ public void ResetDirtyProperties(bool rememberDirty) => BeingDirty.ResetDirtyProperties(rememberDirty);
+
+ ///
+ public IEnumerable GetWereDirtyProperties() => BeingDirty.GetWereDirtyProperties();
+
+ ///
+ /// Disables change tracking.
+ ///
+ public void DisableChangeTracking() => BeingDirty.DisableChangeTracking();
+
+ ///
+ /// Enables change tracking.
+ ///
+ public void EnableChangeTracking() => BeingDirty.EnableChangeTracking();
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs b/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs
index 525e7f839a..bf553b3d30 100644
--- a/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs
+++ b/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs
@@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Models.Entities;
namespace Umbraco.Cms.Core.Models.Identity
{
+
///
/// Abstract class for use in Umbraco Identity for users and members
///
diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs
index 5e6138980a..9024e3c4e1 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs
index b563cc3ec4..872a6ac367 100644
--- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs
+++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs
@@ -17,6 +17,13 @@ namespace Umbraco.Cms.Tests.Common.Builders.Extensions
return builder;
}
+ public static T WithId(this T builder, TId id)
+ where T : IWithIdBuilder
+ {
+ builder.Id = id;
+ return builder;
+ }
+
public static T WithoutIdentity(this T builder)
where T : IWithIdBuilder
{
diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs
index fe26c89d85..604f683dd7 100644
--- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs
@@ -7,4 +7,9 @@ namespace Umbraco.Cms.Tests.Common.Builders.Interfaces
{
int? Id { get; set; }
}
+
+ public interface IWithIdBuilder
+ {
+ TId Id { get; set; }
+ }
}
diff --git a/src/Umbraco.Tests.Common/Builders/UmbracoIdentityRoleBuilder.cs b/src/Umbraco.Tests.Common/Builders/UmbracoIdentityRoleBuilder.cs
new file mode 100644
index 0000000000..6ffe4fd5c5
--- /dev/null
+++ b/src/Umbraco.Tests.Common/Builders/UmbracoIdentityRoleBuilder.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using Umbraco.Cms.Core.Models.Identity;
+using Umbraco.Cms.Tests.Common.Builders.Interfaces;
+
+namespace Umbraco.Cms.Tests.Common.Builders
+{
+ public class UmbracoIdentityRoleBuilder : BuilderBase,
+ IWithIdBuilder,
+ IWithNameBuilder
+ {
+ private string _id;
+ private string _name;
+
+ public UmbracoIdentityRoleBuilder WithTestName(string id)
+ {
+ _name = "testname";
+ _id = id;
+ return this;
+ }
+
+ string IWithNameBuilder.Name
+ {
+ get => _name;
+ set => _name = value;
+ }
+
+ string IWithIdBuilder.Id
+ {
+ get => _id;
+ set => _id = value;
+ }
+
+ public override UmbracoIdentityRole Build()
+ {
+ var id = _id;
+ var name = _name;
+
+ return new UmbracoIdentityRole
+ {
+ Id = id,
+ Name = name,
+ };
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs
index 95fbc3a435..9d00962a9f 100644
--- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs
+++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.Models.Identity;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Builders.Interfaces;
@@ -12,6 +13,7 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.Common.Builders
{
+
public class UserBuilder : UserBuilder