diff --git a/src/Api/SecretsManager/Controllers/SecretsController.cs b/src/Api/SecretsManager/Controllers/SecretsController.cs index dd653bb873..519bc328fa 100644 --- a/src/Api/SecretsManager/Controllers/SecretsController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsController.cs @@ -109,7 +109,7 @@ public class SecretsController : Controller } var result = await _createSecretCommand.CreateAsync(secret, accessPoliciesUpdates); - + await LogSecretEventAsync(secret, EventType.Secret_Created); // Creating a secret means you have read & write permission. return new SecretResponseModel(result, true, true); } @@ -135,10 +135,7 @@ public class SecretsController : Controller throw new NotFoundException(); } - if (_currentContext.IdentityClientType == IdentityClientType.ServiceAccount) - { - await _eventService.LogServiceAccountSecretEventAsync(userId, secret, EventType.Secret_Retrieved); - } + await LogSecretEventAsync(secret, EventType.Secret_Retrieved); return new SecretResponseModel(secret, access.Read, access.Write); } @@ -188,10 +185,10 @@ public class SecretsController : Controller { throw new NotFoundException(); } - } var result = await _updateSecretCommand.UpdateAsync(updatedSecret, accessPoliciesUpdates); + await LogSecretEventAsync(secret, EventType.Secret_Edited); // Updating a secret means you have read & write permission. return new SecretResponseModel(result, true, true); @@ -234,6 +231,7 @@ public class SecretsController : Controller await _deleteSecretCommand.DeleteSecrets(secretsToDelete); var responses = results.Select(r => new BulkDeleteResponseModel(r.Secret.Id, r.Error)); + await LogSecretsEventAsync(secretsToDelete, EventType.Secret_Deleted); return new ListResponseModel(responses); } @@ -253,7 +251,7 @@ public class SecretsController : Controller throw new NotFoundException(); } - await LogSecretsRetrievalAsync(secrets); + await LogSecretsEventAsync(secrets, EventType.Secret_Retrieved); var responses = secrets.Select(s => new BaseSecretResponseModel(s)); return new ListResponseModel(responses); @@ -290,18 +288,28 @@ public class SecretsController : Controller if (syncResult.HasChanges) { - await LogSecretsRetrievalAsync(syncResult.Secrets); + await LogSecretsEventAsync(syncResult.Secrets, EventType.Secret_Retrieved); } return new SecretsSyncResponseModel(syncResult.HasChanges, syncResult.Secrets); } - private async Task LogSecretsRetrievalAsync(IEnumerable secrets) + private async Task LogSecretsEventAsync(IEnumerable secrets, EventType eventType) { - if (_currentContext.IdentityClientType == IdentityClientType.ServiceAccount) + var userId = _userService.GetProperUserId(User)!.Value; + + switch (_currentContext.IdentityClientType) { - var userId = _userService.GetProperUserId(User)!.Value; - await _eventService.LogServiceAccountSecretsEventAsync(userId, secrets, EventType.Secret_Retrieved); + case IdentityClientType.ServiceAccount: + await _eventService.LogServiceAccountSecretsEventAsync(userId, secrets, eventType); + break; + case IdentityClientType.User: + await _eventService.LogUserSecretsEventAsync(userId, secrets, eventType); + break; } } + + private Task LogSecretEventAsync(Secret secret, EventType eventType) => + LogSecretsEventAsync(new[] { secret }, eventType); + } diff --git a/src/Core/AdminConsole/Enums/EventType.cs b/src/Core/AdminConsole/Enums/EventType.cs index 9d9cb09989..2359b922d8 100644 --- a/src/Core/AdminConsole/Enums/EventType.cs +++ b/src/Core/AdminConsole/Enums/EventType.cs @@ -90,4 +90,7 @@ public enum EventType : int OrganizationDomain_NotVerified = 2003, Secret_Retrieved = 2100, + Secret_Created = 2101, + Secret_Edited = 2102, + Secret_Deleted = 2103, } diff --git a/src/Core/AdminConsole/Services/IEventService.cs b/src/Core/AdminConsole/Services/IEventService.cs index 5b4f8731a2..14ef4ba4d4 100644 --- a/src/Core/AdminConsole/Services/IEventService.cs +++ b/src/Core/AdminConsole/Services/IEventService.cs @@ -30,6 +30,6 @@ public interface IEventService Task LogProviderOrganizationEventsAsync(IEnumerable<(ProviderOrganization, EventType, DateTime?)> events); Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, DateTime? date = null); Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, EventSystemUser systemUser, DateTime? date = null); - Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, DateTime? date = null); + Task LogUserSecretsEventAsync(Guid userId, IEnumerable secrets, EventType type, DateTime? date = null); Task LogServiceAccountSecretsEventAsync(Guid serviceAccountId, IEnumerable secrets, EventType type, DateTime? date = null); } diff --git a/src/Core/AdminConsole/Services/Implementations/EventService.cs b/src/Core/AdminConsole/Services/Implementations/EventService.cs index 88d9595b4a..d21e6f25e8 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventService.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventService.cs @@ -409,9 +409,30 @@ public class EventService : IEventService await _eventWriteService.CreateAsync(e); } - public async Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, DateTime? date = null) + public async Task LogUserSecretsEventAsync(Guid userId, IEnumerable secrets, EventType type, DateTime? date = null) { - await LogServiceAccountSecretsEventAsync(serviceAccountId, new[] { secret }, type, date); + var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + var eventMessages = new List(); + + foreach (var secret in secrets) + { + if (!CanUseEvents(orgAbilities, secret.OrganizationId)) + { + continue; + } + + var e = new EventMessage(_currentContext) + { + OrganizationId = secret.OrganizationId, + Type = type, + SecretId = secret.Id, + UserId = userId, + Date = date.GetValueOrDefault(DateTime.UtcNow) + }; + eventMessages.Add(e); + } + + await _eventWriteService.CreateManyAsync(eventMessages); } public async Task LogServiceAccountSecretsEventAsync(Guid serviceAccountId, IEnumerable secrets, EventType type, DateTime? date = null) diff --git a/src/Core/AdminConsole/Services/NoopImplementations/NoopEventService.cs b/src/Core/AdminConsole/Services/NoopImplementations/NoopEventService.cs index d96c4a0ce1..b1ff5b1c4a 100644 --- a/src/Core/AdminConsole/Services/NoopImplementations/NoopEventService.cs +++ b/src/Core/AdminConsole/Services/NoopImplementations/NoopEventService.cs @@ -116,7 +116,7 @@ public class NoopEventService : IEventService return Task.FromResult(0); } - public Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, + public Task LogUserSecretsEventAsync(Guid userId, IEnumerable secrets, EventType type, DateTime? date = null) { return Task.FromResult(0); diff --git a/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs index 4fb2c4b7fb..83a4229f39 100644 --- a/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs @@ -19,6 +19,7 @@ using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using NSubstitute; using Xunit; @@ -152,6 +153,7 @@ public class SecretsControllerTests SecretCreateRequestModel data, Guid organizationId) { data = SetupSecretCreateRequest(sutProvider, data, organizationId); + SetControllerUser(sutProvider, new Guid()); await sutProvider.Sut.CreateAsync(organizationId, data); @@ -186,6 +188,7 @@ public class SecretsControllerTests .AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()).Returns(AuthorizationResult.Success()); + SetControllerUser(sutProvider, new Guid()); await sutProvider.Sut.CreateAsync(organizationId, data); @@ -199,6 +202,7 @@ public class SecretsControllerTests SecretUpdateRequestModel data, Secret currentSecret) { data = SetupSecretUpdateRequest(data); + sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); @@ -239,7 +243,7 @@ public class SecretsControllerTests SecretUpdateRequestModel data, Secret currentSecret) { data = SetupSecretUpdateRequest(data); - + SetControllerUser(sutProvider, new Guid()); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); @@ -260,7 +264,6 @@ public class SecretsControllerTests SecretUpdateRequestModel data, Secret currentSecret, SecretAccessPoliciesUpdates accessPoliciesUpdates) { data = SetupSecretUpdateAccessPoliciesRequest(sutProvider, data, currentSecret, accessPoliciesUpdates); - sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()).Returns(AuthorizationResult.Failed()); @@ -339,6 +342,8 @@ public class SecretsControllerTests { var ids = data.Select(s => s.Id).ToList(); var organizationId = data.First().OrganizationId; + SetControllerUser(sutProvider, new Guid()); + foreach (var secret in data) { secret.OrganizationId = organizationId; @@ -378,7 +383,7 @@ public class SecretsControllerTests sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true); sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - + SetControllerUser(sutProvider, new Guid()); var results = await sutProvider.Sut.BulkDeleteAsync(ids); await sutProvider.GetDependency().Received(1) @@ -434,7 +439,7 @@ public class SecretsControllerTests { var (ids, request) = BuildGetSecretsRequestModel(data); var organizationId = SetOrganizations(ref data); - + SetControllerUser(sutProvider, new Guid()); sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), data, @@ -507,7 +512,7 @@ public class SecretsControllerTests SutProvider sutProvider, Guid organizationId) { var lastSyncedDate = SetupSecretsSyncRequest(nullLastSyncedDate, secrets, sutProvider, organizationId); - + SetControllerUser(sutProvider, new Guid()); var result = await sutProvider.Sut.GetSecretsSyncAsync(organizationId, lastSyncedDate); Assert.True(result.HasChanges); Assert.NotNull(result.Secrets); @@ -610,4 +615,19 @@ public class SecretsControllerTests .ReturnsForAnyArgs(data.ToSecret(currentSecret)); return data; } + + private static void SetControllerUser(SutProvider sutProvider, Guid userId) + { + var claims = new List { new Claim(ClaimTypes.NameIdentifier, userId.ToString()) }; + var identity = new ClaimsIdentity(claims, "Test"); + var principal = new ClaimsPrincipal(identity); + + sutProvider.Sut.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext + { + HttpContext = new DefaultHttpContext { User = principal } + }; + + sutProvider.GetDependency().GetProperUserId(principal).Returns(userId); + } + }