1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00

[SM-1489] machine account events (#6187)

* Adding new logging for secrets

* fixing secrest controller tests

* fixing the tests

* Server side changes for adding ProjectId to Event table, adding Project event logging to projectsController

* Rough draft with TODO's need to work on EventRepository.cs, and ProjectRepository.cs

* Undoing changes to make projects soft delete, we want those to be fully deleted still. Adding GetManyTrashedSecretsByIds to secret repo so we can get soft deleted secrets, getSecrets in eventsController takes in orgdId, so that we can check the permission even if the secret was permanently deleted and doesn' thave the org Id set. Adding Secret Perm Deleted, and Restored to event logs

* db changes

* fixing the way we log events

* Trying to undo some manual changes that should have been migrations

* adding migration files

* fixing test

* setting up userid for project controller tests

* adding sql

* sql

* Rename file

* Trying to get it to for sure add the column before we try and update sprocs

* Adding code to refresh the view to include ProjectId I hope

* code improvements

* Suggested changes

* suggested changes

* trying to fix sql issues

* fixing swagger issue

* Update src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs

Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>

* Suggested changes

* Adding event logging for machine accounts

* fixing two tests

* trying to fix all tests

* trying to fix tests

* fixing test

* Migrations

* fix

* updating eps

* adding migration

* Adding missing SQL changes

* updating sql

* fixing sql

* running migration again

* fixing sql

* adding query to add grantedSErviceAccountId to event table

* Suggested improvements

* removing more migrations

* more removal

* removing all migrations to them redo them

* redoing migration

---------

Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
cd-bitwarden
2025-10-01 09:13:49 -04:00
committed by GitHub
parent 721fda0aaa
commit bca1d585c5
40 changed files with 20553 additions and 28 deletions

View File

@@ -1,10 +1,13 @@
// FIXME: Update this file to be null safe and then delete the line below // FIXME: Update this file to be null safe and then delete the line below
#nullable disable #nullable disable
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
namespace Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts; namespace Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
@@ -13,15 +16,21 @@ public class CreateServiceAccountCommand : ICreateServiceAccountCommand
private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IEventService _eventService;
private readonly ICurrentContext _currentContext;
public CreateServiceAccountCommand( public CreateServiceAccountCommand(
IAccessPolicyRepository accessPolicyRepository, IAccessPolicyRepository accessPolicyRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IServiceAccountRepository serviceAccountRepository) IServiceAccountRepository serviceAccountRepository,
IEventService eventService,
ICurrentContext currentContext)
{ {
_accessPolicyRepository = accessPolicyRepository; _accessPolicyRepository = accessPolicyRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_serviceAccountRepository = serviceAccountRepository; _serviceAccountRepository = serviceAccountRepository;
_eventService = eventService;
_currentContext = currentContext;
} }
public async Task<ServiceAccount> CreateAsync(ServiceAccount serviceAccount, Guid userId) public async Task<ServiceAccount> CreateAsync(ServiceAccount serviceAccount, Guid userId)
@@ -38,6 +47,7 @@ public class CreateServiceAccountCommand : ICreateServiceAccountCommand
Write = true, Write = true,
}; };
await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> { accessPolicy }); await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> { accessPolicy });
await _eventService.LogServiceAccountPeopleEventAsync(user.Id, accessPolicy, EventType.ServiceAccount_UserAdded, _currentContext.IdentityClientType);
return createdServiceAccount; return createdServiceAccount;
} }
} }

View File

@@ -30,6 +30,8 @@ public class EventsController : Controller
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly ISecretRepository _secretRepository; private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository; private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
public EventsController( public EventsController(
IUserService userService, IUserService userService,
@@ -39,7 +41,8 @@ public class EventsController : Controller
IEventRepository eventRepository, IEventRepository eventRepository,
ICurrentContext currentContext, ICurrentContext currentContext,
ISecretRepository secretRepository, ISecretRepository secretRepository,
IProjectRepository projectRepository) IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{ {
_userService = userService; _userService = userService;
_cipherRepository = cipherRepository; _cipherRepository = cipherRepository;
@@ -49,6 +52,7 @@ public class EventsController : Controller
_currentContext = currentContext; _currentContext = currentContext;
_secretRepository = secretRepository; _secretRepository = secretRepository;
_projectRepository = projectRepository; _projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
} }
[HttpGet("")] [HttpGet("")]
@@ -184,6 +188,57 @@ public class EventsController : Controller
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken); return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
} }
[HttpGet("~/organization/{orgId}/service-account/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetServiceAccounts(
Guid orgId,
Guid id,
[FromQuery] DateTime? start = null,
[FromQuery] DateTime? end = null,
[FromQuery] string continuationToken = null)
{
if (id == Guid.Empty || orgId == Guid.Empty)
{
throw new NotFoundException();
}
var serviceAccount = await GetServiceAccount(id, orgId);
var org = _currentContext.GetOrganization(orgId);
if (org == null || !await _currentContext.AccessEventLogs(org.Id))
{
throw new NotFoundException();
}
var (fromDate, toDate) = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationServiceAccountAsync(
serviceAccount.OrganizationId,
serviceAccount.Id,
fromDate,
toDate,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
[ApiExplorerSettings(IgnoreApi = true)]
private async Task<ServiceAccount> GetServiceAccount(Guid serviceAccountId, Guid orgId)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId);
if (serviceAccount != null)
{
return serviceAccount;
}
var fallbackServiceAccount = new ServiceAccount
{
Id = serviceAccountId,
OrganizationId = orgId
};
return fallbackServiceAccount;
}
[HttpGet("~/organizations/{orgId}/users/{id}/events")] [HttpGet("~/organizations/{orgId}/users/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetOrganizationUser(string orgId, string id, public async Task<ListResponseModel<EventResponseModel>> GetOrganizationUser(string orgId, string id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null) [FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)

View File

@@ -35,6 +35,7 @@ public class EventResponseModel : ResponseModel
SecretId = ev.SecretId; SecretId = ev.SecretId;
ProjectId = ev.ProjectId; ProjectId = ev.ProjectId;
ServiceAccountId = ev.ServiceAccountId; ServiceAccountId = ev.ServiceAccountId;
GrantedServiceAccountId = ev.GrantedServiceAccountId;
} }
public EventType Type { get; set; } public EventType Type { get; set; }
@@ -58,4 +59,5 @@ public class EventResponseModel : ResponseModel
public Guid? SecretId { get; set; } public Guid? SecretId { get; set; }
public Guid? ProjectId { get; set; } public Guid? ProjectId { get; set; }
public Guid? ServiceAccountId { get; set; } public Guid? ServiceAccountId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
} }

