mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[AC-2522] Remove collection enhancements opt-in (#4110)
* Delete controller endpoint * Delete command * Drop sproc
This commit is contained in:
@ -14,7 +14,6 @@ using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
@ -53,7 +52,6 @@ public class OrganizationsController : Controller
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IOrganizationEnableCollectionEnhancementsCommand _organizationEnableCollectionEnhancementsCommand;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
@ -74,7 +72,6 @@ public class OrganizationsController : Controller
|
||||
IFeatureService featureService,
|
||||
GlobalSettings globalSettings,
|
||||
IPushNotificationService pushNotificationService,
|
||||
IOrganizationEnableCollectionEnhancementsCommand organizationEnableCollectionEnhancementsCommand,
|
||||
IProviderRepository providerRepository,
|
||||
IProviderBillingService providerBillingService,
|
||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory)
|
||||
@ -94,7 +91,6 @@ public class OrganizationsController : Controller
|
||||
_featureService = featureService;
|
||||
_globalSettings = globalSettings;
|
||||
_pushNotificationService = pushNotificationService;
|
||||
_organizationEnableCollectionEnhancementsCommand = organizationEnableCollectionEnhancementsCommand;
|
||||
_providerRepository = providerRepository;
|
||||
_providerBillingService = providerBillingService;
|
||||
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
||||
@ -558,38 +554,4 @@ public class OrganizationsController : Controller
|
||||
await _organizationService.UpdateAsync(model.ToOrganization(organization), eventType: EventType.Organization_CollectionManagement_Updated);
|
||||
return new OrganizationResponseModel(organization);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Migrates user, collection, and group data to the new Flexible Collections permissions scheme,
|
||||
/// then sets organization.FlexibleCollections to true to enable these new features for the organization.
|
||||
/// This is irreversible.
|
||||
/// </summary>
|
||||
/// <param name="organizationId"></param>
|
||||
/// <exception cref="NotFoundException"></exception>
|
||||
[HttpPost("{id}/enable-collection-enhancements")]
|
||||
[RequireFeature(FeatureFlagKeys.FlexibleCollectionsMigration)]
|
||||
public async Task EnableCollectionEnhancements(Guid id)
|
||||
{
|
||||
if (!await _currentContext.OrganizationOwner(id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await _organizationEnableCollectionEnhancementsCommand.EnableCollectionEnhancements(organization);
|
||||
|
||||
// Force a vault sync for all owners and admins of the organization so that changes show immediately
|
||||
// Custom users are intentionally not handled as they are likely to be less impacted and we want to limit simultaneous syncs
|
||||
var orgUsers = await _organizationUserRepository.GetManyByOrganizationAsync(id, null);
|
||||
await Task.WhenAll(orgUsers
|
||||
.Where(ou => ou.UserId.HasValue &&
|
||||
ou.Status == OrganizationUserStatusType.Confirmed &&
|
||||
ou.Type is OrganizationUserType.Admin or OrganizationUserType.Owner)
|
||||
.Select(ou => _pushNotificationService.PushSyncOrganizationsAsync(ou.UserId.Value)));
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Enable collection enhancements for an organization.
|
||||
/// This command will be deprecated once all organizations have collection enhancements enabled.
|
||||
/// </summary>
|
||||
public interface IOrganizationEnableCollectionEnhancementsCommand
|
||||
{
|
||||
Task EnableCollectionEnhancements(Organization organization);
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements;
|
||||
|
||||
public class OrganizationEnableCollectionEnhancementsCommand : IOrganizationEnableCollectionEnhancementsCommand
|
||||
{
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly ILogger<OrganizationEnableCollectionEnhancementsCommand> _logger;
|
||||
|
||||
public OrganizationEnableCollectionEnhancementsCommand(ICollectionRepository collectionRepository,
|
||||
IGroupRepository groupRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationService organizationService,
|
||||
ILogger<OrganizationEnableCollectionEnhancementsCommand> logger)
|
||||
{
|
||||
_collectionRepository = collectionRepository;
|
||||
_groupRepository = groupRepository;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_organizationService = organizationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task EnableCollectionEnhancements(Organization organization)
|
||||
{
|
||||
if (organization.FlexibleCollections)
|
||||
{
|
||||
throw new BadRequestException("Organization has already been migrated to the new collection enhancements");
|
||||
}
|
||||
|
||||
// Log the Organization data that will change when the migration is complete
|
||||
await LogPreMigrationDataAsync(organization.Id);
|
||||
|
||||
// Run the data migration script
|
||||
await _organizationRepository.EnableCollectionEnhancements(organization.Id);
|
||||
|
||||
organization.FlexibleCollections = true;
|
||||
await _organizationService.ReplaceAndUpdateCacheAsync(organization);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method logs the data that will be migrated to the new collection enhancements so that it can be restored if needed
|
||||
/// </summary>
|
||||
/// <param name="organizationId"></param>
|
||||
private async Task LogPreMigrationDataAsync(Guid organizationId)
|
||||
{
|
||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(organizationId);
|
||||
// Grab Group Ids that have AccessAll enabled as it will be removed in the data migration
|
||||
var groupIdsWithAccessAllEnabled = groups
|
||||
.Where(g => g.AccessAll)
|
||||
.Select(g => g.Id)
|
||||
.ToList();
|
||||
|
||||
var organizationUsers = await _organizationUserRepository.GetManyByOrganizationAsync(organizationId, type: null);
|
||||
// Grab OrganizationUser Ids that have AccessAll enabled as it will be removed in the data migration
|
||||
var organizationUserIdsWithAccessAllEnabled = organizationUsers
|
||||
.Where(ou => ou.AccessAll)
|
||||
.Select(ou => ou.Id)
|
||||
.ToList();
|
||||
// Grab OrganizationUser Ids of Manager users as that will be downgraded to User in the data migration
|
||||
var migratedManagers = organizationUsers
|
||||
.Where(ou => ou.Type == OrganizationUserType.Manager)
|
||||
.Select(ou => ou.Id)
|
||||
.ToList();
|
||||
|
||||
var usersEligibleToManageCollections = organizationUsers
|
||||
.Where(ou =>
|
||||
ou.Type == OrganizationUserType.Manager ||
|
||||
(ou.Type == OrganizationUserType.Custom &&
|
||||
!string.IsNullOrEmpty(ou.Permissions) &&
|
||||
ou.GetPermissions().EditAssignedCollections)
|
||||
)
|
||||
.Select(ou => ou.Id)
|
||||
.ToList();
|
||||
var collectionUsers = await _collectionRepository.GetManyByOrganizationIdWithAccessAsync(organizationId);
|
||||
// Grab CollectionUser permissions that will change in the data migration
|
||||
var collectionUsersData = collectionUsers.SelectMany(tuple =>
|
||||
tuple.Item2.Users.Select(user =>
|
||||
new
|
||||
{
|
||||
CollectionId = tuple.Item1.Id,
|
||||
OrganizationUserId = user.Id,
|
||||
user.ReadOnly,
|
||||
user.HidePasswords
|
||||
}))
|
||||
.Where(cud => usersEligibleToManageCollections.Any(ou => ou == cud.OrganizationUserId))
|
||||
.ToList();
|
||||
|
||||
var logObject = new
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
GroupAccessAll = groupIdsWithAccessAllEnabled,
|
||||
UserAccessAll = organizationUserIdsWithAccessAllEnabled,
|
||||
MigratedManagers = migratedManagers,
|
||||
CollectionUsers = collectionUsersData
|
||||
};
|
||||
|
||||
_logger.LogWarning("Flexible Collections data migration started. Backup data: {LogObject}", JsonSerializer.Serialize(logObject));
|
||||
}
|
||||
}
|
@ -15,5 +15,4 @@ public interface IOrganizationRepository : IRepository<Organization, Guid>
|
||||
Task<SelfHostedOrganizationDetails> GetSelfHostedOrganizationDetailsById(Guid id);
|
||||
Task<ICollection<Organization>> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take);
|
||||
Task<IEnumerable<string>> GetOwnerEmailAddressesById(Guid organizationId);
|
||||
Task EnableCollectionEnhancements(Guid organizationId);
|
||||
}
|
||||
|
@ -114,11 +114,6 @@ public static class FeatureFlagKeys
|
||||
public const string ItemShare = "item-share";
|
||||
public const string KeyRotationImprovements = "key-rotation-improvements";
|
||||
public const string DuoRedirect = "duo-redirect";
|
||||
/// <summary>
|
||||
/// Exposes a migration button in the web vault which allows users to migrate an existing organization to
|
||||
/// flexible collections
|
||||
/// </summary>
|
||||
public const string FlexibleCollectionsMigration = "flexible-collections-migration";
|
||||
public const string PM5766AutomaticTax = "PM-5766-automatic-tax";
|
||||
public const string PM5864DollarThreshold = "PM-5864-dollar-threshold";
|
||||
public const string ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners";
|
||||
|
@ -4,8 +4,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Groups;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
||||
@ -52,7 +50,6 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddOrganizationUserCommands();
|
||||
services.AddOrganizationUserCommandsQueries();
|
||||
services.AddBaseOrganizationSubscriptionCommandsQueries();
|
||||
services.AddOrganizationCollectionEnhancementsCommands();
|
||||
}
|
||||
|
||||
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
||||
@ -148,11 +145,6 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddScoped<IUpdateSecretsManagerSubscriptionCommand, UpdateSecretsManagerSubscriptionCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationCollectionEnhancementsCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IOrganizationEnableCollectionEnhancementsCommand, OrganizationEnableCollectionEnhancementsCommand>();
|
||||
}
|
||||
|
||||
private static void AddTokenizers(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider =>
|
||||
|
@ -169,16 +169,4 @@ public class OrganizationRepository : Repository<Organization, Guid>, IOrganizat
|
||||
new { OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task EnableCollectionEnhancements(Guid organizationId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
"[dbo].[Organization_EnableCollectionEnhancements]",
|
||||
new { OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure,
|
||||
commandTimeout: 180);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,155 +0,0 @@
|
||||
CREATE PROCEDURE [dbo].[Organization_EnableCollectionEnhancements]
|
||||
@OrganizationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
-- Step 1: AccessAll migration for Groups
|
||||
-- Create a temporary table to store the groups with AccessAll = 1
|
||||
SELECT [Id] AS [GroupId], [OrganizationId]
|
||||
INTO #TempGroupsAccessAll
|
||||
FROM [dbo].[Group]
|
||||
WHERE [OrganizationId] = @OrganizationId
|
||||
AND [AccessAll] = 1;
|
||||
|
||||
-- Step 2: AccessAll migration for OrganizationUsers
|
||||
-- Create a temporary table to store the OrganizationUsers with AccessAll = 1
|
||||
SELECT [Id] AS [OrganizationUserId], [OrganizationId]
|
||||
INTO #TempUsersAccessAll
|
||||
FROM [dbo].[OrganizationUser]
|
||||
WHERE [OrganizationId] = @OrganizationId
|
||||
AND [AccessAll] = 1;
|
||||
|
||||
-- Step 3: For all OrganizationUsers with Manager role or 'EditAssignedCollections' permission update their existing CollectionUser rows and insert new rows with [Manage] = 1
|
||||
-- and finally update all OrganizationUsers with Manager role to User role
|
||||
-- Create a temporary table to store the OrganizationUsers with Manager role or 'EditAssignedCollections' permission
|
||||
SELECT ou.[Id] AS [OrganizationUserId],
|
||||
CASE WHEN ou.[Type] = 3 THEN 1 ELSE 0 END AS [IsManager]
|
||||
INTO #TempUserManagers
|
||||
FROM [dbo].[OrganizationUser] ou
|
||||
WHERE ou.[OrganizationId] = @OrganizationId
|
||||
AND (ou.[Type] = 3 OR (ou.[Permissions] IS NOT NULL
|
||||
AND ISJSON(ou.[Permissions]) > 0 AND JSON_VALUE(ou.[Permissions], '$.editAssignedCollections') = 'true'));
|
||||
|
||||
-- Step 4: Bump AccountRevisionDate for all OrganizationUsers updated in the previous steps
|
||||
-- Combine and union the distinct OrganizationUserIds from all steps into a single variable
|
||||
DECLARE @OrgUsersToBump [dbo].[GuidIdArray]
|
||||
INSERT INTO @OrgUsersToBump
|
||||
SELECT DISTINCT [OrganizationUserId] AS Id
|
||||
FROM (
|
||||
-- Step 1
|
||||
SELECT GU.[OrganizationUserId]
|
||||
FROM [dbo].[GroupUser] GU
|
||||
INNER JOIN #TempGroupsAccessAll TG ON GU.[GroupId] = TG.[GroupId]
|
||||
|
||||
UNION
|
||||
|
||||
-- Step 2
|
||||
SELECT [OrganizationUserId]
|
||||
FROM #TempUsersAccessAll
|
||||
|
||||
UNION
|
||||
|
||||
-- Step 3
|
||||
SELECT [OrganizationUserId]
|
||||
FROM #TempUserManagers
|
||||
) AS CombinedOrgUsers;
|
||||
|
||||
BEGIN TRY
|
||||
BEGIN TRANSACTION;
|
||||
-- Step 1
|
||||
-- Update existing rows in [dbo].[CollectionGroup]
|
||||
UPDATE CG
|
||||
SET
|
||||
CG.[ReadOnly] = 0,
|
||||
CG.[HidePasswords] = 0,
|
||||
CG.[Manage] = 0
|
||||
FROM [dbo].[CollectionGroup] CG
|
||||
INNER JOIN [dbo].[Collection] C ON CG.[CollectionId] = C.[Id]
|
||||
INNER JOIN #TempGroupsAccessAll TG ON CG.[GroupId] = TG.[GroupId]
|
||||
WHERE C.[OrganizationId] = TG.[OrganizationId];
|
||||
|
||||
-- Insert new rows into [dbo].[CollectionGroup]
|
||||
INSERT INTO [dbo].[CollectionGroup] ([CollectionId], [GroupId], [ReadOnly], [HidePasswords], [Manage])
|
||||
SELECT C.[Id], TG.[GroupId], 0, 0, 0
|
||||
FROM [dbo].[Collection] C
|
||||
INNER JOIN #TempGroupsAccessAll TG ON C.[OrganizationId] = TG.[OrganizationId]
|
||||
LEFT JOIN [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = TG.[GroupId]
|
||||
WHERE CG.[CollectionId] IS NULL;
|
||||
|
||||
-- Update Group to clear AccessAll flag and update RevisionDate
|
||||
UPDATE G
|
||||
SET [AccessAll] = 0, [RevisionDate] = GETUTCDATE()
|
||||
FROM [dbo].[Group] G
|
||||
INNER JOIN #TempGroupsAccessAll TG ON G.[Id] = TG.[GroupId];
|
||||
|
||||
-- Step 2
|
||||
-- Update existing rows in [dbo].[CollectionUser]
|
||||
UPDATE target
|
||||
SET
|
||||
target.[ReadOnly] = 0,
|
||||
target.[HidePasswords] = 0,
|
||||
target.[Manage] = 0
|
||||
FROM [dbo].[CollectionUser] AS target
|
||||
INNER JOIN [dbo].[Collection] AS C ON target.[CollectionId] = C.[Id]
|
||||
INNER JOIN #TempUsersAccessAll AS TU ON C.[OrganizationId] = TU.[OrganizationId] AND target.[OrganizationUserId] = TU.[OrganizationUserId];
|
||||
|
||||
-- Insert new rows into [dbo].[CollectionUser]
|
||||
INSERT INTO [dbo].[CollectionUser] ([CollectionId], [OrganizationUserId], [ReadOnly], [HidePasswords], [Manage])
|
||||
SELECT C.[Id] AS [CollectionId], TU.[OrganizationUserId], 0, 0, 0
|
||||
FROM [dbo].[Collection] C
|
||||
INNER JOIN #TempUsersAccessAll TU ON C.[OrganizationId] = TU.[OrganizationId]
|
||||
LEFT JOIN [dbo].[CollectionUser] target
|
||||
ON target.[CollectionId] = C.[Id] AND target.[OrganizationUserId] = TU.[OrganizationUserId]
|
||||
WHERE target.[CollectionId] IS NULL;
|
||||
|
||||
-- Update OrganizationUser to clear AccessAll flag
|
||||
UPDATE OU
|
||||
SET [AccessAll] = 0, [RevisionDate] = GETUTCDATE()
|
||||
FROM [dbo].[OrganizationUser] OU
|
||||
INNER JOIN #TempUsersAccessAll TU ON OU.[Id] = TU.[OrganizationUserId];
|
||||
|
||||
-- Step 3
|
||||
-- Update [dbo].[CollectionUser] with [Manage] = 1 using the temporary table
|
||||
UPDATE CU
|
||||
SET CU.[ReadOnly] = 0,
|
||||
CU.[HidePasswords] = 0,
|
||||
CU.[Manage] = 1
|
||||
FROM [dbo].[CollectionUser] CU
|
||||
INNER JOIN #TempUserManagers TUM ON CU.[OrganizationUserId] = TUM.[OrganizationUserId];
|
||||
|
||||
-- Insert rows to [dbo].[CollectionUser] with [Manage] = 1 using the temporary table
|
||||
-- This is for orgUsers who are Managers / EditAssignedCollections but have access via a group
|
||||
-- We cannot give the whole group Manage permissions so we have to give them a direct assignment
|
||||
INSERT INTO [dbo].[CollectionUser] ([CollectionId], [OrganizationUserId], [ReadOnly], [HidePasswords], [Manage])
|
||||
SELECT DISTINCT CG.[CollectionId], TUM.[OrganizationUserId], 0, 0, 1
|
||||
FROM [dbo].[CollectionGroup] CG
|
||||
INNER JOIN [dbo].[GroupUser] GU ON CG.[GroupId] = GU.[GroupId]
|
||||
INNER JOIN #TempUserManagers TUM ON GU.[OrganizationUserId] = TUM.[OrganizationUserId]
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM [dbo].[CollectionUser] CU
|
||||
WHERE CU.[CollectionId] = CG.[CollectionId] AND CU.[OrganizationUserId] = TUM.[OrganizationUserId]
|
||||
);
|
||||
|
||||
-- Update [dbo].[OrganizationUser] to migrate all OrganizationUsers with Manager role to User role
|
||||
UPDATE OU
|
||||
SET OU.[Type] = 2, OU.[RevisionDate] = GETUTCDATE() -- User
|
||||
FROM [dbo].[OrganizationUser] OU
|
||||
INNER JOIN #TempUserManagers TUM ON ou.[Id] = TUM.[OrganizationUserId]
|
||||
WHERE TUM.[IsManager] = 1; -- Filter for Managers
|
||||
|
||||
-- Step 4
|
||||
-- Execute User_BumpAccountRevisionDateByOrganizationUserIds for the distinct OrganizationUserIds
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationUserIds] @OrgUsersToBump;
|
||||
COMMIT TRANSACTION;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
ROLLBACK TRANSACTION;
|
||||
THROW;
|
||||
END CATCH;
|
||||
|
||||
-- Drop the temporary table
|
||||
DROP TABLE #TempGroupsAccessAll;
|
||||
DROP TABLE #TempUsersAccessAll;
|
||||
DROP TABLE #TempUserManagers;
|
||||
END
|
Reference in New Issue
Block a user