Skip to content

Commit aaad9c0

Browse files
V15: Notification Hub (#17776)
* Initial stab at how this could look * Authorization PoC wip * Add connection manager * Add DI to its own class * Use enum instead of string * Use groups * Refactor group management into its own service * Update a users groups when it's saved * Add saved events * Wire up deleted notifications * Ensure update date and create date is the same * Cleanup * Minor cleanup * Remove unusued usings * Move route to constant * Add docstrings to server event router * Fix and suppress warnings * Refactor to authorizer pattern * Update EventType * Remove unused enums * Add trashed events * Notify current user that they've been updated * Add broadcast We don't need it, but seems like a thing that a server event router should be able to do. * Add ServerEventRouterTests * Add ServerEventUserManagerTests * Use TimeProvider * Remove principal null check * Don't assign event type * Minor cleanup * Rename AuthorizedEventSources * Change permission for relations * Exctract event authorization into its own service * Add some tests * Update name * Add forgotten file * Rmember to add to DI
1 parent 7932eb9 commit aaad9c0

File tree

51 files changed

+1709
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1709
-4
lines changed

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
TODO: Fix and remove overrides:
2626
[NU5104] Warning As Error: A stable release of a package should not have a prerelease dependency. Either modify the version spec of dependency
2727
-->
28-
<NoWarn>$(NoWarn),NU5104</NoWarn>
29-
<WarningsNotAsErrors>$(WarningsNotAsErrors),NU5104</WarningsNotAsErrors>
28+
<NoWarn>$(NoWarn),NU5104,SA1309</NoWarn>
29+
<WarningsNotAsErrors>$(WarningsNotAsErrors),NU5104,SA1600</WarningsNotAsErrors>
3030
</PropertyGroup>
3131

3232
<!-- SourceLink -->
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Text.Json.Serialization;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Umbraco.Cms.Api.Management.ServerEvents;
4+
using Umbraco.Cms.Api.Management.ServerEvents.Authorizers;
5+
using Umbraco.Cms.Core.DependencyInjection;
6+
using Umbraco.Cms.Core.Notifications;
7+
using Umbraco.Cms.Core.ServerEvents;
8+
9+
namespace Umbraco.Cms.Api.Management.DependencyInjection;
10+
11+
internal static class ServerEventExtensions
12+
{
13+
internal static IUmbracoBuilder AddServerEvents(this IUmbracoBuilder builder)
14+
{
15+
builder.Services.AddSingleton<IUserConnectionManager, UserConnectionManager>();
16+
builder.Services.AddSingleton<IServerEventRouter, ServerEventRouter>();
17+
builder.Services.AddSingleton<IServerEventUserManager, ServerEventUserManager>();
18+
builder.Services.AddSingleton<IServerEventAuthorizationService, ServerEventAuthorizationService>();
19+
builder.AddNotificationAsyncHandler<UserSavedNotification, UserConnectionRefresher>();
20+
21+
builder
22+
.AddEvents()
23+
.AddAuthorizers();
24+
25+
return builder;
26+
}
27+
28+
private static IUmbracoBuilder AddEvents(this IUmbracoBuilder builder)
29+
{
30+
builder.AddNotificationAsyncHandler<ContentSavedNotification, ServerEventSender>();
31+
builder.AddNotificationAsyncHandler<ContentTypeSavedNotification, ServerEventSender>();
32+
builder.AddNotificationAsyncHandler<MediaSavedNotification, ServerEventSender>();
33+
builder.AddNotificationAsyncHandler<MediaTypeSavedNotification, ServerEventSender>();
34+
builder.AddNotificationAsyncHandler<MemberSavedNotification, ServerEventSender>();
35+
builder.AddNotificationAsyncHandler<MemberTypeSavedNotification, ServerEventSender>();
36+
builder.AddNotificationAsyncHandler<MemberGroupSavedNotification, ServerEventSender>();
37+
builder.AddNotificationAsyncHandler<DataTypeSavedNotification, ServerEventSender>();
38+
builder.AddNotificationAsyncHandler<LanguageSavedNotification, ServerEventSender>();
39+
builder.AddNotificationAsyncHandler<ScriptSavedNotification, ServerEventSender>();
40+
builder.AddNotificationAsyncHandler<StylesheetSavedNotification, ServerEventSender>();
41+
builder.AddNotificationAsyncHandler<TemplateSavedNotification, ServerEventSender>();
42+
builder.AddNotificationAsyncHandler<DictionaryItemSavedNotification, ServerEventSender>();
43+
builder.AddNotificationAsyncHandler<DomainSavedNotification, ServerEventSender>();
44+
builder.AddNotificationAsyncHandler<PartialViewSavedNotification, ServerEventSender>();
45+
builder.AddNotificationAsyncHandler<PublicAccessEntrySavedNotification, ServerEventSender>();
46+
builder.AddNotificationAsyncHandler<RelationSavedNotification, ServerEventSender>();
47+
builder.AddNotificationAsyncHandler<RelationTypeSavedNotification, ServerEventSender>();
48+
builder.AddNotificationAsyncHandler<UserGroupSavedNotification, ServerEventSender>();
49+
builder.AddNotificationAsyncHandler<UserSavedNotification, ServerEventSender>();
50+
builder.AddNotificationAsyncHandler<WebhookSavedNotification, ServerEventSender>();
51+
52+
builder.AddNotificationAsyncHandler<ContentDeletedNotification, ServerEventSender>();
53+
builder.AddNotificationAsyncHandler<ContentTypeDeletedNotification, ServerEventSender>();
54+
builder.AddNotificationAsyncHandler<MediaDeletedNotification, ServerEventSender>();
55+
builder.AddNotificationAsyncHandler<MediaTypeDeletedNotification, ServerEventSender>();
56+
builder.AddNotificationAsyncHandler<MemberDeletedNotification, ServerEventSender>();
57+
builder.AddNotificationAsyncHandler<MemberTypeDeletedNotification, ServerEventSender>();
58+
builder.AddNotificationAsyncHandler<MemberGroupDeletedNotification, ServerEventSender>();
59+
builder.AddNotificationAsyncHandler<DataTypeDeletedNotification, ServerEventSender>();
60+
builder.AddNotificationAsyncHandler<LanguageDeletedNotification, ServerEventSender>();
61+
builder.AddNotificationAsyncHandler<ScriptDeletedNotification, ServerEventSender>();
62+
builder.AddNotificationAsyncHandler<StylesheetDeletedNotification, ServerEventSender>();
63+
builder.AddNotificationAsyncHandler<TemplateDeletedNotification, ServerEventSender>();
64+
builder.AddNotificationAsyncHandler<DictionaryItemDeletedNotification, ServerEventSender>();
65+
builder.AddNotificationAsyncHandler<DomainDeletedNotification, ServerEventSender>();
66+
builder.AddNotificationAsyncHandler<PartialViewDeletedNotification, ServerEventSender>();
67+
builder.AddNotificationAsyncHandler<PublicAccessEntryDeletedNotification, ServerEventSender>();
68+
builder.AddNotificationAsyncHandler<RelationDeletedNotification, ServerEventSender>();
69+
builder.AddNotificationAsyncHandler<RelationTypeDeletedNotification, ServerEventSender>();
70+
builder.AddNotificationAsyncHandler<UserGroupDeletedNotification, ServerEventSender>();
71+
builder.AddNotificationAsyncHandler<UserDeletedNotification, ServerEventSender>();
72+
builder.AddNotificationAsyncHandler<WebhookDeletedNotification, ServerEventSender>();
73+
74+
builder.AddNotificationAsyncHandler<ContentMovedToRecycleBinNotification, ServerEventSender>();
75+
builder.AddNotificationAsyncHandler<MediaMovedToRecycleBinNotification, ServerEventSender>();
76+
77+
return builder;
78+
}
79+
80+
private static IUmbracoBuilder AddAuthorizers(this IUmbracoBuilder builder)
81+
{
82+
builder.EventSourceAuthorizers()
83+
.Append<DocumentEventAuthorizer>()
84+
.Append<DocumentTypeEventAuthorizer>()
85+
.Append<MediaEventAuthorizer>()
86+
.Append<MediaTypeEventAuthorizer>()
87+
.Append<MemberEventAuthorizer>()
88+
.Append<MemberGroupEventAuthorizer>()
89+
.Append<MemberTypeEventAuthorizer>()
90+
.Append<DataTypeEventAuthorizer>()
91+
.Append<LanguageEventAuthorizer>()
92+
.Append<ScriptEventAuthorizer>()
93+
.Append<StylesheetEventAuthorizer>()
94+
.Append<TemplateEventAuthorizer>()
95+
.Append<DictionaryItemEventAuthorizer>()
96+
.Append<DomainEventAuthorizer>()
97+
.Append<PartialViewEventAuthorizer>()
98+
.Append<PublicAccessEntryEventAuthorizer>()
99+
.Append<RelationEventAuthorizer>()
100+
.Append<RelationTypeEventAuthorizer>()
101+
.Append<UserGroupEventAuthorizer>()
102+
.Append<UserEventAuthorizer>()
103+
.Append<WebhookEventAuthorizer>();
104+
return builder;
105+
}
106+
}

src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public static IUmbracoBuilder AddUmbracoManagementApi(this IUmbracoBuilder build
6666
.AddCorsPolicy()
6767
.AddWebhooks()
6868
.AddPreview()
69+
.AddServerEvents()
6970
.AddPasswordConfiguration()
7071
.AddSupplemenataryLocalizedTextFileSources()
7172
.AddUserData()

src/Umbraco.Cms.Api.Management/Factories/DataTypePresentationFactory.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

2+
using Microsoft.Extensions.DependencyInjection;
23
using Umbraco.Cms.Api.Management.ViewModels.DataType;
34
using Umbraco.Cms.Core;
5+
using Umbraco.Cms.Core.DependencyInjection;
46
using Umbraco.Cms.Core.Models;
57
using Umbraco.Cms.Core.PropertyEditors;
68
using Umbraco.Cms.Core.Serialization;
@@ -16,17 +18,35 @@ public class DataTypePresentationFactory : IDataTypePresentationFactory
1618
private readonly PropertyEditorCollection _propertyEditorCollection;
1719
private readonly IDataValueEditorFactory _dataValueEditorFactory;
1820
private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;
21+
private readonly TimeProvider _timeProvider;
1922

2023
public DataTypePresentationFactory(
2124
IDataTypeContainerService dataTypeContainerService,
2225
PropertyEditorCollection propertyEditorCollection,
2326
IDataValueEditorFactory dataValueEditorFactory,
24-
IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
27+
IConfigurationEditorJsonSerializer configurationEditorJsonSerializer,
28+
TimeProvider timeProvider)
2529
{
2630
_dataTypeContainerService = dataTypeContainerService;
2731
_propertyEditorCollection = propertyEditorCollection;
2832
_dataValueEditorFactory = dataValueEditorFactory;
2933
_configurationEditorJsonSerializer = configurationEditorJsonSerializer;
34+
_timeProvider = timeProvider;
35+
}
36+
37+
[Obsolete("Use constructor that takes a TimeProvider")]
38+
public DataTypePresentationFactory(
39+
IDataTypeContainerService dataTypeContainerService,
40+
PropertyEditorCollection propertyEditorCollection,
41+
IDataValueEditorFactory dataValueEditorFactory,
42+
IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
43+
: this(
44+
dataTypeContainerService,
45+
propertyEditorCollection,
46+
dataValueEditorFactory,
47+
configurationEditorJsonSerializer,
48+
StaticServiceProvider.Instance.GetRequiredService<TimeProvider>())
49+
{
3050
}
3151

3252
/// <inheritdoc />
@@ -44,14 +64,16 @@ public async Task<Attempt<IDataType, DataTypeOperationStatus>> CreateAsync(Creat
4464
return Attempt.FailWithStatus<IDataType, DataTypeOperationStatus>(parentAttempt.Status, new DataType(new VoidEditor(_dataValueEditorFactory), _configurationEditorJsonSerializer));
4565
}
4666

67+
DateTime createDate = _timeProvider.GetLocalNow().DateTime;
4768
var dataType = new DataType(editor, _configurationEditorJsonSerializer)
4869
{
4970
Name = requestModel.Name,
5071
EditorUiAlias = requestModel.EditorUiAlias,
5172
DatabaseType = GetEditorValueStorageType(editor),
5273
ConfigurationData = MapConfigurationData(requestModel, editor),
5374
ParentId = parentAttempt.Result,
54-
CreateDate = DateTime.Now,
75+
CreateDate = createDate,
76+
UpdateDate = createDate,
5577
};
5678

5779
if (requestModel.Id.HasValue)

src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.AspNetCore.Routing;
44
using Microsoft.Extensions.Options;
55
using Umbraco.Cms.Api.Management.Controllers.Security;
6+
using Umbraco.Cms.Api.Management.ServerEvents;
67
using Umbraco.Cms.Core;
78
using Umbraco.Cms.Core.Configuration.Models;
89
using Umbraco.Cms.Core.Hosting;
@@ -54,6 +55,7 @@ public void CreateRoutes(IEndpointRouteBuilder endpoints)
5455
case RuntimeLevel.Run:
5556
MapMinimalBackOffice(endpoints);
5657
endpoints.MapHub<BackofficeHub>(_umbracoPathSegment + Constants.Web.BackofficeSignalRHub);
58+
endpoints.MapHub<ServerEventHub>(_umbracoPathSegment + Constants.Web.ServerEventSignalRHub);
5759
break;
5860
case RuntimeLevel.BootFailed:
5961
case RuntimeLevel.Unknown:
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Umbraco.Cms.Core;
3+
using Umbraco.Cms.Web.Common.Authorization;
4+
5+
namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;
6+
7+
public class DataTypeEventAuthorizer : EventSourcePolicyAuthorizer
8+
{
9+
public DataTypeEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
10+
{
11+
}
12+
13+
public override IEnumerable<string> AuthorizableEventSources => [Constants.ServerEvents.EventSource.DataType];
14+
15+
protected override string Policy => AuthorizationPolicies.TreeAccessDataTypes;
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Umbraco.Cms.Core;
3+
using Umbraco.Cms.Web.Common.Authorization;
4+
5+
namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;
6+
7+
public class DictionaryItemEventAuthorizer : EventSourcePolicyAuthorizer
8+
{
9+
public DictionaryItemEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
10+
{
11+
}
12+
13+
public override IEnumerable<string> AuthorizableEventSources => [Constants.ServerEvents.EventSource.DictionaryItem];
14+
15+
protected override string Policy => AuthorizationPolicies.TreeAccessDictionary;
16+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Umbraco.Cms.Core;
3+
using Umbraco.Cms.Web.Common.Authorization;
4+
5+
namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;
6+
7+
public class DocumentEventAuthorizer : EventSourcePolicyAuthorizer
8+
{
9+
public DocumentEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
10+
{
11+
}
12+
13+
14+
public override IEnumerable<string> AuthorizableEventSources => [Constants.ServerEvents.EventSource.Document];
15+
16+
protected override string Policy => AuthorizationPolicies.TreeAccessDocuments;
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Umbraco.Cms.Core;
3+
using Umbraco.Cms.Web.Common.Authorization;
4+
5+
namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;
6+
7+
public class DocumentTypeEventAuthorizer : EventSourcePolicyAuthorizer
8+
{
9+
public DocumentTypeEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
10+
{
11+
}
12+
13+
public override IEnumerable<string> AuthorizableEventSources => [Constants.ServerEvents.EventSource.DocumentType];
14+
15+
protected override string Policy => AuthorizationPolicies.TreeAccessDocumentTypes;
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Umbraco.Cms.Core;
3+
using Umbraco.Cms.Web.Common.Authorization;
4+
5+
namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;
6+
7+
public class DomainEventAuthorizer : EventSourcePolicyAuthorizer
8+
{
9+
public DomainEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
10+
{
11+
}
12+
13+
public override IEnumerable<string> AuthorizableEventSources => [Constants.ServerEvents.EventSource.Domain];
14+
15+
protected override string Policy => AuthorizationPolicies.TreeAccessDocuments;
16+
}

0 commit comments

Comments
 (0)