1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-22 02:51:33 +01:00

Add events for Creating, Adding and Removing ProviderOrganizations (#1475)

This commit is contained in:
Oscar Hinton 2021-07-21 19:40:38 +02:00 committed by GitHub
parent 4e486e5f5d
commit 259bf8d760
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 269 additions and 21 deletions

View File

@ -368,6 +368,7 @@ namespace Bit.CommCore.Services
};
await _providerOrganizationRepository.CreateAsync(providerOrganization);
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Added);
}
public async Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, User user)
@ -382,24 +383,26 @@ namespace Bit.CommCore.Services
};
await _providerOrganizationRepository.CreateAsync(providerOrganization);
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Created);
return providerOrganization;
}
public async Task RemoveOrganization(Guid providerId, Guid providerOrganizationId, Guid removingUserId)
{
var providerOrganization = await _providerOrganizationRepository.GetByIdAsync(providerOrganizationId);
if (providerOrganization == null || providerOrganization.ProviderId != providerId)
{
throw new BadRequestException("Invalid organization");
throw new BadRequestException("Invalid organization.");
}
if (!await _organizationService.HasConfirmedOwnersExceptAsync(providerOrganization.OrganizationId, new Guid[] {}))
{
throw new BadRequestException("Organization needs to have at least one confirmed owner");
throw new BadRequestException("Organization needs to have at least one confirmed owner.");
}
await _providerOrganizationRepository.DeleteAsync(providerOrganization);
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed);
}
private async Task SendInviteAsync(ProviderUser providerUser, Provider provider)

View File

@ -7,6 +7,7 @@ using Bit.CommCore.Services;
using Bit.Core.Enums;
using Bit.Core.Enums.Provider;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.Business.Provider;
using Bit.Core.Models.Table;
using Bit.Core.Models.Table.Provider;
@ -17,6 +18,7 @@ using Bit.Core.Test.AutoFixture.Attributes;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.DataProtection;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Xunit;
using ProviderUser = Bit.Core.Models.Table.Provider.ProviderUser;
@ -397,5 +399,113 @@ namespace Bit.CommCore.Test.Services
Assert.Equal("", result[1].Item2);
Assert.Equal("Invalid user.", result[2].Item2);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task AddOrganization_OrganizationAlreadyBelongsToAProvider_Throws(Provider provider,
Organization organization, ProviderOrganization po, User user, string key,
SutProvider<ProviderService> sutProvider)
{
po.OrganizationId = organization.Id;
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByOrganizationId(organization.Id)
.Returns(po);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.AddOrganization(provider.Id, organization.Id, user.Id, key));
Assert.Equal("Organization already belongs to a provider.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task AddOrganization_Success(Provider provider, Organization organization, User user, string key,
SutProvider<ProviderService> sutProvider)
{
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
providerOrganizationRepository.GetByOrganizationId(organization.Id).ReturnsNull();
await sutProvider.Sut.AddOrganization(provider.Id, organization.Id, user.Id, key);
await providerOrganizationRepository.ReceivedWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<IEventService>()
.Received().LogProviderOrganizationEventAsync(Arg.Any<ProviderOrganization>(),
EventType.ProviderOrganization_Added);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task CreateOrganizationAsync_Success(Provider provider, OrganizationSignup organizationSignup,
Organization organization, User user, SutProvider<ProviderService> sutProvider)
{
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
sutProvider.GetDependency<IOrganizationService>().SignUpAsync(organizationSignup, true)
.Returns(Tuple.Create(organization, null as OrganizationUser));
var providerOrganization =
await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, user);
await providerOrganizationRepository.ReceivedWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<IEventService>()
.Received().LogProviderOrganizationEventAsync(providerOrganization,
EventType.ProviderOrganization_Created);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task RemoveOrganization_ProviderOrganizationIsInvalid_Throws(Provider provider,
ProviderOrganization providerOrganization, User user, SutProvider<ProviderService> sutProvider)
{
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganization.Id)
.ReturnsNull();
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id));
Assert.Equal("Invalid organization.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task RemoveOrganization_ProviderOrganizationBelongsToWrongProvider_Throws(Provider provider,
ProviderOrganization providerOrganization, User user, SutProvider<ProviderService> sutProvider)
{
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganization.Id)
.Returns(providerOrganization);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id));
Assert.Equal("Invalid organization.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task RemoveOrganization_HasNoOwners_Throws(Provider provider,
ProviderOrganization providerOrganization, User user, SutProvider<ProviderService> sutProvider)
{
providerOrganization.ProviderId = provider.Id;
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganization.Id)
.Returns(providerOrganization);
sutProvider.GetDependency<IOrganizationService>().HasConfirmedOwnersExceptAsync(default, default)
.ReturnsForAnyArgs(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id));
Assert.Equal("Organization needs to have at least one confirmed owner.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task RemoveOrganization_Success(Provider provider,
ProviderOrganization providerOrganization, User user, SutProvider<ProviderService> sutProvider)
{
providerOrganization.ProviderId = provider.Id;
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)
.ReturnsForAnyArgs(true);
await sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id);
await providerOrganizationRepository.Received().DeleteAsync(providerOrganization);
await sutProvider.GetDependency<IEventService>().Received()
.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed);
}
}
}

