diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 33d3f6cab8..9355e1cdc2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models.Membership; +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Persistence.Repositories { @@ -12,6 +13,6 @@ namespace Umbraco.Core.Persistence.Repositories /// This is useful when an entire section is removed from config /// /// - void DeleteSectionFromAllUsers(string sectionAlias); + IEnumerable GetUsersAssignedToSection(string sectionAlias); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4c4843a78e..032b0e9bf5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -60,18 +60,8 @@ namespace Umbraco.Core.Persistence.Repositories } var sql = GetBaseQuery(false); - var foundUserTypes = new Dictionary(); - return Database.Fetch(new UserSectionRelator().Map, sql) - .Select(dto => - { - //first we need to get the user type - var userType = foundUserTypes.ContainsKey(dto.Type) - ? foundUserTypes[dto.Type] - : _userTypeRepository.Get(dto.Type); - var userFactory = new UserFactory(userType); - return userFactory.BuildEntity(dto); - }); + return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)); } private IEnumerable PerformGetAllOnIds(params int[] ids) @@ -110,14 +100,22 @@ namespace Umbraco.Core.Persistence.Repositories } else { - sql.Select("*") - .From() - .LeftJoin() - .On(left => left.Id, right => right.UserId); + return GetBaseQuery("*"); } return sql; } + private static Sql GetBaseQuery(string columns) + { + var sql = new Sql(); + sql.Select(columns) + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserId); + return sql; + } + + protected override string GetBaseWhereClause() { return "umbracoUser.id = @Id"; @@ -230,12 +228,53 @@ namespace Umbraco.Core.Persistence.Repositories var query = Query.Builder.Where(x => x.Username == username); return GetByQuery(query).FirstOrDefault(); } - - public void DeleteSectionFromAllUsers(string sectionAlias) + + public IEnumerable GetUsersAssignedToSection(string sectionAlias) { - throw new NotImplementedException(); + //Here we're building up a query that looks like this, a sub query is required because the resulting structure + // needs to still contain all of the section rows per user. + + //SELECT * + //FROM [umbracoUser] + //LEFT JOIN [umbracoUser2app] + //ON [umbracoUser].[id] = [umbracoUser2app].[user] + //WHERE umbracoUser.id IN (SELECT umbracoUser.id + // FROM [umbracoUser] + // LEFT JOIN [umbracoUser2app] + // ON [umbracoUser].[id] = [umbracoUser2app].[user] + // WHERE umbracoUser2app.app = 'content') + + var sql = GetBaseQuery(false); + var innerSql = GetBaseQuery("umbracoUser.id"); + innerSql.Where("umbracoUser2app.app = " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedValue(sectionAlias)); + sql.Where(string.Format("umbracoUser.id IN ({0})", innerSql.SQL)); + + return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)); } #endregion + + private IEnumerable ConvertFromDtos(IEnumerable dtos) + { + var foundUserTypes = new Dictionary(); + return dtos.Select(dto => + { + //first we need to get the user type + IUserType userType; + if (foundUserTypes.ContainsKey(dto.Type)) + { + userType = foundUserTypes[dto.Type]; + } + else + { + userType = _userTypeRepository.Get(dto.Type); + //put it in the local cache + foundUserTypes.Add(dto.Type, userType); + } + + var userFactory = new UserFactory(userType); + return userFactory.BuildEntity(dto); + }); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMembershipUserService.cs b/src/Umbraco.Core/Services/IMembershipUserService.cs new file mode 100644 index 0000000000..5ae7f9af32 --- /dev/null +++ b/src/Umbraco.Core/Services/IMembershipUserService.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Services +{ + /// + /// Defines part of the UserService, which is specific to methods used by the membership provider. + /// + /// + /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. + /// + internal interface IMembershipUserService : IService + { + IMembershipUser CreateMembershipUser(string name, string login, string password, IUserType userType, string email = ""); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 678fbf9292..8549699c53 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -32,16 +32,17 @@ namespace Umbraco.Core.Services /// Name of the UserType to retrieve /// IUserType GetUserTypeByName(string name); - } - /// - /// Defines part of the UserService, which is specific to methods used by the membership provider. - /// - /// - /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. - /// - internal interface IMembershipUserService : IService - { - IMembershipUser CreateUser(string name, string login, string password, IUserType userType, string email = ""); + /// + /// Saves changes to the user object + /// + /// + void SaveUser(IUser user); + + /// + /// This is useful when an entire section is removed from config + /// + /// + void DeleteSectionFromAllUsers(string sectionAlias); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 7836abbcdc..d8fa8f1672 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -1,6 +1,8 @@ using System; using System.Globalization; using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -101,6 +103,45 @@ namespace Umbraco.Core.Services } } + /// + /// Savers changes to a user to the database + /// + /// + public void SaveUser(IUser user) + { + if (UserSaving.IsRaisedEventCancelled(new SaveEventArgs(user), this)) + return; + + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateUserRepository(uow)) + { + repository.AddOrUpdate(user); + uow.Commit(); + } + + UserSaved.RaiseEvent(new SaveEventArgs(user, false), this); + } + + /// + /// This is useful for when a section is removed from config + /// + /// + public void DeleteSectionFromAllUsers(string sectionAlias) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateUserRepository(uow)) + { + var assignedUsers = repository.GetUsersAssignedToSection(sectionAlias); + foreach (var user in assignedUsers) + { + //now remove the section for each user and commit + user.RemoveAllowedSection(sectionAlias); + repository.AddOrUpdate(user); + } + uow.Commit(); + } + } + /// /// Creates a new user for logging into the umbraco backoffice /// @@ -110,7 +151,7 @@ namespace Umbraco.Core.Services /// /// /// - public IMembershipUser CreateUser(string name, string login, string password, IUserType userType, string email = "") + public IMembershipUser CreateMembershipUser(string name, string login, string password, IUserType userType, string email = "") { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) @@ -142,5 +183,15 @@ namespace Umbraco.Core.Services } #endregion + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> UserSaving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> UserSaved; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d424140a95..05a9fdcabf 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -681,6 +681,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 0e68e2fcad..9476cc76bf 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -439,6 +439,32 @@ namespace Umbraco.Tests.Persistence.Repositories AssertPropertyValues(userByUsername, user); } + [Test] + public void Get_Users_Assigned_To_Section() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.ResolveByType(unitOfWork); + var user1 = MockedUser.CreateUser(CreateAndCommitUserType(), "1", "test", "media"); + var user2 = MockedUser.CreateUser(CreateAndCommitUserType(), "2", "media", "settings"); + var user3 = MockedUser.CreateUser(CreateAndCommitUserType(), "3", "test", "settings"); + repository.AddOrUpdate(user1); + repository.AddOrUpdate(user2); + repository.AddOrUpdate(user3); + unitOfWork.Commit(); + + // Act + + var users = repository.GetUsersAssignedToSection("test"); + + // Assert + Assert.AreEqual(2, users.Count()); + var names = users.Select(x => x.Username).ToArray(); + Assert.IsTrue(names.Contains("TestUser1")); + Assert.IsTrue(names.Contains("TestUser3")); + } + private void AssertPropertyValues(IUser updatedItem, IUser originalUser) { Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id)); diff --git a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs index 19f3046211..f0fa1bc84f 100644 --- a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs @@ -5,6 +5,7 @@ using Lucene.Net.Documents; using Lucene.Net.Store; using NUnit.Framework; using Umbraco.Core.Configuration; +using Umbraco.Tests.TestHelpers; using Umbraco.Tests.UmbracoExamine; using umbraco.MacroEngines; @@ -15,13 +16,13 @@ namespace Umbraco.Tests.PublishedContent public override void TestSetup() { base.TestSetup(); - UmbracoSettings.ForceSafeAliases = true; - UmbracoSettings.UmbracoLibraryCacheDuration = 1800; - UmbracoSettings.ForceSafeAliases = true; + SettingsForTests.ForceSafeAliases = true; + SettingsForTests.UmbracoLibraryCacheDuration = 1800; } public override void TestTearDown() { + SettingsForTests.Reset(); base.TestTearDown(); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index 36cba1ad3e..2086b9b5d0 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -38,10 +38,7 @@ namespace Umbraco.Tests.PublishedContent { base.Initialize(); UmbracoExamineSearcher.DisableInitializationCheck = true; - BaseUmbracoIndexer.DisableInitializationCheck = true; - UmbracoSettings.ForceSafeAliases = true; - UmbracoSettings.UmbracoLibraryCacheDuration = 1800; - UmbracoSettings.ForceSafeAliases = true; + BaseUmbracoIndexer.DisableInitializationCheck = true; } public override void TearDown() diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index f91f573ec7..245e404f7d 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Security.Cryptography; using System.Text; using NUnit.Framework; @@ -32,7 +33,7 @@ namespace Umbraco.Tests.Services var userType = userService.GetUserTypeByAlias("admin"); // Act - var membershipUser = userService.CreateUser("John Doe", "john@umbraco.io", "12345", userType, "john@umbraco.io"); + var membershipUser = userService.CreateMembershipUser("John Doe", "john@umbraco.io", "12345", userType, "john@umbraco.io"); // Assert Assert.That(membershipUser.HasIdentity, Is.True); @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Services var hash = new HMACSHA1(); hash.Key = Encoding.Unicode.GetBytes(password); var encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password))); - var membershipUser = userService.CreateUser("John Doe", "john@umbraco.io", encodedPassword, userType, "john@umbraco.io"); + var membershipUser = userService.CreateMembershipUser("John Doe", "john@umbraco.io", encodedPassword, userType, "john@umbraco.io"); // Assert Assert.That(membershipUser.HasIdentity, Is.True); @@ -64,5 +65,30 @@ namespace Umbraco.Tests.Services Assert.That(user, Is.Not.Null); Assert.That(user.Permissions, Is.EqualTo(userType.Permissions)); } + + [Test] + public void Can_Remove_Section_From_All_Assigned_Users() + { + var userType = ServiceContext.UserService.GetUserTypeByAlias("admin"); + //we know this actually is an IUser so we'll just cast + var user1 = (IUser)ServiceContext.UserService.CreateMembershipUser("test1", "test1", "test1", userType, "test1@test.com"); + var user2 = (IUser)ServiceContext.UserService.CreateMembershipUser("test2", "test2", "test2", userType, "test2@test.com"); + + //adds some allowed sections + user1.AddAllowedSection("test"); + user2.AddAllowedSection("test"); + ServiceContext.UserService.SaveUser(user1); + ServiceContext.UserService.SaveUser(user2); + + //now clear the section from all users + ServiceContext.UserService.DeleteSectionFromAllUsers("test"); + + //assert + var result1 = ServiceContext.UserService.GetUserById((int)user1.Id); + var result2 = ServiceContext.UserService.GetUserById((int)user2.Id); + Assert.IsFalse(result1.AllowedSections.Contains("test")); + Assert.IsFalse(result2.AllowedSections.Contains("test")); + + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index c297150f4b..dbf87b4aee 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -18,6 +18,11 @@ namespace Umbraco.Tests.TestHelpers { TestHelper.SetupLog4NetForTests(); TestHelper.InitializeContentDirectories(); + + SettingsForTests.UseLegacyXmlSchema = false; + SettingsForTests.ForceSafeAliases = true; + SettingsForTests.UmbracoLibraryCacheDuration = 1800; + SetupPluginManager(); FreezeResolution(); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs index be40dd2736..19e0148383 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Models.Membership; +using System.Linq; +using Umbraco.Core.Models.Membership; namespace Umbraco.Tests.TestHelpers.Entities { public class MockedUser { - internal static User CreateUser(IUserType userType = null, string suffix = "") + internal static User CreateUser(IUserType userType = null, string suffix = "", params string[] allowedSections) { if (userType == null) { @@ -26,8 +27,18 @@ namespace Umbraco.Tests.TestHelpers.Entities Username = "TestUser" + suffix }; - user.AddAllowedSection("content"); - user.AddAllowedSection("media"); + if (allowedSections.Any()) + { + foreach (var s in allowedSections) + { + user.AddAllowedSection(s); + } + } + else + { + user.AddAllowedSection("content"); + user.AddAllowedSection("media"); + } return user; } diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index 7dd9e64e52..e0b8bf0bae 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -11,6 +11,12 @@ namespace Umbraco.Tests.TestHelpers { // umbracoSettings + public static int UmbracoLibraryCacheDuration + { + get { return UmbracoSettings.UmbracoLibraryCacheDuration; } + set { UmbracoSettings.UmbracoLibraryCacheDuration = value; } + } + public static bool UseLegacyXmlSchema { get { return UmbracoSettings.UseLegacyXmlSchema; } @@ -35,6 +41,12 @@ namespace Umbraco.Tests.TestHelpers set { UmbracoSettings.SettingsFilePath = value; } } + public static bool ForceSafeAliases + { + get { return UmbracoSettings.ForceSafeAliases; } + set { UmbracoSettings.ForceSafeAliases = value; } + } + // from appSettings private static readonly IDictionary SavedAppSettings = new Dictionary();