Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V15: Notification Hub #17776

Open
wants to merge 29 commits into
base: v15/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6ba5c3a
Initial stab at how this could look
nikolajlauridsen Dec 4, 2024
10f506e
Merge branch 'v15/dev' into v15/feature/notification-hub
nikolajlauridsen Dec 9, 2024
797096d
Authorization PoC wip
nikolajlauridsen Dec 9, 2024
b87620d
Add connection manager
nikolajlauridsen Dec 10, 2024
4eb89da
Add DI to its own class
nikolajlauridsen Dec 10, 2024
28a9dd6
Use enum instead of string
nikolajlauridsen Dec 10, 2024
f274ed4
Use groups
nikolajlauridsen Dec 10, 2024
367121f
Refactor group management into its own service
nikolajlauridsen Dec 10, 2024
a48a803
Update a users groups when it's saved
nikolajlauridsen Dec 10, 2024
fd10921
Add saved events
nikolajlauridsen Dec 10, 2024
3b3b0ad
Wire up deleted notifications
nikolajlauridsen Dec 11, 2024
3afc18a
Ensure update date and create date is the same
nikolajlauridsen Dec 11, 2024
5a9c8c2
Cleanup
nikolajlauridsen Dec 11, 2024
0f2e4b8
Minor cleanup
nikolajlauridsen Dec 11, 2024
0a66ee5
Remove unusued usings
nikolajlauridsen Dec 11, 2024
cb61209
Move route to constant
nikolajlauridsen Dec 11, 2024
8f2c62d
Add docstrings to server event router
nikolajlauridsen Dec 11, 2024
d9f4a09
Fix and suppress warnings
nikolajlauridsen Dec 11, 2024
01402bb
Merge branch 'v15/dev' into v15/feature/notification-hub
nikolajlauridsen Dec 11, 2024
668efea
Refactor to authorizer pattern
nikolajlauridsen Dec 12, 2024
d8612ff
Update EventType
nikolajlauridsen Dec 12, 2024
a1f44fb
Merge branch 'v15/dev' into v15/feature/notification-hub
nikolajlauridsen Dec 12, 2024
55173f3
Remove unused enums
nikolajlauridsen Dec 12, 2024
d2a7d4b
Add trashed events
nikolajlauridsen Dec 16, 2024
cfba4ee
Notify current user that they've been updated
nikolajlauridsen Dec 16, 2024
3ca273d
Add broadcast
nikolajlauridsen Dec 16, 2024
9eb86de
Add ServerEventRouterTests
nikolajlauridsen Dec 19, 2024
c49950e
Add ServerEventUserManagerTests
nikolajlauridsen Dec 20, 2024
9da85ad
Merge branch 'v15/dev' into v15/feature/notification-hub
nikolajlauridsen Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
TODO: Fix and remove overrides:
[NU5104] Warning As Error: A stable release of a package should not have a prerelease dependency. Either modify the version spec of dependency
-->
<NoWarn>$(NoWarn),NU5104</NoWarn>
<WarningsNotAsErrors>$(WarningsNotAsErrors),NU5104</WarningsNotAsErrors>
<NoWarn>$(NoWarn),NU5104,SA1309</NoWarn>
<WarningsNotAsErrors>$(WarningsNotAsErrors),NU5104,SA1600</WarningsNotAsErrors>
</PropertyGroup>

<!-- SourceLink -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ServerEvents;
using Umbraco.Cms.Api.Management.ServerEvents.Authorizers;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.ServerEvents;

namespace Umbraco.Cms.Api.Management.DependencyInjection;