View File

@ -58,5 +58,9 @@
ProviderUser_Confirmed = 1801,
ProviderUser_Updated = 1802,
ProviderUser_Removed = 1803,
ProviderOrganization_Created = 1900,
ProviderOrganization_Added = 1901,
ProviderOrganization_Removed = 1902,
}
}

View File

@ -24,6 +24,7 @@ namespace Bit.Core.Models.Api
PolicyId = ev.PolicyId;
OrganizationUserId = ev.OrganizationUserId;
ProviderUserId = ev.ProviderUserId;
ProviderOrganizationId = ev.ProviderOrganizationId;
ActingUserId = ev.ActingUserId;
Date = ev.Date;
DeviceType = ev.DeviceType;
@ -40,6 +41,7 @@ namespace Bit.Core.Models.Api
public Guid? PolicyId { get; set; }
public Guid? OrganizationUserId { get; set; }
public Guid? ProviderUserId { get; set; }
public Guid? ProviderOrganizationId { get; set; }
public Guid? ActingUserId { get; set; }
public DateTime Date { get; set; }
public DeviceType? DeviceType { get; set; }

View File

@ -27,6 +27,7 @@ namespace Bit.Core.Models.Data
public Guid? PolicyId { get; set; }
public Guid? OrganizationUserId { get; set; }
public Guid? ProviderUserId { get; set; }
public Guid? ProviderOrganizationId { get; set; }
public Guid? ActingUserId { get; set; }
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }

View File

@ -23,6 +23,7 @@ namespace Bit.Core.Models.Data
GroupId = e.GroupId;
OrganizationUserId = e.OrganizationUserId;
ProviderUserId = e.ProviderUserId;
ProviderOrganizationId = e.ProviderOrganizationId;
DeviceType = e.DeviceType;
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId;
@ -39,6 +40,7 @@ namespace Bit.Core.Models.Data
public Guid? GroupId { get; set; }
public Guid? OrganizationUserId { get; set; }
public Guid? ProviderUserId { get; set; }
public Guid? ProviderOrganizationId { get; set; }
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }
public Guid? ActingUserId { get; set; }

View File

@ -15,6 +15,7 @@ namespace Bit.Core.Models.Data
Guid? PolicyId { get; set; }
Guid? OrganizationUserId { get; set; }
Guid? ProviderUserId { get; set; }
Guid? ProviderOrganizationId { get; set; }
Guid? ActingUserId { get; set; }
DeviceType? DeviceType { get; set; }
string IpAddress { get; set; }

View File

