1
0
mirror of https://github.com/bitwarden/server.git synced 2024-12-23 17:07:42 +01: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 IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly IOrganizationService _organizationService;
@ -34,11 +35,13 @@ namespace Bit.CommCore.Services
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
IUserService userService, IOrganizationService organizationService, IMailService mailService,
IDataProtectionProvider dataProtectionProvider, IEventService eventService, GlobalSettings globalSettings)
IDataProtectionProvider dataProtectionProvider, IEventService eventService,
IOrganizationRepository organizationRepository, GlobalSettings globalSettings)
{
_providerRepository = providerRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_organizationRepository = organizationRepository;
_userRepository = userRepository;
_userService = userService;
_organizationService = organizationService;
@ -402,7 +405,7 @@ namespace Bit.CommCore.Services
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);
if (providerOrganization == null || providerOrganization.ProviderId != providerId)
@ -410,7 +413,7 @@ namespace Bit.CommCore.Services
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.");
}
@ -419,6 +422,26 @@ namespace Bit.CommCore.Services
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)
{
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);

View File

@ -465,7 +465,7 @@ namespace Bit.CommCore.Test.Services
.ReturnsNull();
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);
}
@ -478,7 +478,7 @@ namespace Bit.CommCore.Test.Services
.Returns(providerOrganization);
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);
}
@ -490,11 +490,11 @@ namespace Bit.CommCore.Test.Services
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganization.Id)
.Returns(providerOrganization);
sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default)
sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default, default)
.ReturnsForAnyArgs(false);
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);
}
@ -506,10 +506,10 @@ namespace Bit.CommCore.Test.Services
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
providerOrganizationRepository.GetByIdAsync(providerOrganization.Id).Returns(providerOrganization);
sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default)
sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default, default)
.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 sutProvider.GetDependency<IEventService>().Received()
.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed);

View File

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

View File

@ -91,7 +91,7 @@ namespace Bit.Api.Controllers
}
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_PurgedVault = 1601,
// Organization_ClientExportedVault = 1602,
Organization_VaultAccessed = 1603,
Policy_Updated = 1700,
@ -63,5 +64,6 @@
ProviderOrganization_Created = 1900,
ProviderOrganization_Added = 1901,
ProviderOrganization_Removed = 1902,
ProviderOrganization_VaultAccessed = 1903,
}
}

View File

@ -59,6 +59,6 @@ namespace Bit.Core.Services
Task RotateApiKeyAsync(Organization organization);
Task DeleteSsoUserAsync(Guid userId, Guid? organizationId);
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<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup,
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;
}
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 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)

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<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();
}
}