View File

@@ -29,6 +29,7 @@ public class AccessPoliciesController : Controller
private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand; private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IEventService _eventService;
private readonly IProjectServiceAccountsAccessPoliciesUpdatesQuery private readonly IProjectServiceAccountsAccessPoliciesUpdatesQuery
_projectServiceAccountsAccessPoliciesUpdatesQuery; _projectServiceAccountsAccessPoliciesUpdatesQuery;
private readonly IUpdateProjectServiceAccountsAccessPoliciesCommand private readonly IUpdateProjectServiceAccountsAccessPoliciesCommand
@@ -47,7 +48,8 @@ public class AccessPoliciesController : Controller
IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery, IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery,
IProjectServiceAccountsAccessPoliciesUpdatesQuery projectServiceAccountsAccessPoliciesUpdatesQuery, IProjectServiceAccountsAccessPoliciesUpdatesQuery projectServiceAccountsAccessPoliciesUpdatesQuery,
IUpdateServiceAccountGrantedPoliciesCommand updateServiceAccountGrantedPoliciesCommand, IUpdateServiceAccountGrantedPoliciesCommand updateServiceAccountGrantedPoliciesCommand,
IUpdateProjectServiceAccountsAccessPoliciesCommand updateProjectServiceAccountsAccessPoliciesCommand) IUpdateProjectServiceAccountsAccessPoliciesCommand updateProjectServiceAccountsAccessPoliciesCommand,
IEventService eventService)
{ {
_authorizationService = authorizationService; _authorizationService = authorizationService;
_userService = userService; _userService = userService;
@@ -61,6 +63,7 @@ public class AccessPoliciesController : Controller
_serviceAccountGrantedPolicyUpdatesQuery = serviceAccountGrantedPolicyUpdatesQuery; _serviceAccountGrantedPolicyUpdatesQuery = serviceAccountGrantedPolicyUpdatesQuery;
_projectServiceAccountsAccessPoliciesUpdatesQuery = projectServiceAccountsAccessPoliciesUpdatesQuery; _projectServiceAccountsAccessPoliciesUpdatesQuery = projectServiceAccountsAccessPoliciesUpdatesQuery;
_updateProjectServiceAccountsAccessPoliciesCommand = updateProjectServiceAccountsAccessPoliciesCommand; _updateProjectServiceAccountsAccessPoliciesCommand = updateProjectServiceAccountsAccessPoliciesCommand;
_eventService = eventService;
} }
[HttpGet("/organizations/{id}/access-policies/people/potential-grantees")] [HttpGet("/organizations/{id}/access-policies/people/potential-grantees")]
@@ -186,7 +189,9 @@ public class AccessPoliciesController : Controller
} }
var userId = _userService.GetProperUserId(User)!.Value; var userId = _userService.GetProperUserId(User)!.Value;
var currentPolicies = await _accessPolicyRepository.GetPeoplePoliciesByGrantedServiceAccountIdAsync(peopleAccessPolicies.Id, userId);
var results = await _accessPolicyRepository.ReplaceServiceAccountPeopleAsync(peopleAccessPolicies, userId); var results = await _accessPolicyRepository.ReplaceServiceAccountPeopleAsync(peopleAccessPolicies, userId);
await LogAccessPolicyServiceAccountChanges(currentPolicies, results, userId);
return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId); return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId);
} }
@@ -336,4 +341,39 @@ public class AccessPoliciesController : Controller
userId, accessClient); userId, accessClient);
return new ServiceAccountGrantedPoliciesPermissionDetailsResponseModel(results); return new ServiceAccountGrantedPoliciesPermissionDetailsResponseModel(results);
} }
public async Task LogAccessPolicyServiceAccountChanges(IEnumerable<BaseAccessPolicy> currentPolicies, IEnumerable<BaseAccessPolicy> updatedPolicies, Guid userId)
{
foreach (var current in currentPolicies.OfType<GroupServiceAccountAccessPolicy>())
{
if (!updatedPolicies.Any(r => r.Id == current.Id))
{
await _eventService.LogServiceAccountGroupEventAsync(userId, current, EventType.ServiceAccount_GroupRemoved, _currentContext.IdentityClientType);
}
}
foreach (var policy in updatedPolicies.OfType<GroupServiceAccountAccessPolicy>())
{
if (!currentPolicies.Any(e => e.Id == policy.Id))
{
await _eventService.LogServiceAccountGroupEventAsync(userId, policy, EventType.ServiceAccount_GroupAdded, _currentContext.IdentityClientType);
}
}
foreach (var current in currentPolicies.OfType<UserServiceAccountAccessPolicy>())
{
if (!updatedPolicies.Any(r => r.Id == current.Id))
{
await _eventService.LogServiceAccountPeopleEventAsync(userId, current, EventType.ServiceAccount_UserRemoved, _currentContext.IdentityClientType);
}
}
foreach (var policy in updatedPolicies.OfType<UserServiceAccountAccessPolicy>())
{
if (!currentPolicies.Any(e => e.Id == policy.Id))
{
await _eventService.LogServiceAccountPeopleEventAsync(userId, policy, EventType.ServiceAccount_UserAdded, _currentContext.IdentityClientType);
}
}
}
} }

View File

