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:
@@ -1,10 +1,13 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
|
||||
|
||||
@@ -13,15 +16,21 @@ public class CreateServiceAccountCommand : ICreateServiceAccountCommand
|
||||
private readonly IAccessPolicyRepository _accessPolicyRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public CreateServiceAccountCommand(
|
||||
IAccessPolicyRepository accessPolicyRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IServiceAccountRepository serviceAccountRepository)
|
||||
IServiceAccountRepository serviceAccountRepository,
|
||||
IEventService eventService,
|
||||
ICurrentContext currentContext)
|
||||
{
|
||||
_accessPolicyRepository = accessPolicyRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
_eventService = eventService;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
public async Task<ServiceAccount> CreateAsync(ServiceAccount serviceAccount, Guid userId)
|
||||
@@ -38,6 +47,7 @@ public class CreateServiceAccountCommand : ICreateServiceAccountCommand
|
||||
Write = true,
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> { accessPolicy });
|
||||
await _eventService.LogServiceAccountPeopleEventAsync(user.Id, accessPolicy, EventType.ServiceAccount_UserAdded, _currentContext.IdentityClientType);
|
||||
return createdServiceAccount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ public class EventsController : Controller
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
|
||||
|
||||
public EventsController(
|
||||
IUserService userService,
|
||||
@@ -39,7 +41,8 @@ public class EventsController : Controller
|
||||
IEventRepository eventRepository,
|
||||
ICurrentContext currentContext,
|
||||
ISecretRepository secretRepository,
|
||||
IProjectRepository projectRepository)
|
||||
IProjectRepository projectRepository,
|
||||
IServiceAccountRepository serviceAccountRepository)
|
||||
{
|
||||
_userService = userService;
|
||||
_cipherRepository = cipherRepository;
|
||||
@@ -49,6 +52,7 @@ public class EventsController : Controller
|
||||
_currentContext = currentContext;
|
||||
_secretRepository = secretRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
@@ -184,6 +188,57 @@ public class EventsController : Controller
|
||||
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")]
|
||||
public async Task<ListResponseModel<EventResponseModel>> GetOrganizationUser(string orgId, string id,
|
||||
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
|
||||
|
||||
@@ -35,6 +35,7 @@ public class EventResponseModel : ResponseModel
|
||||
SecretId = ev.SecretId;
|
||||
ProjectId = ev.ProjectId;
|
||||
ServiceAccountId = ev.ServiceAccountId;
|
||||
GrantedServiceAccountId = ev.GrantedServiceAccountId;
|
||||
}
|
||||
|
||||
public EventType Type { get; set; }
|
||||
@@ -58,4 +59,5 @@ public class EventResponseModel : ResponseModel
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ProjectId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public class AccessPoliciesController : Controller
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IProjectServiceAccountsAccessPoliciesUpdatesQuery
|
||||
_projectServiceAccountsAccessPoliciesUpdatesQuery;
|
||||
private readonly IUpdateProjectServiceAccountsAccessPoliciesCommand
|
||||
@@ -47,7 +48,8 @@ public class AccessPoliciesController : Controller
|
||||
IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery,
|
||||
IProjectServiceAccountsAccessPoliciesUpdatesQuery projectServiceAccountsAccessPoliciesUpdatesQuery,
|
||||
IUpdateServiceAccountGrantedPoliciesCommand updateServiceAccountGrantedPoliciesCommand,
|
||||
IUpdateProjectServiceAccountsAccessPoliciesCommand updateProjectServiceAccountsAccessPoliciesCommand)
|
||||
IUpdateProjectServiceAccountsAccessPoliciesCommand updateProjectServiceAccountsAccessPoliciesCommand,
|
||||
IEventService eventService)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_userService = userService;
|
||||
@@ -61,6 +63,7 @@ public class AccessPoliciesController : Controller
|
||||
_serviceAccountGrantedPolicyUpdatesQuery = serviceAccountGrantedPolicyUpdatesQuery;
|
||||
_projectServiceAccountsAccessPoliciesUpdatesQuery = projectServiceAccountsAccessPoliciesUpdatesQuery;
|
||||
_updateProjectServiceAccountsAccessPoliciesCommand = updateProjectServiceAccountsAccessPoliciesCommand;
|
||||
_eventService = eventService;
|
||||
}
|
||||
|
||||
[HttpGet("/organizations/{id}/access-policies/people/potential-grantees")]
|
||||
@@ -186,7 +189,9 @@ public class AccessPoliciesController : Controller
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User)!.Value;
|
||||
var currentPolicies = await _accessPolicyRepository.GetPeoplePoliciesByGrantedServiceAccountIdAsync(peopleAccessPolicies.Id, userId);
|
||||
var results = await _accessPolicyRepository.ReplaceServiceAccountPeopleAsync(peopleAccessPolicies, userId);
|
||||
await LogAccessPolicyServiceAccountChanges(currentPolicies, results, userId);
|
||||
return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId);
|
||||
}
|
||||
|
||||
@@ -336,4 +341,39 @@ public class AccessPoliciesController : Controller
|
||||
userId, accessClient);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ public class ServiceAccountsController : Controller
|
||||
private readonly IDeleteServiceAccountsCommand _deleteServiceAccountsCommand;
|
||||
private readonly IRevokeAccessTokensCommand _revokeAccessTokensCommand;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
|
||||
public ServiceAccountsController(
|
||||
ICurrentContext currentContext,
|
||||
@@ -58,7 +60,9 @@ public class ServiceAccountsController : Controller
|
||||
IUpdateServiceAccountCommand updateServiceAccountCommand,
|
||||
IDeleteServiceAccountsCommand deleteServiceAccountsCommand,
|
||||
IRevokeAccessTokensCommand revokeAccessTokensCommand,
|
||||
IPricingClient pricingClient)
|
||||
IPricingClient pricingClient,
|
||||
IEventService eventService,
|
||||
IOrganizationUserRepository organizationUserRepository)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_userService = userService;
|
||||
@@ -75,6 +79,8 @@ public class ServiceAccountsController : Controller
|
||||
_pricingClient = pricingClient;
|
||||
_createAccessTokenCommand = createAccessTokenCommand;
|
||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||
_eventService = eventService;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
}
|
||||
|
||||
[HttpGet("/organizations/{organizationId}/service-accounts")]
|
||||
@@ -139,8 +145,15 @@ public class ServiceAccountsController : Controller
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -197,6 +210,9 @@ public class ServiceAccountsController : Controller
|
||||
}
|
||||
|
||||
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));
|
||||
return new ListResponseModel<BulkDeleteResponseModel>(responses);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ public class Event : ITableObject<Guid>, IEvent
|
||||
SecretId = e.SecretId;
|
||||
ProjectId = e.ProjectId;
|
||||
ServiceAccountId = e.ServiceAccountId;
|
||||
GrantedServiceAccountId = e.GrantedServiceAccountId;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
@@ -59,7 +60,7 @@ public class Event : ITableObject<Guid>, IEvent
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ProjectId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
public void SetNewId()
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
|
||||
@@ -109,4 +109,11 @@ public enum EventType : int
|
||||
Project_Created = 2201,
|
||||
Project_Edited = 2202,
|
||||
Project_Deleted = 2203,
|
||||
|
||||
ServiceAccount_UserAdded = 2300,
|
||||
ServiceAccount_UserRemoved = 2301,
|
||||
ServiceAccount_GroupAdded = 2302,
|
||||
ServiceAccount_GroupRemoved = 2303,
|
||||
ServiceAccount_Created = 2304,
|
||||
ServiceAccount_Deleted = 2305,
|
||||
}
|
||||
|
||||
@@ -39,4 +39,5 @@ public class EventMessage : IEvent
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ProjectId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public class AzureEvent : ITableEntity
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ProjectId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
|
||||
public EventTableEntity ToEventTableEntity()
|
||||
{
|
||||
@@ -68,6 +69,7 @@ public class AzureEvent : ITableEntity
|
||||
SecretId = SecretId,
|
||||
ServiceAccountId = ServiceAccountId,
|
||||
ProjectId = ProjectId,
|
||||
GrantedServiceAccountId = GrantedServiceAccountId
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -99,6 +101,7 @@ public class EventTableEntity : IEvent
|
||||
SecretId = e.SecretId;
|
||||
ProjectId = e.ProjectId;
|
||||
ServiceAccountId = e.ServiceAccountId;
|
||||
GrantedServiceAccountId = e.GrantedServiceAccountId;
|
||||
}
|
||||
|
||||
public string PartitionKey { get; set; }
|
||||
@@ -127,6 +130,7 @@ public class EventTableEntity : IEvent
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ProjectId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
|
||||
public AzureEvent ToAzureEvent()
|
||||
{
|
||||
@@ -157,7 +161,8 @@ public class EventTableEntity : IEvent
|
||||
DomainName = DomainName,
|
||||
SecretId = SecretId,
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,4 +28,5 @@ public interface IEvent
|
||||
Guid? SecretId { get; set; }
|
||||
Guid? ProjectId { get; set; }
|
||||
Guid? ServiceAccountId { get; set; }
|
||||
Guid? GrantedServiceAccountId { get; set; }
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public interface IEventRepository
|
||||
DateTime startDate, DateTime endDate, PageOptions pageOptions);
|
||||
Task<PagedResult<IEvent>> GetManyByCipherAsync(Cipher cipher, DateTime startDate, DateTime endDate,
|
||||
PageOptions pageOptions);
|
||||
|
||||
Task CreateAsync(IEvent e);
|
||||
Task CreateManyAsync(IEnumerable<IEvent> e);
|
||||
Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(Guid organizationId, Guid serviceAccountId,
|
||||
|
||||
@@ -77,12 +77,18 @@ public class EventRepository : IEventRepository
|
||||
return await GetManyAsync(partitionKey, $"CipherId={cipher.Id}__Date={{0}}", startDate, endDate, pageOptions);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(Guid organizationId,
|
||||
Guid serviceAccountId, DateTime startDate, DateTime endDate, PageOptions pageOptions)
|
||||
public async Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(
|
||||
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)
|
||||
@@ -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,
|
||||
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}'";
|
||||
}
|
||||
|
||||
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})";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Interfaces;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
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 LogUserProjectsEventAsync(Guid userId, 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);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Interfaces;
|
||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@@ -516,6 +517,135 @@ public class EventService : IEventService
|
||||
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)
|
||||
{
|
||||
if (_currentContext == null || !orgId.HasValue)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Interfaces;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
@@ -139,4 +140,19 @@ public class NoopEventService : IEventService
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,16 @@ public class EventEntityTypeConfiguration : IEntityTypeConfiguration<Event>
|
||||
.Property(e => e.Id)
|
||||
.ValueGeneratedNever();
|
||||
|
||||
builder
|
||||
.HasIndex(e => new { e.Date, e.OrganizationId, e.ActingUserId, e.CipherId })
|
||||
.IsClustered(false);
|
||||
builder.HasKey(e => e.Id)
|
||||
.IsClustered();
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class EventReadPageByOrganizationIdServiceAccountIdQuery : IQuery<Event>
|
||||
(_beforeDate != null || e.Date <= _endDate) &&
|
||||
(_beforeDate == null || e.Date < _beforeDate.Value) &&
|
||||
e.OrganizationId == _organizationId &&
|
||||
e.ServiceAccountId == _serviceAccountId
|
||||
(e.ServiceAccountId == _serviceAccountId || e.GrantedServiceAccountId == _serviceAccountId)
|
||||
orderby e.Date descending
|
||||
select e;
|
||||
return q.Skip(0).Take(_pageOptions.PageSize);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ BEGIN
|
||||
AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate)
|
||||
AND (@BeforeDate IS NULL OR [Date] < @BeforeDate)
|
||||
AND [OrganizationId] = @OrganizationId
|
||||
AND [ServiceAccountId] = @ServiceAccountId
|
||||
AND ([ServiceAccountId] = @ServiceAccountId OR [GrantedServiceAccountId] = @ServiceAccountId)
|
||||
ORDER BY [Date] DESC
|
||||
OFFSET 0 ROWS
|
||||
FETCH NEXT @PageSize ROWS ONLY
|
||||
|
||||
@@ -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
|
||||
@@ -20,7 +20,8 @@
|
||||
@DomainName VARCHAR(256),
|
||||
@SecretId UNIQUEIDENTIFIER = null,
|
||||
@ServiceAccountId UNIQUEIDENTIFIER = null,
|
||||
@ProjectId UNIQUEIDENTIFIER = null
|
||||
@ProjectId UNIQUEIDENTIFIER = null,
|
||||
@GrantedServiceAccountId UNIQUEIDENTIFIER = null
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@@ -48,7 +49,8 @@ BEGIN
|
||||
[DomainName],
|
||||
[SecretId],
|
||||
[ServiceAccountId],
|
||||
[ProjectId]
|
||||
[ProjectId],
|
||||
[GrantedServiceAccountId]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@@ -73,6 +75,7 @@ BEGIN
|
||||
@DomainName,
|
||||
@SecretId,
|
||||
@ServiceAccountId,
|
||||
@ProjectId
|
||||
@ProjectId,
|
||||
@GrantedServiceAccountId
|
||||
)
|
||||
END
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
[SecretId] UNIQUEIDENTIFIER NULL,
|
||||
[ServiceAccountId] UNIQUEIDENTIFIER NULL,
|
||||
[ProjectId] UNIQUEIDENTIFIER NULL,
|
||||
[GrantedServiceAccountId] UNIQUEIDENTIFIER NULL,
|
||||
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
|
||||
);
|
||||
|
||||
|
||||
GO
|
||||
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]);
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ public class ServiceAccountsControllerTests
|
||||
|
||||
[Theory]
|
||||
[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 organizationId = data.First().OrganizationId;
|
||||
@@ -377,6 +377,7 @@ public class ServiceAccountsControllerTests
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).Returns(AuthorizationResult.Failed());
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
|
||||
var results = await sutProvider.Sut.BulkDeleteAsync(ids);
|
||||
|
||||
@@ -390,7 +391,7 @@ public class ServiceAccountsControllerTests
|
||||
|
||||
[Theory]
|
||||
[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 organizationId = data.First().OrganizationId;
|
||||
@@ -404,6 +405,7 @@ public class ServiceAccountsControllerTests
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
|
||||
var results = await sutProvider.Sut.BulkDeleteAsync(ids);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
3278
util/MySqlMigrations/Migrations/20250910211149_AddingMAEventLog.Designer.cs
generated
Normal file
3278
util/MySqlMigrations/Migrations/20250910211149_AddingMAEventLog.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
3284
util/MySqlMigrations/Migrations/20250926144434_AddingIndexToEvents.Designer.cs
generated
Normal file
3284
util/MySqlMigrations/Migrations/20250926144434_AddingIndexToEvents.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -1282,6 +1282,9 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Property<string>("DomainName")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid?>("GrantedServiceAccountId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("GroupId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
@@ -1328,10 +1331,13 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
b.HasKey("Id")
|
||||
.HasAnnotation("SqlServer:Clustered", true);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
3284
util/PostgresMigrations/Migrations/20250910211124_AddingMAEventLog.Designer.cs
generated
Normal file
3284
util/PostgresMigrations/Migrations/20250910211124_AddingMAEventLog.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
3290
util/PostgresMigrations/Migrations/20250926144506_AddingIndexToEvents.Designer.cs
generated
Normal file
3290
util/PostgresMigrations/Migrations/20250926144506_AddingIndexToEvents.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -1287,6 +1287,9 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Property<string>("DomainName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("GrantedServiceAccountId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("GroupId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
@@ -1333,10 +1336,13 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
b.HasKey("Id")
|
||||
.HasAnnotation("SqlServer:Clustered", true);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
3267
util/SqliteMigrations/Migrations/20250910211136_AddingMAEventLog.Designer.cs
generated
Normal file
3267
util/SqliteMigrations/Migrations/20250910211136_AddingMAEventLog.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
3273
util/SqliteMigrations/Migrations/20250926144450_AddingIndexToEvents.Designer.cs
generated
Normal file
3273
util/SqliteMigrations/Migrations/20250926144450_AddingIndexToEvents.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -1271,6 +1271,9 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.Property<string>("DomainName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("GrantedServiceAccountId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("GroupId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -1317,10 +1320,13 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
b.HasKey("Id")
|
||||
.HasAnnotation("SqlServer:Clustered", true);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user