Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tools.Services;
using Microsoft.Extensions.Logging;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
Expand All @@ -19,6 +20,7 @@ public class OrganizationDeleteCommand : IOrganizationDeleteCommand
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISubscriberService _subscriberService;
private readonly IFeatureService _featureService;
private readonly ISendFileStorageService _sendFileStorageService;
private readonly ILogger<OrganizationDeleteCommand> _logger;

public OrganizationDeleteCommand(
Expand All @@ -28,6 +30,7 @@ public OrganizationDeleteCommand(
ISsoConfigRepository ssoConfigRepository,
ISubscriberService subscriberService,
IFeatureService featureService,
ISendFileStorageService sendFileStorageService,
ILogger<OrganizationDeleteCommand> logger)
{
_applicationCacheService = applicationCacheService;
Expand All @@ -36,6 +39,7 @@ public OrganizationDeleteCommand(
_ssoConfigRepository = ssoConfigRepository;
_subscriberService = subscriberService;
_featureService = featureService;
_sendFileStorageService = sendFileStorageService;
_logger = logger;
}

Expand Down Expand Up @@ -66,6 +70,7 @@ public async Task DeleteAsync(Organization organization)
}
}

await _sendFileStorageService.DeleteFilesForOrganizationAsync(organization.Id);
await _organizationRepository.DeleteAsync(organization);
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
}
Expand Down
22 changes: 21 additions & 1 deletion src/Core/Services/Implementations/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.Repositories;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;
Expand Down Expand Up @@ -69,6 +73,8 @@
private readonly IPricingClient _pricingClient;
private readonly IHasPremiumAccessQuery _hasPremiumAccessQuery;
private readonly ISubscriberService _subscriberService;
private readonly ISendRepository _sendRepository;
private readonly ISendFileStorageService _sendFileStorageService;

public UserService(
IUserRepository userRepository,
Expand Down Expand Up @@ -102,7 +108,9 @@
IPolicyRequirementQuery policyRequirementQuery,
IPricingClient pricingClient,
IHasPremiumAccessQuery hasPremiumAccessQuery,
ISubscriberService subscriberService)
ISubscriberService subscriberService,
ISendRepository sendRepository,
ISendFileStorageService sendFileStorageService)
: base(
store,
optionsAccessor,
Expand Down Expand Up @@ -141,6 +149,8 @@
_pricingClient = pricingClient;
_hasPremiumAccessQuery = hasPremiumAccessQuery;
_subscriberService = subscriberService;
_sendRepository = sendRepository;
_sendFileStorageService = sendFileStorageService;
}

public Guid? GetProperUserId(ClaimsPrincipal principal)
Expand All @@ -166,7 +176,7 @@
return null;
}

_currentContext.User = await _userRepository.GetByIdAsync(userIdGuid);

Check warning on line 179 in src/Core/Services/Implementations/UserService.cs

View workflow job for this annotation

GitHub Actions / Sonar / Quality scan

'_currentContext' is null on at least one execution path.
return _currentContext.User;
}

Expand All @@ -177,7 +187,7 @@
return _currentContext.User;
}

_currentContext.User = await _userRepository.GetByIdAsync(userId);

Check warning on line 190 in src/Core/Services/Implementations/UserService.cs

View workflow job for this annotation

GitHub Actions / Sonar / Quality scan

'_currentContext' is null on at least one execution path.
return _currentContext.User;
}

Expand Down Expand Up @@ -281,6 +291,16 @@
catch (BillingException) { }
}

var sends = await _sendRepository.GetManyByUserIdAsync(user.Id);
foreach (var send in sends.Where(s => s.Type == SendType.File))
{
var data = JsonSerializer.Deserialize<SendFileData>(send.Data);
if (data?.Id != null)
{
await _sendFileStorageService.DeleteFileAsync(send, data.Id);
}
}

