mirror of
https://github.com/bitwarden/server.git
synced 2025-02-01 23:31:41 +01:00
Feature.web.534.allow multi select in org vault (#830)
* Set up API methods for bulk admin delete
This commit is contained in:
parent
51fd87df0b
commit
229478adae
@ -360,6 +360,26 @@ namespace Bit.Api.Controllers
|
||||
await _cipherService.DeleteManyAsync(model.Ids.Select(i => new Guid(i)), userId);
|
||||
}
|
||||
|
||||
[HttpDelete("admin")]
|
||||
[HttpPost("delete-admin")]
|
||||
public async Task DeleteManyAdmin([FromBody]CipherBulkDeleteRequestModel model)
|
||||
{
|
||||
if (!_globalSettings.SelfHosted && model.Ids.Count() > 500)
|
||||
{
|
||||
throw new BadRequestException("You can only delete up to 500 items at a time. " +
|
||||
"Consider using the \"Purge Vault\" option instead.");
|
||||
}
|
||||
|
||||
if (model == null || string.IsNullOrWhiteSpace(model.OrganizationId) ||
|
||||
!_currentContext.OrganizationAdmin(new Guid(model.OrganizationId)))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
await _cipherService.DeleteManyAsync(model.Ids.Select(i => new Guid(i)), userId, new Guid(model.OrganizationId), true);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/delete")]
|
||||
public async Task PutDelete(string id)
|
||||
{
|
||||
@ -387,17 +407,35 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
[HttpPut("delete")]
|
||||
public async Task PutDeleteMany([FromBody]CipherBulkRestoreRequestModel model)
|
||||
public async Task PutDeleteMany([FromBody]CipherBulkDeleteRequestModel model)
|
||||
{
|
||||
if (!_globalSettings.SelfHosted && model.Ids.Count() > 500)
|
||||
{
|
||||
throw new BadRequestException("You can only restore up to 500 items at a time.");
|
||||
throw new BadRequestException("You can only delete up to 500 items at a time.");
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
await _cipherService.SoftDeleteManyAsync(model.Ids.Select(i => new Guid(i)), userId);
|
||||
}
|
||||
|
||||
[HttpPut("delete-admin")]
|
||||
public async Task PutDeleteManyAdmin([FromBody]CipherBulkDeleteRequestModel model)
|
||||
{
|
||||
if (!_globalSettings.SelfHosted && model.Ids.Count() > 500)
|
||||
{
|
||||
throw new BadRequestException("You can only delete up to 500 items at a time.");
|
||||
}
|
||||
|
||||
if (model == null || string.IsNullOrWhiteSpace(model.OrganizationId) ||
|
||||
!_currentContext.OrganizationAdmin(new Guid(model.OrganizationId)))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
await _cipherService.SoftDeleteManyAsync(model.Ids.Select(i => new Guid(i)), userId, new Guid(model.OrganizationId), true);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/restore")]
|
||||
public async Task PutRestore(string id)
|
||||
{
|
||||
|
@ -204,6 +204,7 @@ namespace Bit.Core.Models.Api
|
||||
{
|
||||
[Required]
|
||||
public IEnumerable<string> Ids { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
}
|
||||
|
||||
public class CipherBulkRestoreRequestModel
|
||||
|
@ -24,6 +24,7 @@ namespace Bit.Core.Repositories
|
||||
Task UpdateAttachmentAsync(CipherAttachment attachment);
|
||||
Task DeleteAttachmentAsync(Guid cipherId, string attachmentId);
|
||||
Task DeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
Task DeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||
Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId);
|
||||
Task DeleteByUserIdAsync(Guid userId);
|
||||
Task DeleteByOrganizationIdAsync(Guid organizationId);
|
||||
@ -33,6 +34,7 @@ namespace Bit.Core.Repositories
|
||||
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||
IEnumerable<CollectionCipher> collectionCiphers);
|
||||
Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
Task SoftDeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||
Task RestoreAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
}
|
||||
}
|
||||
|
@ -226,6 +226,28 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
$"[{Schema}].[Cipher_DeleteByIdsOrganizationId]",
|
||||
new { Ids = ids.ToGuidIdArrayTVP(), OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SoftDeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
$"[{Schema}].[Cipher_SoftDeleteByIdsOrganizationId]",
|
||||
new { Ids = ids.ToGuidIdArrayTVP(), OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
|
@ -18,7 +18,7 @@ namespace Bit.Core.Services
|
||||
Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, long requestLength, string attachmentId,
|
||||
Guid organizationShareId);
|
||||
Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
||||
Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId);
|
||||
Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false);
|
||||
Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId, bool orgAdmin = false);
|
||||
Task PurgeAsync(Guid organizationId);
|
||||
Task MoveManyAsync(IEnumerable<Guid> cipherIds, Guid? destinationFolderId, Guid movingUserId);
|
||||
@ -34,7 +34,7 @@ namespace Bit.Core.Services
|
||||
Task ImportCiphersAsync(List<Collection> collections, List<CipherDetails> ciphers,
|
||||
IEnumerable<KeyValuePair<int, int>> collectionRelationships, Guid importingUserId);
|
||||
Task SoftDeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
||||
Task SoftDeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId);
|
||||
Task SoftDeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false);
|
||||
Task RestoreAsync(Cipher cipher, Guid restoringUserId, bool orgAdmin = false);
|
||||
Task RestoreManyAsync(IEnumerable<Guid> cipherIds, Guid restoringUserId);
|
||||
}
|
||||
|
@ -288,13 +288,23 @@ namespace Bit.Core.Services
|
||||
await _pushService.PushSyncCipherDeleteAsync(cipher);
|
||||
}
|
||||
|
||||
public async Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId)
|
||||
public async Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false)
|
||||
{
|
||||
var cipherIdsSet = new HashSet<Guid>(cipherIds);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId);
|
||||
var deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit);
|
||||
var deletingCiphers = new List<Cipher>();
|
||||
|
||||
await _cipherRepository.DeleteAsync(cipherIds, deletingUserId);
|
||||
if (orgAdmin && organizationId.HasValue)
|
||||
{
|
||||
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(organizationId.Value);
|
||||
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id)).ToList();
|
||||
await _cipherRepository.DeleteByIdsOrganizationIdAsync(deletingCiphers.Select(c => c.Id), organizationId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId);
|
||||
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(x => (Cipher)x).ToList();
|
||||
await _cipherRepository.DeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId);
|
||||
}
|
||||
|
||||
var events = deletingCiphers.Select(c =>
|
||||
new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_Deleted, null));
|
||||
@ -693,13 +703,23 @@ namespace Bit.Core.Services
|
||||
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
|
||||
}
|
||||
|
||||
public async Task SoftDeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId)
|
||||
public async Task SoftDeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId, Guid? organizationId, bool orgAdmin)
|
||||
{
|
||||
var cipherIdsSet = new HashSet<Guid>(cipherIds);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId);
|
||||
var deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit);
|
||||
var deletingCiphers = new List<Cipher>();
|
||||
|
||||
await _cipherRepository.SoftDeleteAsync(cipherIds, deletingUserId);
|
||||
if (orgAdmin && organizationId.HasValue)
|
||||
{
|
||||
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(organizationId.Value);
|
||||
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id)).ToList();
|
||||
await _cipherRepository.SoftDeleteByIdsOrganizationIdAsync(deletingCiphers.Select(c => c.Id), organizationId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId);
|
||||
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(x => (Cipher)x).ToList();
|
||||
await _cipherRepository.SoftDeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId);
|
||||
}
|
||||
|
||||
var events = deletingCiphers.Select(c =>
|
||||
new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_SoftDeleted, null));
|
||||
|
@ -0,0 +1,19 @@
|
||||
CREATE PROCEDURE [dbo].[Cipher_DeleteByIdsOrganizationId]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@OrganizationId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
-- Delete ciphers
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Cipher]
|
||||
WHERE
|
||||
[Id] IN (SELECT * FROM @Ids)
|
||||
AND OrganizationId = @OrganizationId
|
||||
|
||||
-- Cleanup organization
|
||||
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||
END
|
@ -0,0 +1,22 @@
|
||||
CREATE PROCEDURE [dbo].[Cipher_SoftDeleteByIdsOrganizationId]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@OrganizationId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
-- Delete ciphers
|
||||
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[DeletedDate] = @UtcNow,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT * FROM @Ids)
|
||||
AND OrganizationId = @OrganizationId
|
||||
|
||||
-- Cleanup organization
|
||||
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||
END
|
@ -0,0 +1,55 @@
|
||||
IF OBJECT_ID('[dbo].[Cipher_DeleteByIdsOrganizationId]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[Cipher_DeleteByIdsOrganizationId];
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID('[dbo].[Cipher_SoftDeleteByIdsOrganizationId]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[Cipher_SoftDeleteByIdsOrganizationId];
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[Cipher_DeleteByIdsOrganizationId]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@OrganizationId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
-- Delete ciphers
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Cipher]
|
||||
WHERE
|
||||
[Id] IN (SELECT * FROM @Ids)
|
||||
AND OrganizationId = @OrganizationId
|
||||
|
||||
-- Cleanup organization
|
||||
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[Cipher_SoftDeleteByIdsOrganizationId]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@OrganizationId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
-- Delete ciphers
|
||||
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[DeletedDate] = @UtcNow,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT * FROM @Ids)
|
||||
AND OrganizationId = @OrganizationId
|
||||
|
||||
-- Cleanup organization
|
||||
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||
END
|
Loading…
Reference in New Issue
Block a user