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/Events/DataTypeDeletedNotification.cs b/src/Umbraco.Core/Events/DataTypeDeletedNotification.cs new file mode 100644 index 0000000000..b3461e27f1 --- /dev/null +++ b/src/Umbraco.Core/Events/DataTypeDeletedNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class DataTypeDeletedNotification : DeletedNotification + { + public DataTypeDeletedNotification(IDataType target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/DataTypeDeletingNotification.cs b/src/Umbraco.Core/Events/DataTypeDeletingNotification.cs new file mode 100644 index 0000000000..16b2ee68ac --- /dev/null +++ b/src/Umbraco.Core/Events/DataTypeDeletingNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class DataTypeDeletingNotification : DeletingNotification + { + public DataTypeDeletingNotification(IDataType target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/DataTypeMovedNotification.cs b/src/Umbraco.Core/Events/DataTypeMovedNotification.cs new file mode 100644 index 0000000000..10e42c7a82 --- /dev/null +++ b/src/Umbraco.Core/Events/DataTypeMovedNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class DataTypeMovedNotification : MovedNotification + { + public DataTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/DataTypeMovingNotification.cs b/src/Umbraco.Core/Events/DataTypeMovingNotification.cs new file mode 100644 index 0000000000..4b48f51a00 --- /dev/null +++ b/src/Umbraco.Core/Events/DataTypeMovingNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class DataTypeMovingNotification : MovingNotification + { + public DataTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/DataTypeSavedNotification.cs b/src/Umbraco.Core/Events/DataTypeSavedNotification.cs new file mode 100644 index 0000000000..09c42d32a9 --- /dev/null +++ b/src/Umbraco.Core/Events/DataTypeSavedNotification.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class DataTypeSavedNotification : SavedNotification + { + public DataTypeSavedNotification(IDataType target, EventMessages messages) : base(target, messages) + { + } + + public DataTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/DataTypeSavingNotification.cs b/src/Umbraco.Core/Events/DataTypeSavingNotification.cs new file mode 100644 index 0000000000..dd110df5a3 --- /dev/null +++ b/src/Umbraco.Core/Events/DataTypeSavingNotification.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class DataTypeSavingNotification : SavingNotification + { + public DataTypeSavingNotification(IDataType target, EventMessages messages) : base(target, messages) + { + } + + public DataTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs b/src/Umbraco.Core/Events/DeletingNotification.cs similarity index 84% rename from src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs rename to src/Umbraco.Core/Events/DeletingNotification.cs index 2dd8e09c6b..d88345b427 100644 --- a/src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs +++ b/src/Umbraco.Core/Events/DeletingNotification.cs @@ -1,10 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. - using System.Collections.Generic; -using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Infrastructure.Services.Notifications +namespace Umbraco.Cms.Core.Events { public abstract class DeletingNotification : CancelableEnumerableObjectNotification { diff --git a/src/Umbraco.Core/Events/EntityContainerDeletedNotification.cs b/src/Umbraco.Core/Events/EntityContainerDeletedNotification.cs new file mode 100644 index 0000000000..eca96962be --- /dev/null +++ b/src/Umbraco.Core/Events/EntityContainerDeletedNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class EntityContainerDeletedNotification : DeletedNotification + { + public EntityContainerDeletedNotification(EntityContainer target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/EntityContainerDeletingNotification.cs b/src/Umbraco.Core/Events/EntityContainerDeletingNotification.cs new file mode 100644 index 0000000000..58d0830cf9 --- /dev/null +++ b/src/Umbraco.Core/Events/EntityContainerDeletingNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class EntityContainerDeletingNotification : DeletingNotification + { + public EntityContainerDeletingNotification(EntityContainer target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/EntityContainerRenamedNotification.cs b/src/Umbraco.Core/Events/EntityContainerRenamedNotification.cs new file mode 100644 index 0000000000..dc1b858bd9 --- /dev/null +++ b/src/Umbraco.Core/Events/EntityContainerRenamedNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class EntityContainerRenamedNotification : RenamedNotification + { + public EntityContainerRenamedNotification(EntityContainer target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/EntityContainerRenamingNotification.cs b/src/Umbraco.Core/Events/EntityContainerRenamingNotification.cs new file mode 100644 index 0000000000..f2408a1faf --- /dev/null +++ b/src/Umbraco.Core/Events/EntityContainerRenamingNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class EntityContainerRenamingNotification : RenamingNotification + { + public EntityContainerRenamingNotification(EntityContainer target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/EntityContainerSavedNotification.cs b/src/Umbraco.Core/Events/EntityContainerSavedNotification.cs new file mode 100644 index 0000000000..108d6649df --- /dev/null +++ b/src/Umbraco.Core/Events/EntityContainerSavedNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class EntityContainerSavedNotification : SavedNotification + { + public EntityContainerSavedNotification(EntityContainer target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/EntityContainerSavingNotification.cs b/src/Umbraco.Core/Events/EntityContainerSavingNotification.cs new file mode 100644 index 0000000000..98a8a34926 --- /dev/null +++ b/src/Umbraco.Core/Events/EntityContainerSavingNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Events +{ + public class EntityContainerSavingNotification : SavingNotification + { + public EntityContainerSavingNotification(EntityContainer target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Core/Events/RenamedNotification.cs b/src/Umbraco.Core/Events/RenamedNotification.cs new file mode 100644 index 0000000000..81a568b6df --- /dev/null +++ b/src/Umbraco.Core/Events/RenamedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Events +{ + public abstract class RenamedNotification : EnumerableObjectNotification + { + protected RenamedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + protected RenamedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable Entities => Target; + } +} diff --git a/src/Umbraco.Core/Events/RenamingNotification.cs b/src/Umbraco.Core/Events/RenamingNotification.cs new file mode 100644 index 0000000000..f215a8ea54 --- /dev/null +++ b/src/Umbraco.Core/Events/RenamingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Events +{ + public abstract class RenamingNotification : CancelableEnumerableObjectNotification + { + protected RenamingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + protected RenamingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable Entities => Target; + } +} 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.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ce524a09a1..cade1041ac 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -46,6 +46,17 @@ <_Parameter1>DynamicProxyGenAssembly2 + + + + <_Parameter1>Umbraco.Forms.Core + + + <_Parameter1>Umbraco.Forms.Core.Providers + + + <_Parameter1>Umbraco.Forms.Web + diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs index 1aa4906029..41195be9fe 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache @@ -18,7 +19,23 @@ namespace Umbraco.Cms.Core.Cache /// /// Default implementation. /// - public partial class DistributedCacheBinder + public partial class DistributedCacheBinder : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { private List _unbinders; @@ -49,30 +66,6 @@ namespace Umbraco.Cms.Core.Cache _logger.LogInformation("Initializing Umbraco internal event handlers for cache refreshing."); - // bind to user and user group events - Bind(() => UserService.SavedUserGroup += UserService_SavedUserGroup, - () => UserService.SavedUserGroup -= UserService_SavedUserGroup); - Bind(() => UserService.DeletedUserGroup += UserService_DeletedUserGroup, - () => UserService.DeletedUserGroup -= UserService_DeletedUserGroup); - Bind(() => UserService.SavedUser += UserService_SavedUser, - () => UserService.SavedUser -= UserService_SavedUser); - Bind(() => UserService.DeletedUser += UserService_DeletedUser, - () => UserService.DeletedUser -= UserService_DeletedUser); - Bind(() => UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned, - () => UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned); - - // bind to dictionary events - Bind(() => LocalizationService.DeletedDictionaryItem += LocalizationService_DeletedDictionaryItem, - () => LocalizationService.DeletedDictionaryItem -= LocalizationService_DeletedDictionaryItem); - Bind(() => LocalizationService.SavedDictionaryItem += LocalizationService_SavedDictionaryItem, - () => LocalizationService.SavedDictionaryItem -= LocalizationService_SavedDictionaryItem); - - // bind to data type events - Bind(() => DataTypeService.Deleted += DataTypeService_Deleted, - () => DataTypeService.Deleted -= DataTypeService_Deleted); - Bind(() => DataTypeService.Saved += DataTypeService_Saved, - () => DataTypeService.Saved -= DataTypeService_Saved); - // bind to stylesheet events Bind(() => FileService.SavedStylesheet += FileService_SavedStylesheet, () => FileService.SavedStylesheet -= FileService_SavedStylesheet); @@ -85,12 +78,6 @@ namespace Umbraco.Cms.Core.Cache Bind(() => DomainService.Deleted += DomainService_Deleted, () => DomainService.Deleted -= DomainService_Deleted); - // bind to language events - Bind(() => LocalizationService.SavedLanguage += LocalizationService_SavedLanguage, - () => LocalizationService.SavedLanguage -= LocalizationService_SavedLanguage); - Bind(() => LocalizationService.DeletedLanguage += LocalizationService_DeletedLanguage, - () => LocalizationService.DeletedLanguage -= LocalizationService_DeletedLanguage); - // bind to content type events Bind(() => ContentTypeService.Changed += ContentTypeService_Changed, () => ContentTypeService.Changed -= ContentTypeService_Changed); @@ -111,16 +98,6 @@ namespace Umbraco.Cms.Core.Cache Bind(() => MacroService.Deleted += MacroService_Deleted, () => MacroService.Deleted -= MacroService_Deleted); - // bind to member events - Bind(() => MemberService.Saved += MemberService_Saved, - () => MemberService.Saved -= MemberService_Saved); - Bind(() => MemberService.Deleted += MemberService_Deleted, - () => MemberService.Deleted -= MemberService_Deleted); - Bind(() => MemberGroupService.Saved += MemberGroupService_Saved, - () => MemberGroupService.Saved -= MemberGroupService_Saved); - Bind(() => MemberGroupService.Deleted += MemberGroupService_Deleted, - () => MemberGroupService.Deleted -= MemberGroupService_Deleted); - // bind to media events - handles all media changes Bind(() => MediaService.TreeChanged += MediaService_TreeChanged, () => MediaService.TreeChanged -= MediaService_TreeChanged); @@ -135,12 +112,6 @@ namespace Umbraco.Cms.Core.Cache //Bind(() => ContentService.DeletedBlueprint += ContentService_DeletedBlueprint, // () => ContentService.DeletedBlueprint -= ContentService_DeletedBlueprint); - // bind to public access events - Bind(() => PublicAccessService.Saved += PublicAccessService_Saved, - () => PublicAccessService.Saved -= PublicAccessService_Saved); - Bind(() => PublicAccessService.Deleted += PublicAccessService_Deleted, - () => PublicAccessService.Deleted -= PublicAccessService_Deleted); - // bind to relation type events Bind(() => RelationService.SavedRelationType += RelationService_SavedRelationType, () => RelationService.SavedRelationType -= RelationService_SavedRelationType); @@ -150,15 +121,15 @@ namespace Umbraco.Cms.Core.Cache #region PublicAccessService - private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs e) + public void Handle(PublicAccessEntrySavedNotification notification) { - _distributedCache.RefreshPublicAccess(); } - private void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) + public void Handle(PublicAccessEntryDeletedNotification notification) { _distributedCache.RefreshPublicAccess(); + } #endregion @@ -196,33 +167,40 @@ namespace Umbraco.Cms.Core.Cache #endregion #region LocalizationService / Dictionary - - private void LocalizationService_SavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) + public void Handle(DictionaryItemSavedNotification notification) { - foreach (var entity in e.SavedEntities) + foreach (IDictionaryItem entity in notification.SavedEntities) + { _distributedCache.RefreshDictionaryCache(entity.Id); + } } - private void LocalizationService_DeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) + public void Handle(DictionaryItemDeletedNotification notification) { - foreach (var entity in e.DeletedEntities) + foreach (IDictionaryItem entity in notification.DeletedEntities) + { _distributedCache.RemoveDictionaryCache(entity.Id); + } } #endregion #region DataTypeService - private void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs e) + public void Handle(DataTypeSavedNotification notification) { - foreach (var entity in e.SavedEntities) + foreach (IDataType entity in notification.SavedEntities) + { _distributedCache.RefreshDataTypeCache(entity); + } } - private void DataTypeService_Deleted(IDataTypeService sender, DeleteEventArgs e) + public void Handle(DataTypeDeletedNotification notification) { - foreach (var entity in e.DeletedEntities) + foreach (IDataType entity in notification.DeletedEntities) + { _distributedCache.RemoveDataTypeCache(entity); + } } #endregion @@ -248,23 +226,25 @@ namespace Umbraco.Cms.Core.Cache /// /// Fires when a language is deleted /// - /// - /// - private void LocalizationService_DeletedLanguage(ILocalizationService sender, DeleteEventArgs e) + /// + public void Handle(LanguageDeletedNotification notification) { - foreach (var entity in e.DeletedEntities) + foreach (ILanguage entity in notification.DeletedEntities) + { _distributedCache.RemoveLanguageCache(entity); + } } /// /// Fires when a language is saved /// - /// - /// - private void LocalizationService_SavedLanguage(ILocalizationService sender, SaveEventArgs e) + /// + public void Handle(LanguageSavedNotification notification) { - foreach (var entity in e.SavedEntities) + foreach (ILanguage entity in notification.SavedEntities) + { _distributedCache.RefreshLanguageCache(entity); + } } #endregion @@ -298,39 +278,36 @@ namespace Umbraco.Cms.Core.Cache #region UserService - private void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs e) + public void Handle(UserSavedNotification notification) { - // TODO: Not sure if we need this yet depends if we start caching permissions - //var groupIds = e.SavedEntities.Select(x => x.UserGroupId).Distinct(); - //foreach (var groupId in groupIds) - //{ - // DistributedCache.Instance.RefreshUserGroupPermissionsCache(groupId); - //} - } - - private void UserService_SavedUser(IUserService sender, SaveEventArgs e) - { - foreach (var entity in e.SavedEntities) + foreach (IUser entity in notification.SavedEntities) + { _distributedCache.RefreshUserCache(entity.Id); + } } - private void UserService_DeletedUser(IUserService sender, DeleteEventArgs e) + public void Handle(UserDeletedNotification notification) { - foreach (var entity in e.DeletedEntities) + foreach (IUser entity in notification.DeletedEntities) + { _distributedCache.RemoveUserCache(entity.Id); + } } - private void UserService_SavedUserGroup(IUserService sender, SaveEventArgs e) + public void Handle(UserGroupWithUsersSavedNotification notification) { - foreach (var entity in e.SavedEntities) + foreach (UserGroupWithUsers entity in notification.SavedEntities) + { _distributedCache.RefreshUserGroupCache(entity.UserGroup.Id); + } } - private void UserService_DeletedUserGroup(IUserService sender, DeleteEventArgs e) + public void Handle(UserGroupDeletedNotification notification) { - - foreach (var entity in e.DeletedEntities) + foreach (IUserGroup entity in notification.DeletedEntities) + { _distributedCache.RemoveUserGroupCache(entity.Id); + } } #endregion @@ -392,33 +369,41 @@ namespace Umbraco.Cms.Core.Cache #region MemberService - private void MemberService_Deleted(IMemberService sender, DeleteEventArgs e) + public void Handle(MemberDeletedNotification notification) { - _distributedCache.RemoveMemberCache(e.DeletedEntities.ToArray()); + _distributedCache.RemoveMemberCache(notification.DeletedEntities.ToArray()); } - private void MemberService_Saved(IMemberService sender, SaveEventArgs e) + public void Handle(MemberSavedNotification notification) { - _distributedCache.RefreshMemberCache(e.SavedEntities.ToArray()); + _distributedCache.RefreshMemberCache(notification.SavedEntities.ToArray()); } #endregion #region MemberGroupService - private void MemberGroupService_Deleted(IMemberGroupService sender, DeleteEventArgs e) + /// + /// Fires when a member group is deleted + /// + /// + public void Handle(MemberGroupDeletedNotification notification) { - foreach (var m in e.DeletedEntities.ToArray()) + foreach (IMemberGroup entity in notification.DeletedEntities) { - _distributedCache.RemoveMemberGroupCache(m.Id); + _distributedCache.RemoveMemberGroupCache(entity.Id); } } - private void MemberGroupService_Saved(IMemberGroupService sender, SaveEventArgs e) + /// + /// Fires when a member group is saved + /// + /// + public void Handle(MemberGroupSavedNotification notification) { - foreach (var m in e.SavedEntities.ToArray()) + foreach (IMemberGroup entity in notification.SavedEntities) { - _distributedCache.RemoveMemberGroupCache(m.Id); + _distributedCache.RemoveMemberGroupCache(entity.Id); } } diff --git a/src/Umbraco.Infrastructure/Compose/AuditEventsComposer.cs b/src/Umbraco.Infrastructure/Compose/AuditEventsComposer.cs deleted file mode 100644 index b6d5f60765..0000000000 --- a/src/Umbraco.Infrastructure/Compose/AuditEventsComposer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - public sealed class AuditEventsComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs b/src/Umbraco.Infrastructure/Compose/AuditNotificationsHandler.cs similarity index 61% rename from src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs rename to src/Umbraco.Infrastructure/Compose/AuditNotificationsHandler.cs index 7c526d330c..e2da324b56 100644 --- a/src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/AuditNotificationsHandler.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Text; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; @@ -10,12 +9,21 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose { - public sealed class AuditEventsComponent : IComponent + public sealed class AuditNotificationsHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly IAuditService _auditService; private readonly IUserService _userService; @@ -23,55 +31,26 @@ namespace Umbraco.Cms.Core.Compose private readonly IIpResolver _ipResolver; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly GlobalSettings _globalSettings; + private readonly IMemberService _memberService; - public AuditEventsComponent( + public AuditNotificationsHandler( IAuditService auditService, IUserService userService, IEntityService entityService, IIpResolver ipResolver, IOptions globalSettings, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMemberService memberService) { _auditService = auditService; _userService = userService; _entityService = entityService; _ipResolver = ipResolver; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _memberService = memberService; _globalSettings = globalSettings.Value; } - public void Initialize() - { - UserService.SavedUserGroup += OnSavedUserGroupWithUsers; - - UserService.SavedUser += OnSavedUser; - UserService.DeletedUser += OnDeletedUser; - UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned; - - MemberService.Saved += OnSavedMember; - MemberService.Deleted += OnDeletedMember; - MemberService.AssignedRoles += OnAssignedRoles; - MemberService.RemovedRoles += OnRemovedRoles; - MemberService.Exported += OnMemberExported; - } - - public void Terminate() - { - UserService.SavedUserGroup -= OnSavedUserGroupWithUsers; - - UserService.SavedUser -= OnSavedUser; - UserService.DeletedUser -= OnDeletedUser; - UserService.UserGroupPermissionsAssigned -= UserGroupPermissionAssigned; - - MemberService.Saved -= OnSavedMember; - MemberService.Deleted -= OnDeletedMember; - MemberService.AssignedRoles -= OnAssignedRoles; - MemberService.RemovedRoles -= OnRemovedRoles; - MemberService.Exported -= OnMemberExported; - } - - public static IUser UnknownUser(GlobalSettings globalSettings) => new User(globalSettings) { Id = Cms.Core.Constants.Security.UnknownUserId, Name = Cms.Core.Constants.Security.UnknownUserName, Email = "" }; - private IUser CurrentPerformingUser { get @@ -82,45 +61,48 @@ namespace Umbraco.Cms.Core.Compose } } - private IUser GetPerformingUser(int userId) - { - var found = userId >= 0 ? _userService.GetUserById(userId) : null; - return found ?? UnknownUser(_globalSettings); - } + public static IUser UnknownUser(GlobalSettings globalSettings) => new User(globalSettings) { Id = Cms.Core.Constants.Security.UnknownUserId, Name = Cms.Core.Constants.Security.UnknownUserName, Email = "" }; private string PerformingIp => _ipResolver.GetCurrentRequestIpAddress(); - private string FormatEmail(IMember member) - { - return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; - } + private string FormatEmail(IMember member) => member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; - private string FormatEmail(IUser user) - { - return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - } + private string FormatEmail(IUser user) => user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - private void OnRemovedRoles(IMemberService sender, RolesEventArgs args) + public void Handle(MemberSavedNotification notification) { var performingUser = CurrentPerformingUser; - var roles = string.Join(", ", args.Roles); - var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x); - foreach (var id in args.MemberIds) + var members = notification.SavedEntities; + foreach (var member in members) { - members.TryGetValue(id, out var member); + var dp = string.Join(", ", ((Member)member).GetWereDirtyProperties()); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", - "umbraco/member/roles/removed", $"roles modified, removed {roles}"); + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); } } - private void OnAssignedRoles(IMemberService sender, RolesEventArgs args) + public void Handle(MemberDeletedNotification notification) { var performingUser = CurrentPerformingUser; - var roles = string.Join(", ", args.Roles); - var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x); - foreach (var id in args.MemberIds) + var members = notification.DeletedEntities; + foreach (var member in members) + { + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); + } + } + + public void Handle(AssignedMemberRolesNotification notification) + { + var performingUser = CurrentPerformingUser; + var roles = string.Join(", ", notification.Roles); + var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); + foreach (var id in notification.MemberIds) { members.TryGetValue(id, out var member); _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, @@ -130,10 +112,25 @@ namespace Umbraco.Cms.Core.Compose } } - private void OnMemberExported(IMemberService sender, ExportedMemberEventArgs exportedMemberEventArgs) + public void Handle(RemovedMemberRolesNotification notification) { var performingUser = CurrentPerformingUser; - var member = exportedMemberEventArgs.Member; + var roles = string.Join(", ", notification.Roles); + var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); + foreach (var id in notification.MemberIds) + { + members.TryGetValue(id, out var member); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/removed", $"roles modified, removed {roles}"); + } + } + + public void Handle(ExportedMemberNotification notification) + { + var performingUser = CurrentPerformingUser; + var member = notification.Member; _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, @@ -141,10 +138,40 @@ namespace Umbraco.Cms.Core.Compose "umbraco/member/exported", "exported member data"); } - private void OnSavedUserGroupWithUsers(IUserService sender, SaveEventArgs saveEventArgs) + public void Handle(UserSavedNotification notification) { var performingUser = CurrentPerformingUser; - foreach (var groupWithUser in saveEventArgs.SavedEntities) + var affectedUsers = notification.SavedEntities; + foreach (var affectedUser in affectedUsers) + { + var groups = affectedUser.WasPropertyDirty("Groups") + ? string.Join(", ", affectedUser.Groups.Select(x => x.Alias)) + : null; + + var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties()); + + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); + } + } + + public void Handle(UserDeletedNotification notification) + { + var performingUser = CurrentPerformingUser; + var affectedUsers = notification.DeletedEntities; + foreach (var affectedUser in affectedUsers) + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/delete", "delete user"); + } + + public void Handle(UserGroupWithUsersSavedNotification notification) + { + var performingUser = CurrentPerformingUser; + foreach (var groupWithUser in notification.SavedEntities) { var group = groupWithUser.UserGroup; @@ -192,13 +219,13 @@ namespace Umbraco.Cms.Core.Compose } } - private void UserGroupPermissionAssigned(IUserService sender, SaveEventArgs saveEventArgs) + public void Handle(AssignedUserGroupPermissionsNotification notification) { var performingUser = CurrentPerformingUser; - var perms = saveEventArgs.SavedEntities; + var perms = notification.EntityPermissions; foreach (var perm in perms) { - var group = sender.GetUserGroupById(perm.UserGroupId); + var group = _userService.GetUserGroupById(perm.UserGroupId); var assigned = string.Join(", ", perm.AssignedPermissions); var entity = _entityService.Get(perm.EntityId); @@ -208,100 +235,5 @@ namespace Umbraco.Cms.Core.Compose "umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity.Name}\""); } } - - private void OnSavedMember(IMemberService sender, SaveEventArgs saveEventArgs) - { - var performingUser = CurrentPerformingUser; - var members = saveEventArgs.SavedEntities; - foreach (var member in members) - { - var dp = string.Join(", ", ((Member) member).GetWereDirtyProperties()); - - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); - } - } - - private void OnDeletedMember(IMemberService sender, DeleteEventArgs deleteEventArgs) - { - var performingUser = CurrentPerformingUser; - var members = deleteEventArgs.DeletedEntities; - foreach (var member in members) - { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); - } - } - - private void OnSavedUser(IUserService sender, SaveEventArgs saveEventArgs) - { - var performingUser = CurrentPerformingUser; - var affectedUsers = saveEventArgs.SavedEntities; - foreach (var affectedUser in affectedUsers) - { - var groups = affectedUser.WasPropertyDirty("Groups") - ? string.Join(", ", affectedUser.Groups.Select(x => x.Alias)) - : null; - - var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties()); - - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", - "umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); - } - } - - private void OnDeletedUser(IUserService sender, DeleteEventArgs deleteEventArgs) - { - var performingUser = CurrentPerformingUser; - var affectedUsers = deleteEventArgs.DeletedEntities; - foreach (var affectedUser in affectedUsers) - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", - "umbraco/user/delete", "delete user"); - } - - private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) - { - var performingUser = _userService.GetUserById(performingId); - - var performingDetails = performingUser == null - ? $"User UNKNOWN:{performingId}" - : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - - WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); - } - - private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails) - { - var performingDetails = performingUser == null - ? $"User UNKNOWN" - : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - - WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails); - } - - private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) - { - if (affectedDetails == null) - { - var affectedUser = _userService.GetUserById(affectedId); - affectedDetails = affectedUser == null - ? $"User UNKNOWN:{affectedId}" - : $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}"; - } - - _auditService.Write(performingId, performingDetails, - ipAddress, - DateTime.UtcNow, - affectedId, affectedDetails, - eventType, eventDetails); - } } } diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs deleted file mode 100644 index 687fdbf294..0000000000 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Actions; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -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.Implement; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Compose -{ - /// - /// TODO: this component must be removed entirely - there is some code duplication in in anticipation of this component being deleted - /// - public sealed class NotificationsComponent : IComponent - { - private readonly Notifier _notifier; - private readonly ActionCollection _actions; - private readonly IContentService _contentService; - - public NotificationsComponent(Notifier notifier, ActionCollection actions, IContentService contentService) - { - _notifier = notifier; - _actions = actions; - _contentService = contentService; - } - - public void Initialize() - { - //Send notifications for the public access changed action - PublicAccessService.Saved += PublicAccessService_Saved; - - UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned; - } - - public void Terminate() - { - PublicAccessService.Saved -= PublicAccessService_Saved; - UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned; - } - - private void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs args) - => UserServiceUserGroupPermissionsAssigned(args, _contentService); - - private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs args) - => PublicAccessServiceSaved(args, _contentService); - - private void UserServiceUserGroupPermissionsAssigned(SaveEventArgs args, IContentService contentService) - { - var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray(); - if (entities.Any() == false) - { - return; - } - _notifier.Notify(_actions.GetAction(), entities); - } - - private void PublicAccessServiceSaved(SaveEventArgs args, IContentService contentService) - { - var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); - if (entities.Any() == false) - { - return; - } - _notifier.Notify(_actions.GetAction(), entities); - } - - /// - /// This class is used to send the notifications - /// - public sealed class Notifier - { - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly INotificationService _notificationService; - private readonly IUserService _userService; - private readonly ILocalizedTextService _textService; - private readonly GlobalSettings _globalSettings; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - public Notifier( - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IHostingEnvironment hostingEnvironment, - INotificationService notificationService, - IUserService userService, - ILocalizedTextService textService, - IOptions globalSettings, - ILogger logger) - { - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _hostingEnvironment = hostingEnvironment; - _notificationService = notificationService; - _userService = userService; - _textService = textService; - _globalSettings = globalSettings.Value; - _logger = logger; - } - - public void Notify(IAction action, params IContent[] entities) - { - var user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - - //if there is no current user, then use the admin - if (user == null) - { - _logger.LogDebug("There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = _userService.GetUserById(Constants.Security.SuperUserId); - if (user == null) - { - _logger.LogWarning("Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); - return; - } - } - - SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl); - } - - private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) - { - if (sender == null) throw new ArgumentNullException(nameof(sender)); - if (siteUri == null) - { - _logger.LogWarning("Notifications can not be sent, no site URL is set (might be during boot process?)"); - return; - } - - //group by the content type variation since the emails will be different - foreach(var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) - { - _notificationService.SendNotifications( - sender, - contentVariantGroup, - action.Letter.ToString(CultureInfo.InvariantCulture), - _textService.Localize("actions", action.Alias), - siteUri, - ((IUser user, NotificationEmailSubjectParams subject) x) - => _textService.Localize( - "notifications/mailSubject", - x.user.GetUserCulture(_textService, _globalSettings), - new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), - ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) - => _textService.Localize( - x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", - x.user.GetUserCulture(_textService, _globalSettings), - new[] - { - x.body.RecipientName, - x.body.Action, - x.body.ItemName, - x.body.EditedUser, - x.body.SiteUrl, - x.body.ItemId, - //format the summary depending on if it's variant or not - contentVariantGroup.Key == ContentVariation.Culture - ? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary })) - : x.body.Summary, - x.body.ItemUrl - })); - } - } - - } - } - - -} diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs index c760c33b71..e9e7bf30af 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs @@ -1,10 +1,12 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Infrastructure.Services.Notifications; @@ -12,14 +14,10 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose { - public sealed class NotificationsComposer : ComponentComposer, ICoreComposer + public sealed class NotificationsComposer : ICoreComposer { - public override void Compose(IUmbracoBuilder builder) + public void Compose(IUmbracoBuilder builder) { - base.Compose(builder); - - builder.Services.AddUnique(); - // add handlers for sending user notifications (i.e. emails) builder.Services.AddUnique(); builder @@ -31,7 +29,9 @@ namespace Umbraco.Cms.Core.Compose .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler(); + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); // add handlers for building content relations builder @@ -51,10 +51,12 @@ namespace Umbraco.Cms.Core.Compose .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() + .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler(); + .AddNotificationHandler() + .AddNotificationHandler(); // add notification handlers for redirect tracking builder @@ -62,6 +64,37 @@ namespace Umbraco.Cms.Core.Compose .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler(); + + // Add notification handlers for DistributedCache + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add notification handlers for auditing + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); } } } diff --git a/src/Umbraco.Infrastructure/Compose/PublicAccessComposer.cs b/src/Umbraco.Infrastructure/Compose/PublicAccessComposer.cs index 71e48c44d1..c4dcfeb041 100644 --- a/src/Umbraco.Infrastructure/Compose/PublicAccessComposer.cs +++ b/src/Umbraco.Infrastructure/Compose/PublicAccessComposer.cs @@ -1,10 +1,17 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Infrastructure.Services.Notifications; namespace Umbraco.Cms.Core.Compose { /// /// Used to ensure that the public access data file is kept up to date properly /// - public sealed class PublicAccessComposer : ComponentComposer, ICoreComposer - { } + public sealed class PublicAccessComposer : ICoreComposer + { + public void Compose(IUmbracoBuilder builder) => + builder + .AddNotificationHandler() + .AddNotificationHandler(); + } } diff --git a/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs b/src/Umbraco.Infrastructure/Compose/PublicAccessHandler.cs similarity index 57% rename from src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs rename to src/Umbraco.Infrastructure/Compose/PublicAccessHandler.cs index b8c60c9d8a..b9ded9aff8 100644 --- a/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/PublicAccessHandler.cs @@ -1,34 +1,29 @@ -using System; -using Umbraco.Cms.Core.Composing; +using System; +using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose { - public sealed class PublicAccessComponent : IComponent + public sealed class PublicAccessHandler : + INotificationHandler, + INotificationHandler { private readonly IPublicAccessService _publicAccessService; - public PublicAccessComponent(IPublicAccessService publicAccessService) - { + + public PublicAccessHandler(IPublicAccessService publicAccessService) => _publicAccessService = publicAccessService ?? throw new ArgumentNullException(nameof(publicAccessService)); - } - public void Initialize() - { - MemberGroupService.Saved += MemberGroupService_Saved; - } + public void Handle(MemberGroupSavedNotification notification) => Handle(notification.SavedEntities); - public void Terminate() - { - MemberGroupService.Saved -= MemberGroupService_Saved; - } + public void Handle(MemberGroupDeletedNotification notification) => Handle(notification.DeletedEntities); - private void MemberGroupService_Saved(IMemberGroupService sender, SaveEventArgs e) + private void Handle(IEnumerable affectedEntities) { - foreach (var grp in e.SavedEntities) + foreach (var grp in affectedEntities) { //check if the name has changed if (grp.AdditionalData.ContainsKey("previousName") diff --git a/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs b/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs index 62da73c28b..3df16dda25 100644 --- a/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs +++ b/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs @@ -29,7 +29,9 @@ namespace Umbraco.Cms.Core.Events INotificationHandler, INotificationHandler, INotificationHandler, - INotificationHandler + INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly Notifier _notifier; private readonly ActionCollection _actions; @@ -209,7 +211,26 @@ namespace Umbraco.Cms.Core.Events })); } } + } + public void Handle(AssignedUserGroupPermissionsNotification notification) + { + var entities = _contentService.GetByIds(notification.EntityPermissions.Select(e => e.EntityId)).ToArray(); + if (entities.Any() == false) + { + return; + } + _notifier.Notify(_actions.GetAction(), entities); + } + + public void Handle(PublicAccessEntrySavedNotification notification) + { + var entities = _contentService.GetByIds(notification.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); + if (entities.Any() == false) + { + return; + } + _notifier.Notify(_actions.GetAction(), entities); } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs index abce17a331..2d73413563 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -12,15 +12,18 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { internal class MemberGroupRepository : EntityRepositoryBase, IMemberGroupRepository { - public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) - { } + private readonly IEventMessagesFactory _eventMessagesFactory; + + public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(scopeAccessor, cache, logger) => + _eventMessagesFactory = eventMessagesFactory; protected override IMemberGroup PerformGet(int id) { @@ -156,10 +159,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement }; PersistNewItem(grp); - if (AmbientScope.Events.DispatchCancelable(SavingMemberGroup, this, new SaveEventArgs(grp))) + var evtMsgs = _eventMessagesFactory.Get(); + if (AmbientScope.Notifications.PublishCancelable(new MemberGroupSavingNotification(grp, evtMsgs))) + { return null; + } + + AmbientScope.Notifications.Publish(new MemberGroupSavedNotification(grp, evtMsgs)); - AmbientScope.Events.Dispatch(SavedMemberGroup, this, new SaveEventArgs(grp)); return grp; } @@ -240,13 +247,16 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var missingRoles = roleNames.Except(existingRoles, StringComparer.CurrentCultureIgnoreCase); var missingGroups = missingRoles.Select(x => new MemberGroup {Name = x}).ToArray(); - if (AmbientScope.Events.DispatchCancelable(SavingMemberGroup, this, new SaveEventArgs(missingGroups))) + var evtMsgs = _eventMessagesFactory.Get(); + if (AmbientScope.Notifications.PublishCancelable(new MemberGroupSavingNotification(missingGroups, evtMsgs))) + { return; + } foreach (var m in missingGroups) PersistNewItem(m); - AmbientScope.Events.Dispatch(SavedMemberGroup, this, new SaveEventArgs(missingGroups)); + AmbientScope.Notifications.Publish(new MemberGroupSavedNotification(missingGroups, evtMsgs)); //now go get all the dto's for roles with these role names var rolesForNames = Database.Fetch(existingSql).ToArray(); @@ -310,17 +320,5 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement [Column("MemberGroup")] public int MemberGroupId { get; set; } } - - // TODO: understand why we need these two repository-level events, move them back to service - - /// - /// Occurs before Save - /// - internal static event TypedEventHandler> SavingMemberGroup; - - /// - /// Occurs after Save - /// - internal static event TypedEventHandler> SavedMemberGroup; } } 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/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs index 15d56abfb3..a794d62ce2 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs @@ -27,7 +27,8 @@ namespace Umbraco.Cms.Core.PropertyEditors Icon = "icon-download-alt")] public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler, INotificationHandler, - INotificationHandler, INotificationHandler + INotificationHandler, INotificationHandler, + INotificationHandler { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -95,10 +96,7 @@ namespace Umbraco.Cms.Core.PropertyEditors /// The paths to all file upload property files contained within a collection of content entities /// /// - /// - /// This method must be made private once MemberService events have been replaced by notifications - /// - internal IEnumerable ContainedFilePaths(IEnumerable entities) => entities + private IEnumerable ContainedFilePaths(IEnumerable entities) => entities .SelectMany(x => x.Properties) .Where(IsUploadField) .SelectMany(GetFilePathsFromPropertyValues) @@ -162,6 +160,8 @@ namespace Umbraco.Cms.Core.PropertyEditors public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + public void Handle(MemberDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + private void DeleteContainedFiles(IEnumerable deletedEntities) { var filePathsToDelete = ContainedFilePaths(deletedEntities); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 52972e9f49..902cb32f08 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -34,7 +34,8 @@ namespace Umbraco.Cms.Core.PropertyEditors Icon = "icon-crop")] public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler, INotificationHandler, - INotificationHandler, INotificationHandler + INotificationHandler, INotificationHandler, + INotificationHandler { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -131,10 +132,7 @@ namespace Umbraco.Cms.Core.PropertyEditors /// The paths to all image cropper property files contained within a collection of content entities /// /// - /// - /// This method must be made private once MemberService events have been replaced by notifications - /// - internal IEnumerable ContainedFilePaths(IEnumerable entities) => entities + private IEnumerable ContainedFilePaths(IEnumerable entities) => entities .SelectMany(x => x.Properties) .Where(IsCropperField) .SelectMany(GetFilePathsFromPropertyValues) @@ -218,6 +216,8 @@ namespace Umbraco.Cms.Core.PropertyEditors public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + public void Handle(MemberDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + private void DeleteContainedFiles(IEnumerable deletedEntities) { var filePathsToDelete = ContainedFilePaths(deletedEntities); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs deleted file mode 100644 index b9e9e33889..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; - -namespace Umbraco.Cms.Core.PropertyEditors -{ - // TODO: delete this component and make the "ContainedFilePaths" methods on FileUploadPropertyEditor and ImageCropperPropertyEditor private once MemberService uses notifications instead of static events - public sealed class PropertyEditorsComponent : IComponent - { - private readonly PropertyEditorCollection _propertyEditors; - private readonly List _terminate = new List(); - - public PropertyEditorsComponent(PropertyEditorCollection propertyEditors) - { - _propertyEditors = propertyEditors; - } - - public void Initialize() - { - var fileUpload = _propertyEditors.OfType().FirstOrDefault(); - if (fileUpload != null) Initialize(fileUpload); - - var imageCropper = _propertyEditors.OfType().FirstOrDefault(); - if (imageCropper != null) Initialize(imageCropper); - - // grid/examine moved to ExamineComponent - } - - public void Terminate() - { - foreach (var t in _terminate) t(); - } - - private void Initialize(FileUploadPropertyEditor fileUpload) - { - void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ContainedFilePaths(args.DeletedEntities.Cast())); - MemberService.Deleted += memberServiceDeleted; - _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); - } - - private void Initialize(ImageCropperPropertyEditor imageCropper) - { - void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ContainedFilePaths(args.DeletedEntities.Cast())); - MemberService.Deleted += memberServiceDeleted; - _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); - } - } -} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComposer.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComposer.cs deleted file mode 100644 index 4e876ad554..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComposer.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.PropertyEditors -{ - public sealed class PropertyEditorsComposer : ComponentComposer, ICoreComposer - { } -} 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/DataTypeService.cs b/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs index a2ef8f4544..4bb1e46c29 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs @@ -68,7 +68,8 @@ namespace Umbraco.Cms.Core.Services.Implement CreatorId = userId }; - if (scope.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs); + if (scope.Notifications.PublishCancelable(savingEntityContainerNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs, container); @@ -77,7 +78,8 @@ namespace Umbraco.Cms.Core.Services.Implement _dataTypeContainerRepository.Save(container); scope.Complete(); - scope.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); + scope.Notifications.Publish(new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom(savingEntityContainerNotification)); + // TODO: Audit trail ? return OperationResult.Attempt.Succeed(evtMsgs, container); @@ -153,7 +155,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - if (scope.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs); + if (scope.Notifications.PublishCancelable(savingEntityContainerNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -161,7 +164,7 @@ namespace Umbraco.Cms.Core.Services.Implement _dataTypeContainerRepository.Save(container); - scope.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); + scope.Notifications.Publish(new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom(savingEntityContainerNotification)); scope.Complete(); } @@ -186,7 +189,8 @@ namespace Umbraco.Cms.Core.Services.Implement return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs)); } - if (scope.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs(container, evtMsgs))) + var deletingEntityContainerNotification = new EntityContainerDeletingNotification(container, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingEntityContainerNotification)) { scope.Complete(); return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs)); @@ -194,7 +198,7 @@ namespace Umbraco.Cms.Core.Services.Implement _dataTypeContainerRepository.Delete(container); - scope.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs(container, evtMsgs)); + scope.Notifications.Publish(new EntityContainerDeletedNotification(container, evtMsgs).WithStateFrom(deletingEntityContainerNotification)); scope.Complete(); } @@ -217,11 +221,17 @@ namespace Umbraco.Cms.Core.Services.Implement container.Name = name; + var renamingEntityContainerNotification = new EntityContainerRenamingNotification(container, evtMsgs); + if (scope.Notifications.PublishCancelable(renamingEntityContainerNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(evtMsgs, container); + } + _dataTypeContainerRepository.Save(container); scope.Complete(); - // TODO: triggering SavedContainer with a different name?! - scope.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs), "RenamedContainer"); + scope.Notifications.Publish(new EntityContainerRenamedNotification(container, evtMsgs).WithStateFrom(renamingEntityContainerNotification)); return OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, container); } @@ -341,8 +351,9 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); - var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); - if (scope.Events.DispatchCancelable(Moving, this, moveEventArgs)) + + var movingDataTypeNotification = new DataTypeMovingNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingDataTypeNotification)) { scope.Complete(); return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs); @@ -359,9 +370,8 @@ namespace Umbraco.Cms.Core.Services.Implement } moveInfo.AddRange(_dataTypeRepository.Move(toMove, container)); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - scope.Events.Dispatch(Moved, this, moveEventArgs); + scope.Notifications.Publish(new DataTypeMovedNotification(moveEventInfo, evtMsgs).WithStateFrom(movingDataTypeNotification)); + scope.Complete(); } catch (DataOperationException ex) @@ -381,12 +391,15 @@ namespace Umbraco.Cms.Core.Services.Implement /// Id of the user issuing the save public void Save(IDataType dataType, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); dataType.CreatorId = userId; using (var scope = ScopeProvider.CreateScope()) { var saveEventArgs = new SaveEventArgs(dataType); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + + var savingDataTypeNotification = new DataTypeSavingNotification(dataType, evtMsgs); + if (scope.Notifications.PublishCancelable(savingDataTypeNotification)) { scope.Complete(); return; @@ -404,8 +417,8 @@ namespace Umbraco.Cms.Core.Services.Implement _dataTypeRepository.Save(dataType); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new DataTypeSavedNotification(dataType, evtMsgs).WithStateFrom(savingDataTypeNotification)); + Audit(AuditType.Save, userId, dataType.Id); scope.Complete(); } @@ -429,12 +442,13 @@ namespace Umbraco.Cms.Core.Services.Implement /// Boolean indicating whether or not to raise events public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) { + var evtMsgs = EventMessagesFactory.Get(); var dataTypeDefinitionsA = dataTypeDefinitions.ToArray(); - var saveEventArgs = new SaveEventArgs(dataTypeDefinitionsA); using (var scope = ScopeProvider.CreateScope()) { - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingDataTypeNotification = new DataTypeSavingNotification(dataTypeDefinitions, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingDataTypeNotification)) { scope.Complete(); return; @@ -448,8 +462,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new DataTypeSavedNotification(dataTypeDefinitions, evtMsgs).WithStateFrom(savingDataTypeNotification)); } Audit(AuditType.Save, userId, -1); @@ -468,10 +481,11 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the user issuing the deletion public void Delete(IDataType dataType, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(dataType); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + var deletingDataTypeNotification = new DataTypeDeletingNotification(dataType, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingDataTypeNotification)) { scope.Complete(); return; @@ -506,8 +520,8 @@ namespace Umbraco.Cms.Core.Services.Implement _dataTypeRepository.Delete(dataType); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(Deleted, this, deleteEventArgs); + scope.Notifications.Publish(new DataTypeDeletedNotification(dataType, evtMsgs).WithStateFrom(deletingDataTypeNotification)); + Audit(AuditType.Delete, userId, dataType.Id); scope.Complete(); @@ -527,42 +541,5 @@ namespace Umbraco.Cms.Core.Services.Implement _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.DataType))); } - #region Event Handlers - - public static event TypedEventHandler> SavingContainer; - public static event TypedEventHandler> SavedContainer; - public static event TypedEventHandler> DeletingContainer; - public static event TypedEventHandler> DeletedContainer; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - #endregion } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/LocalizationService.cs b/src/Umbraco.Infrastructure/Services/Implement/LocalizationService.cs index abdda2e68c..a15d782db5 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/LocalizationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/LocalizationService.cs @@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -13,14 +14,19 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// Represents the Localization Service, which is an easy access to operations involving and /// - public class LocalizationService : RepositoryService, ILocalizationService + internal class LocalizationService : RepositoryService, ILocalizationService { private readonly IDictionaryRepository _dictionaryRepository; private readonly ILanguageRepository _languageRepository; private readonly IAuditRepository _auditRepository; - public LocalizationService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IDictionaryRepository dictionaryRepository, IAuditRepository auditRepository, ILanguageRepository languageRepository) + public LocalizationService( + IScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDictionaryRepository dictionaryRepository, + IAuditRepository auditRepository, + ILanguageRepository languageRepository) : base(provider, loggerFactory, eventMessagesFactory) { _dictionaryRepository = dictionaryRepository; @@ -88,9 +94,11 @@ namespace Umbraco.Cms.Core.Services.Implement item.Translations = translations; } - var saveEventArgs = new SaveEventArgs(item); - if (scope.Events.DispatchCancelable(SavingDictionaryItem, this, saveEventArgs)) + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingNotification = new DictionaryItemSavingNotification(item, eventMessages); + + if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return item; @@ -100,8 +108,7 @@ namespace Umbraco.Cms.Core.Services.Implement // ensure the lazy Language callback is assigned EnsureDictionaryItemLanguageCallback(item); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedDictionaryItem, this, saveEventArgs); + scope.Notifications.Publish(new DictionaryItemSavedNotification(item, eventMessages).WithStateFrom(savingNotification)); scope.Complete(); @@ -232,7 +239,9 @@ namespace Umbraco.Cms.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope()) { - if (scope.Events.DispatchCancelable(SavingDictionaryItem, this, new SaveEventArgs(dictionaryItem))) + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingNotification = new DictionaryItemSavingNotification(dictionaryItem, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -244,7 +253,7 @@ namespace Umbraco.Cms.Core.Services.Implement // ensure the lazy Language callback is assigned EnsureDictionaryItemLanguageCallback(dictionaryItem); - scope.Events.Dispatch(SavedDictionaryItem, this, new SaveEventArgs(dictionaryItem, false)); + scope.Notifications.Publish(new DictionaryItemSavedNotification(dictionaryItem, eventMessages).WithStateFrom(savingNotification)); Audit(AuditType.Save, "Save DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem"); scope.Complete(); @@ -261,16 +270,16 @@ namespace Umbraco.Cms.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(dictionaryItem); - if (scope.Events.DispatchCancelable(DeletingDictionaryItem, this, deleteEventArgs)) + EventMessages eventMessages = EventMessagesFactory.Get(); + var deletingNotification = new DictionaryItemDeletingNotification(dictionaryItem, eventMessages); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; } _dictionaryRepository.Delete(dictionaryItem); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(DeletedDictionaryItem, this, deleteEventArgs); + scope.Notifications.Publish(new DictionaryItemDeletedNotification(dictionaryItem, eventMessages).WithStateFrom(deletingNotification)); Audit(AuditType.Delete, "Delete DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem"); @@ -374,16 +383,16 @@ namespace Umbraco.Cms.Core.Services.Implement throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback {languages[language.FallbackLanguageId.Value].IsoCode} as it would create a fallback cycle."); } - var saveEventArgs = new SaveEventArgs(language); - if (scope.Events.DispatchCancelable(SavingLanguage, this, saveEventArgs)) + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingNotification = new LanguageSavingNotification(language, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; } _languageRepository.Save(language); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedLanguage, this, saveEventArgs); + scope.Notifications.Publish(new LanguageSavedNotification(language, eventMessages).WithStateFrom(savingNotification)); Audit(AuditType.Save, "Save Language", userId, language.Id, ObjectTypes.GetName(UmbracoObjectTypes.Language)); @@ -417,8 +426,9 @@ namespace Umbraco.Cms.Core.Services.Implement // write-lock languages to guard against race conds when dealing with default language scope.WriteLock(Cms.Core.Constants.Locks.Languages); - var deleteEventArgs = new DeleteEventArgs(language); - if (scope.Events.DispatchCancelable(DeletingLanguage, this, deleteEventArgs)) + EventMessages eventMessages = EventMessagesFactory.Get(); + var deletingLanguageNotification = new LanguageDeletingNotification(language, eventMessages); + if (scope.Notifications.PublishCancelable(deletingLanguageNotification)) { scope.Complete(); return; @@ -426,9 +436,8 @@ namespace Umbraco.Cms.Core.Services.Implement // NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted _languageRepository.Delete(language); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(DeletedLanguage, this, deleteEventArgs); + scope.Notifications.Publish(new LanguageDeletedNotification(language, eventMessages).WithStateFrom(deletingLanguageNotification)); Audit(AuditType.Delete, "Delete Language", userId, language.Id, ObjectTypes.GetName(UmbracoObjectTypes.Language)); scope.Complete(); @@ -463,47 +472,5 @@ namespace Umbraco.Cms.Core.Services.Implement return _dictionaryRepository.GetDictionaryItemKeyMap(); } } - - #region Events - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingLanguage; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> DeletedLanguage; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingDictionaryItem; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> DeletedDictionaryItem; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> SavingDictionaryItem; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> SavedDictionaryItem; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> SavingLanguage; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> SavedLanguage; - #endregion } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs index 5e6138980a..eb1c4f26ca 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; @@ -6,40 +6,18 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; namespace Umbraco.Cms.Core.Services.Implement { - public class MemberGroupService : RepositoryService, IMemberGroupService + internal class MemberGroupService : RepositoryService, IMemberGroupService { private readonly IMemberGroupRepository _memberGroupRepository; public MemberGroupService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMemberGroupRepository memberGroupRepository) - : base(provider, loggerFactory, eventMessagesFactory) - { + : base(provider, loggerFactory, eventMessagesFactory) => _memberGroupRepository = memberGroupRepository; - //Proxy events! - MemberGroupRepository.SavedMemberGroup += MemberGroupRepository_SavedMemberGroup; - MemberGroupRepository.SavingMemberGroup += MemberGroupRepository_SavingMemberGroup; - } - - #region Proxy event handlers - - void MemberGroupRepository_SavingMemberGroup(IMemberGroupRepository sender, SaveEventArgs e) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(e.SavedEntities), this)) - e.Cancel = true; - } - - void MemberGroupRepository_SavedMemberGroup(IMemberGroupRepository sender, SaveEventArgs e) - { - // same as above! - - Saved.RaiseEvent(new SaveEventArgs(e.SavedEntities, false), this); - } - - #endregion public IEnumerable GetAll() { @@ -92,10 +70,13 @@ namespace Umbraco.Cms.Core.Services.Implement { throw new InvalidOperationException("The name of a MemberGroup can not be empty"); } + + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(memberGroup); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotification = new MemberGroupSavingNotification(memberGroup, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -106,18 +87,19 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MemberGroupSavedNotification(memberGroup, evtMsgs).WithStateFrom(savingNotification)); } } } public void Delete(IMemberGroup memberGroup) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(memberGroup); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + var deletingNotification = new MemberGroupDeletingNotification(memberGroup, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; @@ -125,35 +107,9 @@ namespace Umbraco.Cms.Core.Services.Implement _memberGroupRepository.Delete(memberGroup); scope.Complete(); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(Deleted, this, deleteEventArgs); + + scope.Notifications.Publish(new MemberGroupDeletedNotification(memberGroup, evtMsgs).WithStateFrom(deletingNotification)); } } - - /// - /// Occurs before Delete of a member group - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete of a member group - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save of a member group - /// - /// - /// We need to proxy these events because the events need to take place at the repo level - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save of a member group - /// - /// - /// We need to proxy these events because the events need to take place at the repo level - /// - public static event TypedEventHandler> Saved; } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs index 38b70af19c..aba40333b0 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs @@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -22,7 +23,6 @@ namespace Umbraco.Cms.Core.Services.Implement private readonly IMemberTypeRepository _memberTypeRepository; private readonly IMemberGroupRepository _memberGroupRepository; private readonly IAuditRepository _auditRepository; - private readonly IMemberTypeService _memberTypeService; private readonly IMemberGroupService _memberGroupService; @@ -773,10 +773,12 @@ namespace Umbraco.Cms.Core.Services.Implement member.Username = member.Username.Trim(); member.Email = member.Email.Trim(); + var evtMsgs = EventMessagesFactory.Get(); + using (IScope scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(member); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotification = new MemberSavingNotification(member, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -793,8 +795,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification)); } Audit(AuditType.Save, 0, member.Id); @@ -808,10 +809,12 @@ namespace Umbraco.Cms.Core.Services.Implement { var membersA = members.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(membersA); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotification = new MemberSavingNotification(membersA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -830,8 +833,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MemberSavedNotification(membersA, evtMsgs).WithStateFrom(savingNotification)); } Audit(AuditType.Save, 0, -1, "Save multiple Members"); @@ -849,32 +851,30 @@ namespace Umbraco.Cms.Core.Services.Implement /// to Delete public void Delete(IMember member) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(member); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + var deletingNotification = new MemberDeletingNotification(member, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; } scope.WriteLock(Constants.Locks.MemberTree); - DeleteLocked(scope, member, deleteEventArgs); + DeleteLocked(scope, member, evtMsgs, deletingNotification.State); Audit(AuditType.Delete, 0, member.Id); scope.Complete(); } } - private void DeleteLocked(IScope scope, IMember member, DeleteEventArgs args = null) + private void DeleteLocked(IScope scope, IMember member, EventMessages evtMsgs, IDictionary notificationState = null) { // a member has no descendants _memberRepository.Delete(member); - if (args == null) - args = new DeleteEventArgs(member, false); // raise event & get flagged files - else - args.CanCancel = false; - scope.Events.Dispatch(Deleted, this, args); + scope.Notifications.Publish(new MemberDeletedNotification(member, evtMsgs).WithState(notificationState)); // media files deleted by QueuingEventDispatcher } @@ -1017,7 +1017,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Constants.Locks.MemberTree); int[] ids = _memberGroupRepository.GetMemberIds(usernames); _memberGroupRepository.AssignRoles(ids, roleNames); - scope.Events.Dispatch(AssignedRoles, this, new RolesEventArgs(ids, roleNames), nameof(AssignedRoles)); + scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames)); scope.Complete(); } } @@ -1031,7 +1031,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Constants.Locks.MemberTree); int[] ids = _memberGroupRepository.GetMemberIds(usernames); _memberGroupRepository.DissociateRoles(ids, roleNames); - scope.Events.Dispatch(RemovedRoles, this, new RolesEventArgs(ids, roleNames), nameof(RemovedRoles)); + scope.Notifications.Publish(new RemovedMemberRolesNotification(ids, roleNames)); scope.Complete(); } } @@ -1044,7 +1044,7 @@ namespace Umbraco.Cms.Core.Services.Implement { scope.WriteLock(Constants.Locks.MemberTree); _memberGroupRepository.AssignRoles(memberIds, roleNames); - scope.Events.Dispatch(AssignedRoles, this, new RolesEventArgs(memberIds, roleNames), nameof(AssignedRoles)); + scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames)); scope.Complete(); } } @@ -1057,7 +1057,7 @@ namespace Umbraco.Cms.Core.Services.Implement { scope.WriteLock(Constants.Locks.MemberTree); _memberGroupRepository.DissociateRoles(memberIds, roleNames); - scope.Events.Dispatch(RemovedRoles, this, new RolesEventArgs(memberIds, roleNames), nameof(RemovedRoles)); + scope.Notifications.Publish(new RemovedMemberRolesNotification(memberIds, roleNames)); scope.Complete(); } } @@ -1070,45 +1070,6 @@ namespace Umbraco.Cms.Core.Services.Implement #endregion - #region Event Handlers - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs after roles have been assigned. - /// - public static event TypedEventHandler AssignedRoles; - - /// - /// Occurs after roles have been removed. - /// - public static event TypedEventHandler RemovedRoles; - - /// - /// Occurs after members have been exported. - /// - public static event TypedEventHandler Exported; - - #endregion - #region Membership @@ -1145,7 +1106,7 @@ namespace Umbraco.Cms.Core.Services.Implement Properties = new List(GetPropertyExportItems(member)) }; - scope.Events.Dispatch(Exported, this, new ExportedMemberEventArgs(member, model)); + scope.Notifications.Publish(new ExportedMemberNotification(member, model)); return model; } @@ -1187,6 +1148,8 @@ namespace Umbraco.Cms.Core.Services.Implement /// Id of the MemberType public void DeleteMembersOfType(int memberTypeId) { + var evtMsgs = EventMessagesFactory.Get(); + // note: no tree to manage here using (IScope scope = ScopeProvider.CreateScope()) { @@ -1196,9 +1159,8 @@ namespace Umbraco.Cms.Core.Services.Implement IQuery query = Query().Where(x => x.ContentTypeId == memberTypeId); IMember[] members = _memberRepository.Get(query).ToArray(); - var deleteEventArgs = new DeleteEventArgs(members); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + if (scope.Notifications.PublishCancelable(new MemberDeletingNotification(members, evtMsgs))) { scope.Complete(); return; @@ -1208,7 +1170,7 @@ namespace Umbraco.Cms.Core.Services.Implement { // delete media // triggers the deleted event (and handles the files) - DeleteLocked(scope, member); + DeleteLocked(scope, member, evtMsgs); } scope.Complete(); diff --git a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs index 5b3e2d0a11..b6d12784c6 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -6,11 +6,12 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement { - public class PublicAccessService : RepositoryService, IPublicAccessService + internal class PublicAccessService : RepositoryService, IPublicAccessService { private readonly IPublicAccessRepository _publicAccessRepository; @@ -114,7 +115,7 @@ namespace Umbraco.Cms.Core.Services.Implement { entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id); if (entry == null) - return OperationResult.Attempt.Cannot(evtMsgs); // causes rollback // causes rollback + return OperationResult.Attempt.Cannot(evtMsgs); // causes rollback var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); if (existingRule == null) @@ -127,8 +128,8 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Attempt.Succeed(evtMsgs, entry); } - var saveEventArgs = new SaveEventArgs(entry, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs, entry); @@ -138,8 +139,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.Complete(); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs, entry); @@ -165,8 +165,8 @@ namespace Umbraco.Cms.Core.Services.Implement entry.RemoveRule(existingRule); - var saveEventArgs = new SaveEventArgs(entry, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -175,8 +175,7 @@ namespace Umbraco.Cms.Core.Services.Implement _publicAccessRepository.Save(entry); scope.Complete(); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs); @@ -192,8 +191,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(entry, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -202,8 +201,7 @@ namespace Umbraco.Cms.Core.Services.Implement _publicAccessRepository.Save(entry); scope.Complete(); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs); @@ -219,8 +217,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(entry, evtMsgs); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + var deletingNotification = new PublicAccessEntryDeletingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -229,33 +227,10 @@ namespace Umbraco.Cms.Core.Services.Implement _publicAccessRepository.Delete(entry); scope.Complete(); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(Deleted, this, deleteEventArgs); + scope.Notifications.Publish(new PublicAccessEntryDeletedNotification(entry, evtMsgs).WithStateFrom(deletingNotification)); } return OperationResult.Attempt.Succeed(evtMsgs); } - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index d35bcbfa50..b7b52ae616 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -21,7 +22,7 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// Represents the UserService, which is an easy access to operations involving , and eventually Backoffice Users. /// - public class UserService : RepositoryService, IUserService + internal class UserService : RepositoryService, IUserService { private readonly IUserRepository _userRepository; private readonly IUserGroupRepository _userGroupRepository; @@ -109,6 +110,8 @@ namespace Umbraco.Cms.Core.Services.Implement if (username == null) throw new ArgumentNullException(nameof(username)); if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); + var evtMsgs = EventMessagesFactory.Get(); + // TODO: PUT lock here!! User user; @@ -129,8 +132,8 @@ namespace Umbraco.Cms.Core.Services.Implement IsApproved = isApproved }; - var saveEventArgs = new SaveEventArgs(user); - if (scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) + var savingNotification = new UserSavingNotification(user, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return user; @@ -138,8 +141,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userRepository.Save(user); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedUser, this, saveEventArgs); + scope.Notifications.Publish(new UserSavedNotification(user, evtMsgs).WithStateFrom(savingNotification)); scope.Complete(); } @@ -239,10 +241,12 @@ namespace Umbraco.Cms.Core.Services.Implement } else { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(user); - if (scope.Events.DispatchCancelable(DeletingUser, this, deleteEventArgs)) + var deletingNotification = new UserDeletingNotification(user, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; @@ -250,8 +254,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userRepository.Delete(user); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(DeletedUser, this, deleteEventArgs); + scope.Notifications.Publish(new UserDeletedNotification(user, evtMsgs).WithStateFrom(deletingNotification)); scope.Complete(); } } @@ -272,10 +275,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// Default is True otherwise set to False to not raise events public void Save(IUser entity, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(entity); - if (raiseEvents && scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) + var savingNotification = new UserSavingNotification(entity, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -292,8 +297,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userRepository.Save(entity); if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedUser, this, saveEventArgs); + scope.Notifications.Publish(new UserSavedNotification(entity, evtMsgs).WithStateFrom(savingNotification)); } scope.Complete(); @@ -319,12 +323,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// Default is True otherwise set to False to not raise events public void Save(IEnumerable entities, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + var entitiesA = entities.ToArray(); using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(entitiesA); - if (raiseEvents && scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) + var savingNotification = new UserSavingNotification(entitiesA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -344,8 +350,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedUser, this, saveEventArgs); + scope.Notifications.Publish(new UserSavedNotification(entitiesA, evtMsgs).WithStateFrom(savingNotification)); } //commit the whole lot in one go @@ -709,14 +714,16 @@ namespace Umbraco.Cms.Core.Services.Implement if (entityIds.Length == 0) return; + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { _userGroupRepository.ReplaceGroupPermissions(groupId, permissions, entityIds); scope.Complete(); var assigned = permissions.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray(); - scope.Events.Dispatch(UserGroupPermissionsAssigned, this, - new SaveEventArgs(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false)); + var entityPermissions = entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); + scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); } } @@ -731,14 +738,16 @@ namespace Umbraco.Cms.Core.Services.Implement if (entityIds.Length == 0) return; + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { _userGroupRepository.AssignGroupPermission(groupId, permission, entityIds); scope.Complete(); var assigned = new[] { permission.ToString(CultureInfo.InvariantCulture) }; - scope.Events.Dispatch(UserGroupPermissionsAssigned, this, - new SaveEventArgs(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false)); + var entityPermissions = entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); + scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); } } @@ -809,6 +818,8 @@ namespace Umbraco.Cms.Core.Services.Implement /// Default is True otherwise set to False to not raise events public void Save(IUserGroup userGroup, int[] userIds = null, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { // we need to figure out which users have been added / removed, for audit purposes @@ -826,9 +837,19 @@ namespace Umbraco.Cms.Core.Services.Implement removedUsers = groupIds.Except(userIds).Select(x => xGroupUsers[x]).Where(x => x.Id != 0).ToArray(); } - var saveEventArgs = new SaveEventArgs(new UserGroupWithUsers(userGroup, addedUsers, removedUsers)); + var userGroupWithUsers = new UserGroupWithUsers(userGroup, addedUsers, removedUsers); - if (raiseEvents && scope.Events.DispatchCancelable(SavingUserGroup, this, saveEventArgs)) + // this is the default/expected notification for the IUserGroup entity being saved + var savingNotification = new UserGroupSavingNotification(userGroup, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) + { + scope.Complete(); + return; + } + + // this is an additional notification for special auditing + var savingUserGroupWithUsersNotification = new UserGroupWithUsersSavingNotification(userGroupWithUsers, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingUserGroupWithUsersNotification)) { scope.Complete(); return; @@ -838,8 +859,8 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedUserGroup, this, saveEventArgs); + scope.Notifications.Publish(new UserGroupSavedNotification(userGroup, evtMsgs).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new UserGroupWithUsersSavedNotification(userGroupWithUsers, evtMsgs).WithStateFrom(savingUserGroupWithUsersNotification)); } scope.Complete(); @@ -852,10 +873,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// UserGroup to delete public void DeleteUserGroup(IUserGroup userGroup) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(userGroup); - if (scope.Events.DispatchCancelable(DeletingUserGroup, this, deleteEventArgs)) + var deletingNotification = new UserGroupDeletingNotification(userGroup, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; @@ -863,8 +886,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userGroupRepository.Delete(userGroup); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(DeletedUserGroup, this, deleteEventArgs); + scope.Notifications.Publish(new UserGroupDeletedNotification(userGroup, evtMsgs).WithStateFrom(deletingNotification)); scope.Complete(); } @@ -1144,49 +1166,5 @@ namespace Umbraco.Cms.Core.Services.Implement } #endregion - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> SavingUser; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> SavedUser; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingUser; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> DeletedUser; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> SavingUserGroup; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> SavedUserGroup; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingUserGroup; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> DeletedUserGroup; - - // TODO: still don't know if we need this yet unless we start caching permissions, but that also means we'll need another - // event on the ContentService since there's a method there to modify node permissions too, or we can proxy events if needed. - public static event TypedEventHandler> UserGroupPermissionsAssigned; } } diff --git a/src/Umbraco.Infrastructure/Services/Notifications/AssignedMemberRolesNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/AssignedMemberRolesNotification.cs new file mode 100644 index 0000000000..9d5d707f64 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/AssignedMemberRolesNotification.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class AssignedMemberRolesNotification : MemberRolesNotification + { + public AssignedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) + { + + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/AssignedUserGroupPermissionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/AssignedUserGroupPermissionsNotification.cs new file mode 100644 index 0000000000..e0838f8c33 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/AssignedUserGroupPermissionsNotification.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class AssignedUserGroupPermissionsNotification : EnumerableObjectNotification + { + public AssignedUserGroupPermissionsNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable EntityPermissions => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemDeletedNotification.cs new file mode 100644 index 0000000000..3d3b9588d0 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class DictionaryItemDeletedNotification : DeletedNotification + { + public DictionaryItemDeletedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemDeletingNotification.cs new file mode 100644 index 0000000000..ff85e00a81 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class DictionaryItemDeletingNotification : DeletingNotification + { + public DictionaryItemDeletingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) + { + } + + public DictionaryItemDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemSavedNotification.cs new file mode 100644 index 0000000000..3a64e35979 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class DictionaryItemSavedNotification : SavedNotification + { + public DictionaryItemSavedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) + { + } + + public DictionaryItemSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemSavingNotification.cs new file mode 100644 index 0000000000..5f3d94697e --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DictionaryItemSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class DictionaryItemSavingNotification : SavingNotification + { + public DictionaryItemSavingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) + { + } + + public DictionaryItemSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ExportedMemberNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ExportedMemberNotification.cs new file mode 100644 index 0000000000..449244242f --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ExportedMemberNotification.cs @@ -0,0 +1,19 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class ExportedMemberNotification : INotification + { + public ExportedMemberNotification(IMember member, MemberExportModel exported) + { + Member = member; + Exported = exported; + } + + public IMember Member { get; } + + public MemberExportModel Exported { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/LanguageDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/LanguageDeletedNotification.cs new file mode 100644 index 0000000000..cba1c59406 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/LanguageDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class LanguageDeletedNotification : DeletedNotification + { + public LanguageDeletedNotification(ILanguage target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/LanguageDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/LanguageDeletingNotification.cs new file mode 100644 index 0000000000..79215e25af --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/LanguageDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class LanguageDeletingNotification : DeletingNotification + { + public LanguageDeletingNotification(ILanguage target, EventMessages messages) : base(target, messages) + { + } + + public LanguageDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/LanguageSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/LanguageSavedNotification.cs new file mode 100644 index 0000000000..87c3644df9 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/LanguageSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class LanguageSavedNotification : SavedNotification + { + public LanguageSavedNotification(ILanguage target, EventMessages messages) : base(target, messages) + { + } + + public LanguageSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/LanguageSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/LanguageSavingNotification.cs new file mode 100644 index 0000000000..db416799d9 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/LanguageSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class LanguageSavingNotification : SavingNotification + { + public LanguageSavingNotification(ILanguage target, EventMessages messages) : base(target, messages) + { + } + + public LanguageSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberDeletedNotification.cs new file mode 100644 index 0000000000..41edd5371e --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MemberDeletedNotification : DeletedNotification + { + public MemberDeletedNotification(IMember target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberDeletingNotification.cs new file mode 100644 index 0000000000..4a4cd585c5 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MemberDeletingNotification : DeletingNotification + { + public MemberDeletingNotification(IMember target, EventMessages messages) : base(target, messages) + { + } + + public MemberDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupDeletedNotification.cs new file mode 100644 index 0000000000..1441344ec5 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupDeletedNotification.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class MemberGroupDeletedNotification : DeletedNotification + { + public MemberGroupDeletedNotification(IMemberGroup target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupDeletingNotification.cs new file mode 100644 index 0000000000..34e4140752 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupDeletingNotification.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class MemberGroupDeletingNotification : DeletingNotification + { + public MemberGroupDeletingNotification(IMemberGroup target, EventMessages messages) : base(target, messages) + { + } + + public MemberGroupDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupSavedNotification.cs new file mode 100644 index 0000000000..f9af9806ef --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupSavedNotification.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class MemberGroupSavedNotification : SavedNotification + { + public MemberGroupSavedNotification(IMemberGroup target, EventMessages messages) : base(target, messages) + { + } + + public MemberGroupSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupSavingNotification.cs new file mode 100644 index 0000000000..67e03c2268 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberGroupSavingNotification.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class MemberGroupSavingNotification : SavingNotification + { + public MemberGroupSavingNotification(IMemberGroup target, EventMessages messages) : base(target, messages) + { + } + + public MemberGroupSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberRolesNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberRolesNotification.cs new file mode 100644 index 0000000000..2b7c9bd828 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberRolesNotification.cs @@ -0,0 +1,17 @@ +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MemberRolesNotification : INotification + { + protected MemberRolesNotification(int[] memberIds, string[] roles) + { + MemberIds = memberIds; + Roles = roles; + } + + public int[] MemberIds { get; } + + public string[] Roles { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberSavedNotification.cs new file mode 100644 index 0000000000..6ea4e38870 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MemberSavedNotification : SavedNotification + { + public MemberSavedNotification(IMember target, EventMessages messages) : base(target, messages) + { + } + + public MemberSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberSavingNotification.cs new file mode 100644 index 0000000000..8496731304 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MemberSavingNotification : SavingNotification + { + public MemberSavingNotification(IMember target, EventMessages messages) : base(target, messages) + { + } + + public MemberSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntryDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntryDeletedNotification.cs new file mode 100644 index 0000000000..03f48dff5c --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntryDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class PublicAccessEntryDeletedNotification : DeletedNotification + { + public PublicAccessEntryDeletedNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntryDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntryDeletingNotification.cs new file mode 100644 index 0000000000..521a86caff --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntryDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class PublicAccessEntryDeletingNotification : DeletingNotification + { + public PublicAccessEntryDeletingNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) + { + } + + public PublicAccessEntryDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntrySavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntrySavedNotification.cs new file mode 100644 index 0000000000..ec1781a3d4 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntrySavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class PublicAccessEntrySavedNotification : SavedNotification + { + public PublicAccessEntrySavedNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) + { + } + + public PublicAccessEntrySavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntrySavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntrySavingNotification.cs new file mode 100644 index 0000000000..63f4a490a3 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/PublicAccessEntrySavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class PublicAccessEntrySavingNotification : SavingNotification + { + public PublicAccessEntrySavingNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) + { + } + + public PublicAccessEntrySavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/RemovedMemberRolesNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/RemovedMemberRolesNotification.cs new file mode 100644 index 0000000000..bd902cb078 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/RemovedMemberRolesNotification.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class RemovedMemberRolesNotification : MemberRolesNotification + { + public RemovedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) + { + + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserDeletedNotification.cs new file mode 100644 index 0000000000..3173647652 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserDeletedNotification : DeletedNotification + { + public UserDeletedNotification(IUser target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserDeletingNotification.cs new file mode 100644 index 0000000000..1f57755a5a --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserDeletingNotification : DeletingNotification + { + public UserDeletingNotification(IUser target, EventMessages messages) : base(target, messages) + { + } + + public UserDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserGroupDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupDeletedNotification.cs new file mode 100644 index 0000000000..a9c51dc72d --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserGroupDeletedNotification : DeletedNotification + { + public UserGroupDeletedNotification(IUserGroup target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserGroupDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupDeletingNotification.cs new file mode 100644 index 0000000000..c176e55456 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserGroupDeletingNotification : DeletingNotification + { + public UserGroupDeletingNotification(IUserGroup target, EventMessages messages) : base(target, messages) + { + } + + public UserGroupDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserGroupSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupSavedNotification.cs new file mode 100644 index 0000000000..b542b35b26 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserGroupSavedNotification : SavedNotification + { + public UserGroupSavedNotification(IUserGroup target, EventMessages messages) : base(target, messages) + { + } + + public UserGroupSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserGroupSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupSavingNotification.cs new file mode 100644 index 0000000000..136b2da6a5 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserGroupSavingNotification : SavingNotification + { + public UserGroupSavingNotification(IUserGroup target, EventMessages messages) : base(target, messages) + { + } + + public UserGroupSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserGroupWithUsersSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupWithUsersSavedNotification.cs new file mode 100644 index 0000000000..22e0d67d06 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupWithUsersSavedNotification.cs @@ -0,0 +1,19 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserGroupWithUsersSavedNotification : SavedNotification + { + public UserGroupWithUsersSavedNotification(UserGroupWithUsers target, EventMessages messages) : base(target, messages) + { + } + + public UserGroupWithUsersSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserGroupWithUsersSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupWithUsersSavingNotification.cs new file mode 100644 index 0000000000..c61087038a --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserGroupWithUsersSavingNotification.cs @@ -0,0 +1,19 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserGroupWithUsersSavingNotification : SavingNotification + { + public UserGroupWithUsersSavingNotification(UserGroupWithUsers target, EventMessages messages) : base(target, messages) + { + } + + public UserGroupWithUsersSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserSavedNotification.cs new file mode 100644 index 0000000000..850085ae90 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserSavedNotification : SavedNotification + { + public UserSavedNotification(IUser target, EventMessages messages) : base(target, messages) + { + } + + public UserSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/UserSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/UserSavingNotification.cs new file mode 100644 index 0000000000..0b062c33f1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/UserSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class UserSavingNotification : SavingNotification + { + public UserSavingNotification(IUser target, EventMessages messages) : base(target, messages) + { + } + + public UserSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index e5ee719a05..baa69dddb9 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -101,6 +101,17 @@ <_Parameter1>DynamicProxyGenAssembly2 + + + + <_Parameter1>Umbraco.Forms.Core + + + <_Parameter1>Umbraco.Forms.Core.Providers + + + <_Parameter1>Umbraco.Forms.Web + diff --git a/src/Umbraco.PublishedCache.NuCache/Compose/NotificationsComposer.cs b/src/Umbraco.PublishedCache.NuCache/Compose/NotificationsComposer.cs new file mode 100644 index 0000000000..5ea0cc0678 --- /dev/null +++ b/src/Umbraco.PublishedCache.NuCache/Compose/NotificationsComposer.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Infrastructure.Services.Notifications; + +namespace Umbraco.Cms.Infrastructure.PublishedCache.Compose +{ + public sealed class NotificationsComposer : ICoreComposer + { + public void Compose(IUmbracoBuilder builder) => + builder.AddNotificationHandler(); + } +} diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs index 25ceb9fb6a..df02320d87 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs @@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.PublishedCache @@ -16,7 +17,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache /// /// Subscribes to Umbraco events to ensure nucache remains consistent with the source data /// - public class PublishedSnapshotServiceEventHandler : IDisposable + public class PublishedSnapshotServiceEventHandler : IDisposable, INotificationHandler { private readonly IRuntimeState _runtime; private bool _disposedValue; @@ -79,9 +80,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity; MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity; - - // TODO: This should be a cache refresher call! - LocalizationService.SavedLanguage += OnLanguageSaved; } private void TearDownRepositoryEvents() @@ -95,7 +93,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity; MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity; MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity; - LocalizationService.SavedLanguage -= OnLanguageSaved; // TODO: Shouldn't this be a cache refresher event? } // note: if the service is not ready, ie _isReady is false, then we still handle repository events, @@ -156,13 +153,14 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache } } + // TODO: This should be a cache refresher call! /// /// If a is ever saved with a different culture, we need to rebuild all of the content nucache database table /// - private void OnLanguageSaved(ILocalizationService sender, SaveEventArgs e) + public void Handle(LanguageSavedNotification notification) { // culture changed on an existing language - var cultureChanged = e.SavedEntities.Any(x => !x.WasPropertyDirty(nameof(ILanguage.Id)) && x.WasPropertyDirty(nameof(ILanguage.IsoCode))); + var cultureChanged = notification.SavedEntities.Any(x => !x.WasPropertyDirty(nameof(ILanguage.Id)) && x.WasPropertyDirty(nameof(ILanguage.IsoCode))); if (cultureChanged) { // Rebuild all content for all content types 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 { public UserBuilder() diff --git a/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs index 9b8a1e9c98..80488e91b1 100644 --- a/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs @@ -42,26 +42,12 @@ namespace Umbraco.Cms.Tests.Integration.Cache var definitions = new IEventDefinition[] { - new EventDefinition>(null, UserService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, UserService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, UserService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, UserService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, LocalizationService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, LocalizationService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, DataTypeService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, DataTypeService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, FileService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, FileService, new DeleteEventArgs(Enumerable.Empty())), new EventDefinition>(null, DomainService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, DomainService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, LocalizationService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, LocalizationService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ContentTypeService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, ContentTypeService, new DeleteEventArgs(Enumerable.Empty())), new EventDefinition>(null, MediaTypeService, new SaveEventArgs(Enumerable.Empty())), @@ -76,19 +62,10 @@ namespace Umbraco.Cms.Tests.Integration.Cache new EventDefinition>(null, MacroService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, MacroService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MemberService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MemberService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, MemberGroupService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MemberGroupService, new DeleteEventArgs(Enumerable.Empty())), - // not managed //new EventDefinition>(null, ContentService, new SaveEventArgs(Enumerable.Empty()), "SavedBlueprint"), //new EventDefinition>(null, ContentService, new DeleteEventArgs(Enumerable.Empty()), "DeletedBlueprint"), - new EventDefinition>(null, PublicAccessService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, PublicAccessService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, RelationService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, RelationService, new DeleteEventArgs(Enumerable.Empty())), diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index 96ba30905b..3f7fc54493 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -1,11 +1,11 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; @@ -13,10 +13,12 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Core.Sync; -using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Infrastructure.Sync; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping { @@ -44,6 +46,21 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping return result; } + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + builder.AddNotificationHandler(); + } + [TearDown] public void Teardown() { @@ -61,18 +78,18 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping var user = (IUser)new User(GlobalSettings, "name", "email", "username", "rawPassword"); service.Save(user); - // global cache contains the entity + // User has been saved so the cache has been cleared of it var globalCached = (IUser)globalCache.Get(GetCacheIdKey(user.Id), () => null); + Assert.IsNull(globalCached); + // Get user again to load it into the cache again, this also ensure we don't modify the one that's in the cache. + user = service.GetUserById(user.Id); + + // global cache contains the entity + globalCached = (IUser)globalCache.Get(GetCacheIdKey(user.Id), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(user.Id, globalCached.Id); Assert.AreEqual("name", globalCached.Name); - // get user again - else we'd modify the one that's in the cache - user = service.GetUserById(user.Id); - - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(ServerMessenger, CacheRefresherCollection), GetRequiredService(), GetRequiredService>()); - _distributedCacheBinder.BindEvents(true); - Assert.IsNull(scopeProvider.AmbientScope); using (IScope scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) { @@ -154,9 +171,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping Assert.AreEqual(lang.Id, globalCached.Id); Assert.AreEqual("fr-FR", globalCached.IsoCode); - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(ServerMessenger, CacheRefresherCollection), GetRequiredService(), GetRequiredService>()); - _distributedCacheBinder.BindEvents(true); - Assert.IsNull(scopeProvider.AmbientScope); using (IScope scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) { @@ -244,15 +258,16 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping }; service.Save(item); + // Refresh the cache manually because we can't unbind + service.GetDictionaryItemById(item.Id); + service.GetLanguageById(lang.Id); + // global cache contains the entity var globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Id), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual("item-key", globalCached.ItemKey); - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(ServerMessenger, CacheRefresherCollection), GetRequiredService(), GetRequiredService>()); - _distributedCacheBinder.BindEvents(true); - Assert.IsNull(scopeProvider.AmbientScope); using (IScope scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) { diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.Common/MembersServiceCollectionExtensionsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.Common/MembersServiceCollectionExtensionsTests.cs index c426e26750..e76716c152 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.Common/MembersServiceCollectionExtensionsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.Common/MembersServiceCollectionExtensionsTests.cs @@ -16,10 +16,10 @@ namespace Umbraco.Tests.Integration.Umbraco.Web.Common [Test] public void AddMembersIdentity_ExpectMembersUserStoreResolvable() { - IUserStore userStore = Services.GetService>(); + IUserStore userStore = Services.GetService>(); Assert.IsNotNull(userStore); - Assert.AreEqual(typeof(MembersUserStore), userStore.GetType()); + Assert.AreEqual(typeof(MemberUserStore), userStore.GetType()); } [Test] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserManagerTests.cs index cedb8ba684..ffa778765c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserManagerTests.cs @@ -19,36 +19,36 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security [TestFixture] public class MemberIdentityUserManagerTests { - private Mock> _mockMemberStore; - private Mock> _mockIdentityOptions; - private Mock> _mockPasswordHasher; - private Mock> _mockUserValidators; - private Mock>> _mockPasswordValidators; + private Mock> _mockMemberStore; + private Mock> _mockIdentityOptions; + private Mock> _mockPasswordHasher; + private Mock> _mockUserValidators; + private Mock>> _mockPasswordValidators; private Mock _mockNormalizer; private IdentityErrorDescriber _mockErrorDescriber; private Mock _mockServiceProviders; - private Mock>> _mockLogger; + private Mock>> _mockLogger; private Mock> _mockPasswordConfiguration; public MemberManager CreateSut() { - _mockMemberStore = new Mock>(); - _mockIdentityOptions = new Mock>(); + _mockMemberStore = new Mock>(); + _mockIdentityOptions = new Mock>(); - var idOptions = new MembersIdentityOptions { Lockout = { AllowedForNewUsers = false } }; + var idOptions = new MemberIdentityOptions { Lockout = { AllowedForNewUsers = false } }; _mockIdentityOptions.Setup(o => o.Value).Returns(idOptions); - _mockPasswordHasher = new Mock>(); + _mockPasswordHasher = new Mock>(); - var userValidators = new List>(); - _mockUserValidators = new Mock>(); - var validator = new Mock>(); + var userValidators = new List>(); + _mockUserValidators = new Mock>(); + var validator = new Mock>(); userValidators.Add(validator.Object); - _mockPasswordValidators = new Mock>>(); + _mockPasswordValidators = new Mock>>(); _mockNormalizer = new Mock(); _mockErrorDescriber = new IdentityErrorDescriber(); _mockServiceProviders = new Mock(); - _mockLogger = new Mock>>(); + _mockLogger = new Mock>>(); _mockPasswordConfiguration = new Mock>(); _mockPasswordConfiguration.Setup(x => x.Value).Returns(() => new MemberPasswordConfigurationSettings() @@ -56,9 +56,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security }); - var pwdValidators = new List> + var pwdValidators = new List> { - new PasswordValidator() + new PasswordValidator() }; var userManager = new MemberManager( @@ -70,12 +70,12 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security pwdValidators, new BackOfficeIdentityErrorDescriber(), _mockServiceProviders.Object, - new Mock>>().Object, + new Mock>>().Object, _mockPasswordConfiguration.Object); validator.Setup(v => v.ValidateAsync( userManager, - It.IsAny())) + It.IsAny())) .Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); return userManager; @@ -86,7 +86,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security { //arrange MemberManager sut = CreateSut(); - MembersIdentityUser fakeUser = new MembersIdentityUser() + MemberIdentityUser fakeUser = new MemberIdentityUser() { PasswordConfig = "testConfig" }; @@ -147,7 +147,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security { //arrange MemberManager sut = CreateSut(); - MembersIdentityUser fakeUser = new MembersIdentityUser() + MemberIdentityUser fakeUser = new MemberIdentityUser() { PasswordConfig = "testConfig" }; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserStoreTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserStoreTests.cs deleted file mode 100644 index 1a4f05d984..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserStoreTests.cs +++ /dev/null @@ -1,81 +0,0 @@ -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.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper; - -namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security -{ - [TestFixture] - public class MemberIdentityUserStoreTests - { - private Mock _mockMemberService; - - public MembersUserStore CreateSut() - { - _mockMemberService = new Mock(); - return new MembersUserStore( - _mockMemberService.Object, - new UmbracoMapper(new MapDefinitionCollection(new List())), - new Mock().Object, - new IdentityErrorDescriber()); - } - - [Test] - public void GivenICreateUser_AndTheUserIsNull_ThenIShouldGetAFailedResultAsync() - { - // arrange - MembersUserStore sut = CreateSut(); - CancellationToken fakeCancellationToken = new CancellationToken(){}; - - // act - Action actual = () => sut.CreateAsync(null, fakeCancellationToken); - - // assert - Assert.That(actual, Throws.ArgumentNullException); - } - - - [Test] - public async Task GivenICreateANewUser_AndTheUserIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync() - { - // arrange - MembersUserStore sut = CreateSut(); - var fakeUser = new MembersIdentityUser() { }; - var fakeCancellationToken = new CancellationToken() { }; - - IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); - IMember mockMember = Mock.Of(m => - m.Name == "fakeName" && - m.Email == "fakeemail@umbraco.com" && - m.Username == "fakeUsername" && - m.RawPasswordValue == "fakePassword" && - m.ContentTypeAlias == fakeMemberType.Alias && - m.HasIdentity == true); - - bool raiseEvents = false; - - _mockMemberService.Setup(x => x.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockMember); - _mockMemberService.Setup(x => x.Save(mockMember, raiseEvents)); - - // act - IdentityResult identityResult = await sut.CreateAsync(fakeUser, fakeCancellationToken); - - // assert - 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..15f4b7f30d --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberRoleStoreTests.cs @@ -0,0 +1,400 @@ +using System; +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.Models.Identity; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security +{ + [TestFixture] + public class MemberRoleStoreTests + { + private Mock _mockMemberGroupService; + private IdentityErrorDescriber ErrorDescriber => new IdentityErrorDescriber(); + private UmbracoIdentityRoleBuilder _roleBuilder; + private MemberGroupBuilder _groupBuilder; + + public MemberRoleStore CreateSut() + { + _mockMemberGroupService = new Mock(); + return new MemberRoleStore( + _mockMemberGroupService.Object, + ErrorDescriber); + } + + [SetUp] + public void SetUp() + { + _roleBuilder = new UmbracoIdentityRoleBuilder(); + _groupBuilder = new MemberGroupBuilder(); + } + + [Test] + public void GivenICreateAMemberRole_AndTheGroupIsNull_ThenIShouldGetAFailedIdentityResult() + { + // arrange + MemberRoleStore sut = CreateSut(); + var fakeCancellationToken = new CancellationToken(); + + // act + Action actual = () => sut.CreateAsync(null, fakeCancellationToken); + + // assert + Assert.That(actual, Throws.ArgumentNullException); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenICreateAMemberRole_AndTheGroupIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithTestName("777").Build(); + 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_AndTheGroupExistsWithTheSameName_ThenIShouldGetASuccessResultAsyncButNoUpdatesMade() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithName("fakeGroupName").WithId("777").Build(); + var fakeCancellationToken = new CancellationToken() { }; + + IMemberGroup mockMemberGroup = Mock.Of(m => + m.Name == "fakeGroupName" && m.CreatorId == 777); + + 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.GetById(777)); + } + + [Test] + public async Task GivenIUpdateAMemberRole_AndTheGroupExistsWithADifferentSameName_ThenIShouldGetASuccessResultAsyncWithUpdatesMade() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithName("fakeGroup777").WithId("777").Build(); + var fakeCancellationToken = new CancellationToken() { }; + + IMemberGroup mockMemberGroup = Mock.Of(m => + m.Name == "fakeGroupName" && m.CreatorId == 777); + + 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(It.IsAny(), It.IsAny())); + _mockMemberGroupService.Verify(x => x.GetById(777)); + } + + [Test] + public async Task GivenIUpdateAMemberRole_AndTheGroupDoesntExist_ThenIShouldGetAFailureResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithTestName("777").Build(); + var fakeCancellationToken = new CancellationToken() { }; + + // act + IdentityResult identityResult = await sut.UpdateAsync(fakeRole, fakeCancellationToken); + + // assert + Assert.IsTrue(identityResult.Succeeded == false); + Assert.IsTrue(identityResult.Errors.Any(x => x.Code == "IdentityMemberGroupNotFound" && x.Description == "Member group not found")); + _mockMemberGroupService.Verify(x => x.GetById(777)); + } + + [Test] + public async Task GivenIUpdateAMemberRole_AndTheIdCannotBeParsedToAnInt_ThenIShouldGetAFailureResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithTestName("7a77").Build(); + var fakeCancellationToken = new CancellationToken() { }; + + // act + IdentityResult identityResult = await sut.UpdateAsync(fakeRole, fakeCancellationToken); + + // assert + Assert.IsTrue(identityResult.Succeeded == false); + Assert.IsTrue(identityResult.Errors.Any(x => x.Code == "IdentityIdParseError" && x.Description == "Cannot parse ID to int")); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + [Test] + public void GivenIUpdateAMemberRole_AndTheRoleIsNull_ThenAnExceptionShouldBeThrown() + { + // arrange + MemberRoleStore sut = CreateSut(); + var fakeCancellationToken = new CancellationToken() { }; + + // act + Action actual = () => sut.UpdateAsync(null, fakeCancellationToken); + + // assert + Assert.That(actual, Throws.ArgumentNullException); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenIDeleteAMemberRole_AndItExists_ThenTheMemberGroupShouldBeDeleted_AndIShouldGetASuccessResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithTestName("777").Build(); + 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)); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenIDeleteAMemberRole_AndTheIdCannotBeParsedToAnInt_ThenTheMemberGroupShouldNotBeDeleted_AndIShouldGetAnArgumentException() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithTestName("7a77").Build(); + var fakeCancellationToken = new CancellationToken() { }; + + IMemberGroup mockMemberGroup = Mock.Of(m => + m.Name == "fakeGroupName" && m.CreatorId == 77); + + + // act + Assert.ThrowsAsync(async () => await sut.DeleteAsync(fakeRole, fakeCancellationToken)); + } + + + [Test] + public async Task GivenIDeleteAMemberRole_AndItDoesntExist_ThenTheMemberGroupShouldNotBeDeleted_AndIShouldGetAFailResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithTestName("777").Build(); + 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(x => x.Code == "IdentityMemberGroupNotFound" && x.Description == "Member group not found")); + _mockMemberGroupService.Verify(x => x.GetById(777)); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenIFindAMemberRoleByRoleKey_AndRoleKeyExists_ThenIShouldGetASuccessResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithName("fakeGroupName").WithId("777").Build(); + int fakeRoleId = 777; + + IMemberGroup fakeMemberGroup = _groupBuilder.WithName("fakeGroupName").WithCreatorId(123).WithId(777).WithKey(Guid.NewGuid()).Build(); + + _mockMemberGroupService.Setup(x => x.GetById(fakeRoleId)).Returns(fakeMemberGroup); + + // act + IdentityRole actual = await sut.FindByIdAsync(fakeRole.Id); + + // assert + Assert.AreEqual(fakeRole.Name, actual.Name); + Assert.AreEqual(fakeRole.Id, actual.Id); + _mockMemberGroupService.Verify(x => x.GetById(fakeRoleId)); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenIFindAMemberRoleByRoleId_AndIdCannotBeParsedToAnIntOrGuid_ThenIShouldGetAFailureResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithTestName("7a77").Build(); + var fakeCancellationToken = new CancellationToken() { }; + + // act + Action actual = () => sut.FindByIdAsync(fakeRole.Id, fakeCancellationToken); + + // assert + Assert.That(actual, Throws.TypeOf()); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenIFindAMemberRoleByRoleId_AndIdCannotBeParsedToAnIntButCanBeToGuid_ThenIShouldGetASuccessResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithName("fakeGroupName").WithId("777").Build(); + + var fakeRoleGuid = Guid.NewGuid(); + + IMemberGroup fakeMemberGroup = _groupBuilder.WithName("fakeGroupName").WithCreatorId(123).WithId(777).WithKey(fakeRoleGuid).Build(); + + _mockMemberGroupService.Setup(x => x.GetById(fakeRoleGuid)).Returns(fakeMemberGroup); + + // act + IdentityRole actual = await sut.FindByIdAsync(fakeRoleGuid.ToString()); + + // assert + Assert.AreEqual(fakeRole.Name, actual.Name); + Assert.AreEqual(fakeRole.Id, actual.Id); + _mockMemberGroupService.Verify(x => x.GetById(fakeRoleGuid)); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + + [Test] + public async Task GivenIFindAMemberRoleByRoleId_AndIdCannotBeParsedToAGuidButCanBeToInt_ThenIShouldGetASuccessResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithName("fakeGroupName").WithId("777").Build(); + + var fakeRoleId = 777; + + IMemberGroup fakeMemberGroup = _groupBuilder.WithName("fakeGroupName").WithCreatorId(123).WithId(777).WithKey(Guid.NewGuid()).Build(); + + _mockMemberGroupService.Setup(x => x.GetById(fakeRoleId)).Returns(fakeMemberGroup); + + // act + IdentityRole actual = await sut.FindByIdAsync(fakeRoleId.ToString()); + + // assert + Assert.AreEqual(fakeRole.Name, actual.Name); + Assert.AreEqual(fakeRole.Id, actual.Id); + _mockMemberGroupService.Verify(x => x.GetById(fakeRoleId)); + _mockMemberGroupService.VerifyNoOtherCalls(); + } + + + [Test] + public async Task GivenIFindAMemberRoleByRoleName_AndRoleNameExists_ThenIShouldGetASuccessResultAsync() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithName("fakeGroupName").WithId("777").Build(); + + IMemberGroup mockMemberGroup = Mock.Of(m => + m.Name == "fakeGroupName" && + m.CreatorId == 123 && + m.Id == 777); + + + _mockMemberGroupService.Setup(x => x.GetByName(fakeRole.Name)).Returns(mockMemberGroup); + + // act + IdentityRole actual = await sut.FindByNameAsync(fakeRole.Name); + + // assert + Assert.AreEqual(fakeRole.Name, actual.Name); + Assert.AreEqual(fakeRole.Id, actual.Id); + _mockMemberGroupService.Verify(x => x.GetByName(fakeRole.Name)); + } + + [Test] + public void GivenIFindAMemberRoleByRoleName_AndTheNameIsNull_ThenIShouldGetAnArgumentException() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithId("777").Build(); + var fakeCancellationToken = new CancellationToken() { }; + + // act + Action actual = () => sut.FindByNameAsync(fakeRole.Name, fakeCancellationToken); + + // assert + Assert.That(actual, Throws.ArgumentNullException); + _mockMemberGroupService.VerifyNoOtherCalls(); + + } + + [Test] + public void GivenIGetAMemberRoleId_AndTheRoleIsNull_ThenIShouldGetAnArgumentException() + { + // arrange + MemberRoleStore sut = CreateSut(); + var fakeCancellationToken = new CancellationToken() { }; + + // act + Action actual = () => sut.GetRoleIdAsync(null, fakeCancellationToken); + + // assert + Assert.That(actual, Throws.ArgumentNullException); + } + + [Test] + public void GivenIGetAMemberRoleId_AndTheRoleIsNotNull_ThenIShouldGetTheMemberRole() + { + // arrange + MemberRoleStore sut = CreateSut(); + UmbracoIdentityRole fakeRole = _roleBuilder.WithName("fakeGroupName").WithId("777").Build(); + string fakeRoleId = fakeRole.Id; + + var fakeCancellationToken = new CancellationToken(); + + // act + Task actual = sut.GetRoleIdAsync(fakeRole, fakeCancellationToken); + + // assert + Assert.AreEqual(fakeRoleId, actual.Result); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs new file mode 100644 index 0000000000..7f80ff9382 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs @@ -0,0 +1,237 @@ +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.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security +{ + [TestFixture] + public class MemberUserStoreTests + { + private Mock _mockMemberService; + + public MemberUserStore CreateSut() + { + _mockMemberService = new Mock(); + return new MemberUserStore( + _mockMemberService.Object, + new UmbracoMapper(new MapDefinitionCollection(new List())), + new Mock().Object, + new IdentityErrorDescriber()); + } + + [Test] + public void GivenIGetNormalizedUserName_AndTheUserIsNull_ThenIShouldGetAnException() + { + // arrange + MemberUserStore sut = CreateSut(); + CancellationToken fakeCancellationToken = new CancellationToken() { }; + + // act + Action actual = () => sut.GetNormalizedUserNameAsync(null, fakeCancellationToken); + + // assert + Assert.That(actual, Throws.ArgumentNullException); + } + + [Test] + public async Task GivenIGetNormalizedUserName_AndTheEverythingIsPopulatedCorrectly_ThenIShouldGetACorrectUsername() + { + // arrange + MemberUserStore sut = CreateSut(); + var fakeUser = new MemberIdentityUser() + { + UserName = "fakeuser" + }; + + // act + string actual = await sut.GetNormalizedUserNameAsync(fakeUser); + + // assert + Assert.AreEqual(actual, fakeUser.UserName); + } + + [Test] + public void GivenISetNormalizedUserName_AndTheUserIsNull_ThenIShouldGetAnException() + { + // arrange + MemberUserStore sut = CreateSut(); + var fakeCancellationToken = new CancellationToken() { }; + + // act + Action actual = () => sut.SetNormalizedUserNameAsync(null, "username", fakeCancellationToken); + + // assert + Assert.That(actual, Throws.ArgumentNullException); + _mockMemberService.VerifyNoOtherCalls(); + } + + + [Test] + public void GivenISetNormalizedUserName_AndTheUserNameIsNull_ThenAnExceptionShouldBeThrown() + { + // arrange + MemberUserStore sut = CreateSut(); + CancellationToken fakeCancellationToken = new CancellationToken() { }; + var fakeUser = new MemberIdentityUser() { }; + + // act + Action actual = () => sut.SetNormalizedUserNameAsync(fakeUser, null, fakeCancellationToken); + + // assert + _mockMemberService.VerifyNoOtherCalls(); + } + + [Test] + public void GivenISetNormalizedUserName_AndEverythingIsPopulated_ThenIShouldGetASuccessResult() + { + // arrange + MemberUserStore sut = CreateSut(); + CancellationToken fakeCancellationToken = new CancellationToken() { }; + var fakeUser = new MemberIdentityUser() + { + UserName = "MyName" + }; + + // act + Task actual = sut.SetNormalizedUserNameAsync(fakeUser, "NewName", fakeCancellationToken); + + // assert + Assert.IsTrue(actual.IsCompletedSuccessfully); + } + + [Test] + public async Task GivenICreateUser_AndTheUserIsNull_ThenIShouldGetAFailedResultAsync() + { + // arrange + MemberUserStore sut = CreateSut(); + + // act + IdentityResult actual = await sut.CreateAsync(null); + + // assert + Assert.IsFalse(actual.Succeeded); + Assert.IsTrue(actual.Errors.Any(x => x.Code == "IdentityErrorUserStore" && x.Description == "Value cannot be null. (Parameter 'user')")); + _mockMemberService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenICreateUser_AndTheUserDoesNotHaveIdentity_ThenIShouldGetAFailedResultAsync() + { + // arrange + MemberUserStore sut = CreateSut(); + var fakeUser = new MemberIdentityUser() { }; + var fakeCancellationToken = new CancellationToken() { }; + + IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); + IMember mockMember = Mock.Of(m => + m.Name == "fakeName" && + m.Email == "fakeemail@umbraco.com" && + m.Username == "fakeUsername" && + m.RawPasswordValue == "fakePassword" && + m.ContentTypeAlias == fakeMemberType.Alias && + m.HasIdentity == false); + + _mockMemberService.Setup(x => x.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockMember); + _mockMemberService.Setup(x => x.Save(mockMember, It.IsAny())); + + // act + IdentityResult actual = await sut.CreateAsync(null); + + // assert + Assert.IsFalse(actual.Succeeded); + Assert.IsTrue(actual.Errors.Any(x => x.Code == "IdentityErrorUserStore" && x.Description == "Value cannot be null. (Parameter 'user')")); + _mockMemberService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenICreateANewUser_AndTheUserIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync() + { + // arrange + MemberUserStore sut = CreateSut(); + var fakeUser = new MemberIdentityUser() { }; + var fakeCancellationToken = new CancellationToken() { }; + + IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); + IMember mockMember = Mock.Of(m => + m.Name == "fakeName" && + m.Email == "fakeemail@umbraco.com" && + m.Username == "fakeUsername" && + m.RawPasswordValue == "fakePassword" && + m.ContentTypeAlias == fakeMemberType.Alias && + m.HasIdentity == true); + + bool raiseEvents = false; + + _mockMemberService.Setup(x => x.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockMember); + _mockMemberService.Setup(x => x.Save(mockMember, raiseEvents)); + + // act + IdentityResult identityResult = await sut.CreateAsync(fakeUser, fakeCancellationToken); + + // assert + Assert.IsTrue(identityResult.Succeeded); + Assert.IsTrue(!identityResult.Errors.Any()); + _mockMemberService.Verify(x => x.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + _mockMemberService.Verify(x => x.Save(mockMember, It.IsAny())); + } + + + [Test] + public async Task GivenIDeleteUser_AndTheUserIsNotPresent_ThenIShouldGetAFailedResultAsync() + { + // arrange + MemberUserStore sut = CreateSut(); + + // act + IdentityResult actual = await sut.DeleteAsync(null); + + // assert + Assert.IsTrue(actual.Succeeded == false); + Assert.IsTrue(actual.Errors.Any(x => x.Code == "IdentityErrorUserStore" && x.Description == "Value cannot be null. (Parameter 'user')")); + _mockMemberService.VerifyNoOtherCalls(); + } + + [Test] + public async Task GivenIDeleteUser_AndTheUserIsDeletedCorrectly_ThenIShouldGetASuccessResultAsync() + { + // arrange + MemberUserStore sut = CreateSut(); + var fakeUser = new MemberIdentityUser(777); + var fakeCancellationToken = new CancellationToken() { }; + + IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); + IMember mockMember = new Member(fakeMemberType) + { + Id = 777, + Name = "fakeName", + Email = "fakeemail@umbraco.com", + Username = "fakeUsername", + RawPasswordValue = "fakePassword" + }; + + _mockMemberService.Setup(x => x.GetById(mockMember.Id)).Returns(mockMember); + _mockMemberService.Setup(x => x.Delete(mockMember)); + + // act + IdentityResult identityResult = await sut.DeleteAsync(fakeUser, fakeCancellationToken); + + // assert + Assert.IsTrue(identityResult.Succeeded); + Assert.IsTrue(!identityResult.Errors.Any()); + _mockMemberService.Verify(x => x.GetById(mockMember.Id)); + _mockMemberService.Verify(x => x.Delete(mockMember)); + _mockMemberService.VerifyNoOtherCalls(); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs index 63d587bb94..321f34f517 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs @@ -19,6 +19,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.ContentApps; using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; @@ -38,7 +39,6 @@ using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.BackOffice.Mapping; using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Security; -using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; using MemberMapDefinition = Umbraco.Cms.Web.BackOffice.Mapping.MemberMapDefinition; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers @@ -70,7 +70,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers IMemberGroupService memberGroupService, IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IOptions globalSettings, IUser user) { @@ -80,14 +80,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers sut.ModelState.AddModelError("key", "Invalid model state"); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) + .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) .Setup(x => x.ValidatePasswordAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); var value = new MemberDisplay(); - + // act ActionResult result = sut.PostSave(fakeMemberData).Result; var validation = result.Result as ValidationErrorResult; @@ -108,14 +108,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IOptions globalSettings, IUser user) { // arrange Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) + .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) .Setup(x => x.ValidatePasswordAsync(It.IsAny())) @@ -149,14 +149,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IOptions globalSettings, IUser user) { // arrange Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) + .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) .Setup(x => x.ValidatePasswordAsync(It.IsAny())) @@ -191,13 +191,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IOptions globalSettings, IUser user) { // arrange Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.Save); - var membersIdentityUser = new MembersIdentityUser(123); + var membersIdentityUser = new MemberIdentityUser(123); Mock.Get(umbracoMembersUserManager) .Setup(x => x.FindByIdAsync(It.IsAny())) .ReturnsAsync(() => membersIdentityUser); @@ -206,7 +206,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.UpdateAsync(It.IsAny())) + .Setup(x => x.UpdateAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(globalSettings); @@ -243,13 +243,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IOptions globalSettings, IUser user) { // arrange Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.Save); - var membersIdentityUser = new MembersIdentityUser(123); + var membersIdentityUser = new MemberIdentityUser(123); Mock.Get(umbracoMembersUserManager) .Setup(x => x.FindByIdAsync(It.IsAny())) .ReturnsAsync(() => membersIdentityUser); @@ -258,7 +258,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.UpdateAsync(It.IsAny())) + .Setup(x => x.UpdateAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(globalSettings); @@ -290,7 +290,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Mock.Get(backOfficeSecurity).Setup(x => x.CurrentUser).Returns(user); } - private static void SetupPasswordSuccess(IMemberManager umbracoMembersUserManager, IPasswordChanger passwordChanger, bool successful = true) + private static void SetupPasswordSuccess(IMemberManager umbracoMembersUserManager, IPasswordChanger passwordChanger, bool successful = true) { var passwordChanged = new PasswordChangedModel() { @@ -323,14 +323,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IOptions globalSettings, IUser user) { // arrange Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.CreateAsync(It.IsAny())) + .Setup(x => x.CreateAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); @@ -338,7 +338,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers .Setup(x => x.ValidatePasswordAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.AddToRolesAsync(It.IsAny(), It.IsAny>())) + .Setup(x => x.AddToRolesAsync(It.IsAny(), It.IsAny>())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(memberService).SetupSequence( @@ -346,7 +346,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers .Returns(() => member); MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger, globalSettings, user); - + // act ActionResult result = sut.PostSave(fakeMemberData).Result; var validation = result.Result as ValidationErrorResult; @@ -367,7 +367,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IOptions globalSettings, IUser user) { @@ -378,7 +378,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers { roleName }; - var membersIdentityUser = new MembersIdentityUser(123); + var membersIdentityUser = new MemberIdentityUser(123); Mock.Get(umbracoMembersUserManager) .Setup(x => x.FindByIdAsync(It.IsAny())) .ReturnsAsync(() => membersIdentityUser); @@ -386,10 +386,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers .Setup(x => x.ValidatePasswordAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.UpdateAsync(It.IsAny())) + .Setup(x => x.UpdateAsync(It.IsAny())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) - .Setup(x => x.AddToRolesAsync(It.IsAny(), It.IsAny>())) + .Setup(x => x.AddToRolesAsync(It.IsAny(), It.IsAny>())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); @@ -435,10 +435,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers IMemberService memberService, IMemberTypeService memberTypeService, IMemberGroupService memberGroupService, - IUmbracoUserManager membersUserManager, + IUmbracoUserManager membersUserManager, IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IOptions globalSettings, IUser user) { @@ -484,8 +484,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers memberGroupService, mockPasswordConfig.Object, contentTypeBaseServiceProvider.Object, - propertyEditorCollection), - httpContextAccessor); + propertyEditorCollection)); var map = new MapDefinitionCollection(new List() { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index c3690b4863..9518a9b84d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -2434,6 +2434,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers .Select(_umbracoMapper.Map) .ToArray(); + //TODO: change to role manager var allGroups = _memberGroupService.GetAll().ToArray(); var groups = entry.Rules .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index be652860b5..567303e150 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -58,7 +58,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IJsonSerializer _jsonSerializer; private readonly IShortStringHelper _shortStringHelper; - private readonly IPasswordChanger _passwordChanger; + private readonly IPasswordChanger _passwordChanger; private readonly IScopeProvider _scopeProvider; /// @@ -92,7 +92,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IJsonSerializer jsonSerializer, - IPasswordChanger passwordChanger, + IPasswordChanger passwordChanger, IScopeProvider scopeProvider) : base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, jsonSerializer) { @@ -362,7 +362,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers throw new InvalidOperationException($"No member type found with alias {contentItem.ContentTypeAlias}"); } - var identityMember = MembersIdentityUser.CreateNew( + var identityMember = MemberIdentityUser.CreateNew( contentItem.Username, contentItem.Email, memberType.Alias, @@ -394,9 +394,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - //TODO: do we need to resave the key? - // contentItem.PersistedContent.Key = contentItem.Key; - // now the member has been saved via identity, resave the member with mapped content properties _memberService.Save(member); contentItem.PersistedContent = member; @@ -459,7 +456,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers bool needsResync = false; - MembersIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id.ToString()); + MemberIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id.ToString()); if (identityMember == null) { return new ValidationErrorResult("Member was not found"); @@ -603,7 +600,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// The groups to updates /// The member as an identity user - private async Task> AddOrUpdateRoles(IEnumerable groups, MembersIdentityUser identityMember) + private async Task> AddOrUpdateRoles(IEnumerable groups, MemberIdentityUser identityMember) { var hasChanges = false; @@ -654,7 +651,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [HttpPost] public IActionResult DeleteByKey(Guid key) { - //TODO: move to MembersUserStore IMember foundMember = _memberService.GetByKey(key); if (foundMember == null) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs index 7146cd5820..88825c0d9a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs @@ -30,13 +30,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public MemberGroupController( IMemberGroupService memberGroupService, UmbracoMapper umbracoMapper, - ILocalizedTextService localizedTextService - ) + ILocalizedTextService localizedTextService) { _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService)); _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); - _localizedTextService = - localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); } /// @@ -46,17 +44,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// public ActionResult GetById(int id) { - var memberGroup = _memberGroupService.GetById(id); + IMemberGroup memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { return NotFound(); } - var dto = _umbracoMapper.Map(memberGroup); + MemberGroupDisplay dto = _umbracoMapper.Map(memberGroup); return dto; } - /// /// Gets the member group json for the member group guid /// @@ -64,7 +61,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// public ActionResult GetById(Guid id) { - var memberGroup = _memberGroupService.GetById(id); + IMemberGroup memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { return NotFound(); @@ -82,9 +79,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { var guidUdi = id as GuidUdi; if (guidUdi == null) + { return NotFound(); + } - var memberGroup = _memberGroupService.GetById(guidUdi.Guid); + IMemberGroup memberGroup = _memberGroupService.GetById(guidUdi.Guid); if (memberGroup == null) { return NotFound(); @@ -93,11 +92,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return _umbracoMapper.Map(memberGroup); } - public IEnumerable GetByIds([FromQuery]int[] ids) - { - return _memberGroupService.GetByIds(ids) - .Select(_umbracoMapper.Map); - } + public IEnumerable GetByIds([FromQuery] int[] ids) + => _memberGroupService.GetByIds(ids).Select(_umbracoMapper.Map); [HttpDelete] [HttpPost] @@ -114,10 +110,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } public IEnumerable GetAllGroups() - { - return _memberGroupService.GetAll() + => _memberGroupService.GetAll() .Select(_umbracoMapper.Map); - } public MemberGroupDisplay GetEmpty() { @@ -129,7 +123,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { var id = int.Parse(saveModel.Id.ToString()); - var memberGroup = id > 0 ? _memberGroupService.GetById(id) : new MemberGroup(); + IMemberGroup memberGroup = id > 0 ? _memberGroupService.GetById(id) : new MemberGroup(); if (memberGroup == null) { return NotFound(); @@ -138,7 +132,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers memberGroup.Name = saveModel.Name; _memberGroupService.Save(memberGroup); - var display = _umbracoMapper.Map(memberGroup); + MemberGroupDisplay display = _umbracoMapper.Map(memberGroup); display.AddSuccessNotification( _localizedTextService.Localize("speechBubbles/memberGroupSavedHeader"), diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index cc5f3664a9..21098ffabf 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -90,7 +90,7 @@ namespace Umbraco.Extensions builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique, PasswordChanger>(); - builder.Services.AddUnique, PasswordChanger>(); + builder.Services.AddUnique, PasswordChanger>(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); diff --git a/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs index e88503794e..b5592b08ff 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs @@ -1,4 +1,6 @@ +using System; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -17,15 +19,12 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping private readonly CommonMapper _commonMapper; private readonly CommonTreeNodeMapper _commonTreeNodeMapper; private readonly MemberTabsAndPropertiesMapper _tabsAndPropertiesMapper; - private readonly IHttpContextAccessor _httpContextAccessor; - public MemberMapDefinition(CommonMapper commonMapper, CommonTreeNodeMapper commonTreeNodeMapper, MemberTabsAndPropertiesMapper tabsAndPropertiesMapper, IHttpContextAccessor httpContextAccessor) + public MemberMapDefinition(CommonMapper commonMapper, CommonTreeNodeMapper commonTreeNodeMapper, MemberTabsAndPropertiesMapper tabsAndPropertiesMapper) { _commonMapper = commonMapper; _commonTreeNodeMapper = commonTreeNodeMapper; - _tabsAndPropertiesMapper = tabsAndPropertiesMapper; - _httpContextAccessor = httpContextAccessor; } public void DefineMaps(UmbracoMapper mapper) diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs index 7445f35f29..0559a17a53 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs @@ -28,25 +28,21 @@ namespace Umbraco.Cms.Web.BackOffice.Trees IMemberGroupService memberGroupService, IEventAggregator eventAggregator) : base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator) - { - _memberGroupService = memberGroupService; - } + => _memberGroupService = memberGroupService; protected override IEnumerable GetTreeNodesFromService(string id, FormCollection queryStrings) - { - return _memberGroupService.GetAll() + => _memberGroupService.GetAll() .OrderBy(x => x.Name) .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, Constants.Icons.MemberGroup, false)); - } protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var rootResult = base.CreateRootNode(queryStrings); + ActionResult rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) { return rootResult; } - var root = rootResult.Value; + TreeNode root = rootResult.Value; //check if there are any groups root.HasChildren = _memberGroupService.GetAll().Any(); diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index d49eb6e4f5..967e2043f4 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -1,4 +1,4 @@ - + net5.0 @@ -31,6 +31,11 @@ <_Parameter1>Umbraco.Tests.Integration + + + + <_Parameter1>Umbraco.Forms.Web + diff --git a/src/Umbraco.Web.Common/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/ServiceCollectionExtensions.cs index 8b81fc673c..5182db4e20 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/ServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Web.Caching; using SixLabors.ImageSharp.Web.Commands; @@ -65,17 +66,19 @@ namespace Umbraco.Extensions public static void AddMembersIdentity(this IServiceCollection services) => services.BuildMembersIdentity() .AddDefaultTokenProviders() - .AddUserStore() - .AddMembersManager(); + .AddMemberManager() + .AddUserStore() + .AddRoleStore() + .AddRoleValidator>() + .AddRoleManager>(); - - private static MembersIdentityBuilder BuildMembersIdentity(this IServiceCollection services) + private static MemberIdentityBuilder BuildMembersIdentity(this IServiceCollection services) { // Services used by Umbraco members identity - services.TryAddScoped, UserValidator>(); - services.TryAddScoped, PasswordValidator>(); - services.TryAddScoped, PasswordHasher>(); - return new MembersIdentityBuilder(services); + services.TryAddScoped, UserValidator>(); + services.TryAddScoped, PasswordValidator>(); + services.TryAddScoped, PasswordHasher>(); + return new MemberIdentityBuilder(typeof(IdentityRole), services); } private static void RemoveIntParamenterIfValueGreatherThen(IDictionary commands, string parameter, int maxValue) diff --git a/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs index 4daf5457bf..6940221518 100644 --- a/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs @@ -10,13 +10,13 @@ namespace Umbraco.Extensions public static class IdentityBuilderExtensions { /// - /// Adds a for the . + /// Adds a for the . /// - /// The usermanager interface - /// The usermanager type + /// The member manager interface + /// The member manager type /// The current instance. - public static IdentityBuilder AddMembersManager(this IdentityBuilder identityBuilder) - where TUserManager : UserManager, TInterface + public static IdentityBuilder AddMemberManager(this IdentityBuilder identityBuilder) + where TUserManager : UserManager, TInterface { identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TUserManager)); return identityBuilder; diff --git a/src/Umbraco.Web.Common/Security/MemberManager.cs b/src/Umbraco.Web.Common/Security/MemberManager.cs index a6165bac79..887dfa5b92 100644 --- a/src/Umbraco.Web.Common/Security/MemberManager.cs +++ b/src/Umbraco.Web.Common/Security/MemberManager.cs @@ -10,19 +10,19 @@ using Umbraco.Cms.Core.Security; namespace Umbraco.Cms.Web.Common.Security { - public class MemberManager : UmbracoUserManager, IMemberManager + public class MemberManager : UmbracoUserManager, IMemberManager { public MemberManager( IIpResolver ipResolver, - IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, + IUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, BackOfficeIdentityErrorDescriber errors, IServiceProvider services, - ILogger> logger, + ILogger> logger, IOptions passwordConfiguration) : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, errors, services, logger, passwordConfiguration) { diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index bb2ecbc346..24f20f2f4a 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -40,6 +40,11 @@ <_Parameter1>Umbraco.Tests.UnitTests + + + + <_Parameter1>Umbraco.Forms.Web + diff --git a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj index a7c5e7a277..31a2ef25b2 100644 --- a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj +++ b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj @@ -1,4 +1,4 @@ - + net5.0 @@ -34,5 +34,10 @@ <_Parameter1>Umbraco.Tests.Integration + + + + <_Parameter1>Umbraco.Forms.Web + diff --git a/src/Umbraco.Web/Security/Providers/MembersRoleProvider.cs b/src/Umbraco.Web/Security/Providers/MembersRoleProvider.cs deleted file mode 100644 index f28aa75e48..0000000000 --- a/src/Umbraco.Web/Security/Providers/MembersRoleProvider.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Configuration.Provider; -using System.Linq; -using System.Web.Security; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Persistence.Querying; -using Umbraco.Cms.Core.Services; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Security.Providers -{ - //TODO: Delete: should not be used - [Obsolete("We are now using ASP.NET Core Identity instead of membership providers")] - public class MembersRoleProvider : RoleProvider - { - private readonly IMembershipRoleService _roleService; - private string _applicationName; - - public MembersRoleProvider(IMembershipRoleService roleService) - { - _roleService = roleService; - } - - public MembersRoleProvider() - : this(Current.Services.MemberService) - { - } - - public override bool IsUserInRole(string username, string roleName) - { - return GetRolesForUser(username).Any(x => x == roleName); - } - - public override string[] GetRolesForUser(string username) - { - return _roleService.GetAllRoles(username).ToArray(); - } - - public override void CreateRole(string roleName) - { - _roleService.AddRole(roleName); - } - - public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) - { - return _roleService.DeleteRole(roleName, throwOnPopulatedRole); - } - - /// - /// Returns true if the specified member role name exists - /// - /// Member role name - /// True if member role exists, otherwise false - public override bool RoleExists(string roleName) => _roleService.GetAllRoles().Any(x => x.Name == roleName); - - public override void AddUsersToRoles(string[] usernames, string[] roleNames) - { - _roleService.AssignRoles(usernames, roleNames); - } - - public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) - { - _roleService.DissociateRoles(usernames, roleNames); - } - - public override string[] GetUsersInRole(string roleName) - { - return _roleService.GetMembersInRole(roleName).Select(x => x.Username).ToArray(); - } - - /// - /// Gets all the member roles - /// - /// A list of member roles - public override string[] GetAllRoles() => _roleService.GetAllRoles().Select(x => x.Name).ToArray(); - - public override string[] FindUsersInRole(string roleName, string usernameToMatch) - { - return _roleService.FindMembersInRole(roleName, usernameToMatch, StringPropertyMatchType.Wildcard).Select(x => x.Username).ToArray(); - } - - /// - /// The name of the application using the custom role provider. - /// - /// - /// The name of the application using the custom membership provider. - public override string ApplicationName - { - get - { - return _applicationName; - } - - set - { - if (string.IsNullOrEmpty(value)) - throw new ProviderException("ApplicationName cannot be empty."); - - if (value.Length > 0x100) - throw new ProviderException("Provider application name too long."); - - _applicationName = value; - } - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b06e6446c2..ec1db29f99 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -178,7 +178,6 @@ -