[SM-1575] Add ability to retrieve events based on projectId/SecretId (#6316)

* adding event filters

* allow user to see deleted secret event logs through public api

* nullable changes to event controller

* fixing tests

* fixing permissions issues with public API

* fix for bug

* Update src/Api/AdminConsole/Public/Controllers/EventsController.cs

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

* Update src/Api/AdminConsole/Public/Controllers/EventsController.cs

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

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
This commit is contained in:
cd-bitwarden 2025-11-14 18:21:48 -05:00 committed by GitHub
parent 7eaca9bb7d
commit 1274fe6562
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 8 deletions

View File

@ -1,6 +1,4 @@
// FIXME: Update this file to be null safe and then delete the line below 
#nullable disable
using System.Net; using System.Net;
using Bit.Api.Models.Public.Request; using Bit.Api.Models.Public.Request;
using Bit.Api.Models.Public.Response; using Bit.Api.Models.Public.Response;
@ -8,6 +6,7 @@ using Bit.Api.Utilities.DiagnosticTools;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Vault.Repositories; using Bit.Core.Vault.Repositories;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -22,6 +21,9 @@ public class EventsController : Controller
private readonly IEventRepository _eventRepository; private readonly IEventRepository _eventRepository;
private readonly ICipherRepository _cipherRepository; private readonly ICipherRepository _cipherRepository;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository;
private readonly IUserService _userService;
private readonly ILogger<EventsController> _logger; private readonly ILogger<EventsController> _logger;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
@ -29,12 +31,18 @@ public class EventsController : Controller
IEventRepository eventRepository, IEventRepository eventRepository,
ICipherRepository cipherRepository, ICipherRepository cipherRepository,
ICurrentContext currentContext, ICurrentContext currentContext,
ISecretRepository secretRepository,
IProjectRepository projectRepository,
IUserService userService,
ILogger<EventsController> logger, ILogger<EventsController> logger,
IFeatureService featureService) IFeatureService featureService)
{ {
_eventRepository = eventRepository; _eventRepository = eventRepository;
_cipherRepository = cipherRepository; _cipherRepository = cipherRepository;
_currentContext = currentContext; _currentContext = currentContext;
_secretRepository = secretRepository;
_projectRepository = projectRepository;
_userService = userService;
_logger = logger; _logger = logger;
_featureService = featureService; _featureService = featureService;
} }
@ -50,35 +58,76 @@ public class EventsController : Controller
[ProducesResponseType(typeof(PagedListResponseModel<EventResponseModel>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(PagedListResponseModel<EventResponseModel>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> List([FromQuery] EventFilterRequestModel request) public async Task<IActionResult> List([FromQuery] EventFilterRequestModel request)
{ {
if (!_currentContext.OrganizationId.HasValue)
{
return new JsonResult(new PagedListResponseModel<EventResponseModel>([], ""));
}
var organizationId = _currentContext.OrganizationId.Value;
var dateRange = request.ToDateRange(); var dateRange = request.ToDateRange();
var result = new PagedResult<IEvent>(); var result = new PagedResult<IEvent>();
if (request.ActingUserId.HasValue) if (request.ActingUserId.HasValue)
{ {
result = await _eventRepository.GetManyByOrganizationActingUserAsync( result = await _eventRepository.GetManyByOrganizationActingUserAsync(
_currentContext.OrganizationId.Value, request.ActingUserId.Value, dateRange.Item1, dateRange.Item2, organizationId, request.ActingUserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = request.ContinuationToken }); new PageOptions { ContinuationToken = request.ContinuationToken });
} }
else if (request.ItemId.HasValue) else if (request.ItemId.HasValue)
{ {
var cipher = await _cipherRepository.GetByIdAsync(request.ItemId.Value); var cipher = await _cipherRepository.GetByIdAsync(request.ItemId.Value);
if (cipher != null && cipher.OrganizationId == _currentContext.OrganizationId.Value) if (cipher != null && cipher.OrganizationId == organizationId)
{ {
result = await _eventRepository.GetManyByCipherAsync( result = await _eventRepository.GetManyByCipherAsync(
cipher, dateRange.Item1, dateRange.Item2, cipher, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = request.ContinuationToken }); new PageOptions { ContinuationToken = request.ContinuationToken });
} }
} }
else if (request.SecretId.HasValue)
{
var secret = await _secretRepository.GetByIdAsync(request.SecretId.Value);
if (secret == null)
{
secret = new Core.SecretsManager.Entities.Secret { Id = request.SecretId.Value, OrganizationId = organizationId };
}
if (secret.OrganizationId == organizationId)
{
result = await _eventRepository.GetManyBySecretAsync(
secret, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = request.ContinuationToken });
}
else
{
return new JsonResult(new PagedListResponseModel<EventResponseModel>([], ""));
}
}
else if (request.ProjectId.HasValue)
{
var project = await _projectRepository.GetByIdAsync(request.ProjectId.Value);
if (project != null && project.OrganizationId == organizationId)
{
result = await _eventRepository.GetManyByProjectAsync(
project, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = request.ContinuationToken });
}
else
{
return new JsonResult(new PagedListResponseModel<EventResponseModel>([], ""));
}
}
else else
{ {
result = await _eventRepository.GetManyByOrganizationAsync( result = await _eventRepository.GetManyByOrganizationAsync(
_currentContext.OrganizationId.Value, dateRange.Item1, dateRange.Item2, organizationId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = request.ContinuationToken }); new PageOptions { ContinuationToken = request.ContinuationToken });
} }
var eventResponses = result.Data.Select(e => new EventResponseModel(e)); var eventResponses = result.Data.Select(e => new EventResponseModel(e));
var response = new PagedListResponseModel<EventResponseModel>(eventResponses, result.ContinuationToken); var response = new PagedListResponseModel<EventResponseModel>(eventResponses, result.ContinuationToken ?? "");
_logger.LogAggregateData(_featureService, organizationId, response, request);
_logger.LogAggregateData(_featureService, _currentContext.OrganizationId!.Value, response, request);
return new JsonResult(response); return new JsonResult(response);
} }
} }

View File

@ -24,6 +24,14 @@ public class EventFilterRequestModel
/// </summary> /// </summary>
public Guid? ItemId { get; set; } public Guid? ItemId { get; set; }
/// <summary> /// <summary>
/// The unique identifier of the related secret that the event describes.
/// </summary>
public Guid? SecretId { get; set; }
/// <summary>
/// The unique identifier of the related project that the event describes.
/// </summary>
public Guid? ProjectId { get; set; }
/// <summary>
/// A cursor for use in pagination. /// A cursor for use in pagination.
/// </summary> /// </summary>
public string ContinuationToken { get; set; } public string ContinuationToken { get; set; }