mirror of
https://github.com/bitwarden/server.git
synced 2025-04-17 11:08:16 -05:00
share login with attachments
This commit is contained in:
parent
fbc189544b
commit
f8c749bab5
@ -9,6 +9,7 @@ using Bit.Core.Exceptions;
|
|||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Api.Utilities;
|
using Bit.Api.Utilities;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers
|
namespace Bit.Api.Controllers
|
||||||
{
|
{
|
||||||
@ -133,14 +134,15 @@ namespace Bit.Api.Controllers
|
|||||||
public async Task PutShare(string id, [FromBody]CipherShareRequestModel model)
|
public async Task PutShare(string id, [FromBody]CipherShareRequestModel model)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
|
||||||
if(cipher == null || cipher.UserId != userId ||
|
if(cipher == null || cipher.UserId != userId ||
|
||||||
!_currentContext.OrganizationUser(new Guid(model.Cipher.OrganizationId)))
|
!_currentContext.OrganizationUser(new Guid(model.Cipher.OrganizationId)))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _cipherService.ShareAsync(model.Cipher.ToCipher(cipher), new Guid(model.Cipher.OrganizationId),
|
var original = CoreHelpers.CloneObject(cipher);
|
||||||
|
await _cipherService.ShareAsync(original, model.Cipher.ToCipher(cipher), new Guid(model.Cipher.OrganizationId),
|
||||||
model.CollectionIds.Select(c => new Guid(c)), userId);
|
model.CollectionIds.Select(c => new Guid(c)), userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,15 +226,7 @@ namespace Bit.Api.Controllers
|
|||||||
[DisableFormValueModelBinding]
|
[DisableFormValueModelBinding]
|
||||||
public async Task PostAttachment(string id)
|
public async Task PostAttachment(string id)
|
||||||
{
|
{
|
||||||
if(!Request?.ContentType.Contains("multipart/") ?? true)
|
ValidateAttachment();
|
||||||
{
|
|
||||||
throw new BadRequestException("Invalid content.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Request.ContentLength > 105906176) // 101 MB, give em' 1 extra MB for cushion
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Max file size is 100 MB.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var idGuid = new Guid(id);
|
var idGuid = new Guid(id);
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
@ -244,7 +238,28 @@ namespace Bit.Api.Controllers
|
|||||||
|
|
||||||
await Request.GetFileAsync(async (stream, fileName) =>
|
await Request.GetFileAsync(async (stream, fileName) =>
|
||||||
{
|
{
|
||||||
await _cipherService.CreateAttachmentAsync(cipher, stream, fileName, Request.ContentLength.GetValueOrDefault(0), userId);
|
await _cipherService.CreateAttachmentAsync(cipher, stream, fileName,
|
||||||
|
Request.ContentLength.GetValueOrDefault(0), userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/attachment/{attachmentId}/share")]
|
||||||
|
[DisableFormValueModelBinding]
|
||||||
|
public async Task PostAttachmentShare(string id, string attachmentId, Guid organizationId)
|
||||||
|
{
|
||||||
|
ValidateAttachment();
|
||||||
|
|
||||||
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
|
||||||
|
if(cipher == null || cipher.UserId != userId || !_currentContext.OrganizationUser(organizationId))
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Request.GetFileAsync(async (stream, fileName) =>
|
||||||
|
{
|
||||||
|
await _cipherService.CreateAttachmentShareAsync(cipher, stream, fileName,
|
||||||
|
Request.ContentLength.GetValueOrDefault(0), attachmentId, organizationId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,5 +277,18 @@ namespace Bit.Api.Controllers
|
|||||||
|
|
||||||
await _cipherService.DeleteAttachmentAsync(cipher, attachmentId, userId, false);
|
await _cipherService.DeleteAttachmentAsync(cipher, attachmentId, userId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidateAttachment()
|
||||||
|
{
|
||||||
|
if(!Request?.ContentType.Contains("multipart/") ?? true)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Invalid content.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Request.ContentLength > 105906176) // 101 MB, give em' 1 extra MB for cushion
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Max file size is 100 MB.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ namespace Bit.Core.Models.Api
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cipher ToCipher(Cipher existingCipher)
|
public virtual Cipher ToCipher(Cipher existingCipher)
|
||||||
{
|
{
|
||||||
switch(existingCipher.Type)
|
switch(existingCipher.Type)
|
||||||
{
|
{
|
||||||
@ -64,12 +64,35 @@ namespace Bit.Core.Models.Api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CipherAttachmentRequestModel : CipherRequestModel
|
||||||
|
{
|
||||||
|
public Dictionary<string, string> Attachments { get; set; }
|
||||||
|
|
||||||
|
public override Cipher ToCipher(Cipher existingCipher)
|
||||||
|
{
|
||||||
|
base.ToCipher(existingCipher);
|
||||||
|
|
||||||
|
var attachments = existingCipher.GetAttachments();
|
||||||
|
if((Attachments?.Count ?? 0) > 0 && (attachments?.Count ?? 0) > 0)
|
||||||
|
{
|
||||||
|
foreach(var attachment in existingCipher.GetAttachments().Where(a => Attachments.ContainsKey(a.Key)))
|
||||||
|
{
|
||||||
|
attachment.Value.FileName = Attachments[attachment.Key];
|
||||||
|
}
|
||||||
|
|
||||||
|
existingCipher.SetAttachments(attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingCipher;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class CipherShareRequestModel : IValidatableObject
|
public class CipherShareRequestModel : IValidatableObject
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public IEnumerable<string> CollectionIds { get; set; }
|
public IEnumerable<string> CollectionIds { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public CipherRequestModel Cipher { get; set; }
|
public CipherAttachmentRequestModel Cipher { get; set; }
|
||||||
|
|
||||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public interface IAttachmentStorageService
|
public interface IAttachmentStorageService
|
||||||
{
|
{
|
||||||
Task UploadAttachmentAsync(Stream stream, string name);
|
Task UploadNewAttachmentAsync(Stream stream, Guid cipherId, string attachmentId);
|
||||||
Task DeleteAttachmentAsync(string name);
|
Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, string attachmentId);
|
||||||
|
Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId);
|
||||||
|
Task CommitShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId);
|
||||||
|
Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId);
|
||||||
|
Task DeleteAttachmentAsync(Guid cipherId, string attachmentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,15 @@ namespace Bit.Core.Services
|
|||||||
Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId);
|
Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId);
|
||||||
Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, long requestLength, Guid savingUserId,
|
Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, long requestLength, Guid savingUserId,
|
||||||
bool orgAdmin = false);
|
bool orgAdmin = false);
|
||||||
|
Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, string fileName, long requestLength, string attachmentId,
|
||||||
|
Guid organizationShareId);
|
||||||
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 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);
|
||||||
Task ShareAsync(Cipher cipher, Guid organizationId, IEnumerable<Guid> collectionIds, Guid userId);
|
Task ShareAsync(Cipher originalCipher, Cipher cipher, Guid organizationId, IEnumerable<Guid> collectionIds, Guid userId);
|
||||||
Task SaveCollectionsAsync(Cipher cipher, IEnumerable<Guid> collectionIds, Guid savingUserId, bool orgAdmin);
|
Task SaveCollectionsAsync(Cipher cipher, IEnumerable<Guid> collectionIds, Guid savingUserId, bool orgAdmin);
|
||||||
Task ImportCiphersAsync(List<Folder> folders, List<CipherDetails> ciphers,
|
Task ImportCiphersAsync(List<Folder> folders, List<CipherDetails> ciphers,
|
||||||
IEnumerable<KeyValuePair<int, int>> folderRelationships);
|
IEnumerable<KeyValuePair<int, int>> folderRelationships);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Microsoft.WindowsAzure.Storage;
|
using Microsoft.WindowsAzure.Storage;
|
||||||
using Microsoft.WindowsAzure.Storage.Blob;
|
using Microsoft.WindowsAzure.Storage.Blob;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -19,20 +20,80 @@ namespace Bit.Core.Services
|
|||||||
_blobClient = storageAccount.CreateCloudBlobClient();
|
_blobClient = storageAccount.CreateCloudBlobClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UploadAttachmentAsync(Stream stream, string name)
|
public async Task UploadNewAttachmentAsync(Stream stream, Guid cipherId, string attachmentId)
|
||||||
{
|
{
|
||||||
await InitAsync();
|
await UploadAttachmentAsync(stream, $"{cipherId}/{attachmentId}");
|
||||||
var blob = _attachmentsContainer.GetBlockBlobReference(name);
|
|
||||||
await blob.UploadFromStreamAsync(stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteAttachmentAsync(string name)
|
public async Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
|
{
|
||||||
|
await UploadAttachmentAsync(stream, $"{cipherId}/share/{organizationId}/{attachmentId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
{
|
{
|
||||||
await InitAsync();
|
await InitAsync();
|
||||||
var blob = _attachmentsContainer.GetBlockBlobReference(name);
|
var source = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/share/{organizationId}/{attachmentId}");
|
||||||
|
if(!await source.ExistsAsync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dest = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/{attachmentId}");
|
||||||
|
if(!await dest.ExistsAsync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var original = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/temp/{attachmentId}");
|
||||||
|
await original.DeleteIfExistsAsync();
|
||||||
|
await original.StartCopyAsync(dest);
|
||||||
|
|
||||||
|
await dest.DeleteIfExistsAsync();
|
||||||
|
await dest.StartCopyAsync(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CommitShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
var source = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/share/{organizationId}/{attachmentId}");
|
||||||
|
var original = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/temp/{attachmentId}");
|
||||||
|
await original.DeleteIfExistsAsync();
|
||||||
|
await source.DeleteIfExistsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
var source = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/share/{organizationId}/{attachmentId}");
|
||||||
|
var dest = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/{attachmentId}");
|
||||||
|
var original = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/temp/{attachmentId}");
|
||||||
|
if(!await original.ExistsAsync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await dest.DeleteIfExistsAsync();
|
||||||
|
await dest.StartCopyAsync(original);
|
||||||
|
await original.DeleteIfExistsAsync();
|
||||||
|
await source.DeleteIfExistsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
var blobName = $"{cipherId}/{attachmentId}";
|
||||||
|
var blob = _attachmentsContainer.GetBlockBlobReference(blobName);
|
||||||
await blob.DeleteIfExistsAsync();
|
await blob.DeleteIfExistsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UploadAttachmentAsync(Stream stream, string blobName)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
var blob = _attachmentsContainer.GetBlockBlobReference(blobName);
|
||||||
|
await blob.UploadFromStreamAsync(stream);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task InitAsync()
|
private async Task InitAsync()
|
||||||
{
|
{
|
||||||
if(_attachmentsContainer == null)
|
if(_attachmentsContainer == null)
|
||||||
|
@ -139,8 +139,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var attachmentId = Utilities.CoreHelpers.SecureRandomString(32, upper: false, special: false);
|
var attachmentId = Utilities.CoreHelpers.SecureRandomString(32, upper: false, special: false);
|
||||||
var storageId = $"{cipher.Id}/{attachmentId}";
|
await _attachmentStorageService.UploadNewAttachmentAsync(stream, cipher.Id, attachmentId);
|
||||||
await _attachmentStorageService.UploadAttachmentAsync(stream, storageId);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -165,7 +164,7 @@ namespace Bit.Core.Services
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Clean up since this is not transactional
|
// Clean up since this is not transactional
|
||||||
await _attachmentStorageService.DeleteAttachmentAsync(storageId);
|
await _attachmentStorageService.DeleteAttachmentAsync(cipher.Id, attachmentId);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,6 +172,29 @@ namespace Bit.Core.Services
|
|||||||
await _pushService.PushSyncCipherUpdateAsync(cipher);
|
await _pushService.PushSyncCipherUpdateAsync(cipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, string fileName, long requestLength,
|
||||||
|
string attachmentId, Guid organizationId)
|
||||||
|
{
|
||||||
|
if(requestLength < 1)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("No data to attach.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
||||||
|
if(!org.MaxStorageGb.HasValue)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("This organization cannot use attachments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var storageBytesRemaining = org.StorageBytesRemaining();
|
||||||
|
if(storageBytesRemaining < requestLength)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Not enough storage available for this organization.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _attachmentStorageService.UploadShareAttachmentAsync(stream, cipher.Id, organizationId, attachmentId);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false)
|
public async Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false)
|
||||||
{
|
{
|
||||||
if(!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId)))
|
if(!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId)))
|
||||||
@ -207,9 +229,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
await _cipherRepository.DeleteAttachmentAsync(cipher.Id, attachmentId);
|
await _cipherRepository.DeleteAttachmentAsync(cipher.Id, attachmentId);
|
||||||
cipher.DeleteAttachment(attachmentId);
|
cipher.DeleteAttachment(attachmentId);
|
||||||
|
await _attachmentStorageService.DeleteAttachmentAsync(cipher.Id, attachmentId);
|
||||||
var storedFilename = $"{cipher.Id}/{attachmentId}";
|
|
||||||
await _attachmentStorageService.DeleteAttachmentAsync(storedFilename);
|
|
||||||
|
|
||||||
// push
|
// push
|
||||||
await _pushService.PushSyncCipherUpdateAsync(cipher);
|
await _pushService.PushSyncCipherUpdateAsync(cipher);
|
||||||
@ -258,7 +278,8 @@ namespace Bit.Core.Services
|
|||||||
await _pushService.PushSyncFolderDeleteAsync(folder);
|
await _pushService.PushSyncFolderDeleteAsync(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ShareAsync(Cipher cipher, Guid organizationId, IEnumerable<Guid> collectionIds, Guid sharingUserId)
|
public async Task ShareAsync(Cipher originalCipher, Cipher cipher, Guid organizationId,
|
||||||
|
IEnumerable<Guid> collectionIds, Guid sharingUserId)
|
||||||
{
|
{
|
||||||
if(cipher.Id == default(Guid))
|
if(cipher.Id == default(Guid))
|
||||||
{
|
{
|
||||||
@ -275,12 +296,49 @@ namespace Bit.Core.Services
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var attachments = cipher.GetAttachments();
|
||||||
|
var hasAttachments = (attachments?.Count ?? 0) > 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
// Sproc will not save this UserId on the cipher. It is used limit scope of the collectionIds.
|
// Sproc will not save this UserId on the cipher. It is used limit scope of the collectionIds.
|
||||||
cipher.UserId = sharingUserId;
|
cipher.UserId = sharingUserId;
|
||||||
cipher.OrganizationId = organizationId;
|
cipher.OrganizationId = organizationId;
|
||||||
cipher.RevisionDate = DateTime.UtcNow;
|
cipher.RevisionDate = DateTime.UtcNow;
|
||||||
await _cipherRepository.ReplaceAsync(cipher, collectionIds);
|
await _cipherRepository.ReplaceAsync(cipher, collectionIds);
|
||||||
|
|
||||||
|
if(hasAttachments)
|
||||||
|
{
|
||||||
|
// migrate attachments
|
||||||
|
foreach(var attachment in attachments)
|
||||||
|
{
|
||||||
|
await _attachmentStorageService.StartShareAttachmentAsync(cipher.Id, organizationId, attachment.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// roll everything back
|
||||||
|
await _cipherRepository.ReplaceAsync(originalCipher);
|
||||||
|
if(!hasAttachments)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var attachment in attachments)
|
||||||
|
{
|
||||||
|
await _attachmentStorageService.RollbackShareAttachmentAsync(cipher.Id, organizationId, attachment.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit attachment migration
|
||||||
|
foreach(var attachment in attachments)
|
||||||
|
{
|
||||||
|
await _attachmentStorageService.CommitShareAttachmentAsync(cipher.Id, organizationId, attachment.Key);
|
||||||
|
}
|
||||||
|
|
||||||
// push
|
// push
|
||||||
await _pushService.PushSyncCipherUpdateAsync(cipher);
|
await _pushService.PushSyncCipherUpdateAsync(cipher);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,37 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public class NoopAttachmentStorageService : IAttachmentStorageService
|
public class NoopAttachmentStorageService : IAttachmentStorageService
|
||||||
{
|
{
|
||||||
public Task DeleteAttachmentAsync(string name)
|
public Task CommitShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UploadAttachmentAsync(Stream stream, string name)
|
public Task DeleteAttachmentAsync(Guid cipherId, string attachmentId)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UploadNewAttachmentAsync(Stream stream, Guid cipherId, string attachmentId)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
@ -242,5 +243,10 @@ namespace Bit.Core.Utilities
|
|||||||
// Return formatted number with suffix
|
// Return formatted number with suffix
|
||||||
return readable.ToString("0.## ") + suffix;
|
return readable.ToString("0.## ") + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static T CloneObject<T>(T obj)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,31 @@ AS
|
|||||||
BEGIN
|
BEGIN
|
||||||
SET NOCOUNT ON
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
DECLARE @CipherAttachments NVARCHAR(MAX)
|
||||||
|
SELECT
|
||||||
|
@CipherAttachments = [Attachments]
|
||||||
|
FROM
|
||||||
|
[dbo].[Cipher]
|
||||||
|
WHERE [Id] = @Id
|
||||||
|
|
||||||
|
DECLARE @Size BIGINT
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
@Size = SUM(CAST(JSON_VALUE(value,'$.Size') AS BIGINT))
|
||||||
|
FROM
|
||||||
|
OPENJSON(@CipherAttachments)
|
||||||
|
|
||||||
|
DECLARE @SizeDec BIGINT = @Size * -1
|
||||||
|
|
||||||
UPDATE
|
UPDATE
|
||||||
[dbo].[Cipher]
|
[dbo].[Cipher]
|
||||||
SET
|
SET
|
||||||
[UserId] = NULL,
|
[UserId] = NULL,
|
||||||
[OrganizationId] = @OrganizationId,
|
[OrganizationId] = @OrganizationId,
|
||||||
[Data] = @Data,
|
[Data] = @Data,
|
||||||
|
[Attachments] = @Attachments,
|
||||||
[RevisionDate] = @RevisionDate
|
[RevisionDate] = @RevisionDate
|
||||||
-- No need to update Attachments, CreationDate, Favorites, Folders, or Type since that data will not change
|
-- No need to update CreationDate, Favorites, Folders, or Type since that data will not change
|
||||||
WHERE
|
WHERE
|
||||||
[Id] = @Id
|
[Id] = @Id
|
||||||
|
|
||||||
@ -66,12 +83,7 @@ BEGIN
|
|||||||
WHERE
|
WHERE
|
||||||
[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE])
|
[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE])
|
||||||
|
|
||||||
IF @OrganizationId IS NOT NULL
|
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId, @Size
|
||||||
BEGIN
|
EXEC [dbo].[User_UpdateStorage] @UserId, @SizeDec
|
||||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||||
END
|
|
||||||
ELSE IF @UserId IS NOT NULL
|
|
||||||
BEGIN
|
|
||||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
|
||||||
END
|
|
||||||
END
|
END
|
Loading…
x
Reference in New Issue
Block a user