mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05:00
Migrate deprecated Microsoft.Azure.Storage.Blob to Azure.Storage.Blobs (#1732)
* Migrate from deprecated Microsoft.Azure.Storage to Azure.Storage.Blobs * Remove and order usings * Do not fetch BlobProperties before uploading a new file. * Save an api call by calling GetPropertiesAsync and catching an error instead of calling Exists first * Formatted files * Verified ContentLength is the correct blob property for file-size * Use a generic Exception catch for file validation * Added a catch all to the GetBlobCertificateAsync in case something throws * Remove and sort using * Changes after running dotnet-format * Remove checks for CanGenerateSasUri
This commit is contained in:

committed by
GitHub

parent
bb34de74cb
commit
355bf2127b
@ -2,12 +2,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
using Azure.Storage.Sas;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Azure.Storage;
|
||||
using Microsoft.Azure.Storage.Blob;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@ -18,8 +19,8 @@ namespace Bit.Core.Services
|
||||
private const string _defaultContainerName = "attachments";
|
||||
private readonly static string[] _attachmentContainerName = { "attachments", "attachments-v2" };
|
||||
private static readonly TimeSpan blobLinkLiveTime = TimeSpan.FromMinutes(1);
|
||||
private readonly CloudBlobClient _blobClient;
|
||||
private readonly Dictionary<string, CloudBlobContainer> _attachmentContainers = new Dictionary<string, CloudBlobContainer>();
|
||||
private readonly BlobServiceClient _blobServiceClient;
|
||||
private readonly Dictionary<string, BlobContainerClient> _attachmentContainers = new Dictionary<string, BlobContainerClient>();
|
||||
private string BlobName(Guid cipherId, CipherAttachment.MetaData attachmentData, Guid? organizationId = null, bool temp = false) =>
|
||||
string.Concat(
|
||||
temp ? "temp/" : "",
|
||||
@ -54,121 +55,122 @@ namespace Bit.Core.Services
|
||||
public AzureAttachmentStorageService(
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
var storageAccount = CloudStorageAccount.Parse(globalSettings.Attachment.ConnectionString);
|
||||
_blobClient = storageAccount.CreateCloudBlobClient();
|
||||
_blobServiceClient = new BlobServiceClient(globalSettings.Attachment.ConnectionString);
|
||||
}
|
||||
|
||||
public async Task<string> GetAttachmentDownloadUrlAsync(Cipher cipher, CipherAttachment.MetaData attachmentData)
|
||||
{
|
||||
await InitAsync(attachmentData.ContainerName);
|
||||
var blob = _attachmentContainers[attachmentData.ContainerName].GetBlockBlobReference(BlobName(cipher.Id, attachmentData));
|
||||
var accessPolicy = new SharedAccessBlobPolicy()
|
||||
{
|
||||
SharedAccessExpiryTime = DateTime.UtcNow.Add(blobLinkLiveTime),
|
||||
Permissions = SharedAccessBlobPermissions.Read
|
||||
};
|
||||
|
||||
return blob.Uri + blob.GetSharedAccessSignature(accessPolicy);
|
||||
var blobClient = _attachmentContainers[attachmentData.ContainerName].GetBlobClient(BlobName(cipher.Id, attachmentData));
|
||||
var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Read, DateTime.UtcNow.Add(blobLinkLiveTime));
|
||||
return sasUri.ToString();
|
||||
}
|
||||
|
||||
public async Task<string> GetAttachmentUploadUrlAsync(Cipher cipher, CipherAttachment.MetaData attachmentData)
|
||||
{
|
||||
await InitAsync(EventGridEnabledContainerName);
|
||||
var blob = _attachmentContainers[EventGridEnabledContainerName].GetBlockBlobReference(BlobName(cipher.Id, attachmentData));
|
||||
var blobClient = _attachmentContainers[EventGridEnabledContainerName].GetBlobClient(BlobName(cipher.Id, attachmentData));
|
||||
attachmentData.ContainerName = EventGridEnabledContainerName;
|
||||
var accessPolicy = new SharedAccessBlobPolicy()
|
||||
{
|
||||
SharedAccessExpiryTime = DateTime.UtcNow.Add(blobLinkLiveTime),
|
||||
Permissions = SharedAccessBlobPermissions.Create | SharedAccessBlobPermissions.Write,
|
||||
};
|
||||
|
||||
return blob.Uri + blob.GetSharedAccessSignature(accessPolicy);
|
||||
var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Create | BlobSasPermissions.Write, DateTime.UtcNow.Add(blobLinkLiveTime));
|
||||
return sasUri.ToString();
|
||||
}
|
||||
|
||||
public async Task UploadNewAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachmentData)
|
||||
{
|
||||
attachmentData.ContainerName = _defaultContainerName;
|
||||
await InitAsync(_defaultContainerName);
|
||||
var blob = _attachmentContainers[_defaultContainerName].GetBlockBlobReference(BlobName(cipher.Id, attachmentData));
|
||||
blob.Metadata.Add("cipherId", cipher.Id.ToString());
|
||||
var blobClient = _attachmentContainers[_defaultContainerName].GetBlobClient(BlobName(cipher.Id, attachmentData));
|
||||
|
||||
var metadata = new Dictionary<string, string>();
|
||||
metadata.Add("cipherId", cipher.Id.ToString());
|
||||
if (cipher.UserId.HasValue)
|
||||
{
|
||||
blob.Metadata.Add("userId", cipher.UserId.Value.ToString());
|
||||
metadata.Add("userId", cipher.UserId.Value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
blob.Metadata.Add("organizationId", cipher.OrganizationId.Value.ToString());
|
||||
metadata.Add("organizationId", cipher.OrganizationId.Value.ToString());
|
||||
}
|
||||
blob.Properties.ContentDisposition = $"attachment; filename=\"{attachmentData.AttachmentId}\"";
|
||||
await blob.UploadFromStreamAsync(stream);
|
||||
|
||||
var headers = new BlobHttpHeaders
|
||||
{
|
||||
ContentDisposition = $"attachment; filename=\"{attachmentData.AttachmentId}\""
|
||||
};
|
||||
await blobClient.UploadAsync(stream, new BlobUploadOptions { Metadata = metadata, HttpHeaders = headers });
|
||||
}
|
||||
|
||||
public async Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, CipherAttachment.MetaData attachmentData)
|
||||
{
|
||||
attachmentData.ContainerName = _defaultContainerName;
|
||||
await InitAsync(_defaultContainerName);
|
||||
var blob = _attachmentContainers[_defaultContainerName].GetBlockBlobReference(
|
||||
var blobClient = _attachmentContainers[_defaultContainerName].GetBlobClient(
|
||||
BlobName(cipherId, attachmentData, organizationId, temp: true));
|
||||
blob.Metadata.Add("cipherId", cipherId.ToString());
|
||||
blob.Metadata.Add("organizationId", organizationId.ToString());
|
||||
blob.Properties.ContentDisposition = $"attachment; filename=\"{attachmentData.AttachmentId}\"";
|
||||
await blob.UploadFromStreamAsync(stream);
|
||||
|
||||
var metadata = new Dictionary<string, string>();
|
||||
metadata.Add("cipherId", cipherId.ToString());
|
||||
metadata.Add("organizationId", organizationId.ToString());
|
||||
|
||||
var headers = new BlobHttpHeaders
|
||||
{
|
||||
ContentDisposition = $"attachment; filename=\"{attachmentData.AttachmentId}\""
|
||||
};
|
||||
await blobClient.UploadAsync(stream, new BlobUploadOptions { Metadata = metadata, HttpHeaders = headers });
|
||||
}
|
||||
|
||||
public async Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, CipherAttachment.MetaData data)
|
||||
{
|
||||
await InitAsync(data.ContainerName);
|
||||
var source = _attachmentContainers[data.ContainerName].GetBlockBlobReference(
|
||||
var source = _attachmentContainers[data.ContainerName].GetBlobClient(
|
||||
BlobName(cipherId, data, organizationId, temp: true));
|
||||
if (!(await source.ExistsAsync()))
|
||||
if (!await source.ExistsAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await InitAsync(_defaultContainerName);
|
||||
var dest = _attachmentContainers[_defaultContainerName].GetBlockBlobReference(BlobName(cipherId, data));
|
||||
if (!(await dest.ExistsAsync()))
|
||||
var dest = _attachmentContainers[_defaultContainerName].GetBlobClient(BlobName(cipherId, data));
|
||||
if (!await dest.ExistsAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var original = _attachmentContainers[_defaultContainerName].GetBlockBlobReference(
|
||||
var original = _attachmentContainers[_defaultContainerName].GetBlobClient(
|
||||
BlobName(cipherId, data, temp: true));
|
||||
await original.DeleteIfExistsAsync();
|
||||
await original.StartCopyAsync(dest);
|
||||
await original.StartCopyFromUriAsync(dest.Uri);
|
||||
|
||||
await dest.DeleteIfExistsAsync();
|
||||
await dest.StartCopyAsync(source);
|
||||
await dest.StartCopyFromUriAsync(source.Uri);
|
||||
}
|
||||
|
||||
public async Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, CipherAttachment.MetaData attachmentData, string originalContainer)
|
||||
{
|
||||
await InitAsync(attachmentData.ContainerName);
|
||||
var source = _attachmentContainers[attachmentData.ContainerName].GetBlockBlobReference(
|
||||
var source = _attachmentContainers[attachmentData.ContainerName].GetBlobClient(
|
||||
BlobName(cipherId, attachmentData, organizationId, temp: true));
|
||||
await source.DeleteIfExistsAsync();
|
||||
|
||||
await InitAsync(originalContainer);
|
||||
var original = _attachmentContainers[originalContainer].GetBlockBlobReference(
|
||||
var original = _attachmentContainers[originalContainer].GetBlobClient(
|
||||
BlobName(cipherId, attachmentData, temp: true));
|
||||
if (!(await original.ExistsAsync()))
|
||||
if (!await original.ExistsAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dest = _attachmentContainers[originalContainer].GetBlockBlobReference(
|
||||
var dest = _attachmentContainers[originalContainer].GetBlobClient(
|
||||
BlobName(cipherId, attachmentData));
|
||||
await dest.DeleteIfExistsAsync();
|
||||
await dest.StartCopyAsync(original);
|
||||
await dest.StartCopyFromUriAsync(original.Uri);
|
||||
await original.DeleteIfExistsAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAttachmentAsync(Guid cipherId, CipherAttachment.MetaData attachmentData)
|
||||
{
|
||||
await InitAsync(attachmentData.ContainerName);
|
||||
var blob = _attachmentContainers[attachmentData.ContainerName].GetBlockBlobReference(
|
||||
var blobClient = _attachmentContainers[attachmentData.ContainerName].GetBlobClient(
|
||||
BlobName(cipherId, attachmentData));
|
||||
await blob.DeleteIfExistsAsync();
|
||||
await blobClient.DeleteIfExistsAsync();
|
||||
}
|
||||
|
||||
public async Task CleanupAsync(Guid cipherId) => await DeleteAttachmentsForPathAsync($"temp/{cipherId}");
|
||||
@ -190,35 +192,42 @@ namespace Bit.Core.Services
|
||||
{
|
||||
await InitAsync(attachmentData.ContainerName);
|
||||
|
||||
var blob = _attachmentContainers[attachmentData.ContainerName].GetBlockBlobReference(BlobName(cipher.Id, attachmentData));
|
||||
var blobClient = _attachmentContainers[attachmentData.ContainerName].GetBlobClient(BlobName(cipher.Id, attachmentData));
|
||||
|
||||
if (!blob.Exists())
|
||||
try
|
||||
{
|
||||
var blobProperties = await blobClient.GetPropertiesAsync();
|
||||
|
||||
var metadata = blobProperties.Value.Metadata;
|
||||
metadata["cipherId"] = cipher.Id.ToString();
|
||||
if (cipher.UserId.HasValue)
|
||||
{
|
||||
metadata["userId"] = cipher.UserId.Value.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata["organizationId"] = cipher.OrganizationId.Value.ToString();
|
||||
}
|
||||
await blobClient.SetMetadataAsync(metadata);
|
||||
|
||||
var headers = new BlobHttpHeaders
|
||||
{
|
||||
ContentDisposition = $"attachment; filename=\"{attachmentData.AttachmentId}\""
|
||||
};
|
||||
await blobClient.SetHttpHeadersAsync(headers);
|
||||
|
||||
var length = blobProperties.Value.ContentLength;
|
||||
if (length < attachmentData.Size - leeway || length > attachmentData.Size + leeway)
|
||||
{
|
||||
return (false, length);
|
||||
}
|
||||
|
||||
return (true, length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
blob.FetchAttributes();
|
||||
|
||||
blob.Metadata["cipherId"] = cipher.Id.ToString();
|
||||
if (cipher.UserId.HasValue)
|
||||
{
|
||||
blob.Metadata["userId"] = cipher.UserId.Value.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
blob.Metadata["organizationId"] = cipher.OrganizationId.Value.ToString();
|
||||
}
|
||||
blob.Properties.ContentDisposition = $"attachment; filename=\"{attachmentData.AttachmentId}\"";
|
||||
blob.SetMetadata();
|
||||
blob.SetProperties();
|
||||
|
||||
var length = blob.Properties.Length;
|
||||
if (length < attachmentData.Size - leeway || length > attachmentData.Size + leeway)
|
||||
{
|
||||
return (false, length);
|
||||
}
|
||||
|
||||
return (true, length);
|
||||
}
|
||||
|
||||
private async Task DeleteAttachmentsForPathAsync(string path)
|
||||
@ -226,24 +235,13 @@ namespace Bit.Core.Services
|
||||
foreach (var container in _attachmentContainerName)
|
||||
{
|
||||
await InitAsync(container);
|
||||
var segment = await _attachmentContainers[container].ListBlobsSegmentedAsync(path, true, BlobListingDetails.None, 100, null, null, null);
|
||||
var blobContainerClient = _attachmentContainers[container];
|
||||
|
||||
while (true)
|
||||
var blobItems = blobContainerClient.GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: path);
|
||||
await foreach (var blobItem in blobItems)
|
||||
{
|
||||
foreach (var blob in segment.Results)
|
||||
{
|
||||
if (blob is CloudBlockBlob blockBlob)
|
||||
{
|
||||
await blockBlob.DeleteIfExistsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
if (segment.ContinuationToken == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
segment = await _attachmentContainers[container].ListBlobsSegmentedAsync(segment.ContinuationToken);
|
||||
BlobClient blobClient = blobContainerClient.GetBlobClient(blobItem.Name);
|
||||
await blobClient.DeleteIfExistsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,14 +250,14 @@ namespace Bit.Core.Services
|
||||
{
|
||||
if (!_attachmentContainers.ContainsKey(containerName) || _attachmentContainers[containerName] == null)
|
||||
{
|
||||
_attachmentContainers[containerName] = _blobClient.GetContainerReference(containerName);
|
||||
_attachmentContainers[containerName] = _blobServiceClient.GetBlobContainerClient(containerName);
|
||||
if (containerName == "attachments")
|
||||
{
|
||||
await _attachmentContainers[containerName].CreateIfNotExistsAsync(BlobContainerPublicAccessType.Blob, null, null);
|
||||
await _attachmentContainers[containerName].CreateIfNotExistsAsync(PublicAccessType.Blob, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _attachmentContainers[containerName].CreateIfNotExistsAsync(BlobContainerPublicAccessType.Off, null, null);
|
||||
await _attachmentContainers[containerName].CreateIfNotExistsAsync(PublicAccessType.None, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user