New backoffice: User controller (#13947)
* Add UserResponseModel * Add factory to created UserResponseModel * Add GetByKey controller * Add GetAllUsers endpoint * User proper response model * Make naming consistent * Order by username in GetAll * Add user filter endpoint * Fix includer user states * Remove gravatar from the backend * Send user avatars in response * Add create user model * start working on create * Validate the create model * Add authorization to create * Use UserRepository instead of UserService to ValidateSessíonId * Create IBackofficeUserStore interface This is essentially a core-friendly version of the BackOfficeUserStore, additionally it contains basic methods for managing users, I.E. Get users, save users, create users, etc. * Remove more usages of user service * Remove usages of IUserService in BackofficeUserStore * Add documentation * Fix tests and DI * add IBackOfficeUserStoreAccessor to resolve it in singleton services * Resolve circular dependency * Remove obsolete constructor * Add core friendly user manager * Finish createasync in user service * Add WIP create endpoint * Save newly creates users user groups * Use service scope for user service * Remove now unnecessary accessors * Add response types * Add update user endpoint * Add EmailUserInviteSender * Add technology free way of creating confirmation token * Add invite uri provider * Add invite user to user service * Add invite user controller * Add delete endpoint * Add operation status responses * Add operation status responses * Added temporary file uploads including a repository implementation using local temp folder. * Add Disable users endpoint * missing files * Fixed copy paste error * Fix create users return type * Updated OpenApi.json * Updated OpenApi.json * Handle if created failed in identity * Add enable user * Make users plural in enable/disable We're doing the operation on multiple entities * Added file extension check * Add unlock user endpoint * Clean up. Removed old TemporaryFileService and UploadFileService and updated dictionary items to use this new items * Clean up * Add reset password * Add UpdateUserGroupsOnUsers method * Add UpdateUserGroups * Get rid of stream directly on TemporaryFileModel, and use delegate to open stream instead. * Fix post merge * Use keys instead of IDs * Add ClearAvatar endpoint * Review changes * Moved models to their own files * Reverted launch settings * Move enlist extension to its own namespace * Create set avatar endpoint * Add reponse types * Remove infrastructure extension after merge * Add Cmapatibility suppressions * Add test suppression * Add integration tests * Fix issue found in tests * Add invited user to UserInvitationResult * Add more tests * Add update tests * Hide different tests under parent * Return DuplicatUserName user operation status if username matches an email * Add update tests * Change sorted set to HashSet It doesn't work if it's not IComparable * Change ID to Key when checking super * Add get tests * Add more GetAllTests * Move tests to the right namespace * Add filter test * Fix including disabled users bug found by test * Add test to ensure invited user state * Add test case for UserState.All * Add more filter tests * Add enable disable tests * Add resolver for keys and ids * Replace usages of IUserService with IUserIdKeyResolver * Add CompatibilitySuppressions * Add UserIdKeyResolverTests * Fix UserIdKeyResolver * Add missing user operation results * Updates from review * ID not key * Post instead of patch * Use set instead of params for enable/disable * Don't call to array * Use sets for usergroup keys and user keys instead * LanguageIsoCode instead of Language * Update CompatibilitySuppressions after changin enumerable to set --------- Co-authored-by: Bjarke Berg <mail@bergmania.dk> Co-authored-by: kjac <kja@umbraco.dk>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
|
||||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class UserServiceCrudTests
|
||||
{
|
||||
[Test]
|
||||
[TestCase("test@email.com", "test@email.com", true, true)]
|
||||
[TestCase("test@email.com", "notTheUserName@email.com", true, false)]
|
||||
[TestCase("NotAnEmail", "test@email.com", true, false)]
|
||||
[TestCase("test@email.com", "test@email.com", false, true)]
|
||||
[TestCase("NotAnEmail", "test@email.com", false, true)]
|
||||
[TestCase("aDifferentEmail@email.com", "test@email.com", false, true)]
|
||||
public async Task Creating_User_Name_Must_Be_Email(
|
||||
string username,
|
||||
string email,
|
||||
bool userNameIsEmailEnabled,
|
||||
bool shouldSucceed)
|
||||
{
|
||||
var securitySettings = new SecuritySettings { UsernameIsEmail = userNameIsEmailEnabled };
|
||||
var userService = CreateUserService(securitySettings);
|
||||
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var creationModel = new UserCreateModel
|
||||
{
|
||||
UserName = username,
|
||||
Email = email,
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var result = await userService.CreateAsync(Constants.Security.SuperUserKey, creationModel, true);
|
||||
|
||||
if (shouldSucceed is false)
|
||||
{
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.UserNameIsNotEmail, result.Status);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.Success, result.Status);
|
||||
var createdUser = result.Result.CreatedUser;
|
||||
Assert.IsNotNull(createdUser);
|
||||
Assert.AreEqual(username, createdUser.Username);
|
||||
Assert.AreEqual(email, createdUser.Email);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Create_User_With_Duplicate_Email()
|
||||
{
|
||||
var email = "test@test.com";
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var initialUserCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "Test1",
|
||||
Email = email,
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var userService = CreateUserService(new SecuritySettings { UsernameIsEmail = false });
|
||||
var result = await userService.CreateAsync(Constants.Security.SuperUserKey, initialUserCreateModel, true);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
var duplicateUserCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "Test2",
|
||||
Email = email,
|
||||
Name = "Duplicate Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var secondResult = await userService.CreateAsync(Constants.Security.SuperUserKey, duplicateUserCreateModel, true);
|
||||
Assert.IsFalse(secondResult.Success);
|
||||
Assert.AreEqual(UserOperationStatus.DuplicateEmail, secondResult.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Create_User_With_Duplicate_UserName()
|
||||
{
|
||||
var userName = "UserName";
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var initialUserCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = userName,
|
||||
Email = "test@email.com",
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var userService = CreateUserService(new SecuritySettings { UsernameIsEmail = false });
|
||||
var result = await userService.CreateAsync(Constants.Security.SuperUserKey, initialUserCreateModel, true);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
var duplicateUserCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = userName,
|
||||
Email = "another@email.com",
|
||||
Name = "Duplicate Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var secondResult = await userService.CreateAsync(Constants.Security.SuperUserKey, duplicateUserCreateModel, true);
|
||||
Assert.IsFalse(secondResult.Success);
|
||||
Assert.AreEqual(UserOperationStatus.DuplicateUserName, secondResult.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Create_User_Without_User_Group()
|
||||
{
|
||||
UserCreateModel userCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "NoUser@Group.com",
|
||||
Email = "NoUser@Group.com",
|
||||
Name = "NoUser@Group.com",
|
||||
};
|
||||
|
||||
IUserService userService = CreateUserService();
|
||||
|
||||
var result = await userService.CreateAsync(Constants.Security.SuperUserKey, userCreateModel, true);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.NoUserGroup, result.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Performing_User_Must_Exist_When_Creating()
|
||||
{
|
||||
IUserService userService = CreateUserService();
|
||||
|
||||
var result = await userService.CreateAsync(Guid.Empty, new UserCreateModel(), true);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.MissingUser, result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class UserServiceCrudTests
|
||||
{
|
||||
[Test]
|
||||
public async Task Delete_Returns_Not_Found_If_Not_Found()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
var result = await userService.DeleteAsync(Guid.NewGuid());
|
||||
Assert.AreEqual(UserOperationStatus.NotFound, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Delete_User_With_Login()
|
||||
{
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var userCreateModel = new UserCreateModel
|
||||
{
|
||||
Email = "test@test.com",
|
||||
UserName = "test@test.com",
|
||||
Name = "test@test.com",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var userService = CreateUserService();
|
||||
var creationResult = await userService.CreateAsync(Constants.Security.SuperUserKey, userCreateModel, true);
|
||||
Assert.IsTrue(creationResult.Success);
|
||||
var createdUser = creationResult.Result.CreatedUser;
|
||||
|
||||
createdUser!.LastLoginDate = DateTime.Now;
|
||||
userService.Save(createdUser);
|
||||
|
||||
var result = await userService.DeleteAsync(createdUser.Key);
|
||||
Assert.AreEqual(UserOperationStatus.CannotDelete, result);
|
||||
|
||||
// Asset that it is in fact not deleted
|
||||
var postDeletedUser = await userService.GetAsync(createdUser.Key);
|
||||
Assert.IsNotNull(postDeletedUser);
|
||||
Assert.AreEqual(createdUser.Key, postDeletedUser.Key);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Delete_User_That_Has_Not_Logged_In()
|
||||
{
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var userCreateModel = new UserCreateModel
|
||||
{
|
||||
Email = "test@test.com",
|
||||
UserName = "test@test.com",
|
||||
Name = "test@test.com",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var userService = CreateUserService();
|
||||
var creationResult = await userService.CreateAsync(Constants.Security.SuperUserKey, userCreateModel, true);
|
||||
Assert.IsTrue(creationResult.Success);
|
||||
|
||||
var deletionResult = await userService.DeleteAsync(creationResult.Result.CreatedUser!.Key);
|
||||
Assert.AreEqual(UserOperationStatus.Success, deletionResult);
|
||||
// Make sure it's actually deleted
|
||||
var postDeletedUser = await userService.GetAsync(creationResult.Result.CreatedUser.Key);
|
||||
Assert.IsNull(postDeletedUser);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class UserServiceCrudTests
|
||||
{
|
||||
[Test]
|
||||
[TestCase(UserState.Disabled)]
|
||||
[TestCase(UserState.All)]
|
||||
public async Task Cannot_Request_Disabled_If_Hidden(UserState includeState)
|
||||
{
|
||||
var userService = CreateUserService(new SecuritySettings {HideDisabledUsersInBackOffice = true});
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var createModel = new UserCreateModel
|
||||
{
|
||||
UserName = "editor@mail.com",
|
||||
Email = "editor@mail.com",
|
||||
Name = "Editor",
|
||||
UserGroups = new HashSet<IUserGroup> {editorGroup!}
|
||||
};
|
||||
|
||||
var createAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, createModel, true);
|
||||
Assert.IsTrue(createAttempt.Success);
|
||||
|
||||
var disableStatus =
|
||||
await userService.DisableAsync(Constants.Security.SuperUserKey, new HashSet<Guid>{ createAttempt.Result.CreatedUser!.Key });
|
||||
Assert.AreEqual(UserOperationStatus.Success, disableStatus);
|
||||
|
||||
var filter = new UserFilter {IncludeUserStates = new SortedSet<UserState> {includeState}};
|
||||
|
||||
var filterAttempt = await userService.FilterAsync(Constants.Security.SuperUserKey, filter, 0, 1000);
|
||||
Assert.IsTrue(filterAttempt.Success);
|
||||
Assert.AreEqual(0, filterAttempt.Result.Items.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Only_Super_User_Can_Filter_Super_user()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
|
||||
var nonSuperCreateModel = new UserCreateModel
|
||||
{
|
||||
Email = "not@super.com",
|
||||
UserName = "not@super.com",
|
||||
UserGroups = new HashSet<IUserGroup> {editorGroup!, adminGroup!},
|
||||
Name = "Not A Super User"
|
||||
};
|
||||
|
||||
var createEditorAttempt =
|
||||
await userService.CreateAsync(Constants.Security.SuperUserKey, nonSuperCreateModel, true);
|
||||
Assert.IsTrue(createEditorAttempt.Success);
|
||||
|
||||
var editor = createEditorAttempt.Result.CreatedUser;
|
||||
|
||||
// An empty filter is essentially the same as "Give me everything" but you still can't see super users.
|
||||
var filter = new UserFilter();
|
||||
var filterAttempt = await userService.FilterAsync(editor!.Key, filter, 0, 10000);
|
||||
|
||||
Assert.IsTrue(filterAttempt.Success);
|
||||
var result = filterAttempt.Result;
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(1, result.Items.Count());
|
||||
Assert.AreEqual(1, result.Total);
|
||||
var onlyUser = result.Items.First();
|
||||
Assert.AreEqual(editor.Key, onlyUser.Key);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Super_User_Can_Filter_Super_User()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var nonSuperCreateModel = new UserCreateModel
|
||||
{
|
||||
Email = "not@super.com",
|
||||
UserName = "not@super.com",
|
||||
UserGroups = new HashSet<IUserGroup> {editorGroup!},
|
||||
Name = "Not A Super User"
|
||||
};
|
||||
|
||||
var createEditorAttempt =
|
||||
await userService.CreateAsync(Constants.Security.SuperUserKey, nonSuperCreateModel, true);
|
||||
Assert.IsTrue(createEditorAttempt.Success);
|
||||
|
||||
var filter = new UserFilter {NameFilters = new SortedSet<string> {"admin"}};
|
||||
|
||||
var filterAttempt = await userService.FilterAsync(Constants.Security.SuperUserKey, filter, 0, 10000);
|
||||
Assert.IsTrue(filterAttempt.Success);
|
||||
var result = filterAttempt.Result;
|
||||
|
||||
Assert.AreEqual(1, result.Items.Count());
|
||||
Assert.AreEqual(1, result.Total);
|
||||
Assert.IsNotNull(result.Items.FirstOrDefault(x => x.Key == Constants.Security.SuperUserKey));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Only_Admins_Can_Filter_Admins()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var editorCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "editor@mail.com",
|
||||
Email = "editor@mail.com",
|
||||
Name = "Editor Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> {editorGroup!}
|
||||
};
|
||||
|
||||
var adminCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "admin@mail.com",
|
||||
Email = "admin@mail.com",
|
||||
Name = "Admin Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> {adminGroup!, editorGroup}
|
||||
};
|
||||
|
||||
var createEditorAttempt =
|
||||
await userService.CreateAsync(Constants.Security.SuperUserKey, editorCreateModel, true);
|
||||
var createAdminAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, adminCreateModel, true);
|
||||
|
||||
Assert.IsTrue(createEditorAttempt.Success);
|
||||
Assert.IsTrue(createAdminAttempt.Success);
|
||||
|
||||
var filter = new UserFilter {IncludedUserGroups = new SortedSet<Guid> {adminGroup!.Key}};
|
||||
|
||||
var editorFilterAttempt =
|
||||
await userService.FilterAsync(createEditorAttempt.Result.CreatedUser!.Key, filter, 0, 10000);
|
||||
Assert.IsTrue(editorFilterAttempt.Success);
|
||||
var editorAllUsers = editorFilterAttempt.Result.Items.ToList();
|
||||
Assert.AreEqual(0, editorAllUsers.Count);
|
||||
|
||||
var adminFilterAttempt =
|
||||
await userService.FilterAsync(createAdminAttempt.Result.CreatedUser!.Key, filter, 0, 10000);
|
||||
Assert.IsTrue(adminFilterAttempt.Success);
|
||||
var adminAllUsers = adminFilterAttempt.Result.Items.ToList();
|
||||
Assert.AreEqual(1, adminAllUsers.Count);
|
||||
Assert.IsNotNull(adminAllUsers.FirstOrDefault(x => x.Key == createAdminAttempt.Result.CreatedUser!.Key));
|
||||
}
|
||||
|
||||
private async Task CreateTestUsers(IUserService userService)
|
||||
{
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var writerGroup = await UserGroupService.GetAsync(Constants.Security.WriterGroupAlias);
|
||||
var translatorGroup = await UserGroupService.GetAsync(Constants.Security.TranslatorGroupAlias);
|
||||
|
||||
var createModels = new List<UserCreateModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
UserName = "editor@email.com",
|
||||
Email = "editor@email.com",
|
||||
Name = "Editor",
|
||||
UserGroups = new HashSet<IUserGroup> {editorGroup!}
|
||||
},
|
||||
new()
|
||||
{
|
||||
UserName = "admin@email.com",
|
||||
Email = "admin@email.com",
|
||||
Name = "Admin",
|
||||
UserGroups = new HashSet<IUserGroup> {adminGroup!}
|
||||
},
|
||||
new()
|
||||
{
|
||||
UserName = "write@email.com",
|
||||
Email = "write@email.com",
|
||||
Name = "Write",
|
||||
UserGroups = new HashSet<IUserGroup> {writerGroup}
|
||||
},
|
||||
new()
|
||||
{
|
||||
UserName = "translator@email.com",
|
||||
Email = "translator@email.com",
|
||||
Name = "Translator",
|
||||
UserGroups = new HashSet<IUserGroup> {translatorGroup}
|
||||
},
|
||||
new()
|
||||
{
|
||||
UserName = "EverythingButAdmin@email.com",
|
||||
Email = "EverythingButAdmin@email.com",
|
||||
Name = "Everything But Admin",
|
||||
UserGroups = new HashSet<IUserGroup> {editorGroup, writerGroup, translatorGroup}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var model in createModels)
|
||||
{
|
||||
var result = await userService.CreateAsync(Constants.Security.SuperUserKey, model);
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Include_User_Groups()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
await CreateTestUsers(userService);
|
||||
|
||||
var writerGroup = await UserGroupService.GetAsync(Constants.Security.WriterGroupAlias);
|
||||
var filter = new UserFilter
|
||||
{
|
||||
IncludedUserGroups = new SortedSet<Guid> { writerGroup!.Key }
|
||||
};
|
||||
|
||||
var onlyWritesResult = await userService.FilterAsync(Constants.Security.SuperUserKey, filter, 0, 1000);
|
||||
|
||||
Assert.IsTrue(onlyWritesResult.Success);
|
||||
var users = onlyWritesResult.Result.Items.ToList();
|
||||
Assert.IsTrue(users.Any());
|
||||
Assert.IsFalse(users.Any(x => x.Groups.FirstOrDefault(y => y.Key == writerGroup.Key) is null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Exclude_User_Groups()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
await CreateTestUsers(userService);
|
||||
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
var filter = new UserFilter
|
||||
{
|
||||
ExcludeUserGroups = new SortedSet<Guid> { editorGroup!.Key }
|
||||
};
|
||||
|
||||
var noEditorResult = await userService.FilterAsync(Constants.Security.SuperUserKey, filter, 0, 1000);
|
||||
Assert.IsTrue(noEditorResult);
|
||||
var users = noEditorResult.Result.Items.ToList();
|
||||
Assert.IsTrue(users.Any());
|
||||
Assert.IsFalse(users.Any(x => x.Groups.FirstOrDefault(y => y.Key == editorGroup.Key) is not null));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class UserServiceCrudTests
|
||||
{
|
||||
[Test]
|
||||
public async Task Only_Super_User_Can_Get_Super_user()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
|
||||
var nonSuperCreateModel = new UserCreateModel
|
||||
{
|
||||
Email = "not@super.com",
|
||||
UserName = "not@super.com",
|
||||
UserGroups = new HashSet<IUserGroup> { editorGroup!, adminGroup! },
|
||||
Name = "Not A Super User"
|
||||
};
|
||||
|
||||
var createEditorAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, nonSuperCreateModel, true);
|
||||
Assert.IsTrue(createEditorAttempt.Success);
|
||||
|
||||
var editor = createEditorAttempt.Result.CreatedUser;
|
||||
var allUsersAttempt = await userService.GetAllAsync(editor!.Key, 0, 10000);
|
||||
|
||||
Assert.IsTrue(allUsersAttempt.Success);
|
||||
var result = allUsersAttempt.Result;
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(1, result.Items.Count());
|
||||
Assert.AreEqual(1, result.Total);
|
||||
var onlyUser = result.Items.First();
|
||||
Assert.AreEqual(editor.Key, onlyUser.Key);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Super_User_Can_See_Super_User()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var nonSuperCreateModel = new UserCreateModel
|
||||
{
|
||||
Email = "not@super.com",
|
||||
UserName = "not@super.com",
|
||||
UserGroups = new HashSet<IUserGroup> { editorGroup! },
|
||||
Name = "Not A Super User"
|
||||
};
|
||||
|
||||
var createEditorAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, nonSuperCreateModel, true);
|
||||
Assert.IsTrue(createEditorAttempt.Success);
|
||||
|
||||
var editor = createEditorAttempt.Result.CreatedUser;
|
||||
var allUsersAttempt = await userService.GetAllAsync(Constants.Security.SuperUserKey, 0, 10000);
|
||||
Assert.IsTrue(allUsersAttempt.Success);
|
||||
var result = allUsersAttempt.Result;
|
||||
|
||||
Assert.AreEqual(2, result.Items.Count());
|
||||
Assert.AreEqual(2, result.Total);
|
||||
Assert.IsTrue(result.Items.Any(x => x.Key == Constants.Security.SuperUserKey));
|
||||
Assert.IsTrue(result.Items.Any(x => x.Key == editor!.Key));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Only_Admins_Can_See_Admins()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var editorCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "editor@mail.com",
|
||||
Email = "editor@mail.com",
|
||||
Name = "Editor Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { editorGroup! }
|
||||
};
|
||||
|
||||
var adminCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "admin@mail.com",
|
||||
Email = "admin@mail.com",
|
||||
Name = "Admin Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { adminGroup!, editorGroup }
|
||||
};
|
||||
|
||||
var createEditorAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, editorCreateModel, true);
|
||||
var createAdminAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, adminCreateModel, true);
|
||||
|
||||
Assert.IsTrue(createEditorAttempt.Success);
|
||||
Assert.IsTrue(createAdminAttempt.Success);
|
||||
|
||||
var editorAllUsersAttempt = await userService.GetAllAsync(createEditorAttempt.Result.CreatedUser!.Key, 0, 10000);
|
||||
Assert.IsTrue(editorAllUsersAttempt.Success);
|
||||
var editorAllUsers = editorAllUsersAttempt.Result.Items.ToList();
|
||||
Assert.AreEqual(1, editorAllUsers.Count);
|
||||
Assert.AreEqual(createEditorAttempt.Result.CreatedUser!.Key, editorAllUsers.First().Key);
|
||||
|
||||
var adminAllUsersAttempt = await userService.GetAllAsync(createAdminAttempt.Result.CreatedUser!.Key, 0, 10000);
|
||||
Assert.IsTrue(adminAllUsersAttempt.Success);
|
||||
var adminAllUsers = adminAllUsersAttempt.Result.Items.ToList();
|
||||
Assert.AreEqual(2, adminAllUsers.Count);
|
||||
Assert.IsTrue(adminAllUsers.Any(x => x.Key == createEditorAttempt.Result.CreatedUser!.Key));
|
||||
Assert.IsTrue(adminAllUsers.Any(x => x.Key == createAdminAttempt.Result.CreatedUser!.Key));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_See_Disabled_When_HideDisabled_Is_True()
|
||||
{
|
||||
var userService = CreateUserService(securitySettings: new SecuritySettings { HideDisabledUsersInBackOffice = true });
|
||||
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var firstEditorCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "firstEditor@mail.com",
|
||||
Email = "firstEditor@mail.com",
|
||||
Name = "First Editor",
|
||||
UserGroups = new HashSet<IUserGroup> { editorGroup! }
|
||||
};
|
||||
|
||||
var firstEditorResult = await userService.CreateAsync(Constants.Security.SuperUserKey, firstEditorCreateModel, true);
|
||||
Assert.IsTrue(firstEditorResult.Success);
|
||||
|
||||
var secondEditorCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "secondEditor@mail.com",
|
||||
Email = "secondEditor@mail.com",
|
||||
Name = "Second Editor",
|
||||
UserGroups = new HashSet<IUserGroup> {editorGroup}
|
||||
};
|
||||
|
||||
var secondEditorResult = await userService.CreateAsync(Constants.Security.SuperUserKey, secondEditorCreateModel, true);
|
||||
Assert.IsTrue(secondEditorResult.Success);
|
||||
|
||||
var disableStatus = await userService.DisableAsync(Constants.Security.SuperUserKey, new HashSet<Guid>{ secondEditorResult.Result.CreatedUser!.Key });
|
||||
Assert.AreEqual(disableStatus, UserOperationStatus.Success);
|
||||
|
||||
var allUsersAttempt = await userService.GetAllAsync(Constants.Security.SuperUserKey, 0, 10000);
|
||||
Assert.IsTrue(allUsersAttempt.Success);
|
||||
var allUsers = allUsersAttempt.Result!.Items.ToList();
|
||||
Assert.AreEqual(2, allUsers.Count);
|
||||
Assert.IsTrue(allUsers.Any(x => x.Key == firstEditorResult.Result.CreatedUser!.Key));
|
||||
Assert.IsTrue(allUsers.Any(x => x.Key == Constants.Security.SuperUserKey));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Requesting_User_Must_Exist_When_Calling_Get_All()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
|
||||
var getAllAttempt = await userService.GetAllAsync(Guid.NewGuid(), 0, 10000);
|
||||
Assert.IsFalse(getAllAttempt.Success);
|
||||
Assert.AreEqual(UserOperationStatus.MissingUser, getAllAttempt.Status);
|
||||
Assert.IsNull(getAllAttempt.Result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class UserServiceCrudTests
|
||||
{
|
||||
[Test]
|
||||
[TestCase("test@email.com", "test@email.com", true, true)]
|
||||
[TestCase("test@email.com", "notTheUserName@email.com", true, false)]
|
||||
[TestCase("NotAnEmail", "test@email.com", true, false)]
|
||||
[TestCase("test@email.com", "test@email.com", false, true)]
|
||||
[TestCase("NotAnEmail", "test@email.com", false, true)]
|
||||
[TestCase("aDifferentEmail@email.com", "test@email.com", false, true)]
|
||||
public async Task Invite_User_Name_Must_Be_Email(
|
||||
string username,
|
||||
string email,
|
||||
bool userNameIsEmailEnabled,
|
||||
bool shouldSucceed)
|
||||
{
|
||||
var securitySettings = new SecuritySettings { UsernameIsEmail = userNameIsEmailEnabled };
|
||||
var userService = CreateUserService(securitySettings);
|
||||
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var inviteModel = new UserInviteModel
|
||||
{
|
||||
UserName = username,
|
||||
Email = email,
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var result = await userService.InviteAsync(Constants.Security.SuperUserKey, inviteModel);
|
||||
|
||||
if (shouldSucceed is false)
|
||||
{
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.UserNameIsNotEmail, result.Status);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.Success, result.Status);
|
||||
var invitedUser = result.Result.InvitedUser;
|
||||
Assert.IsNotNull(invitedUser);
|
||||
Assert.AreEqual(username, invitedUser.Username);
|
||||
Assert.AreEqual(email, invitedUser.Email);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Invite_User_With_Duplicate_Email()
|
||||
{
|
||||
var email = "test@test.com";
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var initialUserCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "Test1",
|
||||
Email = email,
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var userService = CreateUserService(new SecuritySettings { UsernameIsEmail = false });
|
||||
var result = await userService.CreateAsync(Constants.Security.SuperUserKey, initialUserCreateModel, true);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
var duplicateUserInviteModel = new UserInviteModel
|
||||
{
|
||||
UserName = "Test2",
|
||||
Email = email,
|
||||
Name = "Duplicate Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var secondResult = await userService.InviteAsync(Constants.Security.SuperUserKey, duplicateUserInviteModel);
|
||||
Assert.IsFalse(secondResult.Success);
|
||||
Assert.AreEqual(UserOperationStatus.DuplicateEmail, secondResult.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Invite_User_With_Duplicate_UserName()
|
||||
{
|
||||
var userName = "UserName";
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var initialUserCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = userName,
|
||||
Email = "test@email.com",
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var userService = CreateUserService(new SecuritySettings { UsernameIsEmail = false });
|
||||
var result = await userService.CreateAsync(Constants.Security.SuperUserKey, initialUserCreateModel, true);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
var duplicateUserInviteModelModel = new UserInviteModel
|
||||
{
|
||||
UserName = userName,
|
||||
Email = "another@email.com",
|
||||
Name = "Duplicate Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var secondResult = await userService.InviteAsync(Constants.Security.SuperUserKey, duplicateUserInviteModelModel);
|
||||
Assert.IsFalse(secondResult.Success);
|
||||
Assert.AreEqual(UserOperationStatus.DuplicateUserName, secondResult.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Invite_User_Without_User_Group()
|
||||
{
|
||||
UserInviteModel userInviteModel = new UserInviteModel
|
||||
{
|
||||
UserName = "NoUser@Group.com",
|
||||
Email = "NoUser@Group.com",
|
||||
Name = "NoUser@Group.com",
|
||||
};
|
||||
|
||||
IUserService userService = CreateUserService();
|
||||
|
||||
var result = await userService.InviteAsync(Constants.Security.SuperUserKey, userInviteModel);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.NoUserGroup, result.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Performing_User_Must_Exist_When_Inviting()
|
||||
{
|
||||
IUserService userService = CreateUserService();
|
||||
|
||||
var result = await userService.InviteAsync(Guid.Empty, new UserInviteModel());
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.MissingUser, result.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Invited_Users_Has_Invited_state()
|
||||
{
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
UserInviteModel userInviteModel = new UserInviteModel
|
||||
{
|
||||
UserName = "some@email.com",
|
||||
Email = "some@email.com",
|
||||
Name = "Bob",
|
||||
UserGroups = new HashSet<IUserGroup> {userGroup!},
|
||||
};
|
||||
|
||||
IUserService userService = CreateUserService();
|
||||
var result = await userService.InviteAsync(Constants.Security.SuperUserKey, userInviteModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
var invitedUser = await userService.GetAsync(result.Result.InvitedUser!.Key);
|
||||
Assert.IsNotNull(invitedUser);
|
||||
Assert.AreEqual(UserState.Invited, invitedUser.UserState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class UserServiceCrudTests
|
||||
{
|
||||
[Test]
|
||||
public async Task Can_Enable_User()
|
||||
{
|
||||
var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var userCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "test@email.com",
|
||||
Email = "test@email.com",
|
||||
Name = "Test User",
|
||||
UserGroups = new HashSet<IUserGroup> { editorUserGroup }
|
||||
};
|
||||
|
||||
var userService = CreateUserService();
|
||||
var createAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, userCreateModel, false);
|
||||
|
||||
Assert.IsTrue(createAttempt.Success);
|
||||
var user = createAttempt.Result.CreatedUser;
|
||||
Assert.AreEqual(UserState.Disabled, user!.UserState);
|
||||
|
||||
var enableStatus = await userService.EnableAsync(Constants.Security.SuperUserKey, new HashSet<Guid> { user.Key });
|
||||
Assert.AreEqual(UserOperationStatus.Success, enableStatus);
|
||||
|
||||
var updatedUser = await userService.GetAsync(user.Key);
|
||||
// The user has not logged in, so after enabling the user, the user state should be inactive
|
||||
Assert.AreEqual(UserState.Inactive, updatedUser!.UserState);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Disable_User()
|
||||
{
|
||||
var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var userCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "test@email.com",
|
||||
Email = "test@email.com",
|
||||
Name = "Test User",
|
||||
UserGroups = new HashSet<IUserGroup> { editorUserGroup }
|
||||
};
|
||||
|
||||
var userService = CreateUserService();
|
||||
var createAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, userCreateModel, true);
|
||||
|
||||
Assert.IsTrue(createAttempt.Success);
|
||||
var user = createAttempt.Result.CreatedUser;
|
||||
Assert.AreEqual(UserState.Inactive, user!.UserState);
|
||||
|
||||
var disableStatus = await userService.DisableAsync(Constants.Security.SuperUserKey, new HashSet<Guid> { user.Key });
|
||||
Assert.AreEqual(UserOperationStatus.Success, disableStatus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task User_Cannot_Disable_Self()
|
||||
{
|
||||
var adminUserGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
|
||||
var userCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "test@email.com",
|
||||
Email = "test@email.com",
|
||||
Name = "Test User",
|
||||
UserGroups = new HashSet<IUserGroup> { adminUserGroup }
|
||||
};
|
||||
|
||||
var userService = CreateUserService();
|
||||
var createAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, userCreateModel, true);
|
||||
Assert.IsTrue(createAttempt.Success);
|
||||
|
||||
var createdUser = createAttempt.Result.CreatedUser;
|
||||
var disableStatus = await userService.DisableAsync(createdUser!.Key, new HashSet<Guid>{ createdUser.Key });
|
||||
Assert.AreEqual(UserOperationStatus.CannotDisableSelf, disableStatus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Disable_Invited_User()
|
||||
{
|
||||
var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias);
|
||||
|
||||
var userInviteModel = new UserInviteModel
|
||||
{
|
||||
UserName = "test@email.com",
|
||||
Email = "test@email.com",
|
||||
Name = "Test User",
|
||||
UserGroups = new HashSet<IUserGroup> { editorUserGroup }
|
||||
};
|
||||
|
||||
var userService = CreateUserService();
|
||||
|
||||
var userInviteAttempt = await userService.InviteAsync(Constants.Security.SuperUserKey, userInviteModel);
|
||||
Assert.IsTrue(userInviteAttempt.Success);
|
||||
|
||||
var invitedUser = userInviteAttempt.Result.InvitedUser;
|
||||
var disableStatus = await userService.DisableAsync(Constants.Security.SuperUserKey, new HashSet<Guid> { invitedUser!.Key });
|
||||
Assert.AreEqual(UserOperationStatus.CannotDisableInvitedUser, disableStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class UserServiceCrudTests
|
||||
{
|
||||
private SortedSet<Guid> GetKeysFromIds(IEnumerable<int>? ids, UmbracoObjectTypes type)
|
||||
{
|
||||
IEnumerable<Guid>? keys = ids?
|
||||
.Select(x => EntityService.GetKey(x, type))
|
||||
.Where(x => x.Success)
|
||||
.Select(x => x.Result);
|
||||
|
||||
return keys is null
|
||||
? new SortedSet<Guid>()
|
||||
: new SortedSet<Guid>(keys);
|
||||
}
|
||||
|
||||
private async Task<UserUpdateModel> MapUserToUpdateModel(IUser user)
|
||||
{
|
||||
var groups = await UserGroupService.GetAsync(user.Groups.Select(x => x.Id).ToArray());
|
||||
return new UserUpdateModel
|
||||
{
|
||||
ExistingUser = user,
|
||||
Email = user.Email,
|
||||
Name = user.Name,
|
||||
UserName = user.Username,
|
||||
Language = user.Language,
|
||||
ContentStartNodeKeys = GetKeysFromIds(user.StartContentIds, UmbracoObjectTypes.Document),
|
||||
MediaStartNodeKeys = GetKeysFromIds(user.StartMediaIds, UmbracoObjectTypes.Media),
|
||||
UserGroups = groups,
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(UserUpdateModel updateModel, IUser createdUser)> CreateUserForUpdate(
|
||||
IUserService userService,
|
||||
string email = "test@test.com",
|
||||
string userName = "test@test.com")
|
||||
{
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var createUserModel = new UserCreateModel
|
||||
{
|
||||
Email = email,
|
||||
UserName = userName,
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var createExistingUser = await userService.CreateAsync(Constants.Security.SuperUserKey, createUserModel, true);
|
||||
|
||||
Assert.IsTrue(createExistingUser.Success);
|
||||
Assert.IsNotNull(createExistingUser.Result.CreatedUser);
|
||||
|
||||
var savedUser = createExistingUser.Result.CreatedUser;
|
||||
var updateModel = await MapUserToUpdateModel(savedUser);
|
||||
return (updateModel, createExistingUser.Result.CreatedUser);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
public async Task Cannot_Change_Email_When_Deny_Local_Login_Is_True(bool denyLocalLogin, bool shouldSucceed)
|
||||
{
|
||||
var localLoginSetting = new Mock<ILocalLoginSettingProvider>();
|
||||
localLoginSetting.Setup(x => x.HasDenyLocalLogin()).Returns(denyLocalLogin);
|
||||
|
||||
var userService = CreateUserService(
|
||||
localLoginSettingProvider: localLoginSetting.Object,
|
||||
securitySettings: new SecuritySettings { UsernameIsEmail = false });
|
||||
|
||||
var (updateModel, _) = await CreateUserForUpdate(userService);
|
||||
|
||||
var updatedEmail = "updated@email.com";
|
||||
updateModel.Email = updatedEmail;
|
||||
|
||||
var result = await userService.UpdateAsync(Constants.Security.SuperUserKey, updateModel);
|
||||
|
||||
if (shouldSucceed is false)
|
||||
{
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.EmailCannotBeChanged, result.Status);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
// We'll get the user again to ensure that the changes has been persisted
|
||||
var updatedUser = await userService.GetAsync(result.Result.Key);
|
||||
Assert.IsNotNull(updatedUser);
|
||||
Assert.AreEqual(updatedEmail, updatedUser.Email);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("same@email.com", "same@email.com", true)]
|
||||
[TestCase("different@email.com", "another@email.com", false)]
|
||||
[TestCase("notAnEmail", "some@email.com", false)]
|
||||
public async Task UserName_And_Email_Must_Be_same_When_UserNameIsEmail_Equals_True(string userName, string email, bool shouldSucceed)
|
||||
{
|
||||
var userService = CreateUserService(securitySettings: new SecuritySettings { UsernameIsEmail = true });
|
||||
|
||||
var (updateModel, createdUser) = await CreateUserForUpdate(userService);
|
||||
|
||||
updateModel.UserName = userName;
|
||||
updateModel.Email = email;
|
||||
|
||||
var result = await userService.UpdateAsync(Constants.Security.SuperUserKey, updateModel);
|
||||
|
||||
if (shouldSucceed is false)
|
||||
{
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(UserOperationStatus.UserNameIsNotEmail, result.Status);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
var updatedUser = await userService.GetAsync(createdUser.Key);
|
||||
Assert.IsNotNull(updatedUser);
|
||||
Assert.AreEqual(userName, updatedUser.Username);
|
||||
Assert.AreEqual(email, updatedUser.Email);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Change_Email_To_Duplicate_Email_On_Update()
|
||||
{
|
||||
var userService = CreateUserService();
|
||||
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var email = "thiswillbe@duplicate.com";
|
||||
var createModel = new UserCreateModel
|
||||
{
|
||||
Email = email,
|
||||
UserName = email,
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var createExisting = await userService.CreateAsync(Constants.Security.SuperUserKey, createModel, true);
|
||||
|
||||
Assert.IsTrue(createExisting.Success);
|
||||
|
||||
var (updateModel, _) = await CreateUserForUpdate(userService);
|
||||
|
||||
updateModel.Email = email;
|
||||
updateModel.UserName = email;
|
||||
|
||||
var updateAttempt = await userService.UpdateAsync(Constants.Security.SuperUserKey, updateModel);
|
||||
|
||||
Assert.IsFalse(updateAttempt.Success);
|
||||
Assert.AreEqual(UserOperationStatus.DuplicateEmail, updateAttempt.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("TestUser", "test@user.com", "TestUser", "another@email.com")]
|
||||
[TestCase("test@email.com", "test@email.com", "test@email.com", "different@email.com")]
|
||||
[TestCase("SomeName", "test@email.com", "test@email.com", "different@email.com")]
|
||||
public async Task Cannot_Change_User_Name_To_Duplicate_UserName(string existingUserName, string existingEmail, string updateUserName, string updateEmail)
|
||||
{
|
||||
// We also ensure that your username cannot be the same as another users email.
|
||||
var userService = CreateUserService(new SecuritySettings { UsernameIsEmail = false });
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
|
||||
var createModel = new UserCreateModel
|
||||
{
|
||||
Email = existingEmail,
|
||||
UserName = existingUserName,
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var createExisting = await userService.CreateAsync(Constants.Security.SuperUserKey, createModel, true);
|
||||
Assert.IsTrue(createExisting.Success);
|
||||
|
||||
var (updateModel, _) = await CreateUserForUpdate(userService);
|
||||
|
||||
updateModel.Email = updateEmail;
|
||||
updateModel.UserName = updateUserName;
|
||||
|
||||
var updateAttempt = await userService.UpdateAsync(Constants.Security.SuperUserKey, updateModel);
|
||||
Assert.IsFalse(updateAttempt.Success);
|
||||
Assert.AreEqual(UserOperationStatus.DuplicateUserName, updateAttempt.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Editors;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public partial class UserServiceCrudTests : UmbracoIntegrationTest
|
||||
{
|
||||
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
|
||||
|
||||
private IEntityService EntityService => GetRequiredService<IEntityService>();
|
||||
|
||||
protected override void ConfigureTestServices(IServiceCollection services)
|
||||
{
|
||||
base.ConfigureTestServices(services);
|
||||
services.RemoveAll<IInviteUriProvider>();
|
||||
services.AddScoped<IInviteUriProvider, TestUriProvider>();
|
||||
}
|
||||
|
||||
// This is resolved from the service scope, so we have to add it to the service collection.
|
||||
private class TestUriProvider : IInviteUriProvider
|
||||
{
|
||||
public Task<Attempt<Uri, UserOperationStatus>> CreateInviteUriAsync(IUser invitee)
|
||||
{
|
||||
var fakePath = "https://localhost:44331/fakeInviteEndpoint";
|
||||
Attempt<Uri, UserOperationStatus> attempt = Attempt<Uri, UserOperationStatus>.Succeed(UserOperationStatus.Success, new Uri(fakePath));
|
||||
return Task.FromResult(attempt);
|
||||
}
|
||||
}
|
||||
|
||||
private IUserService CreateUserService(
|
||||
SecuritySettings? securitySettings = null,
|
||||
IUserInviteSender? inviteSender = null,
|
||||
ILocalLoginSettingProvider? localLoginSettingProvider = null)
|
||||
{
|
||||
securitySettings ??= GetRequiredService<IOptions<SecuritySettings>>().Value;
|
||||
IOptions<SecuritySettings> securityOptions = Options.Create(securitySettings);
|
||||
|
||||
if (inviteSender is null)
|
||||
{
|
||||
var senderMock = new Mock<IUserInviteSender>();
|
||||
senderMock.Setup(x => x.CanSendInvites()).Returns(true);
|
||||
inviteSender = senderMock.Object;
|
||||
}
|
||||
|
||||
localLoginSettingProvider ??= GetRequiredService<ILocalLoginSettingProvider>();
|
||||
|
||||
return new UserService(
|
||||
GetRequiredService<ICoreScopeProvider>(),
|
||||
GetRequiredService<ILoggerFactory>(),
|
||||
GetRequiredService<IEventMessagesFactory>(),
|
||||
GetRequiredService<IUserRepository>(),
|
||||
GetRequiredService<IUserGroupRepository>(),
|
||||
GetRequiredService<IOptions<GlobalSettings>>(),
|
||||
securityOptions,
|
||||
GetRequiredService<UserEditorAuthorizationHelper>(),
|
||||
GetRequiredService<IServiceScopeFactory>(),
|
||||
GetRequiredService<IEntityService>(),
|
||||
localLoginSettingProvider,
|
||||
inviteSender,
|
||||
GetRequiredService<MediaFileManager>(),
|
||||
GetRequiredService<ITemporaryFileService>(),
|
||||
GetRequiredService<IShortStringHelper>(),
|
||||
GetRequiredService<IOptions<ContentSettings>>());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
@@ -16,24 +20,42 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Security;
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public class BackOfficeUserStoreTests : UmbracoIntegrationTest
|
||||
{
|
||||
private IUserService UserService => GetRequiredService<IUserService>();
|
||||
private IEntityService EntityService => GetRequiredService<IEntityService>();
|
||||
|
||||
private IExternalLoginWithKeyService ExternalLoginService => GetRequiredService<IExternalLoginWithKeyService>();
|
||||
|
||||
private IUmbracoMapper UmbracoMapper => GetRequiredService<IUmbracoMapper>();
|
||||
|
||||
private ILocalizedTextService TextService => GetRequiredService<ILocalizedTextService>();
|
||||
|
||||
private ITwoFactorLoginService TwoFactorLoginService => GetRequiredService<ITwoFactorLoginService>();
|
||||
|
||||
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
|
||||
|
||||
private IUserRepository UserRepository => GetRequiredService<IUserRepository>();
|
||||
|
||||
private IRuntimeState RuntimeState => GetRequiredService<IRuntimeState>();
|
||||
|
||||
private IEventMessagesFactory EventMessagesFactory => GetRequiredService<IEventMessagesFactory>();
|
||||
|
||||
private ILogger<BackOfficeUserStore> Logger = NullLogger<BackOfficeUserStore>.Instance;
|
||||
|
||||
|
||||
private BackOfficeUserStore GetUserStore()
|
||||
=> new(
|
||||
ScopeProvider,
|
||||
UserService,
|
||||
EntityService,
|
||||
ExternalLoginService,
|
||||
new TestOptionsSnapshot<GlobalSettings>(GlobalSettings),
|
||||
UmbracoMapper,
|
||||
new BackOfficeErrorDescriber(TextService),
|
||||
AppCaches,
|
||||
TwoFactorLoginService
|
||||
TwoFactorLoginService,
|
||||
UserGroupService,
|
||||
UserRepository,
|
||||
RuntimeState,
|
||||
EventMessagesFactory,
|
||||
Logger
|
||||
);
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public class UserIdKeyResolverTests : UmbracoIntegrationTest
|
||||
{
|
||||
private IUserService UserService => GetRequiredService<IUserService>();
|
||||
|
||||
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
|
||||
|
||||
private IUserIdKeyResolver UserIdKeyResolver => GetRequiredService<IUserIdKeyResolver>();
|
||||
|
||||
[Test]
|
||||
public async Task Can_Resolve_Id_To_Key()
|
||||
{
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var userCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "test@test.com",
|
||||
Email = "test@test.com",
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var creationResult = await UserService.CreateAsync(Constants.Security.SuperUserKey, userCreateModel);
|
||||
Assert.IsTrue(creationResult.Success);
|
||||
var createdUser = creationResult.Result.CreatedUser;
|
||||
Assert.IsNotNull(createdUser);
|
||||
|
||||
var resolvedKey = await UserIdKeyResolver.GetAsync(createdUser.Id);
|
||||
Assert.AreEqual(createdUser.Key, resolvedKey);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Resolve_Key_To_Id()
|
||||
{
|
||||
var userGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias);
|
||||
var userCreateModel = new UserCreateModel
|
||||
{
|
||||
UserName = "test@test.com",
|
||||
Email = "test@test.com",
|
||||
Name = "Test Mc. Gee",
|
||||
UserGroups = new HashSet<IUserGroup> { userGroup! }
|
||||
};
|
||||
|
||||
var creationResult = await UserService.CreateAsync(Constants.Security.SuperUserKey, userCreateModel);
|
||||
Assert.IsTrue(creationResult.Success);
|
||||
var createdUser = creationResult.Result.CreatedUser;
|
||||
Assert.IsNotNull(createdUser);
|
||||
|
||||
var resolvedId = await UserIdKeyResolver.GetAsync(createdUser.Key);
|
||||
Assert.AreEqual(createdUser.Id, resolvedId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Unknown_Key_Resolves_To_Null()
|
||||
{
|
||||
var resolvedId = await UserIdKeyResolver.GetAsync(Guid.NewGuid());
|
||||
Assert.IsNull(resolvedId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Unknown_Id_Resolves_To_Null()
|
||||
{
|
||||
var resolvedKey = await UserIdKeyResolver.GetAsync(1234567890);
|
||||
Assert.IsNull(resolvedKey);
|
||||
}
|
||||
}
|
||||
@@ -61,5 +61,26 @@
|
||||
<Compile Update="Umbraco.Infrastructure\Services\ContentEditingServiceTests.Update.cs">
|
||||
<DependentUpon>ContentEditingServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\UserServiceCrudTests.Create.cs">
|
||||
<DependentUpon>UserServiceCrudTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\UserServiceCrudTests.Delete.cs">
|
||||
<DependentUpon>UserServiceCrudTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\UserServiceCrudTests.Filter.cs">
|
||||
<DependentUpon>UserServiceCrudTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\UserServiceCrudTests.Get.cs">
|
||||
<DependentUpon>UserServiceCrudTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\UserServiceCrudTests.Invite.cs">
|
||||
<DependentUpon>UserServiceCrudTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\UserServiceCrudTests.Update.cs">
|
||||
<DependentUpon>UserServiceCrudTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\UserServiceCrudTests.PartialUpdates.cs">
|
||||
<DependentUpon>UserServiceCrudTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user