Merge remote-tracking branch 'origin/netcore/dev' into netcore/task/member-macros-10577

This commit is contained in:
Shannon
2021-04-07 18:24:57 +10:00
134 changed files with 3632 additions and 2064 deletions

View File

@@ -16,16 +16,6 @@ namespace Umbraco.Cms.Core.Composing
{
private readonly Assembly _entryPointAssembly;
private readonly ILoggerFactory _loggerFactory;
private static readonly string[] UmbracoCoreAssemblyNames = new[]
{
"Umbraco.Core",
"Umbraco.Infrastructure",
"Umbraco.PublishedCache.NuCache",
"Umbraco.Examine.Lucene",
"Umbraco.Web.Common",
"Umbraco.Web.BackOffice",
"Umbraco.Web.Website",
};
public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly, ILoggerFactory loggerFactory)
{
@@ -43,7 +33,7 @@ namespace Umbraco.Cms.Core.Composing
{
get
{
var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, UmbracoCoreAssemblyNames, true, _loggerFactory);
var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, Constants.Composing.UmbracoCoreAssemblyNames, true, _loggerFactory);
return finder.Find();
}
}

View File

@@ -82,9 +82,8 @@ namespace Umbraco.Cms.Core.Composing
assemblyName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase)))
continue;
// don't include this item if it's Umbraco
// TODO: We should maybe pass an explicit list of these names in?
if (assemblyName.FullName.StartsWith("Umbraco.") || assemblyName.Name.EndsWith(".Views"))
// don't include this item if it's Umbraco Core
if (Constants.Composing.UmbracoCoreAssemblyNames.Any(x=>assemblyName.FullName.StartsWith(x) || assemblyName.Name.EndsWith(".Views")))
continue;
var assembly = Assembly.Load(assemblyName);

View File

@@ -9,6 +9,17 @@
/// Defines constants for composition.
/// </summary>
public static class Composing
{ }
{
public static readonly string[] UmbracoCoreAssemblyNames = new[]
{
"Umbraco.Core",
"Umbraco.Infrastructure",
"Umbraco.PublishedCache.NuCache",
"Umbraco.Examine.Lucene",
"Umbraco.Web.Common",
"Umbraco.Web.BackOffice",
"Umbraco.Web.Website",
};
}
}
}

View File

@@ -1,4 +1,5 @@
using Umbraco.Cms.Core.Composing;
using System;
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Core.Dashboards
{
@@ -7,21 +8,10 @@ namespace Umbraco.Cms.Core.Dashboards
{
public string Alias => "contentIntro";
public string[] Sections => new [] { "content" };
public string[] Sections => new[] { "content" };
public string View => "views/dashboard/default/startupdashboardintro.html";
public IAccessRule[] AccessRules
{
get
{
var rules = new IAccessRule[]
{
new AccessRule {Type = AccessRuleType.Deny, Value = Constants.Security.TranslatorGroupAlias},
new AccessRule {Type = AccessRuleType.Grant, Value = Constants.Security.AdminGroupAlias}
};
return rules;
}
}
public IAccessRule[] AccessRules { get; } = Array.Empty<IAccessRule>();
}
}

View File

