1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

Record when a provider user accesses a clients vault (#1496)

* Record when a provider user accesses a clients vault

* Do not allow removal from provider unless owner exists

* PR Review

* Null safe event processing
* append `Async` to async methods
This commit is contained in:
Matt Gibson 2021-08-05 08:50:41 -04:00 committed by GitHub
parent 744e8f1a13
commit cfc7fa071b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 56 additions and 15 deletions

View File

@ -27,6 +27,7 @@ namespace Bit.CommCore.Services
private readonly IProviderRepository _providerRepository; private readonly IProviderRepository _providerRepository;
private readonly IProviderUserRepository _providerUserRepository; private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository; private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
@ -34,11 +35,13 @@ namespace Bit.CommCore.Services
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository, public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository, IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
IUserService userService, IOrganizationService organizationService, IMailService mailService, IUserService userService, IOrganizationService organizationService, IMailService mailService,
IDataProtectionProvider dataProtectionProvider, IEventService eventService, GlobalSettings globalSettings) IDataProtectionProvider dataProtectionProvider, IEventService eventService,
IOrganizationRepository organizationRepository, GlobalSettings globalSettings)
{ {
_providerRepository = providerRepository; _providerRepository = providerRepository;
_providerUserRepository = providerUserRepository; _providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository; _providerOrganizationRepository = providerOrganizationRepository;
_organizationRepository = organizationRepository;
_userRepository = userRepository; _userRepository = userRepository;
_userService = userService; _userService = userService;
_organizationService = organizationService; _organizationService = organizationService;
@ -402,7 +405,7 @@ namespace Bit.CommCore.Services
return providerOrganization; return providerOrganization;
} }
public async Task RemoveOrganization(Guid providerId, Guid providerOrganizationId, Guid removingUserId) public async Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId)
{ {
var providerOrganization = await _providerOrganizationRepository.GetByIdAsync(providerOrganizationId); var providerOrganization = await _providerOrganizationRepository.GetByIdAsync(providerOrganizationId);
if (providerOrganization == null || providerOrganization.ProviderId != providerId) if (providerOrganization == null || providerOrganization.ProviderId != providerId)
@ -410,7 +413,7 @@ namespace Bit.CommCore.Services
throw new BadRequestException("Invalid organization."); throw new BadRequestException("Invalid organization.");
} }
if (!await _organizationService.HasConfirmedOwnersExceptAsync(providerOrganization.OrganizationId, new Guid[] {})) if (!await _organizationService.HasConfirmedOwnersExceptAsync(providerOrganization.OrganizationId, new Guid[] { }, includeProvider: false))
{ {
throw new BadRequestException("Organization needs to have at least one confirmed owner."); throw new BadRequestException("Organization needs to have at least one confirmed owner.");
} }
@ -419,6 +422,26 @@ namespace Bit.CommCore.Services
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed); await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed);
} }
public async Task LogProviderAccessToOrganizationAsync(Guid organizationId)
{
if (organizationId == default)
{
return;
}
var providerOrganization = await _providerOrganizationRepository.GetByOrganizationId(organizationId);
var organization = await _organizationRepository.GetByIdAsync(organizationId);
if (providerOrganization != null)
{
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_VaultAccessed);
}
if (organization != null)
{
await _eventService.LogOrganizationEventAsync(organization, EventType.Organization_VaultAccessed);
}
}
private async Task SendInviteAsync(ProviderUser providerUser, Provider provider) private async Task SendInviteAsync(ProviderUser providerUser, Provider provider)
{ {
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow); var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);

View File

@ -465,7 +465,7 @@ namespace Bit.CommCore.Test.Services
.ReturnsNull(); .ReturnsNull();
var exception = await Assert.ThrowsAsync<BadRequestException>( var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id)); () => sutProvider.Sut.RemoveOrganizationAsync(provider.Id, providerOrganization.Id, user.Id));
Assert.Equal("Invalid organization.", exception.Message); Assert.Equal("Invalid organization.", exception.Message);
} }
@ -478,7 +478,7 @@ namespace Bit.CommCore.Test.Services
.Returns(providerOrganization); .Returns(providerOrganization);
var exception = await Assert.ThrowsAsync<BadRequestException>( var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id)); () => sutProvider.Sut.RemoveOrganizationAsync(provider.Id, providerOrganization.Id, user.Id));
Assert.Equal("Invalid organization.", exception.Message); Assert.Equal("Invalid organization.", exception.Message);
} }
@ -490,11 +490,11 @@ namespace Bit.CommCore.Test.Services
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider); sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganization.Id) sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganization.Id)
.Returns(providerOrganization); .Returns(providerOrganization);
sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default) sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default, default)
.ReturnsForAnyArgs(false); .ReturnsForAnyArgs(false);
var exception = await Assert.ThrowsAsync<BadRequestException>( var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id)); () => sutProvider.Sut.RemoveOrganizationAsync(provider.Id, providerOrganization.Id, user.Id));
Assert.Equal("Organization needs to have at least one confirmed owner.", exception.Message); Assert.Equal("Organization needs to have at least one confirmed owner.", exception.Message);
} }
@ -506,10 +506,10 @@ namespace Bit.CommCore.Test.Services
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider); sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>(); var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
providerOrganizationRepository.GetByIdAsync(providerOrganization.Id).Returns(providerOrganization); providerOrganizationRepository.GetByIdAsync(providerOrganization.Id).Returns(providerOrganization);
sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default) sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default, default)
.ReturnsForAnyArgs(true); .ReturnsForAnyArgs(true);
await sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id); await sutProvider.Sut.RemoveOrganizationAsync(provider.Id, providerOrganization.Id, user.Id);
await providerOrganizationRepository.Received().DeleteAsync(providerOrganization); await providerOrganizationRepository.Received().DeleteAsync(providerOrganization);
await sutProvider.GetDependency<IEventService>().Received() await sutProvider.GetDependency<IEventService>().Received()
.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed); .LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed);

