From 259bf8d76031d7af776bb0123b60c3afda5712a2 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 21 Jul 2021 19:40:38 +0200 Subject: [PATCH] Add events for Creating, Adding and Removing ProviderOrganizations (#1475) --- .../src/CommCore/Services/ProviderService.cs | 11 +- .../Services/ProviderServiceTests.cs | 110 ++++++++++++++++++ src/Core/Enums/EventType.cs | 4 + .../Models/Api/Response/EventResponseModel.cs | 2 + src/Core/Models/Data/EventMessage.cs | 1 + src/Core/Models/Data/EventTableEntity.cs | 2 + src/Core/Models/Data/IEvent.cs | 1 + src/Core/Models/Table/Event.cs | 2 + src/Core/Services/IEventService.cs | 2 +- .../Services/Implementations/EventService.cs | 22 +++- .../NoopImplementations/NoopEventService.cs | 6 + .../dbo/Stored Procedures/Event_Create.sql | 3 + .../Organization_DeleteById.sql | 6 + src/Sql/dbo/Tables/Event.sql | 31 ++--- ...rovider.sql => 2021-07-20_00_Provider.sql} | 87 ++++++++++++++ 15 files changed, 269 insertions(+), 21 deletions(-) rename util/Migrator/DbScripts/{2021-07-08_00_Provider.sql => 2021-07-20_00_Provider.sql} (94%) diff --git a/bitwarden_license/src/CommCore/Services/ProviderService.cs b/bitwarden_license/src/CommCore/Services/ProviderService.cs index ec127b79c8..f36052b3b3 100644 --- a/bitwarden_license/src/CommCore/Services/ProviderService.cs +++ b/bitwarden_license/src/CommCore/Services/ProviderService.cs @@ -368,6 +368,7 @@ namespace Bit.CommCore.Services }; await _providerOrganizationRepository.CreateAsync(providerOrganization); + await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Added); } public async Task 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) diff --git a/bitwarden_license/test/CmmCore.Test/Services/ProviderServiceTests.cs b/bitwarden_license/test/CmmCore.Test/Services/ProviderServiceTests.cs index d8443cd266..13a798bb1b 100644 --- a/bitwarden_license/test/CmmCore.Test/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/CmmCore.Test/Services/ProviderServiceTests.cs @@ -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 sutProvider) + { + po.OrganizationId = organization.Id; + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + sutProvider.GetDependency().GetByOrganizationId(organization.Id) + .Returns(po); + + var exception = await Assert.ThrowsAsync( + () => 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 sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + var providerOrganizationRepository = sutProvider.GetDependency(); + providerOrganizationRepository.GetByOrganizationId(organization.Id).ReturnsNull(); + + await sutProvider.Sut.AddOrganization(provider.Id, organization.Id, user.Id, key); + + await providerOrganizationRepository.ReceivedWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency() + .Received().LogProviderOrganizationEventAsync(Arg.Any(), + EventType.ProviderOrganization_Added); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task CreateOrganizationAsync_Success(Provider provider, OrganizationSignup organizationSignup, + Organization organization, User user, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + var providerOrganizationRepository = sutProvider.GetDependency(); + sutProvider.GetDependency().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() + .Received().LogProviderOrganizationEventAsync(providerOrganization, + EventType.ProviderOrganization_Created); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task RemoveOrganization_ProviderOrganizationIsInvalid_Throws(Provider provider, + ProviderOrganization providerOrganization, User user, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + sutProvider.GetDependency().GetByIdAsync(providerOrganization.Id) + .ReturnsNull(); + + var exception = await Assert.ThrowsAsync( + () => 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 sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + sutProvider.GetDependency().GetByIdAsync(providerOrganization.Id) + .Returns(providerOrganization); + + var exception = await Assert.ThrowsAsync( + () => 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 sutProvider) + { + providerOrganization.ProviderId = provider.Id; + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + sutProvider.GetDependency().GetByIdAsync(providerOrganization.Id) + .Returns(providerOrganization); + sutProvider.GetDependency().HasConfirmedOwnersExceptAsync(default, default) + .ReturnsForAnyArgs(false); + + var exception = await Assert.ThrowsAsync( + () => 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 sutProvider) + { + providerOrganization.ProviderId = provider.Id; + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + var providerOrganizationRepository = sutProvider.GetDependency(); + providerOrganizationRepository.GetByIdAsync(providerOrganization.Id).Returns(providerOrganization); + sutProvider.GetDependency().HasConfirmedOwnersExceptAsync(default, default) + .ReturnsForAnyArgs(true); + + await sutProvider.Sut.RemoveOrganization(provider.Id, providerOrganization.Id, user.Id); + await providerOrganizationRepository.Received().DeleteAsync(providerOrganization); + await sutProvider.GetDependency().Received() + .LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed); + } } } diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index 5d7f85d363..c3882ece0c 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -58,5 +58,9 @@ ProviderUser_Confirmed = 1801, ProviderUser_Updated = 1802, ProviderUser_Removed = 1803, + + ProviderOrganization_Created = 1900, + ProviderOrganization_Added = 1901, + ProviderOrganization_Removed = 1902, } } diff --git a/src/Core/Models/Api/Response/EventResponseModel.cs b/src/Core/Models/Api/Response/EventResponseModel.cs index 768455dc90..d810869bda 100644 --- a/src/Core/Models/Api/Response/EventResponseModel.cs +++ b/src/Core/Models/Api/Response/EventResponseModel.cs @@ -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; } diff --git a/src/Core/Models/Data/EventMessage.cs b/src/Core/Models/Data/EventMessage.cs index 584ca72412..054fdae338 100644 --- a/src/Core/Models/Data/EventMessage.cs +++ b/src/Core/Models/Data/EventMessage.cs @@ -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; } diff --git a/src/Core/Models/Data/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs index 85de9859e1..3b0f2fe145 100644 --- a/src/Core/Models/Data/EventTableEntity.cs +++ b/src/Core/Models/Data/EventTableEntity.cs @@ -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; } diff --git a/src/Core/Models/Data/IEvent.cs b/src/Core/Models/Data/IEvent.cs index ba5a02276c..d0fc0cab38 100644 --- a/src/Core/Models/Data/IEvent.cs +++ b/src/Core/Models/Data/IEvent.cs @@ -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; } diff --git a/src/Core/Models/Table/Event.cs b/src/Core/Models/Table/Event.cs index 165b5427cf..ba02dbfcc6 100644 --- a/src/Core/Models/Table/Event.cs +++ b/src/Core/Models/Table/Event.cs @@ -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; } diff --git a/src/Core/Services/IEventService.cs b/src/Core/Services/IEventService.cs index a7234bac95..7408765170 100644 --- a/src/Core/Services/IEventService.cs +++ b/src/Core/Services/IEventService.cs @@ -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); } } diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index 06201e4b57..3c272eb019 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -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 GetProviderIdAsync(Guid? orgId) { if (_currentContext == null || !orgId.HasValue) diff --git a/src/Core/Services/NoopImplementations/NoopEventService.cs b/src/Core/Services/NoopImplementations/NoopEventService.cs index f2e14d3208..466f2be98a 100644 --- a/src/Core/Services/NoopImplementations/NoopEventService.cs +++ b/src/Core/Services/NoopImplementations/NoopEventService.cs @@ -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) { diff --git a/src/Sql/dbo/Stored Procedures/Event_Create.sql b/src/Sql/dbo/Stored Procedures/Event_Create.sql index dad922c35a..9b55c7fb6f 100644 --- a/src/Sql/dbo/Stored Procedures/Event_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Event_Create.sql @@ -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, diff --git a/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql b/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql index b7ab430aa8..f19b050e72 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql @@ -51,6 +51,12 @@ BEGIN WHERE [OrganizationId] = @Id + DELETE + FROM + [dbo].[ProviderOrganization] + WHERE + [OrganizationId] = @Id + DELETE FROM [dbo].[Organization] diff --git a/src/Sql/dbo/Tables/Event.sql b/src/Sql/dbo/Tables/Event.sql index 409c947566..e9e8754fdb 100644 --- a/src/Sql/dbo/Tables/Event.sql +++ b/src/Sql/dbo/Tables/Event.sql @@ -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) ); diff --git a/util/Migrator/DbScripts/2021-07-08_00_Provider.sql b/util/Migrator/DbScripts/2021-07-20_00_Provider.sql similarity index 94% rename from util/Migrator/DbScripts/2021-07-08_00_Provider.sql rename to util/Migrator/DbScripts/2021-07-20_00_Provider.sql index 6f3f2707b9..e8553c66f5 100644 --- a/util/Migrator/DbScripts/2021-07-08_00_Provider.sql +++ b/util/Migrator/DbScripts/2021-07-20_00_Provider.sql @@ -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