@@ -30,7 +30,8 @@ namespace Umbraco.Cms.Core.DependencyInjection
builder.Services.AddSingleton<IValidateOptions<UnattendedSettings>, UnattendedSettingsValidator>();
// Register configuration sections.
builder.Services.Configure<ConnectionStrings>(builder.Config.GetSection(Constants.Configuration.ConfigModelsBuilder), o => o.BindNonPublicProperties = true);
builder.Services.Configure<ModelsBuilderSettings>(builder.Config.GetSection(Constants.Configuration.ConfigModelsBuilder), o => o.BindNonPublicProperties = true);
builder.Services.Configure<ConnectionStrings>(builder.Config.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true);
AddOptions<ActiveDirectorySettings>(builder, Constants.Configuration.ConfigActiveDirectory);

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class DataTypeDeletedNotification : DeletedNotification<IDataType>
{
public DataTypeDeletedNotification(IDataType target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class DataTypeDeletingNotification : DeletingNotification<IDataType>
{
public DataTypeDeletingNotification(IDataType target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class DataTypeMovedNotification : MovedNotification<IDataType>
{
public DataTypeMovedNotification(MoveEventInfo<IDataType> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class DataTypeMovingNotification : MovingNotification<IDataType>
{
public DataTypeMovingNotification(MoveEventInfo<IDataType> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class DataTypeSavedNotification : SavedNotification<IDataType>
{
public DataTypeSavedNotification(IDataType target, EventMessages messages) : base(target, messages)
{
}
public DataTypeSavedNotification(IEnumerable<IDataType> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class DataTypeSavingNotification : SavingNotification<IDataType>
{
public DataTypeSavingNotification(IDataType target, EventMessages messages) : base(target, messages)
{
}
public DataTypeSavingNotification(IEnumerable<IDataType> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<T> : CancelableEnumerableObjectNotification<T>
{

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class EntityContainerDeletedNotification : DeletedNotification<EntityContainer>
{
public EntityContainerDeletedNotification(EntityContainer target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class EntityContainerDeletingNotification : DeletingNotification<EntityContainer>
{
public EntityContainerDeletingNotification(EntityContainer target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class EntityContainerRenamedNotification : RenamedNotification<EntityContainer>
{
public EntityContainerRenamedNotification(EntityContainer target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class EntityContainerRenamingNotification : RenamingNotification<EntityContainer>
{
public EntityContainerRenamingNotification(EntityContainer target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class EntityContainerSavedNotification : SavedNotification<EntityContainer>
{
public EntityContainerSavedNotification(EntityContainer target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
public class EntityContainerSavingNotification : SavingNotification<EntityContainer>
{
public EntityContainerSavingNotification(EntityContainer target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<T> : EnumerableObjectNotification<T>
{
protected RenamedNotification(T target, EventMessages messages) : base(target, messages)
{
}
protected RenamedNotification(IEnumerable<T> target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<T> Entities => Target;
}
}

View File

@@ -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<T> : CancelableEnumerableObjectNotification<T>
{
protected RenamingNotification(T target, EventMessages messages) : base(target, messages)
{
}
protected RenamingNotification(IEnumerable<T> target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<T> Entities => Target;
}
}

View File

@@ -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
/// <summary>
/// Returns the login property display field
/// </summary>
/// <param name="memberTypeService"></param>
/// <param name="member"></param>
/// <param name="display"></param>
/// <param name="localizedText"></param>
@@ -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.
/// </remarks>
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<string, bool> GetMemberGroupValue(string username)
{
var userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username);
IEnumerable<string> 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;
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@@ -46,6 +46,17 @@
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>DynamicProxyGenAssembly2</_Parameter1>
</AssemblyAttribute>
<!-- Making internals visible to Umbraco Forms -->
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Forms.Core</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Forms.Core.Providers</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Forms.Web</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>

View File

@@ -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
/// <summary>
/// Default <see cref="IDistributedCacheBinder"/> implementation.
/// </summary>
public partial class DistributedCacheBinder
public partial class DistributedCacheBinder :
INotificationHandler<DictionaryItemDeletedNotification>,
INotificationHandler<DictionaryItemSavedNotification>,
INotificationHandler<LanguageSavedNotification>,
INotificationHandler<LanguageDeletedNotification>,
INotificationHandler<MemberSavedNotification>,
INotificationHandler<MemberDeletedNotification>,
INotificationHandler<PublicAccessEntrySavedNotification>,
INotificationHandler<PublicAccessEntryDeletedNotification>,
INotificationHandler<UserSavedNotification>,
INotificationHandler<UserDeletedNotification>,
INotificationHandler<UserGroupWithUsersSavedNotification>,
INotificationHandler<UserGroupDeletedNotification>,
INotificationHandler<MemberGroupDeletedNotification>,
INotificationHandler<MemberGroupSavedNotification>,
INotificationHandler<DataTypeDeletedNotification>,
INotificationHandler<DataTypeSavedNotification>
{
private List<Action> _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<PublicAccessEntry> e)
public void Handle(PublicAccessEntrySavedNotification notification)
{
_distributedCache.RefreshPublicAccess();
}
private void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs<PublicAccessEntry> 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<IDictionaryItem> 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<IDictionaryItem> 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<IDataType> 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<IDataType> 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
/// <summary>
/// Fires when a language is deleted
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LocalizationService_DeletedLanguage(ILocalizationService sender, DeleteEventArgs<ILanguage> e)
/// <param name="notification"></param>
public void Handle(LanguageDeletedNotification notification)
{
foreach (var entity in e.DeletedEntities)
foreach (ILanguage entity in notification.DeletedEntities)
{
_distributedCache.RemoveLanguageCache(entity);
}
}
/// <summary>
/// Fires when a language is saved
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LocalizationService_SavedLanguage(ILocalizationService sender, SaveEventArgs<ILanguage> e)
/// <param name="notification"></param>
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<EntityPermission> 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<IUser> e)
{
foreach (var entity in e.SavedEntities)
foreach (IUser entity in notification.SavedEntities)
{
_distributedCache.RefreshUserCache(entity.Id);
}
}
private void UserService_DeletedUser(IUserService sender, DeleteEventArgs<IUser> 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<UserGroupWithUsers> 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<IUserGroup> 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<IMember> e)
public void Handle(MemberDeletedNotification notification)
{
_distributedCache.RemoveMemberCache(e.DeletedEntities.ToArray());
_distributedCache.RemoveMemberCache(notification.DeletedEntities.ToArray());
}
private void MemberService_Saved(IMemberService sender, SaveEventArgs<IMember> 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<IMemberGroup> e)
/// <summary>
/// Fires when a member group is deleted
/// </summary>
/// <param name="notification"></param>
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<IMemberGroup> e)
/// <summary>
/// Fires when a member group is saved
/// </summary>
/// <param name="notification"></param>
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);
}
}

View File

@@ -1,7 +0,0 @@
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Core.Compose
{
public sealed class AuditEventsComposer : ComponentComposer<AuditEventsComponent>, ICoreComposer
{ }
}

View File

@@ -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<MemberSavedNotification>,
INotificationHandler<MemberDeletedNotification>,
INotificationHandler<AssignedMemberRolesNotification>,
INotificationHandler<RemovedMemberRolesNotification>,
INotificationHandler<ExportedMemberNotification>,
INotificationHandler<UserSavedNotification>,
INotificationHandler<UserDeletedNotification>,
INotificationHandler<UserGroupWithUsersSavedNotification>,
INotificationHandler<AssignedUserGroupPermissionsNotification>
{
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> 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<UserGroupWithUsers> 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<EntityPermission> 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<IMember> 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<IMember> 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<IUser> 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<IUser> 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);
}
}
}

View File

@@ -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
{
/// <remarks>
/// TODO: this component must be removed entirely - there is some code duplication in <see cref="UserNotificationsHandler"/> in anticipation of this component being deleted
/// </remarks>
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<EntityPermission> args)
=> UserServiceUserGroupPermissionsAssigned(args, _contentService);
private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs<PublicAccessEntry> args)
=> PublicAccessServiceSaved(args, _contentService);
private void UserServiceUserGroupPermissionsAssigned(SaveEventArgs<EntityPermission> args, IContentService contentService)
{
var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray();
if (entities.Any() == false)
{
return;
}
_notifier.Notify(_actions.GetAction<ActionRights>(), entities);
}
private void PublicAccessServiceSaved(SaveEventArgs<PublicAccessEntry> args, IContentService contentService)
{
var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray();
if (entities.Any() == false)
{
return;
}
_notifier.Notify(_actions.GetAction<ActionProtect>(), entities);
}
/// <summary>
/// This class is used to send the notifications
/// </summary>
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<Notifier> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="Notifier"/> class.
/// </summary>
public Notifier(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IHostingEnvironment hostingEnvironment,
INotificationService notificationService,
IUserService userService,
ILocalizedTextService textService,
IOptions<GlobalSettings> globalSettings,
ILogger<Notifier> 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<IContent> 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
}));
}
}
}
}
}

View File

@@ -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<NotificationsComponent>, ICoreComposer
public sealed class NotificationsComposer : ICoreComposer
{
public override void Compose(IUmbracoBuilder builder)
public void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
builder.Services.AddUnique<NotificationsComponent.Notifier>();
// add handlers for sending user notifications (i.e. emails)
builder.Services.AddUnique<UserNotificationsHandler.Notifier>();
builder
@@ -31,7 +29,9 @@ namespace Umbraco.Cms.Core.Compose
.AddNotificationHandler<ContentCopiedNotification, UserNotificationsHandler>()
.AddNotificationHandler<ContentRolledBackNotification, UserNotificationsHandler>()
.AddNotificationHandler<ContentSentToPublishNotification, UserNotificationsHandler>()
.AddNotificationHandler<ContentUnpublishedNotification, UserNotificationsHandler>();
.AddNotificationHandler<ContentUnpublishedNotification, UserNotificationsHandler>()
.AddNotificationHandler<AssignedUserGroupPermissionsNotification, UserNotificationsHandler>()
.AddNotificationHandler<PublicAccessEntrySavedNotification, UserNotificationsHandler>();
// add handlers for building content relations
builder
@@ -51,10 +51,12 @@ namespace Umbraco.Cms.Core.Compose
.AddNotificationHandler<ContentDeletedNotification, FileUploadPropertyEditor>()
.AddNotificationHandler<MediaDeletedNotification, FileUploadPropertyEditor>()
.AddNotificationHandler<MediaSavingNotification, FileUploadPropertyEditor>()
.AddNotificationHandler<MemberDeletedNotification, FileUploadPropertyEditor>()
.AddNotificationHandler<ContentCopiedNotification, ImageCropperPropertyEditor>()
.AddNotificationHandler<ContentDeletedNotification, ImageCropperPropertyEditor>()
.AddNotificationHandler<MediaDeletedNotification, ImageCropperPropertyEditor>()
.AddNotificationHandler<MediaSavingNotification, ImageCropperPropertyEditor>();
.AddNotificationHandler<MediaSavingNotification, ImageCropperPropertyEditor>()
.AddNotificationHandler<MemberDeletedNotification, ImageCropperPropertyEditor>();
// add notification handlers for redirect tracking
builder
@@ -62,6 +64,37 @@ namespace Umbraco.Cms.Core.Compose
.AddNotificationHandler<ContentPublishedNotification, RedirectTrackingHandler>()
.AddNotificationHandler<ContentMovingNotification, RedirectTrackingHandler>()
.AddNotificationHandler<ContentMovedNotification, RedirectTrackingHandler>();
// Add notification handlers for DistributedCache
builder
.AddNotificationHandler<DictionaryItemDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<DictionaryItemSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<LanguageSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<LanguageDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<MemberSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<MemberDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<PublicAccessEntrySavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<PublicAccessEntryDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<UserSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<UserDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<UserGroupWithUsersSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<UserGroupDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<MemberGroupDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<MemberGroupSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<DataTypeDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<DataTypeSavedNotification, DistributedCacheBinder>();
// add notification handlers for auditing
builder
.AddNotificationHandler<MemberSavedNotification, AuditNotificationsHandler>()
.AddNotificationHandler<MemberDeletedNotification, AuditNotificationsHandler>()
.AddNotificationHandler<AssignedMemberRolesNotification, AuditNotificationsHandler>()
.AddNotificationHandler<RemovedMemberRolesNotification, AuditNotificationsHandler>()
.AddNotificationHandler<ExportedMemberNotification, AuditNotificationsHandler>()
.AddNotificationHandler<UserSavedNotification, AuditNotificationsHandler>()
.AddNotificationHandler<UserDeletedNotification, AuditNotificationsHandler>()
.AddNotificationHandler<UserGroupWithUsersSavedNotification, AuditNotificationsHandler>()
.AddNotificationHandler<AssignedUserGroupPermissionsNotification, AuditNotificationsHandler>();
}
}
}

View File

@@ -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
{
/// <summary>
/// Used to ensure that the public access data file is kept up to date properly
/// </summary>
public sealed class PublicAccessComposer : ComponentComposer<PublicAccessComponent>, ICoreComposer
{ }
public sealed class PublicAccessComposer : ICoreComposer
{
public void Compose(IUmbracoBuilder builder) =>
builder
.AddNotificationHandler<MemberGroupSavedNotification, PublicAccessHandler>()
.AddNotificationHandler<MemberGroupDeletedNotification, PublicAccessHandler>();
}
}

View File

@@ -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<MemberGroupSavedNotification>,
INotificationHandler<MemberGroupDeletedNotification>
{
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<IMemberGroup> e)
private void Handle(IEnumerable<IMemberGroup> affectedEntities)
{
foreach (var grp in e.SavedEntities)
foreach (var grp in affectedEntities)
{
//check if the name has changed
if (grp.AdditionalData.ContainsKey("previousName")

View File

@@ -29,7 +29,9 @@ namespace Umbraco.Cms.Core.Events
INotificationHandler<ContentCopiedNotification>,
INotificationHandler<ContentRolledBackNotification>,
INotificationHandler<ContentSentToPublishNotification>,
INotificationHandler<ContentUnpublishedNotification>
INotificationHandler<ContentUnpublishedNotification>,
INotificationHandler<AssignedUserGroupPermissionsNotification>,
INotificationHandler<PublicAccessEntrySavedNotification>
{
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<ActionRights>(), 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<ActionProtect>(), entities);
}
}
}

View File

@@ -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<int, IMemberGroup>, IMemberGroupRepository
{
public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<MemberGroupRepository> logger)
: base(scopeAccessor, cache, logger)
{ }
private readonly IEventMessagesFactory _eventMessagesFactory;
public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<MemberGroupRepository> 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<IMemberGroup>(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<IMemberGroup>(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<IMemberGroup>(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<IMemberGroup>(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<NodeDto>(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
/// <summary>
/// Occurs before Save
/// </summary>
internal static event TypedEventHandler<IMemberGroupRepository, SaveEventArgs<IMemberGroup>> SavingMemberGroup;
/// <summary>
/// Occurs after Save
/// </summary>
internal static event TypedEventHandler<IMemberGroupRepository, SaveEventArgs<IMemberGroup>> SavedMemberGroup;
}
}

View File

@@ -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())

View File

@@ -27,7 +27,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
Icon = "icon-download-alt")]
public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator,
INotificationHandler<ContentCopiedNotification>, INotificationHandler<ContentDeletedNotification>,
INotificationHandler<MediaDeletedNotification>, INotificationHandler<MediaSavingNotification>
INotificationHandler<MediaDeletedNotification>, INotificationHandler<MediaSavingNotification>,
INotificationHandler<MemberDeletedNotification>
{
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
/// </summary>
/// <param name="entities"></param>
/// <remarks>
/// This method must be made private once MemberService events have been replaced by notifications
/// </remarks>
internal IEnumerable<string> ContainedFilePaths(IEnumerable<IContentBase> entities) => entities
private IEnumerable<string> ContainedFilePaths(IEnumerable<IContentBase> 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<IContentBase> deletedEntities)
{
var filePathsToDelete = ContainedFilePaths(deletedEntities);

View File

@@ -202,8 +202,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
_richTextPropertyValueEditor.GetReferences(x.Value)))
yield return umbracoEntityReference;
foreach (var umbracoEntityReference in mediaValues.Where(x=>x.Value.HasValues).SelectMany(x =>
_mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"])))
foreach (var umbracoEntityReference in mediaValues.Where(x=>x.Value.HasValues)
.SelectMany(x => _mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"])))
yield return umbracoEntityReference;
}
}

View File

@@ -34,7 +34,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
Icon = "icon-crop")]
public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator,
INotificationHandler<ContentCopiedNotification>, INotificationHandler<ContentDeletedNotification>,
INotificationHandler<MediaDeletedNotification>, INotificationHandler<MediaSavingNotification>
INotificationHandler<MediaDeletedNotification>, INotificationHandler<MediaSavingNotification>,
INotificationHandler<MemberDeletedNotification>
{
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
/// </summary>
/// <param name="entities"></param>
/// <remarks>
/// This method must be made private once MemberService events have been replaced by notifications
/// </remarks>
internal IEnumerable<string> ContainedFilePaths(IEnumerable<IContentBase> entities) => entities
private IEnumerable<string> ContainedFilePaths(IEnumerable<IContentBase> 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<IContentBase> deletedEntities)
{
var filePathsToDelete = ContainedFilePaths(deletedEntities);

View File

@@ -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<Action> _terminate = new List<Action>();
public PropertyEditorsComponent(PropertyEditorCollection propertyEditors)
{
_propertyEditors = propertyEditors;
}
public void Initialize()
{
var fileUpload = _propertyEditors.OfType<FileUploadPropertyEditor>().FirstOrDefault();
if (fileUpload != null) Initialize(fileUpload);
var imageCropper = _propertyEditors.OfType<ImageCropperPropertyEditor>().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<IMember> args) => args.MediaFilesToDelete.AddRange(fileUpload.ContainedFilePaths(args.DeletedEntities.Cast<ContentBase>()));
MemberService.Deleted += memberServiceDeleted;
_terminate.Add(() => MemberService.Deleted -= memberServiceDeleted);
}
private void Initialize(ImageCropperPropertyEditor imageCropper)
{
void memberServiceDeleted(IMemberService sender, DeleteEventArgs<IMember> args) => args.MediaFilesToDelete.AddRange(imageCropper.ContainedFilePaths(args.DeletedEntities.Cast<ContentBase>()));
MemberService.Deleted += memberServiceDeleted;
_terminate.Add(() => MemberService.Deleted -= memberServiceDeleted);
}
}
}

View File

@@ -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<PropertyEditorsComponent>, ICoreComposer
{ }
}

View File

@@ -23,20 +23,29 @@ namespace Umbraco.Cms.Core.Security
{
}
/// <inheritdoc />
/// <remarks>
/// Returns a ClaimsIdentity that has the required claims, and allows flowing of claims from external identity
/// </remarks>
public override async Task<ClaimsPrincipal> CreateAsync(BackOfficeIdentityUser user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
protected virtual string AuthenticationType { get; } = Constants.Security.BackOfficeAuthenticationType;
/// <inheritdoc />
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(BackOfficeIdentityUser user)
{
// NOTE: Have a look at the base implementation https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L79
// since it's setting an authentication type which is not what we want.
// so we override this method to change it.
// get the base
ClaimsIdentity baseIdentity = await base.GenerateClaimsAsync(user);
baseIdentity.AddRequiredClaims(
// now create a new one with the correct authentication type
var id = new ClaimsIdentity(
AuthenticationType,
Options.ClaimsIdentity.UserNameClaimType,
Options.ClaimsIdentity.RoleClaimType);
// and merge all others from the base implementation
id.MergeAllClaims(baseIdentity);
// ensure our required claims are there
id.AddRequiredClaims(
user.Id,
user.UserName,
user.Name,
@@ -49,23 +58,9 @@ namespace Umbraco.Cms.Core.Security
// now we can flow any custom claims that the actual user has currently
// assigned which could be done in the OnExternalLogin callback
baseIdentity.MergeClaimsFromBackOfficeIdentity(user);
id.MergeClaimsFromBackOfficeIdentity(user);
return new ClaimsPrincipal(baseIdentity);
}
/// <inheritdoc />
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(BackOfficeIdentityUser user)
{
// TODO: Have a look at the base implementation https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L79
// since it's setting an authentication type that is probably not what we want.
// also, this is the method that we should be returning our UmbracoBackOfficeIdentity from , not the method above,
// the method above just returns a principal that wraps the identity and we dont use a custom principal,
// see https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L66
ClaimsIdentity identity = await base.GenerateClaimsAsync(user);
return identity;
return id;
}
}
}

View File

@@ -13,6 +13,15 @@ namespace Umbraco.Extensions
// is re-issued and we don't want to merge old values of these.
private static readonly string[] s_ignoredClaims = new[] { ClaimTypes.CookiePath, Constants.Security.SessionIdClaimType };
public static void MergeAllClaims(this ClaimsIdentity destination, ClaimsIdentity source)
{
foreach (Claim claim in source.Claims
.Where(claim => !destination.HasClaim(claim.Type, claim.Value)))
{
destination.AddClaim(new Claim(claim.Type, claim.Value));
}
}
public static void MergeClaimsFromBackOfficeIdentity(this ClaimsIdentity destination, ClaimsIdentity source)
{
foreach (Claim claim in source.Claims

View File

@@ -3,7 +3,7 @@ namespace Umbraco.Cms.Core.Security
/// <summary>
/// The user manager for members
/// </summary>
public interface IMemberManager : IUmbracoUserManager<MembersIdentityUser>
public interface IMemberManager : IUmbracoUserManager<MemberIdentityUser>
{
}
}

View File

@@ -48,10 +48,10 @@ namespace Umbraco.Cms.Core.Security
target.EnableChangeTracking();
});
mapper.Define<IMember, MembersIdentityUser>(
mapper.Define<IMember, MemberIdentityUser>(
(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;

View File

@@ -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)
{
}
/// <summary>
/// Adds a token provider for the <seealso cref="MembersIdentityUser"/>.
/// Adds a token provider for the <seealso cref="MemberIdentityUser"/>.
/// </summary>
/// <param name="providerName">The name of the provider to add.</param>
/// <param name="provider">The type of the <see cref="IUserTwoFactorTokenProvider{UmbracoMembersIdentityUser}"/> to add.</param>
@@ -27,7 +27,7 @@ namespace Umbraco.Cms.Core.Security
{
throw new InvalidOperationException($"Invalid Type for TokenProvider: {provider.FullName}");
}
Services.Configure<MembersIdentityOptions>(options =>
Services.Configure<MemberIdentityOptions>(options =>
{
options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider);
});

View File

@@ -5,7 +5,7 @@ namespace Umbraco.Cms.Core.Security
/// <summary>
/// Identity options specifically for the Umbraco members identity implementation
/// </summary>
public class MembersIdentityOptions : IdentityOptions
public class MemberIdentityOptions : IdentityOptions
{
}
}

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Core.Security
/// <summary>
/// The identity user used for the member
/// </summary>
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());
/// <summary>
/// Initializes a new instance of the <see cref="MembersIdentityUser"/> class.
/// Initializes a new instance of the <see cref="MemberIdentityUser"/> class.
/// </summary>
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()
{
}
/// <summary>
/// Used to construct a new instance without an identity
/// </summary>
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;

View File

@@ -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
{
/// <summary>
/// A custom user store that uses Umbraco member data
/// </summary>
public class MemberRoleStore : IRoleStore<UmbracoIdentityRole>
{
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));
}
/// <summary>
/// Gets or sets the <see cref="IdentityErrorDescriber"/> for any error that occurred with the current operation.
/// </summary>
public IdentityErrorDescriber ErrorDescriber { get; set; }
/// <inheritdoc />
public Task<IdentityResult> 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);
}
/// <inheritdoc />
public Task<IdentityResult> 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));
}
}
/// <inheritdoc />
public Task<IdentityResult> 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);
}
/// <inheritdoc />
public Task<string> GetRoleIdAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return Task.FromResult(role.Id);
}
/// <inheritdoc />
public Task<string> GetRoleNameAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return Task.FromResult(role.Name);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public Task<string> GetNormalizedRoleNameAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
=> GetRoleNameAsync(role, cancellationToken);
/// <inheritdoc />
public Task SetNormalizedRoleNameAsync(UmbracoIdentityRole role, string normalizedName, CancellationToken cancellationToken = default)
=> SetRoleNameAsync(role, normalizedName, cancellationToken);
/// <inheritdoc />
public Task<UmbracoIdentityRole> 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));
}
/// <inheritdoc />
public Task<UmbracoIdentityRole> 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));
}
/// <summary>
/// Maps a member group to an identity role
/// </summary>
/// <param name="memberGroup"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Map an identity role to a member group
/// </summary>
/// <param name="role"></param>
/// <param name="memberGroup"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Dispose the store
/// </summary>
public void Dispose() => _disposed = true;
/// <summary>
/// Throws if this class has been disposed.
/// </summary>
protected void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
}
}