@@ -42,6 +42,8 @@ public class ServiceAccountsController : Controller
private readonly IDeleteServiceAccountsCommand _deleteServiceAccountsCommand; private readonly IDeleteServiceAccountsCommand _deleteServiceAccountsCommand;
private readonly IRevokeAccessTokensCommand _revokeAccessTokensCommand; private readonly IRevokeAccessTokensCommand _revokeAccessTokensCommand;
private readonly IPricingClient _pricingClient; private readonly IPricingClient _pricingClient;
private readonly IEventService _eventService;
private readonly IOrganizationUserRepository _organizationUserRepository;
public ServiceAccountsController( public ServiceAccountsController(
ICurrentContext currentContext, ICurrentContext currentContext,
@@ -58,7 +60,9 @@ public class ServiceAccountsController : Controller
IUpdateServiceAccountCommand updateServiceAccountCommand, IUpdateServiceAccountCommand updateServiceAccountCommand,
IDeleteServiceAccountsCommand deleteServiceAccountsCommand, IDeleteServiceAccountsCommand deleteServiceAccountsCommand,
IRevokeAccessTokensCommand revokeAccessTokensCommand, IRevokeAccessTokensCommand revokeAccessTokensCommand,
IPricingClient pricingClient) IPricingClient pricingClient,
IEventService eventService,
IOrganizationUserRepository organizationUserRepository)
{ {
_currentContext = currentContext; _currentContext = currentContext;
_userService = userService; _userService = userService;
@@ -75,6 +79,8 @@ public class ServiceAccountsController : Controller
_pricingClient = pricingClient; _pricingClient = pricingClient;
_createAccessTokenCommand = createAccessTokenCommand; _createAccessTokenCommand = createAccessTokenCommand;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand; _updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_eventService = eventService;
_organizationUserRepository = organizationUserRepository;
} }
[HttpGet("/organizations/{organizationId}/service-accounts")] [HttpGet("/organizations/{organizationId}/service-accounts")]
@@ -139,8 +145,15 @@ public class ServiceAccountsController : Controller
} }
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var result = var result =
await _createServiceAccountCommand.CreateAsync(createRequest.ToServiceAccount(organizationId), userId); await _createServiceAccountCommand.CreateAsync(serviceAccount, userId);
if (result != null)
{
await _eventService.LogServiceAccountEventAsync(userId, [serviceAccount], EventType.ServiceAccount_Created, _currentContext.IdentityClientType);
}
return new ServiceAccountResponseModel(result); return new ServiceAccountResponseModel(result);
} }
@@ -197,6 +210,9 @@ public class ServiceAccountsController : Controller
} }
await _deleteServiceAccountsCommand.DeleteServiceAccounts(serviceAccountsToDelete); await _deleteServiceAccountsCommand.DeleteServiceAccounts(serviceAccountsToDelete);
var userId = _userService.GetProperUserId(User)!.Value;
await _eventService.LogServiceAccountEventAsync(userId, serviceAccountsToDelete, EventType.ServiceAccount_Deleted, _currentContext.IdentityClientType);
var responses = results.Select(r => new BulkDeleteResponseModel(r.ServiceAccount.Id, r.Error)); var responses = results.Select(r => new BulkDeleteResponseModel(r.ServiceAccount.Id, r.Error));
return new ListResponseModel<BulkDeleteResponseModel>(responses); return new ListResponseModel<BulkDeleteResponseModel>(responses);
} }

View File

@@ -34,6 +34,7 @@ public class Event : ITableObject<Guid>, IEvent
SecretId = e.SecretId; SecretId = e.SecretId;
ProjectId = e.ProjectId; ProjectId = e.ProjectId;
ServiceAccountId = e.ServiceAccountId; ServiceAccountId = e.ServiceAccountId;
GrantedServiceAccountId = e.GrantedServiceAccountId;
} }
public Guid Id { get; set; } public Guid Id { get; set; }
@@ -59,7 +60,7 @@ public class Event : ITableObject<Guid>, IEvent
public Guid? SecretId { get; set; } public Guid? SecretId { get; set; }
public Guid? ProjectId { get; set; } public Guid? ProjectId { get; set; }
public Guid? ServiceAccountId { get; set; } public Guid? ServiceAccountId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public void SetNewId() public void SetNewId()
{ {
Id = CoreHelpers.GenerateComb(); Id = CoreHelpers.GenerateComb();

View File

@@ -109,4 +109,11 @@ public enum EventType : int
Project_Created = 2201, Project_Created = 2201,
Project_Edited = 2202, Project_Edited = 2202,
Project_Deleted = 2203, Project_Deleted = 2203,
ServiceAccount_UserAdded = 2300,
ServiceAccount_UserRemoved = 2301,
ServiceAccount_GroupAdded = 2302,
ServiceAccount_GroupRemoved = 2303,
ServiceAccount_Created = 2304,
ServiceAccount_Deleted = 2305,
} }

View File

@@ -39,4 +39,5 @@ public class EventMessage : IEvent
public Guid? SecretId { get; set; } public Guid? SecretId { get; set; }
public Guid? ProjectId { get; set; } public Guid? ProjectId { get; set; }
public Guid? ServiceAccountId { get; set; } public Guid? ServiceAccountId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
} }

View File

@@ -37,6 +37,7 @@ public class AzureEvent : ITableEntity
public Guid? SecretId { get; set; } public Guid? SecretId { get; set; }
public Guid? ProjectId { get; set; } public Guid? ProjectId { get; set; }
public Guid? ServiceAccountId { get; set; } public Guid? ServiceAccountId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public EventTableEntity ToEventTableEntity() public EventTableEntity ToEventTableEntity()
{ {
@@ -68,6 +69,7 @@ public class AzureEvent : ITableEntity
SecretId = SecretId, SecretId = SecretId,
ServiceAccountId = ServiceAccountId, ServiceAccountId = ServiceAccountId,
ProjectId = ProjectId, ProjectId = ProjectId,
GrantedServiceAccountId = GrantedServiceAccountId
}; };
} }
} }
@@ -99,6 +101,7 @@ public class EventTableEntity : IEvent
SecretId = e.SecretId; SecretId = e.SecretId;
ProjectId = e.ProjectId; ProjectId = e.ProjectId;
ServiceAccountId = e.ServiceAccountId; ServiceAccountId = e.ServiceAccountId;
GrantedServiceAccountId = e.GrantedServiceAccountId;
} }
public string PartitionKey { get; set; } public string PartitionKey { get; set; }
@@ -127,6 +130,7 @@ public class EventTableEntity : IEvent
public Guid? SecretId { get; set; } public Guid? SecretId { get; set; }
public Guid? ProjectId { get; set; } public Guid? ProjectId { get; set; }
public Guid? ServiceAccountId { get; set; } public Guid? ServiceAccountId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public AzureEvent ToAzureEvent() public AzureEvent ToAzureEvent()
{ {
@@ -157,7 +161,8 @@ public class EventTableEntity : IEvent
DomainName = DomainName, DomainName = DomainName,
SecretId = SecretId, SecretId = SecretId,
ProjectId = ProjectId, ProjectId = ProjectId,
ServiceAccountId = ServiceAccountId ServiceAccountId = ServiceAccountId,
GrantedServiceAccountId = GrantedServiceAccountId
}; };
} }
@@ -232,6 +237,15 @@ public class EventTableEntity : IEvent
}); });
} }
if (e.GrantedServiceAccountId.HasValue)
{
entities.Add(new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"GrantedServiceAccountId={e.GrantedServiceAccountId}__Date={dateKey}__Uniquifier={uniquifier}"
});
}
return entities; return entities;
} }