await _userRepository.DeleteAsync(user);
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
Expand Down Expand Up @@ -732,7 +752,7 @@
{
if (!CoreHelpers.FixedTimeEquals(
user.TwoFactorRecoveryCode,
recoveryCode.Replace(" ", string.Empty).Trim().ToLower()))

Check warning on line 755 in src/Core/Services/Implementations/UserService.cs

View workflow job for this annotation

GitHub Actions / Build MSSQL migrator utility (win-x64)

The behavior of 'string.ToLower()' could vary based on the current user's locale settings. Replace this call in 'UserService.RecoverTwoFactorAsync(User, string)' with a call to 'string.ToLower(CultureInfo)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1304)

Check warning on line 755 in src/Core/Services/Implementations/UserService.cs

View workflow job for this annotation

GitHub Actions / Build Docker images (Notifications, ./src, true)

The behavior of 'string.ToLower()' could vary based on the current user's locale settings. Replace this call in 'UserService.RecoverTwoFactorAsync(User, string)' with a call to 'string.ToLower(CultureInfo)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1304)
{
return false;
}
Expand Down Expand Up @@ -1151,7 +1171,7 @@

public async Task<bool> ActiveNewDeviceVerificationException(Guid userId)
{
var cacheKey = string.Format(AuthConstants.NewDeviceVerificationExceptionCacheKeyFormat, userId.ToString());

Check warning on line 1174 in src/Core/Services/Implementations/UserService.cs

View workflow job for this annotation

GitHub Actions / Build MSSQL migrator utility (win-x64)

The behavior of 'string.Format(string, object)' could vary based on the current user's locale settings. Replace this call in 'UserService.ActiveNewDeviceVerificationException(Guid)' with a call to 'string.Format(IFormatProvider, string, params object[])'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 1174 in src/Core/Services/Implementations/UserService.cs

View workflow job for this annotation

GitHub Actions / Build Docker images (Notifications, ./src, true)

The behavior of 'string.Format(string, object)' could vary based on the current user's locale settings. Replace this call in 'UserService.ActiveNewDeviceVerificationException(Guid)' with a call to 'string.Format(IFormatProvider, string, params object[])'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)
var cacheValue = await _distributedCache.GetAsync(cacheKey);
return cacheValue != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ public async Task UploadFileToExistingSendAsync(Stream stream, Send send)
}
public async Task DeleteSendAsync(Send send)
{
await _sendRepository.DeleteAsync(send);
if (send.Type == Enums.SendType.File)
{
var data = JsonSerializer.Deserialize<SendFileData>(send.Data);
await _sendFileStorageService.DeleteFileAsync(send, data.Id);
}
await _sendRepository.DeleteAsync(send);
await _pushNotificationService.PushSyncSendDeleteAsync(send);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,27 @@ public async Task DeleteBlobAsync(string blobName)
public async Task DeleteFilesForOrganizationAsync(Guid organizationId)
{
await InitAsync();
await DeleteBlobsByMetadataAsync("organizationId", organizationId.ToString());
}

public async Task DeleteFilesForUserAsync(Guid userId)
{
await InitAsync();
await DeleteBlobsByMetadataAsync("userId", userId.ToString());
}

private async Task DeleteBlobsByMetadataAsync(string metadataKey, string metadataValue)
{
await foreach (var blobItem in _sendFilesContainerClient!.GetBlobsAsync(BlobTraits.Metadata))
{
if (blobItem.Metadata != null &&
blobItem.Metadata.TryGetValue(metadataKey, out var value) &&
string.Equals(value, metadataValue, StringComparison.OrdinalIgnoreCase))
{
var blob = _sendFilesContainerClient.GetBlobClient(blobItem.Name);
await blob.DeleteIfExistsAsync();
}
}
}

public async Task<string> GetSendFileDownloadUrlAsync(Send send, string fileId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ await dbContext.NotificationStatuses.Where(ns => ns.Notification.OrganizationId
await dbContext.Notifications.Where(n => n.OrganizationId == organization.Id)
.ExecuteDeleteAsync();

await dbContext.Sends.Where(s => s.OrganizationId == organization.Id)
.ExecuteDeleteAsync();

// The below section are 3 SPROCS in SQL Server but are only called by here
await dbContext.OrganizationApiKeys.Where(oa => oa.OrganizationId == organization.Id)
.ExecuteDeleteAsync();
Expand Down
7 changes: 7 additions & 0 deletions src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ BEGIN
WHERE
[OrganizationId] = @Id

-- Delete Organization Owned Sends
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ Differs in formatting, add blank line before comment

DELETE
FROM
[dbo].[Send]
WHERE
[OrganizationId] = @Id

DELETE
FROM
[dbo].[Organization]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
CREATE OR ALTER PROCEDURE [dbo].[Organization_DeleteById]
@Id UNIQUEIDENTIFIER
WITH RECOMPILE
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].[AuthRequest]
WHERE
[OrganizationId] = @Id

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 AP
FROM
[dbo].[AccessPolicy] AP
INNER JOIN
[dbo].[OrganizationUser] OU ON [AP].[OrganizationUserId] = [OU].[Id]
WHERE
[OU].[OrganizationId] = @Id

DELETE GU
FROM
[dbo].[GroupUser] GU
INNER JOIN
[dbo].[OrganizationUser] OU ON [GU].[OrganizationUserId] = [OU].[Id]
WHERE
[OU].[OrganizationId] = @Id

DELETE
FROM
[dbo].[OrganizationUser]
WHERE
[OrganizationId] = @Id

DELETE
FROM
[dbo].[ProviderOrganization]
WHERE
[OrganizationId] = @Id

EXEC [dbo].[OrganizationApiKey_OrganizationDeleted] @Id
EXEC [dbo].[OrganizationConnection_OrganizationDeleted] @Id
EXEC [dbo].[OrganizationSponsorship_OrganizationDeleted] @Id
EXEC [dbo].[OrganizationDomain_OrganizationDeleted] @Id
EXEC [dbo].[OrganizationIntegration_OrganizationDeleted] @Id

DELETE
FROM
[dbo].[Project]
WHERE
[OrganizationId] = @Id

DELETE
FROM
[dbo].[Secret]
WHERE
[OrganizationId] = @Id

DELETE AK
FROM
[dbo].[ApiKey] AK
INNER JOIN
[dbo].[ServiceAccount] SA ON [AK].[ServiceAccountId] = [SA].[Id]
WHERE
[SA].[OrganizationId] = @Id

DELETE AP
FROM
[dbo].[AccessPolicy] AP
INNER JOIN
[dbo].[ServiceAccount] SA ON [AP].[GrantedServiceAccountId] = [SA].[Id]
WHERE
[SA].[OrganizationId] = @Id

DELETE
FROM
[dbo].[ServiceAccount]
WHERE
[OrganizationId] = @Id

-- Delete Notification Status
DELETE
NS
FROM
[dbo].[NotificationStatus] NS
INNER JOIN
[dbo].[Notification] N ON N.[Id] = NS.[NotificationId]
WHERE
N.[OrganizationId] = @Id

-- Delete Notification
DELETE
FROM
[dbo].[Notification]
WHERE
[OrganizationId] = @Id

-- Delete Organization Application
DELETE
FROM
[dbo].[OrganizationApplication]
WHERE
[OrganizationId] = @Id

-- Delete Organization Report
DELETE
FROM
[dbo].[OrganizationReport]
WHERE
[OrganizationId] = @Id

-- Delete Organization Owned Sends
DELETE
FROM
[dbo].[Send]
WHERE
[OrganizationId] = @Id

DELETE
FROM
[dbo].[Organization]
WHERE
[Id] = @Id

COMMIT TRANSACTION Organization_DeleteById
END
Loading