@ -23,6 +23,7 @@ namespace Bit.Core.Models.Table
GroupId = e.GroupId;
OrganizationUserId = e.OrganizationUserId;
ProviderUserId = e.ProviderUserId;
ProviderOrganizationId = e.ProviderOrganizationId;
DeviceType = e.DeviceType;
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId;
@ -40,6 +41,7 @@ namespace Bit.Core.Models.Table
public Guid? GroupId { get; set; }
public Guid? OrganizationUserId { get; set; }
public Guid? ProviderUserId { get; set; }
public Guid? ProviderOrganizationId { get; set; }
public DeviceType? DeviceType { get; set; }
[MaxLength(50)]
public string IpAddress { get; set; }

View File

@ -20,6 +20,6 @@ namespace Bit.Core.Services
Task LogOrganizationEventAsync(Organization organization, EventType type, DateTime? date = null);
Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null);
Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events);
Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type, DateTime? date = null);
}
}

View File

@ -274,7 +274,27 @@ namespace Bit.Core.Services
await _eventWriteService.CreateManyAsync(eventMessages);
}
public async Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type,
DateTime? date = null)
{
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync();
if (!CanUseProviderEvents(providerAbilities, providerOrganization.ProviderId))
{
return;
}
var e = new EventMessage(_currentContext)
{
ProviderId = providerOrganization.ProviderId,
ProviderOrganizationId = providerOrganization.Id,
Type = type,
ActingUserId = _currentContext?.UserId,
Date = date.GetValueOrDefault(DateTime.UtcNow)
};
await _eventWriteService.CreateAsync(e);
}
private async Task<Guid?> GetProviderIdAsync(Guid? orgId)
{
if (_currentContext == null || !orgId.HasValue)

View File

@ -49,6 +49,12 @@ namespace Bit.Core.Services
return Task.FromResult(0);
}
public Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type,
DateTime? date = null)
{
return Task.FromResult(0);
}
public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type,
DateTime? date = null)
{

View File

@ -10,6 +10,7 @@
@GroupId UNIQUEIDENTIFIER,
@OrganizationUserId UNIQUEIDENTIFIER,
@ProviderUserId UNIQUEIDENTIFIER,
@ProviderOrganizationId UNIQUEIDENTIFIER = null,
@ActingUserId UNIQUEIDENTIFIER,
@DeviceType SMALLINT,
@IpAddress VARCHAR(50),
@ -31,6 +32,7 @@ BEGIN
[GroupId],
[OrganizationUserId],
[ProviderUserId],
[ProviderOrganizationId],
[ActingUserId],
[DeviceType],
[IpAddress],
@ -49,6 +51,7 @@ BEGIN
@GroupId,
@OrganizationUserId,
@ProviderUserId,
@ProviderOrganizationId,
@ActingUserId,
@DeviceType,
@IpAddress,

View File

@ -51,6 +51,12 @@ BEGIN
WHERE
[OrganizationId] = @Id
DELETE
FROM
[dbo].[ProviderOrganization]
WHERE
[OrganizationId] = @Id
DELETE
FROM
[dbo].[Organization]

View File

@ -1,19 +1,20 @@
CREATE TABLE [dbo].[Event] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Type] INT NOT NULL,
[UserId] UNIQUEIDENTIFIER NULL,
[OrganizationId] UNIQUEIDENTIFIER NULL,
[CipherId] UNIQUEIDENTIFIER NULL,
[CollectionId] UNIQUEIDENTIFIER NULL,
[PolicyId] UNIQUEIDENTIFIER NULL,
[GroupId] UNIQUEIDENTIFIER NULL,
[OrganizationUserId] UNIQUEIDENTIFIER NULL,
[ActingUserId] UNIQUEIDENTIFIER NULL,
[DeviceType] SMALLINT NULL,
[IpAddress] VARCHAR(50) NULL,
[Date] DATETIME2 (7) NOT NULL,
[ProviderId] UNIQUEIDENTIFIER NULL,
[ProviderUserId] UNIQUEIDENTIFIER NULL,
[Id] UNIQUEIDENTIFIER NOT NULL,
[Type] INT NOT NULL,
[UserId] UNIQUEIDENTIFIER NULL,
[OrganizationId] UNIQUEIDENTIFIER NULL,
[CipherId] UNIQUEIDENTIFIER NULL,
[CollectionId] UNIQUEIDENTIFIER NULL,
[PolicyId] UNIQUEIDENTIFIER NULL,
[GroupId] UNIQUEIDENTIFIER NULL,
[OrganizationUserId] UNIQUEIDENTIFIER NULL,
[ActingUserId] UNIQUEIDENTIFIER NULL,
[DeviceType] SMALLINT NULL,
[IpAddress] VARCHAR(50) NULL,
[Date] DATETIME2 (7) NOT NULL,
[ProviderId] UNIQUEIDENTIFIER NULL,
[ProviderUserId] UNIQUEIDENTIFIER NULL,
[ProviderOrganizationId] UNIQUEIDENTIFIER NULL,
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
);