View File

@@ -28,4 +28,5 @@ public interface IEvent
Guid? SecretId { get; set; } Guid? SecretId { get; set; }
Guid? ProjectId { get; set; } Guid? ProjectId { get; set; }
Guid? ServiceAccountId { get; set; } Guid? ServiceAccountId { get; set; }
Guid? GrantedServiceAccountId { get; set; }
} }

View File

@@ -27,6 +27,7 @@ public interface IEventRepository
DateTime startDate, DateTime endDate, PageOptions pageOptions); DateTime startDate, DateTime endDate, PageOptions pageOptions);
Task<PagedResult<IEvent>> GetManyByCipherAsync(Cipher cipher, DateTime startDate, DateTime endDate, Task<PagedResult<IEvent>> GetManyByCipherAsync(Cipher cipher, DateTime startDate, DateTime endDate,
PageOptions pageOptions); PageOptions pageOptions);
Task CreateAsync(IEvent e); Task CreateAsync(IEvent e);
Task CreateManyAsync(IEnumerable<IEvent> e); Task CreateManyAsync(IEnumerable<IEvent> e);
Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(Guid organizationId, Guid serviceAccountId, Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(Guid organizationId, Guid serviceAccountId,

View File

@@ -77,12 +77,18 @@ public class EventRepository : IEventRepository
return await GetManyAsync(partitionKey, $"CipherId={cipher.Id}__Date={{0}}", startDate, endDate, pageOptions); return await GetManyAsync(partitionKey, $"CipherId={cipher.Id}__Date={{0}}", startDate, endDate, pageOptions);
} }
public async Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(Guid organizationId, public async Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(
Guid serviceAccountId, DateTime startDate, DateTime endDate, PageOptions pageOptions) Guid organizationId,
Guid serviceAccountId,
DateTime startDate,
DateTime endDate,
PageOptions pageOptions)
{ {
return await GetManyServiceAccountAsync(
$"OrganizationId={organizationId}",
serviceAccountId.ToString(),
startDate, endDate, pageOptions);
return await GetManyAsync($"OrganizationId={organizationId}",
$"ServiceAccountId={serviceAccountId}__Date={{0}}", startDate, endDate, pageOptions);
} }
public async Task CreateAsync(IEvent e) public async Task CreateAsync(IEvent e)
@@ -141,6 +147,40 @@ public class EventRepository : IEventRepository
} }
} }
public async Task<PagedResult<IEvent>> GetManyServiceAccountAsync(
string partitionKey,
string serviceAccountId,
DateTime startDate,
DateTime endDate,
PageOptions pageOptions)
{
var start = CoreHelpers.DateTimeToTableStorageKey(startDate);
var end = CoreHelpers.DateTimeToTableStorageKey(endDate);
var filter = MakeFilterForServiceAccount(partitionKey, serviceAccountId, startDate, endDate);
var result = new PagedResult<IEvent>();
var query = _tableClient.QueryAsync<AzureEvent>(filter, pageOptions.PageSize);
await using (var enumerator = query.AsPages(pageOptions.ContinuationToken,
pageOptions.PageSize).GetAsyncEnumerator())
{
if (await enumerator.MoveNextAsync())
{
result.ContinuationToken = enumerator.Current.ContinuationToken;
var events = enumerator.Current.Values
.Select(e => e.ToEventTableEntity())
.ToList();
events = events.OrderByDescending(e => e.Date).ToList();
result.Data.AddRange(events);
}
}
return result;
}
public async Task<PagedResult<IEvent>> GetManyAsync(string partitionKey, string rowKey, public async Task<PagedResult<IEvent>> GetManyAsync(string partitionKey, string rowKey,
DateTime startDate, DateTime endDate, PageOptions pageOptions) DateTime startDate, DateTime endDate, PageOptions pageOptions)
{ {
@@ -172,4 +212,27 @@ public class EventRepository : IEventRepository
{ {
return $"PartitionKey eq '{partitionKey}' and RowKey le '{rowStart}' and RowKey ge '{rowEnd}'"; return $"PartitionKey eq '{partitionKey}' and RowKey le '{rowStart}' and RowKey ge '{rowEnd}'";
} }
private string MakeFilterForServiceAccount(
string partitionKey,
string machineAccountId,
DateTime startDate,
DateTime endDate)
{
var start = CoreHelpers.DateTimeToTableStorageKey(startDate);
var end = CoreHelpers.DateTimeToTableStorageKey(endDate);
var rowKey1Start = $"ServiceAccountId={machineAccountId}__Date={start}";
var rowKey1End = $"ServiceAccountId={machineAccountId}__Date={end}";
var rowKey2Start = $"GrantedServiceAccountId={machineAccountId}__Date={start}";
var rowKey2End = $"GrantedServiceAccountId={machineAccountId}__Date={end}";
var left = $"PartitionKey eq '{partitionKey}' and RowKey le '{rowKey1Start}' and RowKey ge '{rowKey1End}'";
var right = $"PartitionKey eq '{partitionKey}' and RowKey le '{rowKey2Start}' and RowKey ge '{rowKey2End}'";
return $"({left}) or ({right})";
}
} }

View File

@@ -4,6 +4,7 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Interfaces; using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.Auth.Identity;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
@@ -37,4 +38,7 @@ public interface IEventService
Task LogServiceAccountSecretsEventAsync(Guid serviceAccountId, IEnumerable<Secret> secrets, EventType type, DateTime? date = null); Task LogServiceAccountSecretsEventAsync(Guid serviceAccountId, IEnumerable<Secret> secrets, EventType type, DateTime? date = null);
Task LogUserProjectsEventAsync(Guid userId, IEnumerable<Project> projects, EventType type, DateTime? date = null); Task LogUserProjectsEventAsync(Guid userId, IEnumerable<Project> projects, EventType type, DateTime? date = null);
Task LogServiceAccountProjectsEventAsync(Guid serviceAccountId, IEnumerable<Project> projects, EventType type, DateTime? date = null); Task LogServiceAccountProjectsEventAsync(Guid serviceAccountId, IEnumerable<Project> projects, EventType type, DateTime? date = null);
Task LogServiceAccountPeopleEventAsync(Guid userId, UserServiceAccountAccessPolicy policy, EventType type, IdentityClientType identityClientType, DateTime? date = null);
Task LogServiceAccountGroupEventAsync(Guid userId, GroupServiceAccountAccessPolicy policy, EventType type, IdentityClientType identityClientType, DateTime? date = null);
Task LogServiceAccountEventAsync(Guid userId, List<ServiceAccount> serviceAccount, EventType type, IdentityClientType identityClientType, DateTime? date = null);
} }