View File

@@ -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
{
/// <summary>
/// A custom user store that uses Umbraco member data
/// </summary>
public class MemberRolesUserStore : RoleStoreBase<IdentityRole<string>, string, IdentityUserRole<string>, IdentityRoleClaim<string>>
{
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));
}
/// <inheritdoc />
public override IQueryable<IdentityRole<string>> Roles { get; }
/// <inheritdoc />
public override Task<IdentityResult> CreateAsync(IdentityRole<string> role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
/// <inheritdoc />
public override Task<IdentityResult> UpdateAsync(IdentityRole<string> role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
/// <inheritdoc />
public override Task<IdentityResult> DeleteAsync(IdentityRole<string> role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
/// <inheritdoc />
public override Task<IdentityRole<string>> FindByIdAsync(string id, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
/// <inheritdoc />
public override Task<IdentityRole<string>> FindByNameAsync(string normalizedName, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
/// <inheritdoc />
public override Task<IList<Claim>> GetClaimsAsync(IdentityRole<string> role, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
/// <inheritdoc />
public override Task AddClaimAsync(IdentityRole<string> role, Claim claim, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
/// <inheritdoc />
public override Task RemoveClaimAsync(IdentityRole<string> role, Claim claim, CancellationToken cancellationToken = new CancellationToken()) => throw new System.NotImplementedException();
}
}

View File

@@ -19,20 +19,21 @@ namespace Umbraco.Cms.Core.Security
/// <summary>
/// A custom user store that uses Umbraco member data
/// </summary>
public class MembersUserStore : UserStoreBase<MembersIdentityUser, IdentityRole<string>, string, IdentityUserClaim<string>, IdentityUserRole<string>, IdentityUserLogin<string>, IdentityUserToken<string>, IdentityRoleClaim<string>>
public class MemberUserStore : UserStoreBase<MemberIdentityUser, UmbracoIdentityRole, string, IdentityUserClaim<string>, IdentityUserRole<string>, IdentityUserLogin<string>, IdentityUserToken<string>, IdentityRoleClaim<string>>
{
private const string genericIdentityErrorCode = "IdentityErrorUserStore";
private readonly IMemberService _memberService;
private readonly UmbracoMapper _mapper;
private readonly IScopeProvider _scopeProvider;
/// <summary>
/// Initializes a new instance of the <see cref="MembersUserStore"/> class for the members identity store
/// Initializes a new instance of the <see cref="MemberUserStore"/> class for the members identity store
/// </summary>
/// <param name="memberService">The member service</param>
/// <param name="mapper">The mapper for properties</param>
/// <param name="scopeProvider">The scope provider</param>
/// <param name="describer">The error describer</param>
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?
/// <summary>
/// Not supported in Umbraco
/// </summary>
/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override IQueryable<MembersIdentityUser> Users => throw new NotImplementedException();
public override IQueryable<MemberIdentityUser> Users => throw new NotImplementedException();
/// <inheritdoc />
public override Task<string> GetNormalizedUserNameAsync(MembersIdentityUser user, CancellationToken cancellationToken) => GetUserNameAsync(user, cancellationToken);
public override Task<string> GetNormalizedUserNameAsync(MemberIdentityUser user, CancellationToken cancellationToken = default) => GetUserNameAsync(user, cancellationToken);
/// <inheritdoc />
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);
/// <inheritdoc />
public override Task<IdentityResult> CreateAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
public override Task<IdentityResult> 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);
}
/// <inheritdoc />
public override Task<IdentityResult> UpdateAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
Attempt<int> asInt = user.Id.TryConvertTo<int>();
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 }));
}
}
/// <inheritdoc />
public override Task<IdentityResult> DeleteAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
public override Task<IdentityResult> 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<int> asInt = user.Id.TryConvertTo<int>();
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);
}
/// <inheritdoc />
public override Task<MembersIdentityUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default) => FindUserAsync(userId, cancellationToken);
public override Task<IdentityResult> 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 }));
}
}
/// <inheritdoc />
protected override Task<MembersIdentityUser> FindUserAsync(string userId, CancellationToken cancellationToken)
public override Task<MemberIdentityUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default) => FindUserAsync(userId, cancellationToken);
/// <inheritdoc />
protected override Task<MemberIdentityUser> 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<MembersIdentityUser>(user)));
return Task.FromResult(AssignLoginsCallback(_mapper.Map<MemberIdentityUser>(user)));
}
/// <inheritdoc />
public override Task<MembersIdentityUser> FindByNameAsync(string userName, CancellationToken cancellationToken = default)
public override Task<MemberIdentityUser> 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<MembersIdentityUser>(user));
MemberIdentityUser result = AssignLoginsCallback(_mapper.Map<MemberIdentityUser>(user));
return Task.FromResult(result);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override async Task<bool> HasPasswordAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
public override async Task<bool> 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
}
/// <inheritdoc />
public override Task<MembersIdentityUser> FindByEmailAsync(string email, CancellationToken cancellationToken = default)
public override Task<MemberIdentityUser> 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<MembersIdentityUser>(member);
: _mapper.Map<MemberIdentityUser>(member);
return Task.FromResult(AssignLoginsCallback(result));
}
/// <inheritdoc />
public override Task<string> GetNormalizedEmailAsync(MembersIdentityUser user, CancellationToken cancellationToken)
public override Task<string> GetNormalizedEmailAsync(MemberIdentityUser user, CancellationToken cancellationToken)
=> GetEmailAsync(user, cancellationToken);
/// <inheritdoc />
public override Task SetNormalizedEmailAsync(MembersIdentityUser user, string normalizedEmail, CancellationToken cancellationToken)
public override Task SetNormalizedEmailAsync(MemberIdentityUser user, string normalizedEmail, CancellationToken cancellationToken)
=> SetEmailAsync(user, normalizedEmail, cancellationToken);
/// <inheritdoc />
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<IIdentityUserLogin> 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
}
/// <inheritdoc />
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
}
/// <inheritdoc />
public override Task<IList<UserLoginInfo>> GetLoginsAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
public override Task<IList<UserLoginInfo>> 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<string>)null);
}
IList<UserLoginInfo> 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<string>)null);
}
return new IdentityUserLogin<string>
{
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<IIdentityUserLogin>();
// 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
});
}
/// <inheritdoc />
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
}
/// <inheritdoc/>
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
/// <summary>
/// Gets a list of role names the specified user belongs to.
/// </summary>
public override Task<IList<string>> GetRolesAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
public override Task<IList<string>> GetRolesAsync(MemberIdentityUser user, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -443,7 +524,7 @@ namespace Umbraco.Cms.Core.Security
/// <summary>
/// Returns true if a user is in the role
/// </summary>
public override Task<bool> IsInRoleAsync(MembersIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
public override Task<bool> 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));
}
/// <summary>
/// Lists all users of a given role.
/// </summary>
public override Task<IList<MembersIdentityUser>> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default)
public override Task<IList<MemberIdentityUser>> 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<IMember> members = _memberService.GetMembersByMemberType(normalizedRoleName);
IEnumerable<IMember> members = _memberService.GetMembersByMemberType(roleName);
IList<MembersIdentityUser> membersIdentityUsers = members.Select(x => _mapper.Map<MembersIdentityUser>(x)).ToList();
IList<MemberIdentityUser> membersIdentityUsers = members.Select(x => _mapper.Map<MemberIdentityUser>(x)).ToList();
return Task.FromResult(membersIdentityUsers);
}
/// <inheritdoc/>
protected override Task<IdentityRole<string>> FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken)
protected override Task<UmbracoIdentityRole> 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<string>)null);
throw new ArgumentNullException(nameof(roleName));
}
return Task.FromResult(new IdentityRole<string>(group.Name)
IMemberGroup group = _memberService.GetAllRoles().SingleOrDefault(x => x.Name == roleName);
if (group == null)
{
return Task.FromResult((UmbracoIdentityRole)null);
}
return Task.FromResult(new UmbracoIdentityRole(group.Name)
{
//TODO: what should the alias be?
Id = @group.Id.ToString()
Id = group.Id.ToString()
});
}
/// <inheritdoc/>
protected override async Task<IdentityUserRole<string>> 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
}
/// <inheritdoc />
public override Task<string> GetSecurityStampAsync(MembersIdentityUser user, CancellationToken cancellationToken = default)
public override Task<string> 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<IEnumerable<IIdentityUserLogin>>(() => _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
/// </summary>
/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override Task<IList<Claim>> GetClaimsAsync(MembersIdentityUser user, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public override Task<IList<Claim>> GetClaimsAsync(MemberIdentityUser user, CancellationToken cancellationToken = default) => throw new NotImplementedException();
/// <summary>
/// Not supported in Umbraco
/// </summary>
/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override Task AddClaimsAsync(MembersIdentityUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public override Task AddClaimsAsync(MemberIdentityUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
/// <summary>
/// Not supported in Umbraco
/// </summary>
/// <inheritdoc />
[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();
/// <summary>
/// Not supported in Umbraco
/// </summary>
/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override Task RemoveClaimsAsync(MembersIdentityUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public override Task RemoveClaimsAsync(MemberIdentityUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken = default) => throw new NotImplementedException();
/// <summary>
/// Not supported in Umbraco
/// </summary>
/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override Task<IList<MembersIdentityUser>> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public override Task<IList<MemberIdentityUser>> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default) => throw new NotImplementedException();
/// <summary>
/// Not supported in Umbraco
/// </summary>
/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
protected override Task<IdentityUserToken<string>> FindTokenAsync(MembersIdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) => throw new NotImplementedException();
protected override Task<IdentityUserToken<string>> FindTokenAsync(MemberIdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) => throw new NotImplementedException();
/// <summary>
/// Not supported in Umbraco

View File

@@ -0,0 +1,105 @@
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 UmbracoIdentityRole(string roleName) : base(roleName)
{
}
public UmbracoIdentityRole()
{
}
public event PropertyChangedEventHandler PropertyChanged
{
add
{
BeingDirty.PropertyChanged += value;
}
remove
{
BeingDirty.PropertyChanged -= value;
}
}
/// <inheritdoc />
public override string Id
{
get => _id;
set
{
_id = value;
HasIdentity = true;
}
}
/// <inheritdoc />
public override string Name
{
get => _name;
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
}
/// <inheritdoc />
public override string NormalizedName { get => base.Name; set => base.Name = value; }
/// <summary>
/// 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
/// </summary>
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; }
/// <summary>
/// Gets the <see cref="BeingDirty"/> for change tracking
/// </summary>
protected BeingDirty BeingDirty { get; } = new BeingDirty();
/// <inheritdoc />
public bool IsDirty() => BeingDirty.IsDirty();
/// <inheritdoc />
public bool IsPropertyDirty(string propName) => BeingDirty.IsPropertyDirty(propName);
/// <inheritdoc />
public IEnumerable<string> GetDirtyProperties() => BeingDirty.GetDirtyProperties();
/// <inheritdoc />
public void ResetDirtyProperties() => BeingDirty.ResetDirtyProperties();
/// <inheritdoc />
public bool WasDirty() => BeingDirty.WasDirty();
/// <inheritdoc />
public bool WasPropertyDirty(string propertyName) => BeingDirty.WasPropertyDirty(propertyName);
/// <inheritdoc />
public void ResetWereDirtyProperties() => BeingDirty.ResetWereDirtyProperties();
/// <inheritdoc />
public void ResetDirtyProperties(bool rememberDirty) => BeingDirty.ResetDirtyProperties(rememberDirty);
/// <inheritdoc />
public IEnumerable<string> GetWereDirtyProperties() => BeingDirty.GetWereDirtyProperties();
/// <summary>
/// Disables change tracking.
/// </summary>
public void DisableChangeTracking() => BeingDirty.DisableChangeTracking();
/// <summary>
/// Enables change tracking.
/// </summary>
public void EnableChangeTracking() => BeingDirty.EnableChangeTracking();
}
}

View File

@@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Models.Entities;
namespace Umbraco.Cms.Core.Models.Identity
{
/// <summary>
/// Abstract class for use in Umbraco Identity for users and members
/// </summary>

View File

@@ -68,7 +68,8 @@ namespace Umbraco.Cms.Core.Services.Implement
CreatorId = userId
};
if (scope.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs<EntityContainer>(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<EntityContainer>(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<EntityContainer>(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<EntityContainer>(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<EntityContainer>(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<EntityContainer>(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<EntityContainer>(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<IDataType>(toMove, toMove.Path, parentId);
var moveEventArgs = new MoveEventArgs<IDataType>(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<MoveOperationStatusType> ex)
@@ -381,12 +391,15 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <param name="userId">Id of the user issuing the save</param>
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<IDataType>(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
/// <param name="raiseEvents">Boolean indicating whether or not to raise events</param>
public void Save(IEnumerable<IDataType> dataTypeDefinitions, int userId, bool raiseEvents)
{
var evtMsgs = EventMessagesFactory.Get();
var dataTypeDefinitionsA = dataTypeDefinitions.ToArray();
var saveEventArgs = new SaveEventArgs<IDataType>(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
/// <param name="userId">Optional Id of the user issuing the deletion</param>
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<IDataType>(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<IDataTypeService, SaveEventArgs<EntityContainer>> SavingContainer;
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<EntityContainer>> SavedContainer;
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<EntityContainer>> DeletingContainer;
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<EntityContainer>> DeletedContainer;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<IDataType>> Deleting;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<IDataType>> Deleted;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<IDataType>> Saving;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<IDataType>> Saved;
/// <summary>
/// Occurs before Move
/// </summary>
public static event TypedEventHandler<IDataTypeService, MoveEventArgs<IDataType>> Moving;
/// <summary>
/// Occurs after Move
/// </summary>
public static event TypedEventHandler<IDataTypeService, MoveEventArgs<IDataType>> Moved;
#endregion
}
}

View File

@@ -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
/// <summary>
/// Represents the Localization Service, which is an easy access to operations involving <see cref="Language"/> and <see cref="DictionaryItem"/>
/// </summary>
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<IDictionaryItem>(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<IDictionaryItem>(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<IDictionaryItem>(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<IDictionaryItem>(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<ILanguage>(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<ILanguage>(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
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<ILocalizationService, DeleteEventArgs<ILanguage>> DeletingLanguage;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<ILocalizationService, DeleteEventArgs<ILanguage>> DeletedLanguage;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<ILocalizationService, DeleteEventArgs<IDictionaryItem>> DeletingDictionaryItem;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<ILocalizationService, DeleteEventArgs<IDictionaryItem>> DeletedDictionaryItem;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<ILocalizationService, SaveEventArgs<IDictionaryItem>> SavingDictionaryItem;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<ILocalizationService, SaveEventArgs<IDictionaryItem>> SavedDictionaryItem;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<ILocalizationService, SaveEventArgs<ILanguage>> SavingLanguage;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<ILocalizationService, SaveEventArgs<ILanguage>> SavedLanguage;
#endregion
}
}

View File

@@ -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<IMemberGroup> e)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMemberGroup>(e.SavedEntities), this))
e.Cancel = true;
}
void MemberGroupRepository_SavedMemberGroup(IMemberGroupRepository sender, SaveEventArgs<IMemberGroup> e)
{
// same as above!
Saved.RaiseEvent(new SaveEventArgs<IMemberGroup>(e.SavedEntities, false), this);
}
#endregion
public IEnumerable<IMemberGroup> 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<IMemberGroup>(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<IMemberGroup>(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));
}
}
/// <summary>
/// Occurs before Delete of a member group
/// </summary>
public static event TypedEventHandler<IMemberGroupService, DeleteEventArgs<IMemberGroup>> Deleting;
/// <summary>
/// Occurs after Delete of a member group
/// </summary>
public static event TypedEventHandler<IMemberGroupService, DeleteEventArgs<IMemberGroup>> Deleted;
/// <summary>
/// Occurs before Save of a member group
/// </summary>
/// <remarks>
/// We need to proxy these events because the events need to take place at the repo level
/// </remarks>
public static event TypedEventHandler<IMemberGroupService, SaveEventArgs<IMemberGroup>> Saving;
/// <summary>
/// Occurs after Save of a member group
/// </summary>
/// <remarks>
/// We need to proxy these events because the events need to take place at the repo level
/// </remarks>
public static event TypedEventHandler<IMemberGroupService, SaveEventArgs<IMemberGroup>> Saved;
}
}

View File

@@ -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<IMember>(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<IMember>(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
/// <param name="member"><see cref="IMember"/> to Delete</param>
public void Delete(IMember member)
{
var evtMsgs = EventMessagesFactory.Get();
using (var scope = ScopeProvider.CreateScope())
{
var deleteEventArgs = new DeleteEventArgs<IMember>(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<IMember> args = null)
private void DeleteLocked(IScope scope, IMember member, EventMessages evtMsgs, IDictionary<string, object> notificationState = null)
{
// a member has no descendants
_memberRepository.Delete(member);
if (args == null)
args = new DeleteEventArgs<IMember>(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
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IMemberService, DeleteEventArgs<IMember>> Deleting;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IMemberService, DeleteEventArgs<IMember>> Deleted;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IMemberService, SaveEventArgs<IMember>> Saving;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IMemberService, SaveEventArgs<IMember>> Saved;
/// <summary>
/// Occurs after roles have been assigned.
/// </summary>
public static event TypedEventHandler<IMemberService, RolesEventArgs> AssignedRoles;
/// <summary>
/// Occurs after roles have been removed.
/// </summary>
public static event TypedEventHandler<IMemberService, RolesEventArgs> RemovedRoles;
/// <summary>
/// Occurs after members have been exported.
/// </summary>
public static event TypedEventHandler<IMemberService, ExportedMemberEventArgs> Exported;
#endregion
#region Membership
@@ -1145,7 +1106,7 @@ namespace Umbraco.Cms.Core.Services.Implement
Properties = new List<MemberExportProperty>(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
/// <param name="memberTypeId">Id of the MemberType</param>
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<IMember> query = Query<IMember>().Where(x => x.ContentTypeId == memberTypeId);
IMember[] members = _memberRepository.Get(query).ToArray();
var deleteEventArgs = new DeleteEventArgs<IMember>(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();

View File

@@ -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<PublicAccessEntry>(evtMsgs); // causes rollback // causes rollback
return OperationResult.Attempt.Cannot<PublicAccessEntry>(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<PublicAccessEntry>(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<PublicAccessEntry>(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<PublicAccessEntry>(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<PublicAccessEntry>(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);
}
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IPublicAccessService, SaveEventArgs<PublicAccessEntry>> Saving;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IPublicAccessService, SaveEventArgs<PublicAccessEntry>> Saved;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>> Deleting;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>> Deleted;
}
}

View File

@@ -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
/// <summary>
/// Represents the UserService, which is an easy access to operations involving <see cref="IProfile"/>, <see cref="IMembershipUser"/> and eventually Backoffice Users.
/// </summary>
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<IUser>(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<IUser>(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 <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IUser entity, bool raiseEvents = true)
{
var evtMsgs = EventMessagesFactory.Get();
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IUser>(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 <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IEnumerable<IUser> entities, bool raiseEvents = true)
{
var evtMsgs = EventMessagesFactory.Get();
var entitiesA = entities.ToArray();
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IUser>(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<EntityPermission>(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<EntityPermission>(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 <c>True</c> otherwise set to <c>False</c> to not raise events</param>
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<UserGroupWithUsers>(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
/// <param name="userGroup">UserGroup to delete</param>
public void DeleteUserGroup(IUserGroup userGroup)
{
var evtMsgs = EventMessagesFactory.Get();
using (var scope = ScopeProvider.CreateScope())
{
var deleteEventArgs = new DeleteEventArgs<IUserGroup>(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
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<IUser>> SavingUser;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<IUser>> SavedUser;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IUserService, DeleteEventArgs<IUser>> DeletingUser;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IUserService, DeleteEventArgs<IUser>> DeletedUser;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<UserGroupWithUsers>> SavingUserGroup;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<UserGroupWithUsers>> SavedUserGroup;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IUserService, DeleteEventArgs<IUserGroup>> DeletingUserGroup;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IUserService, DeleteEventArgs<IUserGroup>> 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<IUserService, SaveEventArgs<EntityPermission>> UserGroupPermissionsAssigned;
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Cms.Infrastructure.Services.Notifications
{
public class AssignedMemberRolesNotification : MemberRolesNotification
{
public AssignedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles)
{
}
}
}

View File

@@ -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<EntityPermission>
{
public AssignedUserGroupPermissionsNotification(IEnumerable<EntityPermission> target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<EntityPermission> EntityPermissions => Target;
}
}

View File

@@ -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<IDictionaryItem>
{
public DictionaryItemDeletedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IDictionaryItem>
{
public DictionaryItemDeletingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages)
{
}
public DictionaryItemDeletingNotification(IEnumerable<IDictionaryItem> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IDictionaryItem>
{
public DictionaryItemSavedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages)
{
}
public DictionaryItemSavedNotification(IEnumerable<IDictionaryItem> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IDictionaryItem>
{
public DictionaryItemSavingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages)
{
}
public DictionaryItemSavingNotification(IEnumerable<IDictionaryItem> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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<ILanguage>
{
public LanguageDeletedNotification(ILanguage target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<ILanguage>
{
public LanguageDeletingNotification(ILanguage target, EventMessages messages) : base(target, messages)
{
}
public LanguageDeletingNotification(IEnumerable<ILanguage> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<ILanguage>
{
public LanguageSavedNotification(ILanguage target, EventMessages messages) : base(target, messages)
{
}
public LanguageSavedNotification(IEnumerable<ILanguage> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<ILanguage>
{
public LanguageSavingNotification(ILanguage target, EventMessages messages) : base(target, messages)
{
}
public LanguageSavingNotification(IEnumerable<ILanguage> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IMember>
{
public MemberDeletedNotification(IMember target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IMember>
{
public MemberDeletingNotification(IMember target, EventMessages messages) : base(target, messages)
{
}
public MemberDeletingNotification(IEnumerable<IMember> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,12 @@
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Infrastructure.Services.Notifications
{
public class MemberGroupDeletedNotification : DeletedNotification<IMemberGroup>
{
public MemberGroupDeletedNotification(IMemberGroup target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IMemberGroup>
{
public MemberGroupDeletingNotification(IMemberGroup target, EventMessages messages) : base(target, messages)
{
}
public MemberGroupDeletingNotification(IEnumerable<IMemberGroup> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IMemberGroup>
{
public MemberGroupSavedNotification(IMemberGroup target, EventMessages messages) : base(target, messages)
{
}
public MemberGroupSavedNotification(IEnumerable<IMemberGroup> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IMemberGroup>
{
public MemberGroupSavingNotification(IMemberGroup target, EventMessages messages) : base(target, messages)
{
}
public MemberGroupSavingNotification(IEnumerable<IMemberGroup> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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<IMember>
{
public MemberSavedNotification(IMember target, EventMessages messages) : base(target, messages)
{
}
public MemberSavedNotification(IEnumerable<IMember> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IMember>
{
public MemberSavingNotification(IMember target, EventMessages messages) : base(target, messages)
{
}
public MemberSavingNotification(IEnumerable<IMember> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<PublicAccessEntry>
{
public PublicAccessEntryDeletedNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<PublicAccessEntry>
{
public PublicAccessEntryDeletingNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages)
{
}
public PublicAccessEntryDeletingNotification(IEnumerable<PublicAccessEntry> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<PublicAccessEntry>
{
public PublicAccessEntrySavedNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages)
{
}
public PublicAccessEntrySavedNotification(IEnumerable<PublicAccessEntry> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<PublicAccessEntry>
{
public PublicAccessEntrySavingNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages)
{
}
public PublicAccessEntrySavingNotification(IEnumerable<PublicAccessEntry> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Cms.Infrastructure.Services.Notifications
{
public class RemovedMemberRolesNotification : MemberRolesNotification
{
public RemovedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles)
{
}
}
}

View File

@@ -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<IUser>
{
public UserDeletedNotification(IUser target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IUser>
{
public UserDeletingNotification(IUser target, EventMessages messages) : base(target, messages)
{
}
public UserDeletingNotification(IEnumerable<IUser> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IUserGroup>
{
public UserGroupDeletedNotification(IUserGroup target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IUserGroup>
{
public UserGroupDeletingNotification(IUserGroup target, EventMessages messages) : base(target, messages)
{
}
public UserGroupDeletingNotification(IEnumerable<IUserGroup> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IUserGroup>
{
public UserGroupSavedNotification(IUserGroup target, EventMessages messages) : base(target, messages)
{
}
public UserGroupSavedNotification(IEnumerable<IUserGroup> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IUserGroup>
{
public UserGroupSavingNotification(IUserGroup target, EventMessages messages) : base(target, messages)
{
}
public UserGroupSavingNotification(IEnumerable<IUserGroup> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<UserGroupWithUsers>
{
public UserGroupWithUsersSavedNotification(UserGroupWithUsers target, EventMessages messages) : base(target, messages)
{
}
public UserGroupWithUsersSavedNotification(IEnumerable<UserGroupWithUsers> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<UserGroupWithUsers>
{
public UserGroupWithUsersSavingNotification(UserGroupWithUsers target, EventMessages messages) : base(target, messages)
{
}
public UserGroupWithUsersSavingNotification(IEnumerable<UserGroupWithUsers> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IUser>
{
public UserSavedNotification(IUser target, EventMessages messages) : base(target, messages)
{
}
public UserSavedNotification(IEnumerable<IUser> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -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<IUser>
{
public UserSavingNotification(IUser target, EventMessages messages) : base(target, messages)
{
}
public UserSavingNotification(IEnumerable<IUser> target, EventMessages messages) : base(target, messages)
{
}
}
}

View File

@@ -101,6 +101,17 @@
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>DynamicProxyGenAssembly2</_Parameter1>
</AssemblyAttribute>
<!-- Making internals visible to Umbraco Forms -->
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Forms.Core</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Forms.Core.Providers</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Forms.Web</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>

View File

@@ -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<LanguageSavedNotification, PublishedSnapshotServiceEventHandler>();
}
}

View File

@@ -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
/// <summary>
/// Subscribes to Umbraco events to ensure nucache remains consistent with the source data
/// </summary>
public class PublishedSnapshotServiceEventHandler : IDisposable
public class PublishedSnapshotServiceEventHandler : IDisposable, INotificationHandler<LanguageSavedNotification>
{
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!
/// <summary>
/// If a <see cref="ILanguage"/> is ever saved with a different culture, we need to rebuild all of the content nucache database table
/// </summary>
private void OnLanguageSaved(ILocalizationService sender, SaveEventArgs<ILanguage> 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

View File

@@ -17,6 +17,13 @@ namespace Umbraco.Cms.Tests.Common.Builders.Extensions
return builder;
}
public static T WithId<T, TId>(this T builder, TId id)
where T : IWithIdBuilder<TId>
{
builder.Id = id;
return builder;
}
public static T WithoutIdentity<T>(this T builder)
where T : IWithIdBuilder
{

View File

@@ -7,4 +7,9 @@ namespace Umbraco.Cms.Tests.Common.Builders.Interfaces
{
int? Id { get; set; }
}
public interface IWithIdBuilder<TId>
{
TId Id { get; set; }
}
}

View File

@@ -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<UmbracoIdentityRole>,
IWithIdBuilder<string>,
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<string>.Id
{
get => _id;
set => _id = value;
}
public override UmbracoIdentityRole Build()
{
var id = _id;
var name = _name;
return new UmbracoIdentityRole
{
Id = id,
Name = name,
};
}
}
}

View File

@@ -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<object>
{
public UserBuilder()

View File

@@ -42,26 +42,12 @@ namespace Umbraco.Cms.Tests.Integration.Cache
var definitions = new IEventDefinition[]
{
new EventDefinition<IUserService, SaveEventArgs<IUser>>(null, UserService, new SaveEventArgs<IUser>(Enumerable.Empty<IUser>())),
new EventDefinition<IUserService, DeleteEventArgs<IUser>>(null, UserService, new DeleteEventArgs<IUser>(Enumerable.Empty<IUser>())),
new EventDefinition<IUserService, SaveEventArgs<UserGroupWithUsers>>(null, UserService, new SaveEventArgs<UserGroupWithUsers>(Enumerable.Empty<UserGroupWithUsers>())),
new EventDefinition<IUserService, DeleteEventArgs<IUserGroup>>(null, UserService, new DeleteEventArgs<IUserGroup>(Enumerable.Empty<IUserGroup>())),
new EventDefinition<ILocalizationService, SaveEventArgs<IDictionaryItem>>(null, LocalizationService, new SaveEventArgs<IDictionaryItem>(Enumerable.Empty<IDictionaryItem>())),
new EventDefinition<ILocalizationService, DeleteEventArgs<IDictionaryItem>>(null, LocalizationService, new DeleteEventArgs<IDictionaryItem>(Enumerable.Empty<IDictionaryItem>())),
new EventDefinition<IDataTypeService, SaveEventArgs<IDataType>>(null, DataTypeService, new SaveEventArgs<IDataType>(Enumerable.Empty<IDataType>())),
new EventDefinition<IDataTypeService, DeleteEventArgs<IDataType>>(null, DataTypeService, new DeleteEventArgs<IDataType>(Enumerable.Empty<IDataType>())),
new EventDefinition<IFileService, SaveEventArgs<IStylesheet>>(null, FileService, new SaveEventArgs<IStylesheet>(Enumerable.Empty<IStylesheet>())),
new EventDefinition<IFileService, DeleteEventArgs<IStylesheet>>(null, FileService, new DeleteEventArgs<IStylesheet>(Enumerable.Empty<IStylesheet>())),
new EventDefinition<IDomainService, SaveEventArgs<IDomain>>(null, DomainService, new SaveEventArgs<IDomain>(Enumerable.Empty<IDomain>())),
new EventDefinition<IDomainService, DeleteEventArgs<IDomain>>(null, DomainService, new DeleteEventArgs<IDomain>(Enumerable.Empty<IDomain>())),
new EventDefinition<ILocalizationService, SaveEventArgs<ILanguage>>(null, LocalizationService, new SaveEventArgs<ILanguage>(Enumerable.Empty<ILanguage>())),
new EventDefinition<ILocalizationService, DeleteEventArgs<ILanguage>>(null, LocalizationService, new DeleteEventArgs<ILanguage>(Enumerable.Empty<ILanguage>())),
new EventDefinition<IContentTypeService, SaveEventArgs<IContentType>>(null, ContentTypeService, new SaveEventArgs<IContentType>(Enumerable.Empty<IContentType>())),
new EventDefinition<IContentTypeService, DeleteEventArgs<IContentType>>(null, ContentTypeService, new DeleteEventArgs<IContentType>(Enumerable.Empty<IContentType>())),
new EventDefinition<IMediaTypeService, SaveEventArgs<IMediaType>>(null, MediaTypeService, new SaveEventArgs<IMediaType>(Enumerable.Empty<IMediaType>())),
@@ -76,19 +62,10 @@ namespace Umbraco.Cms.Tests.Integration.Cache
new EventDefinition<IMacroService, SaveEventArgs<IMacro>>(null, MacroService, new SaveEventArgs<IMacro>(Enumerable.Empty<IMacro>())),
new EventDefinition<IMacroService, DeleteEventArgs<IMacro>>(null, MacroService, new DeleteEventArgs<IMacro>(Enumerable.Empty<IMacro>())),
new EventDefinition<IMemberService, SaveEventArgs<IMember>>(null, MemberService, new SaveEventArgs<IMember>(Enumerable.Empty<IMember>())),
new EventDefinition<IMemberService, DeleteEventArgs<IMember>>(null, MemberService, new DeleteEventArgs<IMember>(Enumerable.Empty<IMember>())),
new EventDefinition<IMemberGroupService, SaveEventArgs<IMemberGroup>>(null, MemberGroupService, new SaveEventArgs<IMemberGroup>(Enumerable.Empty<IMemberGroup>())),
new EventDefinition<IMemberGroupService, DeleteEventArgs<IMemberGroup>>(null, MemberGroupService, new DeleteEventArgs<IMemberGroup>(Enumerable.Empty<IMemberGroup>())),
// not managed
//new EventDefinition<IContentService, SaveEventArgs<IContent>>(null, ContentService, new SaveEventArgs<IContent>(Enumerable.Empty<IContent>()), "SavedBlueprint"),
//new EventDefinition<IContentService, DeleteEventArgs<IContent>>(null, ContentService, new DeleteEventArgs<IContent>(Enumerable.Empty<IContent>()), "DeletedBlueprint"),
new EventDefinition<IPublicAccessService, SaveEventArgs<PublicAccessEntry>>(null, PublicAccessService, new SaveEventArgs<PublicAccessEntry>(Enumerable.Empty<PublicAccessEntry>())),
new EventDefinition<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>>(null, PublicAccessService, new DeleteEventArgs<PublicAccessEntry>(Enumerable.Empty<PublicAccessEntry>())),
new EventDefinition<IRelationService, SaveEventArgs<IRelationType>>(null, RelationService, new SaveEventArgs<IRelationType>(Enumerable.Empty<IRelationType>())),
new EventDefinition<IRelationService, DeleteEventArgs<IRelationType>>(null, RelationService, new DeleteEventArgs<IRelationType>(Enumerable.Empty<IRelationType>())),

View File

@@ -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<IServerMessenger, LocalServerMessenger>();
builder
.AddNotificationHandler<DictionaryItemDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<DictionaryItemSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<LanguageSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<LanguageDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<UserSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<LanguageDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<MemberGroupDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<MemberGroupSavedNotification, DistributedCacheBinder>();
builder.AddNotificationHandler<LanguageSavedNotification, PublishedSnapshotServiceEventHandler>();
}
[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<IUser>(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<IUser>(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<IUmbracoContextFactory>(), GetRequiredService<ILogger<DistributedCacheBinder>>());
_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<IUmbracoContextFactory>(), GetRequiredService<ILogger<DistributedCacheBinder>>());
_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<IDictionaryItem>(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<IUmbracoContextFactory>(), GetRequiredService<ILogger<DistributedCacheBinder>>());
_distributedCacheBinder.BindEvents(true);
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
{

Some files were not shown because too many files have changed in this diff Show More