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:
parent
744e8f1a13
commit
cfc7fa071b
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user