View File

@@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Interfaces; using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.AdminConsole.Models.Data.Provider;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Identity;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
@@ -516,6 +517,135 @@ public class EventService : IEventService
await _eventWriteService.CreateManyAsync(eventMessages); await _eventWriteService.CreateManyAsync(eventMessages);
} }
public async Task LogServiceAccountPeopleEventAsync(Guid userId, UserServiceAccountAccessPolicy policy, EventType type, IdentityClientType identityClientType, DateTime? date = null)
{
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
var eventMessages = new List<IEvent>();
var orgUser = await _organizationUserRepository.GetByIdAsync((Guid)policy.OrganizationUserId);
if (!CanUseEvents(orgAbilities, orgUser.OrganizationId))
{
return;
}
var (actingUserId, serviceAccountId) = MapIdentityClientType(userId, identityClientType);
if (actingUserId is null && serviceAccountId is null)
{
return;
}
if (policy.OrganizationUserId != null)
{
var e = new EventMessage(_currentContext)
{
OrganizationId = orgUser.OrganizationId,
Type = type,
GrantedServiceAccountId = policy.GrantedServiceAccountId,
ServiceAccountId = serviceAccountId,
UserId = policy.OrganizationUserId,
ActingUserId = actingUserId,
Date = date.GetValueOrDefault(DateTime.UtcNow)
};
eventMessages.Add(e);
await _eventWriteService.CreateManyAsync(eventMessages);
}
}
public async Task LogServiceAccountGroupEventAsync(Guid userId, GroupServiceAccountAccessPolicy policy, EventType type, IdentityClientType identityClientType, DateTime? date = null)
{
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
var eventMessages = new List<IEvent>();
if (!CanUseEvents(orgAbilities, policy.Group.OrganizationId))
{
return;
}
var (actingUserId, serviceAccountId) = MapIdentityClientType(userId, identityClientType);
if (actingUserId is null && serviceAccountId is null)
{
return;
}
if (policy.GroupId != null)
{
var e = new EventMessage(_currentContext)
{
OrganizationId = policy.Group.OrganizationId,
Type = type,
GrantedServiceAccountId = policy.GrantedServiceAccountId,
ServiceAccountId = serviceAccountId,
GroupId = policy.GroupId,
ActingUserId = actingUserId,
Date = date.GetValueOrDefault(DateTime.UtcNow)
};
eventMessages.Add(e);
await _eventWriteService.CreateManyAsync(eventMessages);
}
}
public async Task LogServiceAccountEventAsync(Guid userId, List<ServiceAccount> serviceAccounts, EventType type, IdentityClientType identityClientType, DateTime? date = null)
{
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
var eventMessages = new List<IEvent>();
foreach (var serviceAccount in serviceAccounts)
{
if (!CanUseEvents(orgAbilities, serviceAccount.OrganizationId))
{
continue;
}
var (actingUserId, serviceAccountId) = MapIdentityClientType(userId, identityClientType);
if (actingUserId is null && serviceAccountId is null)
{
continue;
}
if (serviceAccount != null)
{
var e = new EventMessage(_currentContext)
{
OrganizationId = serviceAccount.OrganizationId,
Type = type,
GrantedServiceAccountId = serviceAccount.Id,
ServiceAccountId = serviceAccountId,
ActingUserId = actingUserId,
Date = date.GetValueOrDefault(DateTime.UtcNow)
};
eventMessages.Add(e);
}
}
if (eventMessages.Any())
{
await _eventWriteService.CreateManyAsync(eventMessages);
}
}
private (Guid? actingUserId, Guid? serviceAccountId) MapIdentityClientType(
Guid userId, IdentityClientType identityClientType)
{
if (identityClientType == IdentityClientType.Organization)
{
return (null, null);
}
return identityClientType switch
{
IdentityClientType.User => (userId, null),
IdentityClientType.ServiceAccount => (null, userId),
_ => throw new InvalidOperationException("Unknown identity client type.")
};
}
private async Task<Guid?> GetProviderIdAsync(Guid? orgId) private async Task<Guid?> GetProviderIdAsync(Guid? orgId)
{ {
if (_currentContext == null || !orgId.HasValue) if (_currentContext == null || !orgId.HasValue)

View File

@@ -1,6 +1,7 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Interfaces; using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.Auth.Identity;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
@@ -139,4 +140,19 @@ public class NoopEventService : IEventService
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task LogServiceAccountPeopleEventAsync(Guid userId, UserServiceAccountAccessPolicy policy, EventType type, IdentityClientType identityClientType, DateTime? date = null)
{
return Task.FromResult(0);
}
public Task LogServiceAccountGroupEventAsync(Guid userId, GroupServiceAccountAccessPolicy policy, EventType type, IdentityClientType identityClientType, DateTime? date = null)
{
return Task.FromResult(0);
}
public Task LogServiceAccountEventAsync(Guid userId, List<ServiceAccount> serviceAccount, EventType type, IdentityClientType identityClientType, DateTime? date = null)
{
return Task.FromResult(0);
}
} }

View File

@@ -12,9 +12,16 @@ public class EventEntityTypeConfiguration : IEntityTypeConfiguration<Event>
.Property(e => e.Id) .Property(e => e.Id)
.ValueGeneratedNever(); .ValueGeneratedNever();
builder builder.HasKey(e => e.Id)
.HasIndex(e => new { e.Date, e.OrganizationId, e.ActingUserId, e.CipherId }) .IsClustered();
.IsClustered(false);
var index = builder.HasIndex(e => new { e.Date, e.OrganizationId, e.ActingUserId, e.CipherId })
.IsClustered(false)
.HasDatabaseName("IX_Event_DateOrganizationIdUserId");
SqlServerIndexBuilderExtensions.IncludeProperties(
index,
e => new { e.ServiceAccountId, e.GrantedServiceAccountId });
builder.ToTable(nameof(Event)); builder.ToTable(nameof(Event));
} }