internal static class ServerEventExtensions
{
internal static IUmbracoBuilder AddServerEvents(this IUmbracoBuilder builder)
{
builder.Services.AddSingleton<IUserConnectionManager, UserConnectionManager>();
builder.Services.AddSingleton<IServerEventRouter, ServerEventRouter>();
builder.Services.AddSingleton<IServerEventUserManager, ServerEventUserManager>();
builder.AddNotificationAsyncHandler<UserSavedNotification, UserConnectionRefresher>();

builder
.AddEvents()
.AddAuthorizers();

return builder;
}

private static IUmbracoBuilder AddEvents(this IUmbracoBuilder builder)
{
builder.AddNotificationAsyncHandler<ContentSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<ContentTypeSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MediaSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MediaTypeSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MemberSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MemberTypeSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MemberGroupSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<DataTypeSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<LanguageSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<ScriptSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<StylesheetSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<TemplateSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<DictionaryItemSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<DomainSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<PartialViewSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<PublicAccessEntrySavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<RelationSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<RelationTypeSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<UserGroupSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<UserSavedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<WebhookSavedNotification, ServerEventSender>();

builder.AddNotificationAsyncHandler<ContentDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<ContentTypeDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MediaDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MediaTypeDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MemberDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MemberTypeDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MemberGroupDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<DataTypeDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<LanguageDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<ScriptDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<StylesheetDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<TemplateDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<DictionaryItemDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<DomainDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<PartialViewDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<PublicAccessEntryDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<RelationDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<RelationTypeDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<UserGroupDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<UserDeletedNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<WebhookDeletedNotification, ServerEventSender>();

builder.AddNotificationAsyncHandler<ContentMovedToRecycleBinNotification, ServerEventSender>();
builder.AddNotificationAsyncHandler<MediaMovedToRecycleBinNotification, ServerEventSender>();

return builder;
}

private static IUmbracoBuilder AddAuthorizers(this IUmbracoBuilder builder)
{
builder.EventSourceAuthorizers()
.Append<DocumentEventAuthorizer>()
.Append<DocumentTypeEventAuthorizer>()
.Append<MediaEventAuthorizer>()
.Append<MediaTypeEventAuthorizer>()
.Append<MemberEventAuthorizer>()
.Append<MemberGroupEventAuthorizer>()
.Append<MemberTypeEventAuthorizer>()
.Append<DataTypeEventAuthorizer>()
.Append<LanguageEventAuthorizer>()
.Append<ScriptEventAuthorizer>()
.Append<StylesheetEventAuthorizer>()
.Append<TemplateEventAuthorizer>()
.Append<DictionaryItemEventAuthorizer>()
.Append<DomainEventAuthorizer>()
.Append<PartialViewEventAuthorizer>()
.Append<PublicAccessEntryEventAuthorizer>()
.Append<RelationEventAuthorizer>()
.Append<RelationTypeEventAuthorizer>()
.Append<UserGroupEventAuthorizer>()
.Append<UserEventAuthorizer>()
.Append<WebhookEventAuthorizer>();
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
.AddCorsPolicy()
.AddWebhooks()
.AddPreview()
.AddServerEvents()

Check warning on line 69 in src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ Getting worse: Large Method

AddUmbracoManagementApi increases from 75 to 76 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
.AddPasswordConfiguration()
.AddSupplemenataryLocalizedTextFileSources()
.AddUserData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,16 @@ public async Task<Attempt<IDataType, DataTypeOperationStatus>> CreateAsync(Creat
return Attempt.FailWithStatus<IDataType, DataTypeOperationStatus>(parentAttempt.Status, new DataType(new VoidEditor(_dataValueEditorFactory), _configurationEditorJsonSerializer));
}

var createDate = DateTime.Now;
var dataType = new DataType(editor, _configurationEditorJsonSerializer)
{
Name = requestModel.Name,
EditorUiAlias = requestModel.EditorUiAlias,
DatabaseType = GetEditorValueStorageType(editor),
ConfigurationData = MapConfigurationData(requestModel, editor),
ParentId = parentAttempt.Result,
CreateDate = DateTime.Now,
CreateDate = createDate,
UpdateDate = createDate,
};

if (requestModel.Id.HasValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Controllers.Security;
using Umbraco.Cms.Api.Management.ServerEvents;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
Expand Down Expand Up @@ -54,6 +55,7 @@ public void CreateRoutes(IEndpointRouteBuilder endpoints)
case RuntimeLevel.Run:
MapMinimalBackOffice(endpoints);
endpoints.MapHub<BackofficeHub>(_umbracoPathSegment + Constants.Web.BackofficeSignalRHub);
endpoints.MapHub<ServerEventHub>(_umbracoPathSegment + Constants.Web.ServerEventSignalRHub);
break;
case RuntimeLevel.BootFailed:
case RuntimeLevel.Unknown:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class DataTypeEventAuthorizer : EventSourcePolicyAuthorizer
{
public DataTypeEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.DataType];

protected override string Policy => AuthorizationPolicies.TreeAccessDataTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class DictionaryItemEventAuthorizer : EventSourcePolicyAuthorizer
{
public DictionaryItemEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.DictionaryItem];

protected override string Policy => AuthorizationPolicies.TreeAccessDictionary;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class DocumentEventAuthorizer : EventSourcePolicyAuthorizer
{
public DocumentEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}


public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.Document];

protected override string Policy => AuthorizationPolicies.TreeAccessDocuments;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class DocumentTypeEventAuthorizer : EventSourcePolicyAuthorizer
{
public DocumentTypeEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.DocumentType];

protected override string Policy => AuthorizationPolicies.TreeAccessDocumentTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class DomainEventAuthorizer : EventSourcePolicyAuthorizer
{
public DomainEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.Domain];

protected override string Policy => AuthorizationPolicies.TreeAccessDocuments;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class LanguageEventAuthorizer : EventSourcePolicyAuthorizer
{
public LanguageEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.Language];

protected override string Policy => AuthorizationPolicies.TreeAccessLanguages;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class MediaEventAuthorizer : EventSourcePolicyAuthorizer
{
public MediaEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.Media];

protected override string Policy => AuthorizationPolicies.TreeAccessMediaOrMediaTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class MediaTypeEventAuthorizer : EventSourcePolicyAuthorizer
{
public MediaTypeEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.MediaType];

protected override string Policy => AuthorizationPolicies.TreeAccessMediaTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class MemberEventAuthorizer : EventSourcePolicyAuthorizer
{
public MemberEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.Member];

protected override string Policy => AuthorizationPolicies.TreeAccessMembersOrMemberTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class MemberGroupEventAuthorizer : EventSourcePolicyAuthorizer
{
public MemberGroupEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.MemberGroup];

protected override string Policy => AuthorizationPolicies.TreeAccessMemberGroups;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class MemberTypeEventAuthorizer : EventSourcePolicyAuthorizer
{
public MemberTypeEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.MemberType];

protected override string Policy => AuthorizationPolicies.TreeAccessMemberTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class PartialViewEventAuthorizer : EventSourcePolicyAuthorizer
{
public PartialViewEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.PartialView];

protected override string Policy => AuthorizationPolicies.TreeAccessPartialViews;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class PublicAccessEntryEventAuthorizer : EventSourcePolicyAuthorizer
{
public PublicAccessEntryEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.PublicAccessEntry];

protected override string Policy => AuthorizationPolicies.TreeAccessDocuments;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.ServerEvents.Authorizers;

public class RelationEventAuthorizer : EventSourcePolicyAuthorizer
{
public RelationEventAuthorizer(IAuthorizationService authorizationService) : base(authorizationService)
{
}

public override IEnumerable<string> AuthorizedEventSources => [Constants.ServerEvents.EventSource.Relation];

protected override string Policy => AuthorizationPolicies.TreeAccessDocuments;
}
Loading
Loading