mirror of
https://github.com/bitwarden/server.git
synced 2025-04-06 21:48:12 -05:00
delete attachments
This commit is contained in:
parent
d3c18381f9
commit
43262e577c
@ -20,7 +20,6 @@ namespace Bit.Api.Controllers
|
|||||||
private readonly ICollectionCipherRepository _collectionCipherRepository;
|
private readonly ICollectionCipherRepository _collectionCipherRepository;
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IAttachmentStorageService _attachmentStorageService;
|
|
||||||
private readonly CurrentContext _currentContext;
|
private readonly CurrentContext _currentContext;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
|
|
||||||
@ -29,7 +28,6 @@ namespace Bit.Api.Controllers
|
|||||||
ICollectionCipherRepository collectionCipherRepository,
|
ICollectionCipherRepository collectionCipherRepository,
|
||||||
ICipherService cipherService,
|
ICipherService cipherService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IAttachmentStorageService attachmentStorageService,
|
|
||||||
CurrentContext currentContext,
|
CurrentContext currentContext,
|
||||||
GlobalSettings globalSettings)
|
GlobalSettings globalSettings)
|
||||||
{
|
{
|
||||||
@ -37,7 +35,6 @@ namespace Bit.Api.Controllers
|
|||||||
_collectionCipherRepository = collectionCipherRepository;
|
_collectionCipherRepository = collectionCipherRepository;
|
||||||
_cipherService = cipherService;
|
_cipherService = cipherService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_attachmentStorageService = attachmentStorageService;
|
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
@ -263,10 +260,7 @@ namespace Bit.Api.Controllers
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check and remove attachmentId from cipher in database
|
await _cipherService.DeleteAttachmentAsync(cipher, attachmentId, userId, false);
|
||||||
|
|
||||||
var storedFilename = $"{idGuid}/{attachmentId}";
|
|
||||||
await _attachmentStorageService.DeleteAttachmentAsync(storedFilename);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,21 +26,13 @@ namespace Bit.Core.Models.Api
|
|||||||
|
|
||||||
public static IEnumerable<AttachmentResponseModel> FromCipher(Cipher cipher, GlobalSettings globalSettings)
|
public static IEnumerable<AttachmentResponseModel> FromCipher(Cipher cipher, GlobalSettings globalSettings)
|
||||||
{
|
{
|
||||||
if(string.IsNullOrWhiteSpace(cipher.Attachments))
|
var attachments = cipher.GetAttachments();
|
||||||
|
if(attachments == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
return attachments.Select(a => new AttachmentResponseModel(a.Key, a.Value, cipher, globalSettings));
|
||||||
{
|
|
||||||
var attachments =
|
|
||||||
JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(cipher.Attachments);
|
|
||||||
return attachments.Select(a => new AttachmentResponseModel(a.Key, a.Value, cipher, globalSettings));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class Cipher : IDataObject<Guid>
|
public class Cipher : IDataObject<Guid>
|
||||||
{
|
{
|
||||||
|
private Dictionary<string, CipherAttachment.MetaData> _attachmentData;
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid? UserId { get; set; }
|
public Guid? UserId { get; set; }
|
||||||
public Guid? OrganizationId { get; set; }
|
public Guid? OrganizationId { get; set; }
|
||||||
@ -20,5 +25,71 @@ namespace Bit.Core.Models.Table
|
|||||||
{
|
{
|
||||||
Id = CoreHelpers.GenerateComb();
|
Id = CoreHelpers.GenerateComb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, CipherAttachment.MetaData> GetAttachments()
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(Attachments))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_attachmentData != null)
|
||||||
|
{
|
||||||
|
return _attachmentData;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_attachmentData = JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(Attachments);
|
||||||
|
return _attachmentData;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAttachments(Dictionary<string, CipherAttachment.MetaData> data)
|
||||||
|
{
|
||||||
|
if(data == null || data.Count == 0)
|
||||||
|
{
|
||||||
|
_attachmentData = null;
|
||||||
|
Attachments = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_attachmentData = data;
|
||||||
|
Attachments = JsonConvert.SerializeObject(_attachmentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAttachment(string id, CipherAttachment.MetaData data)
|
||||||
|
{
|
||||||
|
var attachments = GetAttachments();
|
||||||
|
if(attachments == null)
|
||||||
|
{
|
||||||
|
attachments = new Dictionary<string, CipherAttachment.MetaData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments.Add(id, data);
|
||||||
|
SetAttachments(attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteAttachment(string id)
|
||||||
|
{
|
||||||
|
var attachments = GetAttachments();
|
||||||
|
if(!attachments?.ContainsKey(id) ?? true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments.Remove(id);
|
||||||
|
SetAttachments(attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsAttachment(string id)
|
||||||
|
{
|
||||||
|
var attachments = GetAttachments();
|
||||||
|
return attachments?.ContainsKey(id) ?? false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ namespace Bit.Core.Repositories
|
|||||||
Task ReplaceAsync(Cipher obj, IEnumerable<Guid> collectionIds);
|
Task ReplaceAsync(Cipher obj, IEnumerable<Guid> collectionIds);
|
||||||
Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite);
|
Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite);
|
||||||
Task UpdateAttachmentAsync(CipherAttachment attachment);
|
Task UpdateAttachmentAsync(CipherAttachment attachment);
|
||||||
|
Task DeleteAttachmentAsync(Guid cipherId, string attachmentId);
|
||||||
Task DeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
Task DeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
||||||
Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId);
|
Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId);
|
||||||
Task UpdateUserKeysAndCiphersAsync(User user, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
Task UpdateUserKeysAndCiphersAsync(User user, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
||||||
|
@ -188,6 +188,17 @@ namespace Bit.Core.Repositories.SqlServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId)
|
||||||
|
{
|
||||||
|
using(var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.ExecuteAsync(
|
||||||
|
$"[{Schema}].[Cipher_DeleteAttachment]",
|
||||||
|
new { Id = cipherId, AttachmentId = attachmentId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
||||||
{
|
{
|
||||||
using(var connection = new SqlConnection(ConnectionString))
|
using(var connection = new SqlConnection(ConnectionString))
|
||||||
|
@ -15,6 +15,7 @@ namespace Bit.Core.Services
|
|||||||
bool orgAdmin = false);
|
bool orgAdmin = false);
|
||||||
Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
||||||
Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId);
|
Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId);
|
||||||
|
Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId, bool orgAdmin = false);
|
||||||
Task MoveManyAsync(IEnumerable<Guid> cipherIds, Guid? destinationFolderId, Guid movingUserId);
|
Task MoveManyAsync(IEnumerable<Guid> cipherIds, Guid? destinationFolderId, Guid movingUserId);
|
||||||
Task SaveFolderAsync(Folder folder);
|
Task SaveFolderAsync(Folder folder);
|
||||||
Task DeleteFolderAsync(Folder folder);
|
Task DeleteFolderAsync(Folder folder);
|
||||||
|
@ -154,6 +154,7 @@ namespace Bit.Core.Services
|
|||||||
};
|
};
|
||||||
|
|
||||||
await _cipherRepository.UpdateAttachmentAsync(attachment);
|
await _cipherRepository.UpdateAttachmentAsync(attachment);
|
||||||
|
cipher.AddAttachment(attachmentId, data);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -186,6 +187,28 @@ namespace Bit.Core.Services
|
|||||||
await _pushService.PushSyncCiphersAsync(deletingUserId);
|
await _pushService.PushSyncCiphersAsync(deletingUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId, bool orgAdmin = false)
|
||||||
|
{
|
||||||
|
if(!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId)))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You do not have permissions to delete this.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!cipher.ContainsAttachment(attachmentId))
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _cipherRepository.DeleteAttachmentAsync(cipher.Id, attachmentId);
|
||||||
|
cipher.DeleteAttachment(attachmentId);
|
||||||
|
|
||||||
|
var storedFilename = $"{cipher.Id}/{attachmentId}";
|
||||||
|
await _attachmentStorageService.DeleteAttachmentAsync(storedFilename);
|
||||||
|
|
||||||
|
// push
|
||||||
|
await _pushService.PushSyncCipherUpdateAsync(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task MoveManyAsync(IEnumerable<Guid> cipherIds, Guid? destinationFolderId, Guid movingUserId)
|
public async Task MoveManyAsync(IEnumerable<Guid> cipherIds, Guid? destinationFolderId, Guid movingUserId)
|
||||||
{
|
{
|
||||||
if(destinationFolderId.HasValue)
|
if(destinationFolderId.HasValue)
|
||||||
|
@ -203,5 +203,6 @@
|
|||||||
<Build Include="dbo\Stored Procedures\Cipher_UpdateAttachment.sql" />
|
<Build Include="dbo\Stored Procedures\Cipher_UpdateAttachment.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\Organization_UpdateStorage.sql" />
|
<Build Include="dbo\Stored Procedures\Organization_UpdateStorage.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\User_UpdateStorage.sql" />
|
<Build Include="dbo\Stored Procedures\User_UpdateStorage.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\Cipher_DeleteAttachment.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
43
src/Sql/dbo/Stored Procedures/Cipher_DeleteAttachment.sql
Normal file
43
src/Sql/dbo/Stored Procedures/Cipher_DeleteAttachment.sql
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[Cipher_DeleteAttachment]
|
||||||
|
@Id UNIQUEIDENTIFIER,
|
||||||
|
@AttachmentId VARCHAR(50)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
DECLARE @AttachmentIdKey VARCHAR(50) = CONCAT('"', @AttachmentId, '"')
|
||||||
|
DECLARE @AttachmentIdPath VARCHAR(50) = CONCAT('$.', @AttachmentIdKey)
|
||||||
|
|
||||||
|
DECLARE @Attachments NVARCHAR(MAX)
|
||||||
|
DECLARE @UserId UNIQUEIDENTIFIER
|
||||||
|
DECLARE @OrganizationId UNIQUEIDENTIFIER
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
@UserId = [UserId],
|
||||||
|
@OrganizationId = [OrganizationId],
|
||||||
|
@Attachments = [Attachments]
|
||||||
|
FROM
|
||||||
|
[dbo].[Cipher]
|
||||||
|
WHERE [Id] = @Id
|
||||||
|
|
||||||
|
DECLARE @AttachmentData NVARCHAR(MAX) = JSON_QUERY(@Attachments, @AttachmentIdPath)
|
||||||
|
DECLARE @Size BIGINT = (CAST(JSON_VALUE(@AttachmentData, '$.Size') AS BIGINT) * -1)
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
[dbo].[Cipher]
|
||||||
|
SET
|
||||||
|
[Attachments] = JSON_MODIFY([Attachments], @AttachmentIdPath, NULL)
|
||||||
|
WHERE
|
||||||
|
[Id] = @Id
|
||||||
|
|
||||||
|
IF @OrganizationId IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId, @Size
|
||||||
|
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||||
|
END
|
||||||
|
ELSE IF @UserId IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
EXEC [dbo].[User_UpdateStorage] @UserId, @Size
|
||||||
|
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||||
|
END
|
||||||
|
END
|
Loading…
x
Reference in New Issue
Block a user