View File

@@ -30,7 +30,7 @@ public class EventReadPageByOrganizationIdServiceAccountIdQuery : IQuery<Event>
(_beforeDate != null || e.Date <= _endDate) && (_beforeDate != null || e.Date <= _endDate) &&
(_beforeDate == null || e.Date < _beforeDate.Value) && (_beforeDate == null || e.Date < _beforeDate.Value) &&
e.OrganizationId == _organizationId && e.OrganizationId == _organizationId &&
e.ServiceAccountId == _serviceAccountId (e.ServiceAccountId == _serviceAccountId || e.GrantedServiceAccountId == _serviceAccountId)
orderby e.Date descending orderby e.Date descending
select e; select e;
return q.Skip(0).Take(_pageOptions.PageSize); return q.Skip(0).Take(_pageOptions.PageSize);

View File

@@ -0,0 +1,48 @@
using Bit.Core.Models.Data;
using Bit.Core.SecretsManager.Entities;
using Event = Bit.Infrastructure.EntityFramework.Models.Event;
namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
public class EventReadPageByServiceAccountQuery : IQuery<Event>
{
private readonly ServiceAccount _serviceAccount;
private readonly DateTime _startDate;
private readonly DateTime _endDate;
private readonly DateTime? _beforeDate;
private readonly PageOptions _pageOptions;
public EventReadPageByServiceAccountQuery(ServiceAccount serviceAccount, DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
_serviceAccount = serviceAccount;
_startDate = startDate;
_endDate = endDate;
_beforeDate = null;
_pageOptions = pageOptions;
}
public EventReadPageByServiceAccountQuery(ServiceAccount serviceAccount, DateTime startDate, DateTime endDate, DateTime? beforeDate, PageOptions pageOptions)
{
_serviceAccount = serviceAccount;
_startDate = startDate;
_endDate = endDate;
_beforeDate = beforeDate;
_pageOptions = pageOptions;
}
public IQueryable<Event> Run(DatabaseContext dbContext)
{
var q = from e in dbContext.Events
where e.Date >= _startDate &&
(_beforeDate == null || e.Date < _beforeDate.Value) &&
(
(_serviceAccount.OrganizationId == Guid.Empty && !e.OrganizationId.HasValue) ||
(_serviceAccount.OrganizationId != Guid.Empty && e.OrganizationId == _serviceAccount.OrganizationId)
) &&
e.GrantedServiceAccountId == _serviceAccount.Id
orderby e.Date descending
select e;
return q.Take(_pageOptions.PageSize);
}
}

View File

@@ -18,7 +18,7 @@ BEGIN
AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate) AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate)
AND (@BeforeDate IS NULL OR [Date] < @BeforeDate) AND (@BeforeDate IS NULL OR [Date] < @BeforeDate)
AND [OrganizationId] = @OrganizationId AND [OrganizationId] = @OrganizationId
AND [ServiceAccountId] = @ServiceAccountId AND ([ServiceAccountId] = @ServiceAccountId OR [GrantedServiceAccountId] = @ServiceAccountId)
ORDER BY [Date] DESC ORDER BY [Date] DESC
OFFSET 0 ROWS OFFSET 0 ROWS
FETCH NEXT @PageSize ROWS ONLY FETCH NEXT @PageSize ROWS ONLY

View File

@@ -0,0 +1,45 @@
CREATE PROCEDURE [dbo].[Event_ReadPageByServiceAccountId]
@GrantedServiceAccountId UNIQUEIDENTIFIER,
@StartDate DATETIME2(7),
@EndDate DATETIME2(7),
@BeforeDate DATETIME2(7),
@PageSize INT
AS
BEGIN
SET NOCOUNT ON
SELECT
e.Id,
e.Date,
e.Type,
e.UserId,
e.OrganizationId,
e.InstallationId,
e.ProviderId,
e.CipherId,
e.CollectionId,
e.PolicyId,
e.GroupId,
e.OrganizationUserId,
e.ProviderUserId,
e.ProviderOrganizationId,
e.DeviceType,
e.IpAddress,
e.ActingUserId,
e.SystemUser,
e.DomainName,
e.SecretId,
e.ServiceAccountId,
e.ProjectId,
e.GrantedServiceAccountId
FROM
[dbo].[EventView] e
WHERE
[Date] >= @StartDate
AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate)
AND (@BeforeDate IS NULL OR [Date] < @BeforeDate)
AND [GrantedServiceAccountId] = @GrantedServiceAccountId
ORDER BY [Date] DESC
OFFSET 0 ROWS
FETCH NEXT @PageSize ROWS ONLY
END

View File

@@ -20,7 +20,8 @@
@DomainName VARCHAR(256), @DomainName VARCHAR(256),
@SecretId UNIQUEIDENTIFIER = null, @SecretId UNIQUEIDENTIFIER = null,
@ServiceAccountId UNIQUEIDENTIFIER = null, @ServiceAccountId UNIQUEIDENTIFIER = null,
@ProjectId UNIQUEIDENTIFIER = null @ProjectId UNIQUEIDENTIFIER = null,
@GrantedServiceAccountId UNIQUEIDENTIFIER = null
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@@ -48,7 +49,8 @@ BEGIN
[DomainName], [DomainName],
[SecretId], [SecretId],
[ServiceAccountId], [ServiceAccountId],
[ProjectId] [ProjectId],
[GrantedServiceAccountId]
) )
VALUES VALUES
( (
@@ -73,6 +75,7 @@ BEGIN
@DomainName, @DomainName,
@SecretId, @SecretId,
@ServiceAccountId, @ServiceAccountId,
@ProjectId @ProjectId,
@GrantedServiceAccountId
) )
END END

View File

@@ -21,11 +21,12 @@
[SecretId] UNIQUEIDENTIFIER NULL, [SecretId] UNIQUEIDENTIFIER NULL,
[ServiceAccountId] UNIQUEIDENTIFIER NULL, [ServiceAccountId] UNIQUEIDENTIFIER NULL,
[ProjectId] UNIQUEIDENTIFIER NULL, [ProjectId] UNIQUEIDENTIFIER NULL,
[GrantedServiceAccountId] UNIQUEIDENTIFIER NULL,
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC) CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
); );
GO GO
CREATE NONCLUSTERED INDEX [IX_Event_DateOrganizationIdUserId] CREATE NONCLUSTERED INDEX [IX_Event_DateOrganizationIdUserId]
ON [dbo].[Event]([Date] DESC, [OrganizationId] ASC, [ActingUserId] ASC, [CipherId] ASC); ON [dbo].[Event]([Date] DESC, [OrganizationId] ASC, [ActingUserId] ASC, [CipherId] ASC) INCLUDE ([ServiceAccountId], [GrantedServiceAccountId]);

