1
0
mirror of https://github.com/bitwarden/server synced 2026-01-02 16:43:25 +00:00

Merge branch 'main' into auth/pm-22975/client-version-validator

This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-11-24 11:37:52 -05:00
committed by GitHub
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)

View File

@@ -688,4 +688,21 @@ public class OrganizationUserRepository : Repository<OrganizationUser, Guid>, IO
return rowCount > 0;
}
public async Task<OrganizationUserUserDetails?> GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var result = await connection.QuerySingleOrDefaultAsync<OrganizationUserUserDetails>(
"[dbo].[OrganizationUserUserDetails_ReadByOrganizationIdUserId]",
new
{
OrganizationId = organizationId,
UserId = userId
},
commandType: CommandType.StoredProcedure);
return result;
}
}
}

View File

@@ -965,4 +965,20 @@ public class OrganizationUserRepository : Repository<Core.Entities.OrganizationU
return true;
}
#nullable enable
public async Task<OrganizationUserUserDetails?> GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var view = new OrganizationUserUserDetailsViewQuery();
var entity = await view.Run(dbContext).SingleOrDefaultAsync(ou => ou.OrganizationId == organizationId && ou.UserId == userId);
return entity;
}
}
#nullable disable
}

View File

@@ -12,6 +12,7 @@ using Bit.Core.AdminConsole.Models.Business.Tokenables;
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;
@@ -889,8 +890,9 @@ public static class ServiceCollectionExtensions
eventIntegrationPublisher: provider.GetRequiredService<IEventIntegrationPublisher>(),
integrationFilterService: provider.GetRequiredService<IIntegrationFilterService>(),
configurationCache: provider.GetRequiredService<IIntegrationConfigurationDetailsCache>(),
userRepository: provider.GetRequiredService<IUserRepository>(),
groupRepository: provider.GetRequiredService<IGroupRepository>(),
organizationRepository: provider.GetRequiredService<IOrganizationRepository>(),
organizationUserRepository: provider.GetRequiredService<IOrganizationUserRepository>(),
logger: provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>()
)
);
@@ -1016,8 +1018,9 @@ public static class ServiceCollectionExtensions
eventIntegrationPublisher: provider.GetRequiredService<IEventIntegrationPublisher>(),
integrationFilterService: provider.GetRequiredService<IIntegrationFilterService>(),
configurationCache: provider.GetRequiredService<IIntegrationConfigurationDetailsCache>(),
userRepository: provider.GetRequiredService<IUserRepository>(),
groupRepository: provider.GetRequiredService<IGroupRepository>(),
organizationRepository: provider.GetRequiredService<IOrganizationRepository>(),
organizationUserRepository: provider.GetRequiredService<IOrganizationUserRepository>(),
logger: provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>()
)
);

View File

@@ -0,0 +1,17 @@
CREATE PROCEDURE [dbo].[OrganizationUserUserDetails_ReadByOrganizationIdUserId]
@OrganizationId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[OrganizationUserUserDetailsView]
WHERE
[OrganizationId] = @OrganizationId
AND
[UserId] = @UserId
END
GO