diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs
index fe33c45156d1..c44e550d15b7 100644
--- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs
+++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs
@@ -1,8 +1,8 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
-using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
+using Bit.Core.Models.Data.Organizations.OrganizationUsers;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
@@ -36,13 +36,18 @@ public class IntegrationTemplateContext(EventMessage eventMessage)
public string DateIso8601 => Date.ToString("o");
public string EventMessage => JsonSerializer.Serialize(Event);
- public User? User { get; set; }
+ public OrganizationUserUserDetails? User { get; set; }
public string? UserName => User?.Name;
public string? UserEmail => User?.Email;
+ public OrganizationUserType? UserType => User?.Type;
- public User? ActingUser { get; set; }
+ public OrganizationUserUserDetails? ActingUser { get; set; }
public string? ActingUserName => ActingUser?.Name;
public string? ActingUserEmail => ActingUser?.Email;
+ public OrganizationUserType? ActingUserType => ActingUser?.Type;
+
+ public Group? Group { get; set; }
+ public string? GroupName => Group?.Name;
public Organization? Organization { get; set; }
public string? OrganizationName => Organization?.DisplayName();
diff --git a/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs b/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs
index b17de3c51d3b..142c6855a394 100644
--- a/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs
+++ b/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs
@@ -96,4 +96,15 @@ UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId,
/// Accepted OrganizationUser to confirm
/// True, if the user was updated. False, if not performed.
Task ConfirmOrganizationUserAsync(OrganizationUser organizationUser);
+
+ ///
+ /// Returns the OrganizationUserUserDetails if found.
+ ///
+ /// The id of the organization
+ /// The id of the User to fetch
+ /// OrganizationUserUserDetails of the specified user or null if not found
+ ///
+ /// Similar to GetByOrganizationAsync, but returns the user details.
+ ///
+ Task GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId);
}
diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs
index 8423652eb81d..e29d0eaaadf0 100644
--- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs
+++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs
@@ -1,5 +1,6 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
+using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Utilities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@@ -13,8 +14,9 @@ public class EventIntegrationHandler(
IEventIntegrationPublisher eventIntegrationPublisher,
IIntegrationFilterService integrationFilterService,
IIntegrationConfigurationDetailsCache configurationCache,
- IUserRepository userRepository,
+ IGroupRepository groupRepository,
IOrganizationRepository organizationRepository,
+ IOrganizationUserRepository organizationUserRepository,
ILogger> logger)
: IEventMessageHandler
{
@@ -89,19 +91,35 @@ private async Task BuildContextAsync(EventMessage ev
{
var context = new IntegrationTemplateContext(eventMessage);
+ if (IntegrationTemplateProcessor.TemplateRequiresGroup(template) && eventMessage.GroupId.HasValue)
+ {
+ context.Group = await groupRepository.GetByIdAsync(eventMessage.GroupId.Value);
+ }
+
+ if (eventMessage.OrganizationId is not Guid organizationId)
+ {
+ return context;
+ }
+
if (IntegrationTemplateProcessor.TemplateRequiresUser(template) && eventMessage.UserId.HasValue)
{
- context.User = await userRepository.GetByIdAsync(eventMessage.UserId.Value);
+ context.User = await organizationUserRepository.GetDetailsByOrganizationIdUserIdAsync(
+ organizationId: organizationId,
+ userId: eventMessage.UserId.Value
+ );
}
if (IntegrationTemplateProcessor.TemplateRequiresActingUser(template) && eventMessage.ActingUserId.HasValue)
{
- context.ActingUser = await userRepository.GetByIdAsync(eventMessage.ActingUserId.Value);
+ context.ActingUser = await organizationUserRepository.GetDetailsByOrganizationIdUserIdAsync(
+ organizationId: organizationId,
+ userId: eventMessage.ActingUserId.Value
+ );
}
- if (IntegrationTemplateProcessor.TemplateRequiresOrganization(template) && eventMessage.OrganizationId.HasValue)
+ if (IntegrationTemplateProcessor.TemplateRequiresOrganization(template))
{
- context.Organization = await organizationRepository.GetByIdAsync(eventMessage.OrganizationId.Value);
+ context.Organization = await organizationRepository.GetByIdAsync(organizationId);
}
return context;
diff --git a/src/Core/AdminConsole/Utilities/IntegrationTemplateProcessor.cs b/src/Core/AdminConsole/Utilities/IntegrationTemplateProcessor.cs
index b561e58a869c..62df3b2bc9f0 100644
--- a/src/Core/AdminConsole/Utilities/IntegrationTemplateProcessor.cs
+++ b/src/Core/AdminConsole/Utilities/IntegrationTemplateProcessor.cs
@@ -26,7 +26,7 @@ public static string ReplaceTokens(string template, object values)
return match.Value; // Return unknown keys as keys - i.e. #Key#
}
- return property?.GetValue(values)?.ToString() ?? "";
+ return property.GetValue(values)?.ToString() ?? string.Empty;
});
}
@@ -38,7 +38,8 @@ public static bool TemplateRequiresUser(string template)
}
return template.Contains("#UserName#", StringComparison.Ordinal)
- || template.Contains("#UserEmail#", StringComparison.Ordinal);
+ || template.Contains("#UserEmail#", StringComparison.Ordinal)
+ || template.Contains("#UserType#", StringComparison.Ordinal);
}
public static bool TemplateRequiresActingUser(string template)
@@ -49,7 +50,18 @@ public static bool TemplateRequiresActingUser(string template)
}
return template.Contains("#ActingUserName#", StringComparison.Ordinal)
- || template.Contains("#ActingUserEmail#", StringComparison.Ordinal);
+ || template.Contains("#ActingUserEmail#", StringComparison.Ordinal)
+ || template.Contains("#ActingUserType#", StringComparison.Ordinal);
+ }
+
+ public static bool TemplateRequiresGroup(string template)
+ {
+ if (string.IsNullOrEmpty(template))
+ {
+ return false;
+ }
+
+ return template.Contains("#GroupName#", StringComparison.Ordinal);
}
public static bool TemplateRequiresOrganization(string template)
diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs
index dc4fc74ff8a9..2f2af1322721 100644
--- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs
+++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs
@@ -686,4 +686,21 @@ public async Task ConfirmOrganizationUserAsync(OrganizationUser organizati
return rowCount > 0;
}
+
+ public async Task GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QuerySingleOrDefaultAsync(
+ "[dbo].[OrganizationUserUserDetails_ReadByOrganizationIdUserId]",
+ new
+ {
+ OrganizationId = organizationId,
+ UserId = userId
+ },
+ commandType: CommandType.StoredProcedure);
+
+ return result;
+ }
+ }
}
diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs
index b871ec44bf9d..e034648cd21f 100644
--- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs
+++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs
@@ -962,4 +962,20 @@ public async Task ConfirmOrganizationUserAsync(Core.Entities.OrganizationU
return true;
}
+
+#nullable enable
+
+ public async Task GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId)
+ {
+ using (var scope = ServiceScopeFactory.CreateScope())
+ {
+ var dbContext = GetDatabaseContext(scope);
+ var view = new OrganizationUserUserDetailsViewQuery();
+ var entity = await view.Run(dbContext).FirstOrDefaultAsync(ou => ou.OrganizationId == organizationId && ou.UserId == userId);
+ return entity;
+ }
+ }
+#nullable disable
+
+
}
diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
index ef143b042cb2..c9d4e613b72f 100644
--- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
+++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
@@ -13,6 +13,7 @@
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.AdminConsole.Models.Teams;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
+using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
using Bit.Core.AdminConsole.Services.Implementations;
using Bit.Core.AdminConsole.Services.NoopImplementations;
@@ -898,8 +899,9 @@ private static IServiceCollection AddAzureServiceBusIntegration(),
integrationFilterService: provider.GetRequiredService(),
configurationCache: provider.GetRequiredService(),
- userRepository: provider.GetRequiredService(),
+ groupRepository: provider.GetRequiredService(),
organizationRepository: provider.GetRequiredService(),
+ organizationUserRepository: provider.GetRequiredService(),
logger: provider.GetRequiredService>>()
)
);
@@ -1025,8 +1027,9 @@ private static IServiceCollection AddRabbitMqIntegration(),
integrationFilterService: provider.GetRequiredService(),
configurationCache: provider.GetRequiredService(),
- userRepository: provider.GetRequiredService(),
+ groupRepository: provider.GetRequiredService(),
organizationRepository: provider.GetRequiredService(),
+ organizationUserRepository: provider.GetRequiredService(),
logger: provider.GetRequiredService>>()
)
);
diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadByOrganizationIdUserId.sql b/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadByOrganizationIdUserId.sql
new file mode 100644
index 000000000000..0026d73ed007
--- /dev/null
+++ b/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadByOrganizationIdUserId.sql
@@ -0,0 +1,18 @@
+CREATE PROCEDURE [dbo].[OrganizationUserUserDetails_ReadByOrganizationIdUserId]
+ @OrganizationId UNIQUEIDENTIFIER,
+ @UserId UNIQUEIDENTIFIER
+AS
+BEGIN
+ SET NOCOUNT ON
+
+SELECT
+ TOP 1 *
+FROM
+ [dbo].[OrganizationUserUserDetailsView]
+WHERE
+ [OrganizationId] = @OrganizationId
+AND
+ [UserId] = @UserId
+END
+go
+
diff --git a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs b/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs
index cdb109e28597..d9a3cd6e8a67 100644
--- a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs
+++ b/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs
@@ -2,8 +2,8 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
-using Bit.Core.Entities;
using Bit.Core.Models.Data;
+using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
@@ -35,7 +35,7 @@ public void DateIso8601_ReturnsIso8601FormattedDate(EventMessage eventMessage)
}
[Theory, BitAutoData]
- public void UserName_WhenUserIsSet_ReturnsName(EventMessage eventMessage, User user)
+ public void UserName_WhenUserIsSet_ReturnsName(EventMessage eventMessage, OrganizationUserUserDetails user)
{
var sut = new IntegrationTemplateContext(eventMessage) { User = user };
@@ -51,7 +51,7 @@ public void UserName_WhenUserIsNull_ReturnsNull(EventMessage eventMessage)
}
[Theory, BitAutoData]
- public void UserEmail_WhenUserIsSet_ReturnsEmail(EventMessage eventMessage, User user)
+ public void UserEmail_WhenUserIsSet_ReturnsEmail(EventMessage eventMessage, OrganizationUserUserDetails user)
{
var sut = new IntegrationTemplateContext(eventMessage) { User = user };
@@ -67,7 +67,23 @@ public void UserEmail_WhenUserIsNull_ReturnsNull(EventMessage eventMessage)
}
[Theory, BitAutoData]
- public void ActingUserName_WhenActingUserIsSet_ReturnsName(EventMessage eventMessage, User actingUser)
+ public void UserType_WhenUserIsSet_ReturnsType(EventMessage eventMessage, OrganizationUserUserDetails user)
+ {
+ var sut = new IntegrationTemplateContext(eventMessage) { User = user };
+
+ Assert.Equal(user.Type, sut.UserType);
+ }
+
+ [Theory, BitAutoData]
+ public void UserType_WhenUserIsNull_ReturnsNull(EventMessage eventMessage)
+ {
+ var sut = new IntegrationTemplateContext(eventMessage) { User = null };
+
+ Assert.Null(sut.UserType);
+ }
+
+ [Theory, BitAutoData]
+ public void ActingUserName_WhenActingUserIsSet_ReturnsName(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
{
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
@@ -83,7 +99,7 @@ public void ActingUserName_WhenActingUserIsNull_ReturnsNull(EventMessage eventMe
}
[Theory, BitAutoData]
- public void ActingUserEmail_WhenActingUserIsSet_ReturnsEmail(EventMessage eventMessage, User actingUser)
+ public void ActingUserEmail_WhenActingUserIsSet_ReturnsEmail(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
{
var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
@@ -98,6 +114,22 @@ public void ActingUserEmail_WhenActingUserIsNull_ReturnsNull(EventMessage eventM
Assert.Null(sut.ActingUserEmail);
}
+ [Theory, BitAutoData]
+ public void ActingUserType_WhenActingUserIsSet_ReturnsType(EventMessage eventMessage, OrganizationUserUserDetails actingUser)
+ {
+ var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = actingUser };
+
+ Assert.Equal(actingUser.Type, sut.ActingUserType);
+ }
+
+ [Theory, BitAutoData]
+ public void ActingUserType_WhenActingUserIsNull_ReturnsNull(EventMessage eventMessage)
+ {
+ var sut = new IntegrationTemplateContext(eventMessage) { ActingUser = null };
+
+ Assert.Null(sut.ActingUserType);
+ }
+
[Theory, BitAutoData]
public void OrganizationName_WhenOrganizationIsSet_ReturnsDisplayName(EventMessage eventMessage, Organization organization)
{
@@ -113,4 +145,20 @@ public void OrganizationName_WhenOrganizationIsNull_ReturnsNull(EventMessage eve
Assert.Null(sut.OrganizationName);
}
+
+ [Theory, BitAutoData]
+ public void GroupName_WhenGroupIsSet_ReturnsName(EventMessage eventMessage, Group group)
+ {
+ var sut = new IntegrationTemplateContext(eventMessage) { Group = group };
+
+ Assert.Equal(group.Name, sut.GroupName);
+ }
+
+ [Theory, BitAutoData]
+ public void GroupName_WhenGroupIsNull_ReturnsNull(EventMessage eventMessage)
+ {
+ var sut = new IntegrationTemplateContext(eventMessage) { Group = null };
+
+ Assert.Null(sut.GroupName);
+ }
}
diff --git a/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs b/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs
index 1d94d58aa5f9..c556c1fae0b8 100644
--- a/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs
+++ b/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs
@@ -1,10 +1,11 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
-using Bit.Core.Entities;
+using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations;
+using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
@@ -20,9 +21,11 @@ namespace Bit.Core.Test.Services;
public class EventIntegrationHandlerTests
{
private const string _templateBase = "Date: #Date#, Type: #Type#, UserId: #UserId#";
+ private const string _templateWithGroup = "Group: #GroupName#";
private const string _templateWithOrganization = "Org: #OrganizationName#";
- private const string _templateWithUser = "#UserName#, #UserEmail#";
- private const string _templateWithActingUser = "#ActingUserName#, #ActingUserEmail#";
+ private const string _templateWithUser = "#UserName#, #UserEmail#, #UserType#";
+ private const string _templateWithActingUser = "#ActingUserName#, #ActingUserEmail#, #ActingUserType#";
+ private static readonly Guid _groupId = Guid.NewGuid();
private static readonly Guid _organizationId = Guid.NewGuid();
private static readonly Uri _uri = new Uri("https://localhost");
private static readonly Uri _uri2 = new Uri("https://example.com");
@@ -45,7 +48,7 @@ private SutProvider expectedMessage(string template)
+ private static IntegrationMessage ExpectedMessage(string template)
{
return new IntegrationMessage()
{
@@ -105,7 +108,7 @@ private static List ValidFilterConf
config.Configuration = null;
config.IntegrationConfiguration = JsonSerializer.Serialize(new { Uri = _uri });
config.Template = _templateBase;
- config.Filters = JsonSerializer.Serialize(new IntegrationFilterGroup() { });
+ config.Filters = JsonSerializer.Serialize(new IntegrationFilterGroup());
return [config];
}
@@ -138,15 +141,16 @@ public async Task HandleEventAsync_BaseTemplateOneConfiguration_PublishesIntegra
await sutProvider.Sut.HandleEventAsync(eventMessage);
- var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
);
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
- await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetDetailsByOrganizationIdUserIdAsync(Arg.Any(), Arg.Any());
}
[Theory, BitAutoData]
@@ -157,7 +161,7 @@ public async Task HandleEventAsync_BaseTemplateTwoConfigurations_PublishesIntegr
await sutProvider.Sut.HandleEventAsync(eventMessage);
- var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
);
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
@@ -167,29 +171,56 @@ await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
- await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetDetailsByOrganizationIdUserIdAsync(Arg.Any(), Arg.Any());
}
[Theory, BitAutoData]
public async Task HandleEventAsync_ActingUserTemplate_LoadsUserFromRepository(EventMessage eventMessage)
{
var sutProvider = GetSutProvider(OneConfiguration(_templateWithActingUser));
- var user = Substitute.For();
+ var user = Substitute.For();
user.Email = "test@example.com";
user.Name = "Test";
eventMessage.OrganizationId = _organizationId;
- sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(user);
+ sutProvider.GetDependency()
+ .GetDetailsByOrganizationIdUserIdAsync(Arg.Any(), Arg.Any()).Returns(user);
await sutProvider.Sut.HandleEventAsync(eventMessage);
- var expectedMessage = EventIntegrationHandlerTests.expectedMessage($"{user.Name}, {user.Email}");
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage($"{user.Name}, {user.Email}, {user.Type}");
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
- await sutProvider.GetDependency().Received(1).GetByIdAsync(eventMessage.ActingUserId ?? Guid.Empty);
+ await sutProvider.GetDependency().Received(1).GetDetailsByOrganizationIdUserIdAsync(Arg.Any(), eventMessage.ActingUserId ?? Guid.Empty);
+ }
+
+ [Theory, BitAutoData]
+ public async Task HandleEventAsync_GroupTemplate_LoadsGroupFromRepository(EventMessage eventMessage)
+ {
+ var sutProvider = GetSutProvider(OneConfiguration(_templateWithGroup));
+ var group = Substitute.For();
+ group.Name = "Test";
+ eventMessage.GroupId = _groupId;
+ eventMessage.OrganizationId = _organizationId;
+
+ sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(group);
+ await sutProvider.Sut.HandleEventAsync(eventMessage);
+
+ Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
+
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage($"Group: {group.Name}");
+
+ Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
+ await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
+ AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
+ await sutProvider.GetDependency().Received(1).GetByIdAsync(eventMessage.GroupId ?? Guid.Empty);
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetDetailsByOrganizationIdUserIdAsync(Arg.Any(), Arg.Any());
}
[Theory, BitAutoData]
@@ -205,34 +236,37 @@ public async Task HandleEventAsync_OrganizationTemplate_LoadsOrganizationFromRep
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
- var expectedMessage = EventIntegrationHandlerTests.expectedMessage($"Org: {organization.Name}");
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage($"Org: {organization.Name}");
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
await sutProvider.GetDependency().Received(1).GetByIdAsync(eventMessage.OrganizationId ?? Guid.Empty);
- await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetDetailsByOrganizationIdUserIdAsync(Arg.Any(), Arg.Any());
}
[Theory, BitAutoData]
public async Task HandleEventAsync_UserTemplate_LoadsUserFromRepository(EventMessage eventMessage)
{
var sutProvider = GetSutProvider(OneConfiguration(_templateWithUser));
- var user = Substitute.For();
+ var user = Substitute.For();
user.Email = "test@example.com";
user.Name = "Test";
eventMessage.OrganizationId = _organizationId;
- sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(user);
+ sutProvider.GetDependency()
+ .GetDetailsByOrganizationIdUserIdAsync(Arg.Any(), Arg.Any()).Returns(user);
await sutProvider.Sut.HandleEventAsync(eventMessage);
- var expectedMessage = EventIntegrationHandlerTests.expectedMessage($"{user.Name}, {user.Email}");
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage($"{user.Name}, {user.Email}, {user.Type}");
Assert.Single(_eventIntegrationPublisher.ReceivedCalls());
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" })));
+ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any());
- await sutProvider.GetDependency().Received(1).GetByIdAsync(eventMessage.UserId ?? Guid.Empty);
+ await sutProvider.GetDependency().Received(1).GetDetailsByOrganizationIdUserIdAsync(Arg.Any(), eventMessage.UserId ?? Guid.Empty);
}
[Theory, BitAutoData]
@@ -256,7 +290,7 @@ public async Task HandleEventAsync_FilterReturnsTrue_PublishesIntegrationMessage
await sutProvider.Sut.HandleEventAsync(eventMessage);
- var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
);
@@ -298,7 +332,7 @@ public async Task HandleManyEventsAsync_BaseTemplateOneConfiguration_PublishesIn
foreach (var eventMessage in eventMessages)
{
- var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
);
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(
@@ -316,7 +350,7 @@ public async Task HandleManyEventsAsync_BaseTemplateTwoConfigurations_PublishesI
foreach (var eventMessage in eventMessages)
{
- var expectedMessage = EventIntegrationHandlerTests.expectedMessage(
+ var expectedMessage = EventIntegrationHandlerTests.ExpectedMessage(
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}"
);
await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is(AssertHelper.AssertPropertyEqual(
diff --git a/test/Core.Test/AdminConsole/Utilities/IntegrationTemplateProcessorTests.cs b/test/Core.Test/AdminConsole/Utilities/IntegrationTemplateProcessorTests.cs
index d9df9486b6d0..aee4af346cb2 100644
--- a/test/Core.Test/AdminConsole/Utilities/IntegrationTemplateProcessorTests.cs
+++ b/test/Core.Test/AdminConsole/Utilities/IntegrationTemplateProcessorTests.cs
@@ -83,6 +83,7 @@ public void ReplaceTokens_TemplateIsEmpty_ReturnsOriginalString(EventMessage eve
[Theory]
[InlineData("User name is #UserName#")]
[InlineData("Email: #UserEmail#")]
+ [InlineData("User type = #UserType#")]
public void TemplateRequiresUser_ContainingKeys_ReturnsTrue(string template)
{
var result = IntegrationTemplateProcessor.TemplateRequiresUser(template);
@@ -102,6 +103,7 @@ public void TemplateRequiresUser_EmptyInputOrNoMatchingKeys_ReturnsFalse(string
[Theory]
[InlineData("Acting user is #ActingUserName#")]
[InlineData("Acting user's email is #ActingUserEmail#")]
+ [InlineData("Acting user's type is #ActingUserType#")]
public void TemplateRequiresActingUser_ContainingKeys_ReturnsTrue(string template)
{
var result = IntegrationTemplateProcessor.TemplateRequiresActingUser(template);
@@ -118,6 +120,25 @@ public void TemplateRequiresActingUser_EmptyInputOrNoMatchingKeys_ReturnsFalse(s
Assert.False(result);
}
+ [Theory]
+ [InlineData("Group name is #GroupName#!")]
+ [InlineData("Group: #GroupName#")]
+ public void TemplateRequiresGroup_ContainingKeys_ReturnsTrue(string template)
+ {
+ var result = IntegrationTemplateProcessor.TemplateRequiresGroup(template);
+ Assert.True(result);
+ }
+
+ [Theory]
+ [InlineData("#GroupId#")] // This is on the base class, not fetched, so should be false
+ [InlineData("No Group Tokens")]
+ [InlineData("")]
+ public void TemplateRequiresGroup_EmptyInputOrNoMatchingKeys_ReturnsFalse(string template)
+ {
+ var result = IntegrationTemplateProcessor.TemplateRequiresGroup(template);
+ Assert.False(result);
+ }
+
[Theory]
[InlineData("Organization: #OrganizationName#")]
[InlineData("Welcome to #OrganizationName#")]
diff --git a/util/Migrator/DbScripts/2025-11-05_00_OrganizationUserUserDetails_ReadByOrganizationIdUserId.sql b/util/Migrator/DbScripts/2025-11-05_00_OrganizationUserUserDetails_ReadByOrganizationIdUserId.sql
new file mode 100644
index 000000000000..ea599d0776f8
--- /dev/null
+++ b/util/Migrator/DbScripts/2025-11-05_00_OrganizationUserUserDetails_ReadByOrganizationIdUserId.sql
@@ -0,0 +1,17 @@
+CREATE OR ALTER PROCEDURE [dbo].[OrganizationUserUserDetails_ReadByOrganizationIdUserId]
+ @OrganizationId UNIQUEIDENTIFIER,
+ @UserId UNIQUEIDENTIFIER
+AS
+BEGIN
+ SET NOCOUNT ON
+
+SELECT
+ TOP 1 *
+FROM
+ [dbo].[OrganizationUserUserDetailsView]
+WHERE
+ [OrganizationId] = @OrganizationId
+ AND
+ [UserId] = @UserId
+END
+go