View File

@@ -361,7 +361,7 @@ public class ServiceAccountsControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task BulkDelete_ReturnsAccessDeniedForProjectsWithoutAccess_Success(SutProvider<ServiceAccountsController> sutProvider, List<ServiceAccount> data) public async Task BulkDelete_ReturnsAccessDeniedForProjectsWithoutAccess_Success(SutProvider<ServiceAccountsController> sutProvider, List<ServiceAccount> data, Guid userId)
{ {
var ids = data.Select(sa => sa.Id).ToList(); var ids = data.Select(sa => sa.Id).ToList();
var organizationId = data.First().OrganizationId; var organizationId = data.First().OrganizationId;
@@ -377,6 +377,7 @@ public class ServiceAccountsControllerTests
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).Returns(AuthorizationResult.Failed()); Arg.Any<IEnumerable<IAuthorizationRequirement>>()).Returns(AuthorizationResult.Failed());
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true); sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IServiceAccountRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); sutProvider.GetDependency<IServiceAccountRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
var results = await sutProvider.Sut.BulkDeleteAsync(ids); var results = await sutProvider.Sut.BulkDeleteAsync(ids);
@@ -390,7 +391,7 @@ public class ServiceAccountsControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task BulkDelete_Success(SutProvider<ServiceAccountsController> sutProvider, List<ServiceAccount> data) public async Task BulkDelete_Success(SutProvider<ServiceAccountsController> sutProvider, List<ServiceAccount> data, Guid userId)
{ {
var ids = data.Select(sa => sa.Id).ToList(); var ids = data.Select(sa => sa.Id).ToList();
var organizationId = data.First().OrganizationId; var organizationId = data.First().OrganizationId;
@@ -404,6 +405,7 @@ public class ServiceAccountsControllerTests
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true); sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IServiceAccountRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); sutProvider.GetDependency<IServiceAccountRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
var results = await sutProvider.Sut.BulkDeleteAsync(ids); var results = await sutProvider.Sut.BulkDeleteAsync(ids);

View File

@@ -0,0 +1,12 @@
IF COL_LENGTH('[dbo].[Event]', 'GrantedServiceAccountId') IS NULL
BEGIN
ALTER TABLE [dbo].[Event]
ADD [GrantedServiceAccountId] UNIQUEIDENTIFIER NULL;
END
GO
IF OBJECT_ID('[dbo].[EventView]', 'V') IS NOT NULL
BEGIN
EXECUTE sp_refreshview N'[dbo].[EventView]'
END
GO

View File

@@ -0,0 +1,189 @@
-- Create or alter Event_Create procedure
CREATE OR ALTER PROCEDURE [dbo].[Event_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@Type INT,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@InstallationId UNIQUEIDENTIFIER,
@ProviderId UNIQUEIDENTIFIER,
@CipherId UNIQUEIDENTIFIER,
@CollectionId UNIQUEIDENTIFIER,
@PolicyId UNIQUEIDENTIFIER,
@GroupId UNIQUEIDENTIFIER,
@OrganizationUserId UNIQUEIDENTIFIER,
@ProviderUserId UNIQUEIDENTIFIER,
@ProviderOrganizationId UNIQUEIDENTIFIER = NULL,
@ActingUserId UNIQUEIDENTIFIER,
@DeviceType SMALLINT,
@IpAddress VARCHAR(50),
@Date DATETIME2(7),
@SystemUser TINYINT = NULL,
@DomainName VARCHAR(256),
@SecretId UNIQUEIDENTIFIER = NULL,
@ServiceAccountId UNIQUEIDENTIFIER = NULL,
@ProjectId UNIQUEIDENTIFIER = NULL,
@GrantedServiceAccountId UNIQUEIDENTIFIER = NULL
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[Event]
(
[Id],
[Type],
[UserId],
[OrganizationId],
[InstallationId],
[ProviderId],
[CipherId],
[CollectionId],
[PolicyId],
[GroupId],
[OrganizationUserId],
[ProviderUserId],
[ProviderOrganizationId],
[ActingUserId],
[DeviceType],
[IpAddress],
[Date],
[SystemUser],
[DomainName],
[SecretId],
[ServiceAccountId],
[ProjectId],
[GrantedServiceAccountId]
)
VALUES
(
@Id,
@Type,
@UserId,
@OrganizationId,
@InstallationId,
@ProviderId,
@CipherId,
@CollectionId,
@PolicyId,
@GroupId,
@OrganizationUserId,
@ProviderUserId,
@ProviderOrganizationId,
@ActingUserId,
@DeviceType,
@IpAddress,
@Date,
@SystemUser,
@DomainName,
@SecretId,
@ServiceAccountId,
@ProjectId,
@GrantedServiceAccountId
);
END
GO
-- Create or alter Event_ReadPageByServiceAccountId procedure
CREATE OR ALTER PROCEDURE [dbo].[Event_ReadPageByServiceAccountId]
@GrantedServiceAccountId UNIQUEIDENTIFIER,
@StartDate DATETIME2(7),
@EndDate DATETIME2(7),
@BeforeDate DATETIME2(7),
@PageSize INT
AS
BEGIN
SET NOCOUNT ON;
SELECT
e.Id,
e.Date,
e.Type,
e.UserId,
e.OrganizationId,
e.InstallationId,
e.ProviderId,
e.CipherId,
e.CollectionId,
e.PolicyId,
e.GroupId,
e.OrganizationUserId,
e.ProviderUserId,
e.ProviderOrganizationId,
e.DeviceType,
e.IpAddress,
e.ActingUserId,
e.SystemUser,
e.DomainName,
e.SecretId,
e.ServiceAccountId,
e.ProjectId,
e.GrantedServiceAccountId
FROM
[dbo].[EventView] e
WHERE
[Date] >= @StartDate
AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate)
AND (@BeforeDate IS NULL OR [Date] < @BeforeDate)
AND [GrantedServiceAccountId] = @GrantedServiceAccountId
ORDER BY [Date] DESC
OFFSET 0 ROWS
FETCH NEXT @PageSize ROWS ONLY;
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Event_ReadPageByOrganizationIdServiceAccountId]
@OrganizationId UNIQUEIDENTIFIER,
@ServiceAccountId UNIQUEIDENTIFIER,
@StartDate DATETIME2(7),
@EndDate DATETIME2(7),
@BeforeDate DATETIME2(7),
@PageSize INT
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[EventView]
WHERE
[Date] >= @StartDate
AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate)
AND (@BeforeDate IS NULL OR [Date] < @BeforeDate)
AND [OrganizationId] = @OrganizationId
AND ([ServiceAccountId] = @ServiceAccountId OR [GrantedServiceAccountId] = @ServiceAccountId)
ORDER BY [Date] DESC
OFFSET 0 ROWS
FETCH NEXT @PageSize ROWS ONLY
END
GO
IF EXISTS(SELECT 1 FROM sys.indexes WHERE name = 'IX_Event_DateOrganizationIdUserId')
BEGIN
-- Check if neither ServiceAccountId nor GrantedServiceAccountId are included columns
IF NOT EXISTS (
SELECT 1
FROM
sys.indexes i
INNER JOIN
sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
INNER JOIN
sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE
i.object_id = OBJECT_ID('[dbo].[Event]')
AND i.name = 'IX_Event_DateOrganizationIdUserId'
AND c.name IN ('ServiceAccountId', 'GrantedServiceAccountId')
AND ic.is_included_column = 1
)
BEGIN
CREATE NONCLUSTERED INDEX [IX_Event_DateOrganizationIdUserId]
ON [dbo].[Event]
( [Date] DESC,
[OrganizationId] ASC,
[ActingUserId] ASC,
[CipherId] ASC
)
INCLUDE ([ServiceAccountId], [GrantedServiceAccountId])
WITH (DROP_EXISTING = ON)
END
END
GO

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class AddingMAEventLog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "GrantedServiceAccountId",
table: "Event",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GrantedServiceAccountId",
table: "Event");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class AddingIndexToEvents : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_Event_Date_OrganizationId_ActingUserId_CipherId",
table: "Event",
newName: "IX_Event_DateOrganizationIdUserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_Event_DateOrganizationIdUserId",
table: "Event",
newName: "IX_Event_Date_OrganizationId_ActingUserId_CipherId");
}
}