View File

@ -30,6 +30,7 @@ namespace Bit.Api.Controllers
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IAttachmentStorageService _attachmentStorageService; private readonly IAttachmentStorageService _attachmentStorageService;
private readonly IProviderService _providerService;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly ILogger<CiphersController> _logger; private readonly ILogger<CiphersController> _logger;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
@ -40,6 +41,7 @@ namespace Bit.Api.Controllers
ICipherService cipherService, ICipherService cipherService,
IUserService userService, IUserService userService,
IAttachmentStorageService attachmentStorageService, IAttachmentStorageService attachmentStorageService,
IProviderService providerService,
ICurrentContext currentContext, ICurrentContext currentContext,
ILogger<CiphersController> logger, ILogger<CiphersController> logger,
GlobalSettings globalSettings) GlobalSettings globalSettings)
@ -49,6 +51,7 @@ namespace Bit.Api.Controllers
_cipherService = cipherService; _cipherService = cipherService;
_userService = userService; _userService = userService;
_attachmentStorageService = attachmentStorageService; _attachmentStorageService = attachmentStorageService;
_providerService = providerService;
_currentContext = currentContext; _currentContext = currentContext;
_logger = logger; _logger = logger;
_globalSettings = globalSettings; _globalSettings = globalSettings;
@ -224,6 +227,12 @@ namespace Bit.Api.Controllers
var responses = ciphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings, var responses = ciphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings,
collectionCiphersGroupDict)); collectionCiphersGroupDict));
var providerId = await _currentContext.ProviderIdForOrg(orgIdGuid);
if (providerId.HasValue)
{
await _providerService.LogProviderAccessToOrganizationAsync(orgIdGuid);
}
return new ListResponseModel<CipherMiniDetailsResponseModel>(responses); return new ListResponseModel<CipherMiniDetailsResponseModel>(responses);
} }

View File

@ -91,7 +91,7 @@ namespace Bit.Api.Controllers
} }
var userId = _userService.GetProperUserId(User); var userId = _userService.GetProperUserId(User);
await _providerService.RemoveOrganization(providerId, id, userId.Value); await _providerService.RemoveOrganizationAsync(providerId, id, userId.Value);
} }
} }
} }

View File

@ -52,6 +52,7 @@
Organization_Updated = 1600, Organization_Updated = 1600,
Organization_PurgedVault = 1601, Organization_PurgedVault = 1601,
// Organization_ClientExportedVault = 1602, // Organization_ClientExportedVault = 1602,
Organization_VaultAccessed = 1603,
Policy_Updated = 1700, Policy_Updated = 1700,
@ -63,5 +64,6 @@
ProviderOrganization_Created = 1900, ProviderOrganization_Created = 1900,
ProviderOrganization_Added = 1901, ProviderOrganization_Added = 1901,
ProviderOrganization_Removed = 1902, ProviderOrganization_Removed = 1902,
ProviderOrganization_VaultAccessed = 1903,
} }
} }

View File

@ -59,6 +59,6 @@ namespace Bit.Core.Services
Task RotateApiKeyAsync(Organization organization); Task RotateApiKeyAsync(Organization organization);
Task DeleteSsoUserAsync(Guid userId, Guid? organizationId); Task DeleteSsoUserAsync(Guid userId, Guid? organizationId);
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey); Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId); Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId, bool includeProvider = true);
} }
} }

View File

@ -27,6 +27,7 @@ namespace Bit.Core.Services
Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key); Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key);
Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup,
string clientOwnerEmail, User user); string clientOwnerEmail, User user);
Task RemoveOrganization(Guid providerId, Guid providerOrganizationId, Guid removingUserId); Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId);
Task LogProviderAccessToOrganizationAsync(Guid organizationId);
} }
} }

View File

@ -1689,11 +1689,16 @@ namespace Bit.Core.Services
return result; return result;
} }
public async Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId) public async Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId, bool includeProvider = true)
{ {
var confirmedOwners = await GetConfirmedOwnersAsync(organizationId); var confirmedOwners = await GetConfirmedOwnersAsync(organizationId);
var confirmedOwnersIds = confirmedOwners.Select(u => u.Id); var confirmedOwnersIds = confirmedOwners.Select(u => u.Id);
return confirmedOwnersIds.Except(organizationUsersId).Any(); bool hasOtherOwner = confirmedOwnersIds.Except(organizationUsersId).Any();
if (!hasOtherOwner && includeProvider)
{
return (await _currentContext.ProviderIdForOrg(organizationId)).HasValue;
}
return hasOtherOwner;
} }
public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId) public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId)

View File

@ -31,6 +31,7 @@ namespace Bit.Core.Services
public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException(); public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException();
public Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user) => throw new NotImplementedException(); public Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user) => throw new NotImplementedException();
public Task RemoveOrganization(Guid providerId, Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException(); public Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException();
public Task LogProviderAccessToOrganizationAsync(Guid organizationId) => throw new NotImplementedException();
} }
} }