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/MembersRoleStore.cs b/src/Umbraco.Infrastructure/Security/MembersRoleStore.cs
new file mode 100644
index 0000000000..3f238c5c48
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Security/MembersRoleStore.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections;
+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.Models;
+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 MembersRoleStore : RoleStoreBase, string, IdentityUserRole, IdentityRoleClaim>
+ {
+ private readonly IMemberService _memberService;
+ private readonly IMemberGroupService _memberGroupService;
+ private readonly IScopeProvider _scopeProvider;
+
+ public MembersRoleStore(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
+ {
+ IEnumerable memberGroups = _memberGroupService.GetAll();
+ var identityRoles = new List>();
+ foreach (IMemberGroup group in memberGroups)
+ {
+ IdentityRole identityRole = MapFromMemberGroup(group);
+ identityRoles.Add(identityRole);
+ }
+
+ return identityRoles.AsQueryable();
+ }
+ }
+
+ ///
+ public override Task CreateAsync(
+ IdentityRole role,
+ CancellationToken cancellationToken = new CancellationToken())
+ {
+ 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 override Task UpdateAsync(IdentityRole role,
+ CancellationToken cancellationToken = new CancellationToken())
+ {
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+
+ if (!int.TryParse(role.Id, out int roleId))
+ {
+ return new Task(() => IdentityResult.Failed());
+ }
+
+ IMemberGroup memberGroup = _memberGroupService.GetById(roleId);
+ if (memberGroup != null)
+ {
+ if (MapToMemberGroup(role, memberGroup))
+ {
+ _memberGroupService.Save(memberGroup);
+ }
+ }
+
+ return Task.FromResult(IdentityResult.Success);
+ }
+
+ ///
+ public override Task DeleteAsync(IdentityRole role,
+ CancellationToken cancellationToken = new CancellationToken())
+ {
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+
+ if (!int.TryParse(role.Id, out int roleId))
+ {
+ return new Task(() => IdentityResult.Failed());
+ }
+
+ IMemberGroup memberGroup = _memberGroupService.GetById(roleId);
+ if (memberGroup != null)
+ {
+ _memberGroupService.Delete(memberGroup);
+ }
+ else
+ {
+ //TODO: throw exception when not found, or return failure?
+ return Task.FromResult(IdentityResult.Failed());
+ }
+
+ return Task.FromResult(IdentityResult.Success);
+ }
+
+ ///
+ public override Task> FindByIdAsync(string id,
+ CancellationToken cancellationToken = new CancellationToken())
+ {
+ if (!int.TryParse(id, out int roleId))
+ {
+ return null;
+ }
+
+ IMemberGroup memberGroup = _memberGroupService.GetById(roleId);
+
+ return Task.FromResult(memberGroup == null ? null : MapFromMemberGroup(memberGroup));
+ }
+
+ ///
+ public override Task> FindByNameAsync(string normalizedName,
+ CancellationToken cancellationToken = new CancellationToken())
+ {
+ IMemberGroup memberGroup = _memberGroupService.GetByName(normalizedName);
+
+ return Task.FromResult(memberGroup == null ? null : MapFromMemberGroup(memberGroup));
+ }
+
+ ///
+ 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();
+
+ ///
+ /// Maps a member group to an identity role
+ ///
+ ///
+ ///
+ private IdentityRole MapFromMemberGroup(IMemberGroup memberGroup)
+ {
+ var result = new IdentityRole
+ {
+ Id = memberGroup.Id.ToString(),
+ Name = memberGroup.Name
+ };
+
+ return result;
+ }
+
+ ///
+ /// Map an identity role to a member group
+ ///
+ ///
+ ///
+ ///
+ private bool MapToMemberGroup(IdentityRole role, IMemberGroup memberGroup)
+ {
+ var anythingChanged = false;
+
+ if (!string.IsNullOrEmpty(role.Name) && memberGroup.Name != role.Name)
+ {
+ memberGroup.Name = role.Name;
+ anythingChanged = true;
+ }
+
+ return anythingChanged;
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserStoreTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserStoreTests.cs
index 1a4f05d984..8afe7fe198 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserStoreTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserStoreTests.cs
@@ -74,8 +74,5 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security
Assert.IsTrue(identityResult.Succeeded);
Assert.IsTrue(!identityResult.Errors.Any());
}
-
- //GetPasswordHashAsync
- //GetUserIdAsync
}
}
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberRoleStoreTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberRoleStoreTests.cs
new file mode 100644
index 0000000000..d9337f6f10
--- /dev/null
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberRoleStoreTests.cs
@@ -0,0 +1,222 @@
+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.Cms.Core.Models;
+using Umbraco.Cms.Core.Scoping;
+using Umbraco.Cms.Core.Security;
+using Umbraco.Cms.Core.Services;
+
+namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security
+{
+ [TestFixture]
+ public class MemberRoleStoreTests
+ {
+ private Mock _mockMemberService;
+ private Mock _mockMemberGroupService;
+
+ public MembersRoleStore CreateSut()
+ {
+ _mockMemberService = new Mock();
+ _mockMemberGroupService = new Mock();
+ return new MembersRoleStore(
+ _mockMemberService.Object,
+ _mockMemberGroupService.Object,
+ new Mock().Object,
+ new IdentityErrorDescriber());
+ }
+
+ [Test]
+ public void GivenICreateAMemberRole_AndTheGroupIsNull_ThenIShouldGetAFailedResultAsync()
+ {
+ // arrange
+ MembersRoleStore sut = CreateSut();
+ CancellationToken fakeCancellationToken = new CancellationToken() { };
+
+ // act
+ Action actual = () => sut.CreateAsync(null, fakeCancellationToken);
+
+ // assert
+ Assert.That(actual, Throws.ArgumentNullException);
+ }
+
+
+ [Test]
+ public async Task GivenICreateAMemberRole_AndTheGroupIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync()
+ {
+ // arrange
+ MembersRoleStore sut = CreateSut();
+ var fakeRole = new IdentityRole()
+ {
+ Id = "777",
+ Name = "testname"
+ };
+ var fakeCancellationToken = new CancellationToken() { };
+
+ IMemberGroup mockMemberGroup = Mock.Of(m =>
+ m.Name == "fakeGroupName" && m.CreatorId == 77);
+
+ bool raiseEvents = false;
+
+ _mockMemberGroupService.Setup(x => x.Save(mockMemberGroup, raiseEvents));
+
+ // act
+ IdentityResult identityResult = await sut.CreateAsync(fakeRole, fakeCancellationToken);
+
+ // assert
+ Assert.IsTrue(identityResult.Succeeded);
+ Assert.IsTrue(!identityResult.Errors.Any());
+ _mockMemberGroupService.Verify(x => x.Save(It.IsAny(), It.IsAny()));
+ }
+
+ [Test]
+ public async Task GivenIUpdateAMemberRole_AndTheGroupExists_ThenIShouldGetASuccessResultAsync()
+ {
+ // arrange
+ MembersRoleStore sut = CreateSut();
+ var fakeRole = new IdentityRole()
+ {
+ Id = "777",
+ Name = "testname"
+ };
+ var fakeCancellationToken = new CancellationToken() { };
+
+ IMemberGroup mockMemberGroup = Mock.Of(m =>
+ m.Name == "fakeGroupName" && m.CreatorId == 77);
+
+ bool raiseEvents = false;
+
+ _mockMemberGroupService.Setup(x => x.GetById(777)).Returns(mockMemberGroup);
+ _mockMemberGroupService.Setup(x => x.Save(mockMemberGroup, raiseEvents));
+
+ // act
+ IdentityResult identityResult = await sut.UpdateAsync(fakeRole, fakeCancellationToken);
+
+ // assert
+ Assert.IsTrue(identityResult.Succeeded);
+ Assert.IsTrue(!identityResult.Errors.Any());
+ _mockMemberGroupService.Verify(x => x.Save(mockMemberGroup, false));
+ _mockMemberGroupService.Verify(x => x.GetById(777));
+ }
+
+ [Test]
+ public async Task GivenIUpdateAMemberRole_AndTheGroupDoesntExist_ThenIShouldGetAFailureResultAsync()
+ {
+ // arrange
+ MembersRoleStore sut = CreateSut();
+ var fakeRole = new IdentityRole()
+ {
+ Id = "777",
+ Name = "testname"
+ };
+ var fakeCancellationToken = new CancellationToken() { };
+
+ IMemberGroup mockMemberGroup = Mock.Of(m =>
+ m.Name == "fakeGroupName" && m.CreatorId == 77);
+
+ bool raiseEvents = false;
+
+
+ // act
+ IdentityResult identityResult = await sut.UpdateAsync(fakeRole, fakeCancellationToken);
+
+ // assert
+ Assert.IsTrue(identityResult.Succeeded == false);
+ Assert.IsTrue(identityResult.Errors.Any());
+ _mockMemberGroupService.Verify(x => x.GetById(777));
+ }
+
+ [Test]
+ public async Task GivenIDeleteAMemberRole_AndItExists_ThenTheMemberGroupShouldBeDeleted_AndIShouldGetASuccessResultAsync()
+ {
+ // arrange
+ MembersRoleStore sut = CreateSut();
+ var fakeRole = new IdentityRole()
+ {
+ Id = "777",
+ Name = "testname"
+ };
+ var fakeCancellationToken = new CancellationToken() { };
+
+ IMemberGroup mockMemberGroup = Mock.Of(m =>
+ m.Name == "fakeGroupName" && m.CreatorId == 77);
+
+ _mockMemberGroupService.Setup(x => x.GetById(777)).Returns(mockMemberGroup);
+
+ // act
+ IdentityResult identityResult = await sut.DeleteAsync(fakeRole, fakeCancellationToken);
+
+ // assert
+ Assert.IsTrue(identityResult.Succeeded);
+ Assert.IsTrue(!identityResult.Errors.Any());
+ _mockMemberGroupService.Verify(x => x.GetById(777));
+ _mockMemberGroupService.Verify(x => x.Delete(mockMemberGroup));
+ }
+
+ [Test]
+ public async Task GivenIDeleteAMemberRole_AndItDoesntExist_ThenTheMemberGroupShouldBeDeleted_AndIShouldGetASuccessResultAsync()
+ {
+ // arrange
+ MembersRoleStore sut = CreateSut();
+ var fakeRole = new IdentityRole()
+ {
+ Id = "777",
+ Name = "testname"
+ };
+ var fakeCancellationToken = new CancellationToken() { };
+
+ IMemberGroup mockMemberGroup = Mock.Of(m =>
+ m.Name == "fakeGroupName" && m.CreatorId == 77);
+
+
+ // act
+ IdentityResult identityResult = await sut.DeleteAsync(fakeRole, fakeCancellationToken);
+
+ // assert
+ Assert.IsTrue(identityResult.Succeeded == false);
+ Assert.IsTrue(identityResult.Errors.Any());
+ _mockMemberGroupService.Verify(x => x.GetById(777));
+ _mockMemberGroupService.Verify(x => x.Delete(mockMemberGroup));
+ }
+
+ [Test]
+ public async Task GivenIGetAllMemberRoles_ThenIShouldGetAllMemberGroups_AndASuccessResultAsync()
+ {
+ // arrange
+ MembersRoleStore sut = CreateSut();
+ var fakeRole = new IdentityRole()
+ {
+ Id = "777",
+ Name = "testname"
+ };
+ IEnumerable> expected = new List>()
+ {
+ new IdentityRole("fakeGroupName")
+ {
+ Id = "77"
+ }
+ };
+
+ IMemberGroup mockMemberGroup = Mock.Of(m =>
+ m.Name == "fakeGroupName" && m.CreatorId == 77);
+
+ IEnumerable fakeMemberGroups = new List()
+ {
+ mockMemberGroup
+ };
+
+ _mockMemberGroupService.Setup(x => x.GetAll()).Returns(fakeMemberGroups);
+
+ // act
+ IQueryable> actual = sut.Roles;
+
+ // assert
+ Assert.AreEqual(expected.AsQueryable(), actual);
+ _mockMemberGroupService.Verify(x => x.GetAll());
+ }
+ }
+}