View File

@ -1254,6 +1254,15 @@ IF COL_LENGTH('[dbo].[Event]', 'ProviderUserId') IS NULL
END
GO
IF COL_LENGTH('[dbo].[Event]', 'ProviderOrganizationId') IS NULL
BEGIN
ALTER TABLE
[dbo].[Event]
ADD
[ProviderOrganizationId] UNIQUEIDENTIFIER NULL
END
GO
IF OBJECT_ID('[dbo].[Event_Create]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Event_Create]
@ -1272,6 +1281,7 @@ CREATE PROCEDURE [dbo].[Event_Create]
@GroupId UNIQUEIDENTIFIER,
@OrganizationUserId UNIQUEIDENTIFIER,
@ProviderUserId UNIQUEIDENTIFIER,
@ProviderOrganizationId UNIQUEIDENTIFIER = null,
@ActingUserId UNIQUEIDENTIFIER,
@DeviceType SMALLINT,
@IpAddress VARCHAR(50),
@ -1293,6 +1303,7 @@ BEGIN
[GroupId],
[OrganizationUserId],
[ProviderUserId],
[ProviderOrganizationId],
[ActingUserId],
[DeviceType],
[IpAddress],
@ -1311,6 +1322,7 @@ BEGIN
@GroupId,
@OrganizationUserId,
@ProviderUserId,
@ProviderOrganizationId,
@ActingUserId,
@DeviceType,
@IpAddress,
@ -1408,3 +1420,78 @@ BEGIN
WHERE
[OrganizationId] = @OrganizationId
END
GO
IF OBJECT_ID('[dbo].[Organization_DeleteById]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Organization_DeleteById]
END
GO
CREATE PROCEDURE [dbo].[Organization_DeleteById]
@Id UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @Id
DECLARE @BatchSize INT = 100
WHILE @BatchSize > 0
BEGIN
BEGIN TRANSACTION Organization_DeleteById_Ciphers
DELETE TOP(@BatchSize)
FROM
[dbo].[Cipher]
WHERE
[UserId] IS NULL
AND [OrganizationId] = @Id
SET @BatchSize = @@ROWCOUNT
COMMIT TRANSACTION Organization_DeleteById_Ciphers
END
BEGIN TRANSACTION Organization_DeleteById
DELETE
FROM
[dbo].[SsoUser]
WHERE
[OrganizationId] = @Id
DELETE
FROM
[dbo].[SsoConfig]
WHERE
[OrganizationId] = @Id
DELETE CU
FROM
[dbo].[CollectionUser] CU
INNER JOIN
[dbo].[OrganizationUser] OU ON [CU].[OrganizationUserId] = [OU].[Id]
WHERE
[OU].[OrganizationId] = @Id
DELETE
FROM
[dbo].[OrganizationUser]
WHERE
[OrganizationId] = @Id
DELETE
FROM
[dbo].[ProviderOrganization]
WHERE
[OrganizationId] = @Id
DELETE
FROM
[dbo].[Organization]
WHERE
[Id] = @Id
COMMIT TRANSACTION Organization_DeleteById
END