using System.Threading.Tasks; using Microsoft.Azure.Storage; using Microsoft.Azure.Storage.Blob; using System.IO; using System; using Bit.Core.Models.Table; namespace Bit.Core.Services { public class AzureAttachmentStorageService : IAttachmentStorageService { private const string AttchmentContainerName = "attachments"; private readonly CloudBlobClient _blobClient; private CloudBlobContainer _attachmentsContainer; public AzureAttachmentStorageService( GlobalSettings globalSettings) { var storageAccount = CloudStorageAccount.Parse(globalSettings.Attachment.ConnectionString); _blobClient = storageAccount.CreateCloudBlobClient(); } public async Task UploadNewAttachmentAsync(Stream stream, Cipher cipher, string attachmentId) { await InitAsync(); var blob = _attachmentsContainer.GetBlockBlobReference($"{cipher.Id}/{attachmentId}"); blob.Metadata.Add("cipherId", cipher.Id.ToString()); if (cipher.UserId.HasValue) { blob.Metadata.Add("userId", cipher.UserId.Value.ToString()); } else { blob.Metadata.Add("organizationId", cipher.OrganizationId.Value.ToString()); } blob.Properties.ContentDisposition = $"attachment; filename=\"{attachmentId}\""; await blob.UploadFromStreamAsync(stream); } public async Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, string attachmentId) { await InitAsync(); var blob = _attachmentsContainer.GetBlockBlobReference($"temp/{cipherId}/{organizationId}/{attachmentId}"); blob.Metadata.Add("cipherId", cipherId.ToString()); blob.Metadata.Add("organizationId", organizationId.ToString()); blob.Properties.ContentDisposition = $"attachment; filename=\"{attachmentId}\""; await blob.UploadFromStreamAsync(stream); } public async Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId) { await InitAsync(); var source = _attachmentsContainer.GetBlockBlobReference($"temp/{cipherId}/{organizationId}/{attachmentId}"); if (!(await source.ExistsAsync())) { return; } var dest = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/{attachmentId}"); if (!(await dest.ExistsAsync())) { return; } var original = _attachmentsContainer.GetBlockBlobReference($"temp/{cipherId}/{attachmentId}"); await original.DeleteIfExistsAsync(); await original.StartCopyAsync(dest); await dest.DeleteIfExistsAsync(); await dest.StartCopyAsync(source); } public async Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId) { await InitAsync(); var source = _attachmentsContainer.GetBlockBlobReference($"temp/{cipherId}/{organizationId}/{attachmentId}"); await source.DeleteIfExistsAsync(); var original = _attachmentsContainer.GetBlockBlobReference($"temp/{cipherId}/{attachmentId}"); if (!(await original.ExistsAsync())) { return; } var dest = _attachmentsContainer.GetBlockBlobReference($"{cipherId}/{attachmentId}"); await dest.DeleteIfExistsAsync(); await dest.StartCopyAsync(original); await original.DeleteIfExistsAsync(); } public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId) { await InitAsync(); var blobName = $"{cipherId}/{attachmentId}"; var blob = _attachmentsContainer.GetBlockBlobReference(blobName); await blob.DeleteIfExistsAsync(); } public async Task CleanupAsync(Guid cipherId) { await InitAsync(); var segment = await _attachmentsContainer.ListBlobsSegmentedAsync($"temp/{cipherId}", true, BlobListingDetails.None, 100, null, null, null); while (true) { foreach (var blob in segment.Results) { if (blob is CloudBlockBlob blockBlob) { await blockBlob.DeleteIfExistsAsync(); } } if (segment.ContinuationToken == null) { break; } segment = await _attachmentsContainer.ListBlobsSegmentedAsync(segment.ContinuationToken); } } public async Task DeleteAttachmentsForCipherAsync(Guid cipherId) { await InitAsync(); var segment = await _attachmentsContainer.ListBlobsSegmentedAsync(cipherId.ToString(), true, BlobListingDetails.None, 100, null, null, null); while (true) { foreach (var blob in segment.Results) { if (blob is CloudBlockBlob blockBlob) { await blockBlob.DeleteIfExistsAsync(); } } if (segment.ContinuationToken == null) { break; } segment = await _attachmentsContainer.ListBlobsSegmentedAsync(segment.ContinuationToken); } } public async Task DeleteAttachmentsForOrganizationAsync(Guid organizationId) { await InitAsync(); } public async Task DeleteAttachmentsForUserAsync(Guid userId) { await InitAsync(); } private async Task InitAsync() { if (_attachmentsContainer == null) { _attachmentsContainer = _blobClient.GetContainerReference(AttchmentContainerName); await _attachmentsContainer.CreateIfNotExistsAsync(BlobContainerPublicAccessType.Blob, null, null); } } } }