1
0
mirror of https://github.com/bitwarden/server synced 2025-12-30 23:23:37 +00:00

Add template properties for Datadog (#6528)

* Add template properites for Datadog

* Add test and implementation for including User and ActingUser when only the Type is referenced

* Refactored database calls to fetch the user details in a single DB call

* Refactor to use a dedicated stored procedure for Dapper

* Remove TOP 1 from stored procedure

* Accept Claude's optimization of SingleOrDefaultAsync to unify Dapper/EF

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* Revert earlier change and add TOP 1 back into stored procedure

* Change go to GO

* Revert back to version that assumes uniqueness, remove TOP 1

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
This commit is contained in:
Brant DeBow
2025-11-24 10:30:45 -05:00
committed by GitHub
parent fcc879bd23
commit 9573cab37e
12 changed files with 259 additions and 40 deletions

View File

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

View File

@@ -97,4 +97,15 @@ public interface IOrganizationUserRepository : IRepository<OrganizationUser, Gui
/// <param name="organizationUserToConfirm">Accepted OrganizationUser to confirm</param>
/// <returns>True, if the user was updated. False, if not performed.</returns>
Task<bool> ConfirmOrganizationUserAsync(AcceptedOrganizationUserToConfirm organizationUserToConfirm);
/// <summary>
/// Returns the OrganizationUserUserDetails if found.
/// </summary>
/// <param name="organizationId">The id of the organization</param>
/// <param name="userId">The id of the User to fetch</param>
/// <returns>OrganizationUserUserDetails of the specified user or null if not found</returns>
/// <remarks>
/// Similar to GetByOrganizationAsync, but returns the user details.
/// </remarks>
Task<OrganizationUserUserDetails?> GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId);
}

View File

@@ -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<T>(
IEventIntegrationPublisher eventIntegrationPublisher,
IIntegrationFilterService integrationFilterService,
IIntegrationConfigurationDetailsCache configurationCache,
IUserRepository userRepository,
IGroupRepository groupRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ILogger<EventIntegrationHandler<T>> logger)
: IEventMessageHandler
{
@@ -89,19 +91,35 @@ public class EventIntegrationHandler<T>(
{
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;

View File

@@ -26,7 +26,7 @@ public static partial class IntegrationTemplateProcessor
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 partial class IntegrationTemplateProcessor
}
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 partial class IntegrationTemplateProcessor
}
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)