View File

@@ -1282,6 +1282,9 @@ namespace Bit.MySqlMigrations.Migrations
b.Property<string>("DomainName") b.Property<string>("DomainName")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<Guid?>("GrantedServiceAccountId")
.HasColumnType("char(36)");
b.Property<Guid?>("GroupId") b.Property<Guid?>("GroupId")
.HasColumnType("char(36)"); .HasColumnType("char(36)");
@@ -1328,10 +1331,13 @@ namespace Bit.MySqlMigrations.Migrations
b.Property<Guid?>("UserId") b.Property<Guid?>("UserId")
.HasColumnType("char(36)"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id")
.HasAnnotation("SqlServer:Clustered", true);
b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId")
.HasAnnotation("SqlServer:Clustered", false); .HasDatabaseName("IX_Event_DateOrganizationIdUserId")
.HasAnnotation("SqlServer:Clustered", false)
.HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" });
b.ToTable("Event", (string)null); b.ToTable("Event", (string)null);
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class AddingMAEventLog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "GrantedServiceAccountId",
table: "Event",
type: "uuid",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GrantedServiceAccountId",
table: "Event");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class AddingIndexToEvents : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_Event_Date_OrganizationId_ActingUserId_CipherId",
table: "Event",
newName: "IX_Event_DateOrganizationIdUserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_Event_DateOrganizationIdUserId",
table: "Event",
newName: "IX_Event_Date_OrganizationId_ActingUserId_CipherId");
}
}

View File

@@ -1287,6 +1287,9 @@ namespace Bit.PostgresMigrations.Migrations
b.Property<string>("DomainName") b.Property<string>("DomainName")
.HasColumnType("text"); .HasColumnType("text");
b.Property<Guid?>("GrantedServiceAccountId")
.HasColumnType("uuid");
b.Property<Guid?>("GroupId") b.Property<Guid?>("GroupId")
.HasColumnType("uuid"); .HasColumnType("uuid");
@@ -1333,10 +1336,13 @@ namespace Bit.PostgresMigrations.Migrations
b.Property<Guid?>("UserId") b.Property<Guid?>("UserId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.HasKey("Id"); b.HasKey("Id")
.HasAnnotation("SqlServer:Clustered", true);
b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId")
.HasAnnotation("SqlServer:Clustered", false); .HasDatabaseName("IX_Event_DateOrganizationIdUserId")
.HasAnnotation("SqlServer:Clustered", false)
.HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" });
b.ToTable("Event", (string)null); b.ToTable("Event", (string)null);
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class AddingMAEventLog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "GrantedServiceAccountId",
table: "Event",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GrantedServiceAccountId",
table: "Event");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class AddingIndexToEvents : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_Event_Date_OrganizationId_ActingUserId_CipherId",
table: "Event",
newName: "IX_Event_DateOrganizationIdUserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_Event_DateOrganizationIdUserId",
table: "Event",
newName: "IX_Event_Date_OrganizationId_ActingUserId_CipherId");
}
}

View File

@@ -1271,6 +1271,9 @@ namespace Bit.SqliteMigrations.Migrations
b.Property<string>("DomainName") b.Property<string>("DomainName")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<Guid?>("GrantedServiceAccountId")
.HasColumnType("TEXT");
b.Property<Guid?>("GroupId") b.Property<Guid?>("GroupId")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@@ -1317,10 +1320,13 @@ namespace Bit.SqliteMigrations.Migrations
b.Property<Guid?>("UserId") b.Property<Guid?>("UserId")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.HasKey("Id"); b.HasKey("Id")
.HasAnnotation("SqlServer:Clustered", true);
b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId")
.HasAnnotation("SqlServer:Clustered", false); .HasDatabaseName("IX_Event_DateOrganizationIdUserId")
.HasAnnotation("SqlServer:Clustered", false)
.HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" });
b.ToTable("Event", (string)null); b.ToTable("Event", (string)null);
}); });