Files
Mole aaad9c0b23 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
2025-01-10 09:36:44 +01:00

125 lines
6.0 KiB
C#

using System.Security.Claims;
using Microsoft.AspNetCore.SignalR;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.ServerEvents;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.ServerEvents;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.ServerEvents;
[TestFixture]
public class ServerEventUserManagerTests
{
[Test]
public async Task AssignsUserToEventSourceGroup()
{
var userKey = Guid.NewGuid();
var user = CreateFakeUser(userKey);
var authorizationService = CreateServeEventAuthorizationService(new FakeAuthorizer(["source"]));
var mocks = CreateHubContextMocks();
// Add a connection to the user
var connection = "connection1";
var connectionManager = new UserConnectionManager();
connectionManager.AddConnection(userKey, connection);
var sut = new ServerEventUserManager(connectionManager, authorizationService, mocks.HubContextMock.Object);
await sut.AssignToGroupsAsync(user, connection);
// Ensure AddToGroupAsync was called once, and only once with the expected parameters.
mocks.GroupManagerMock.Verify(x => x.AddToGroupAsync(connection, "source", It.IsAny<CancellationToken>()), Times.Once);
mocks.GroupManagerMock.Verify(x => x.AddToGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public async Task DoesNotAssignUserToEventSourceGroupWhenUnauthorized()
{
var userKey = Guid.NewGuid();
var user = CreateFakeUser(userKey);
var authorizationService = CreateServeEventAuthorizationService(new FakeAuthorizer(["source"], (_, _) => false));
var mocks = CreateHubContextMocks();
// Add a connection to the user
var connection = "connection1";
var connectionManager = new UserConnectionManager();
connectionManager.AddConnection(userKey, connection);
var sut = new ServerEventUserManager(connectionManager, authorizationService, mocks.HubContextMock.Object);
await sut.AssignToGroupsAsync(user, connection);
// Ensure AddToGroupAsync was never called.
mocks.GroupManagerMock.Verify(x => x.AddToGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task RefreshGroupsAsyncRefreshesUserGroups()
{
var userKey = Guid.NewGuid();
var user = CreateFakeUser(userKey);
var allowedSource = "allowedSource";
var disallowedSource = "NotAllowed";
var authorizationService = CreateServeEventAuthorizationService(new FakeAuthorizer([allowedSource]), new FakeAuthorizer([disallowedSource], (_, _) => false));
var mocks = CreateHubContextMocks();
// Add a connection to the user
var connection = "connection1";
var connectionManager = new UserConnectionManager();
connectionManager.AddConnection(userKey, connection);
var sut = new ServerEventUserManager(connectionManager, authorizationService, mocks.HubContextMock.Object);
await sut.RefreshGroupsAsync(user);
// Ensure AddToGroupAsync was called once, and only once with the expected parameters.
mocks.GroupManagerMock.Verify(x => x.AddToGroupAsync(connection, allowedSource, It.IsAny<CancellationToken>()), Times.Once);
mocks.GroupManagerMock.Verify(x => x.AddToGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
// Ensure RemoveToGroup was called for the disallowed source, and only the disallowed source.
mocks.GroupManagerMock.Verify(x => x.RemoveFromGroupAsync(connection, disallowedSource, It.IsAny<CancellationToken>()), Times.Once());
mocks.GroupManagerMock.Verify(x => x.RemoveFromGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once());
}
[Test]
public async Task RefreshUserGroupsDoesNothingIfNoConnections()
{
var userKey = Guid.NewGuid();
var user = CreateFakeUser(userKey);
var authorizationService = CreateServeEventAuthorizationService(new FakeAuthorizer(["source"]), new FakeAuthorizer(["disallowedSource"], (_, _) => false)) ?? throw new ArgumentNullException("CreateServeEventAuthorizationService(new FakeAuthorizer([\"source\"]), new FakeAuthorizer([\"disallowedSource\"], (_, _) => false))");
var mocks = CreateHubContextMocks();
var connectionManager = new UserConnectionManager();
var sut = new ServerEventUserManager(connectionManager, authorizationService, mocks.HubContextMock.Object);
await sut.RefreshGroupsAsync(user);
// Ensure AddToGroupAsync was never called.
mocks.GroupManagerMock.Verify(x => x.AddToGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
}
private ClaimsPrincipal CreateFakeUser(Guid key) =>
new(new ClaimsIdentity([
// This is the claim that's used to store the ID
new Claim(Constants.Security.OpenIdDictSubClaimType, key.ToString())
]));
private IServerEventAuthorizationService CreateServeEventAuthorizationService(params IEnumerable<IEventSourceAuthorizer> authorizers)
=> new ServerEventAuthorizationService(new EventSourceAuthorizerCollection(() => authorizers));
private (Mock<IServerEventHub> HubMock, Mock<IHubClients<IServerEventHub>> HubClientsMock, Mock<IGroupManager> GroupManagerMock, Mock<IHubContext<ServerEventHub, IServerEventHub>> HubContextMock) CreateHubContextMocks()
{
var hubMock = new Mock<IServerEventHub>();
var hubClients = new Mock<IHubClients<IServerEventHub>>();
hubClients.Setup(x => x.All).Returns(hubMock.Object);
var groupManagerMock = new Mock<IGroupManager>();
var hubContext = new Mock<IHubContext<ServerEventHub, IServerEventHub>>();
hubContext.Setup(x => x.Clients).Returns(hubClients.Object);
hubContext.Setup(x => x.Groups).Returns(groupManagerMock.Object);
return (hubMock, hubClients, groupManagerMock, hubContext);
}
}