1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-06 21:48:12 -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:
Daniel James Smith 2021-12-22 19:47:35 +01:00 committed by GitHub
parent bb34de74cb
commit 355bf2127b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 181 additions and 177 deletions

View File

@ -22,6 +22,8 @@
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.0.1" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.0.1" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.101.182" /> <PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.101.182" />
<PackageReference Include="AWSSDK.SQS" Version="3.3.103.15" /> <PackageReference Include="AWSSDK.SQS" Version="3.3.103.15" />
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.2.1" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
<PackageReference Include="Azure.Storage.Queues" Version="12.3.2" /> <PackageReference Include="Azure.Storage.Queues" Version="12.3.2" />
<PackageReference Include="BitPay.Light" Version="1.0.1907" /> <PackageReference Include="BitPay.Light" Version="1.0.1907" />
<PackageReference Include="Fido2.AspNet" Version="1.1.0" /> <PackageReference Include="Fido2.AspNet" Version="1.1.0" />
@ -29,12 +31,10 @@
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="linq2db.EntityFrameworkCore" Version="5.2.1" /> <PackageReference Include="linq2db.EntityFrameworkCore" Version="5.2.1" />
<PackageReference Include="MailKit" Version="2.8.0" /> <PackageReference Include="MailKit" Version="2.8.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureStorage" Version="3.1.16" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" />
<PackageReference Include="Microsoft.Azure.Cosmos.Table" Version="1.0.7" /> <PackageReference Include="Microsoft.Azure.Cosmos.Table" Version="1.0.7" />
<PackageReference Include="Microsoft.Azure.NotificationHubs" Version="3.3.0" /> <PackageReference Include="Microsoft.Azure.NotificationHubs" Version="3.3.0" />
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="5.1.3" /> <PackageReference Include="Microsoft.Azure.ServiceBus" Version="5.1.3" />
<PackageReference Include="Microsoft.Azure.Storage.Blob" Version="11.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="5.0.0" />

View File

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

View File

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Sas;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using Bit.Core.Settings; using Bit.Core.Settings;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
@ -13,8 +16,8 @@ namespace Bit.Core.Services
{ {
public const string FilesContainerName = "sendfiles"; public const string FilesContainerName = "sendfiles";
private static readonly TimeSpan _downloadLinkLiveTime = TimeSpan.FromMinutes(1); private static readonly TimeSpan _downloadLinkLiveTime = TimeSpan.FromMinutes(1);
private readonly CloudBlobClient _blobClient; private readonly BlobServiceClient _blobServiceClient;
private CloudBlobContainer _sendFilesContainer; private BlobContainerClient _sendFilesContainerClient;
public FileUploadType FileUploadType => FileUploadType.Azure; public FileUploadType FileUploadType => FileUploadType.Azure;
@ -24,24 +27,31 @@ namespace Bit.Core.Services
public AzureSendFileStorageService( public AzureSendFileStorageService(
GlobalSettings globalSettings) GlobalSettings globalSettings)
{ {
var storageAccount = CloudStorageAccount.Parse(globalSettings.Send.ConnectionString); _blobServiceClient = new BlobServiceClient(globalSettings.Send.ConnectionString);
_blobClient = storageAccount.CreateCloudBlobClient();
} }
public async Task UploadNewFileAsync(Stream stream, Send send, string fileId) public async Task UploadNewFileAsync(Stream stream, Send send, string fileId)
{ {
await InitAsync(); await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(BlobName(send, fileId));
var blobClient = _sendFilesContainerClient.GetBlobClient(BlobName(send, fileId));
var metadata = new Dictionary<string, string>();
if (send.UserId.HasValue) if (send.UserId.HasValue)
{ {
blob.Metadata.Add("userId", send.UserId.Value.ToString()); metadata.Add("userId", send.UserId.Value.ToString());
} }
else else
{ {
blob.Metadata.Add("organizationId", send.OrganizationId.Value.ToString()); metadata.Add("organizationId", send.OrganizationId.Value.ToString());
} }
blob.Properties.ContentDisposition = $"attachment; filename=\"{fileId}\"";
await blob.UploadFromStreamAsync(stream); var headers = new BlobHttpHeaders
{
ContentDisposition = $"attachment; filename=\"{fileId}\""
};
await blobClient.UploadAsync(stream, new BlobUploadOptions { Metadata = metadata, HttpHeaders = headers });
} }
public async Task DeleteFileAsync(Send send, string fileId) => await DeleteBlobAsync(BlobName(send, fileId)); public async Task DeleteFileAsync(Send send, string fileId) => await DeleteBlobAsync(BlobName(send, fileId));
@ -49,8 +59,8 @@ namespace Bit.Core.Services
public async Task DeleteBlobAsync(string blobName) public async Task DeleteBlobAsync(string blobName)
{ {
await InitAsync(); await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(blobName); var blobClient = _sendFilesContainerClient.GetBlobClient(blobName);
await blob.DeleteIfExistsAsync(); await blobClient.DeleteIfExistsAsync();
} }
public async Task DeleteFilesForOrganizationAsync(Guid organizationId) public async Task DeleteFilesForOrganizationAsync(Guid organizationId)
@ -66,70 +76,66 @@ namespace Bit.Core.Services
public async Task<string> GetSendFileDownloadUrlAsync(Send send, string fileId) public async Task<string> GetSendFileDownloadUrlAsync(Send send, string fileId)
{ {
await InitAsync(); await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(BlobName(send, fileId)); var blobClient = _sendFilesContainerClient.GetBlobClient(BlobName(send, fileId));
var accessPolicy = new SharedAccessBlobPolicy() var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Read, DateTime.UtcNow.Add(_downloadLinkLiveTime));
{ return sasUri.ToString();
SharedAccessExpiryTime = DateTime.UtcNow.Add(_downloadLinkLiveTime),
Permissions = SharedAccessBlobPermissions.Read,
};
return blob.Uri + blob.GetSharedAccessSignature(accessPolicy);
} }
public async Task<string> GetSendFileUploadUrlAsync(Send send, string fileId) public async Task<string> GetSendFileUploadUrlAsync(Send send, string fileId)
{ {
await InitAsync(); await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(BlobName(send, fileId)); var blobClient = _sendFilesContainerClient.GetBlobClient(BlobName(send, fileId));
var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Create | BlobSasPermissions.Write, DateTime.UtcNow.Add(_downloadLinkLiveTime));
var accessPolicy = new SharedAccessBlobPolicy() return sasUri.ToString();
{
SharedAccessExpiryTime = DateTime.UtcNow.Add(_downloadLinkLiveTime),
Permissions = SharedAccessBlobPermissions.Create | SharedAccessBlobPermissions.Write,
};
return blob.Uri + blob.GetSharedAccessSignature(accessPolicy);
} }
public async Task<(bool, long?)> ValidateFileAsync(Send send, string fileId, long expectedFileSize, long leeway) public async Task<(bool, long?)> ValidateFileAsync(Send send, string fileId, long expectedFileSize, long leeway)
{ {
await InitAsync(); await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(BlobName(send, fileId)); var blobClient = _sendFilesContainerClient.GetBlobClient(BlobName(send, fileId));
if (!blob.Exists()) try
{
var blobProperties = await blobClient.GetPropertiesAsync();
var metadata = blobProperties.Value.Metadata;
if (send.UserId.HasValue)
{
metadata["userId"] = send.UserId.Value.ToString();
}
else
{
metadata["organizationId"] = send.OrganizationId.Value.ToString();
}
await blobClient.SetMetadataAsync(metadata);
var headers = new BlobHttpHeaders
{
ContentDisposition = $"attachment; filename=\"{fileId}\""
};
await blobClient.SetHttpHeadersAsync(headers);
var length = blobProperties.Value.ContentLength;
if (length < expectedFileSize - leeway || length > expectedFileSize + leeway)
{
return (false, length);
}
return (true, length);
}
catch (Exception ex)
{ {
return (false, null); return (false, null);
} }
blob.FetchAttributes();
if (send.UserId.HasValue)
{
blob.Metadata["userId"] = send.UserId.Value.ToString();
}
else
{
blob.Metadata["organizationId"] = send.OrganizationId.Value.ToString();
}
blob.Properties.ContentDisposition = $"attachment; filename=\"{fileId}\"";
blob.SetMetadata();
blob.SetProperties();
var length = blob.Properties.Length;
if (length < expectedFileSize - leeway || length > expectedFileSize + leeway)
{
return (false, length);
}
return (true, length);
} }
private async Task InitAsync() private async Task InitAsync()
{ {
if (_sendFilesContainer == null) if (_sendFilesContainerClient == null)
{ {
_sendFilesContainer = _blobClient.GetContainerReference(FilesContainerName); _sendFilesContainerClient = _blobServiceClient.GetBlobContainerClient(FilesContainerName);
await _sendFilesContainer.CreateIfNotExistsAsync(BlobContainerPublicAccessType.Off, null, null); await _sendFilesContainerClient.CreateIfNotExistsAsync(PublicAccessType.None, null, null);
} }
} }
} }

View File

@ -11,7 +11,6 @@ using Bit.Core.Repositories;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Azure.Storage;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -57,8 +56,7 @@ namespace Bit.Core.Services
else if (CoreHelpers.SettingHasValue(_globalSettings.Storage?.ConnectionString) && else if (CoreHelpers.SettingHasValue(_globalSettings.Storage?.ConnectionString) &&
CoreHelpers.SettingHasValue(_globalSettings.LicenseCertificatePassword)) CoreHelpers.SettingHasValue(_globalSettings.LicenseCertificatePassword))
{ {
var storageAccount = CloudStorageAccount.Parse(globalSettings.Storage.ConnectionString); _certificate = CoreHelpers.GetBlobCertificateAsync(globalSettings.Storage.ConnectionString, "certificates",
_certificate = CoreHelpers.GetBlobCertificateAsync(storageAccount, "certificates",
"licensing.pfx", _globalSettings.LicenseCertificatePassword) "licensing.pfx", _globalSettings.LicenseCertificatePassword)
.GetAwaiter().GetResult(); .GetAwaiter().GetResult();
} }

View File

@ -10,10 +10,11 @@ using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Azure.Storage.Queues; using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Queues.Models; using Azure.Storage.Queues.Models;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
@ -24,8 +25,6 @@ using Bit.Core.Settings;
using Dapper; using Dapper;
using IdentityModel; using IdentityModel;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using MimeKit; using MimeKit;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -253,22 +252,27 @@ namespace Bit.Core.Utilities
} }
} }
public async static Task<X509Certificate2> GetBlobCertificateAsync(CloudStorageAccount cloudStorageAccount, public async static Task<X509Certificate2> GetBlobCertificateAsync(string connectionString, string container, string file, string password)
string container, string file, string password)
{ {
var blobClient = cloudStorageAccount.CreateCloudBlobClient(); try
var containerRef = blobClient.GetContainerReference(container);
if (await containerRef.ExistsAsync().ConfigureAwait(false))
{ {
var blobRef = containerRef.GetBlobReference(file); var blobServiceClient = new BlobServiceClient(connectionString);
if (await blobRef.ExistsAsync().ConfigureAwait(false)) var containerRef2 = blobServiceClient.GetBlobContainerClient(container);
{ var blobRef = containerRef2.GetBlobClient(file);
var blobBytes = new byte[blobRef.Properties.Length];
await blobRef.DownloadToByteArrayAsync(blobBytes, 0).ConfigureAwait(false); using var memStream = new MemoryStream();
return new X509Certificate2(blobBytes, password); await blobRef.DownloadToAsync(memStream).ConfigureAwait(false);
} return new X509Certificate2(memStream.ToArray(), password);
}
catch (RequestFailedException ex)
when (ex.ErrorCode == BlobErrorCode.ContainerNotFound || ex.ErrorCode == BlobErrorCode.BlobNotFound)
{
return null;
}
catch (Exception)
{
return null;
} }
return null;
} }
public static long ToEpocMilliseconds(DateTime date) public static long ToEpocMilliseconds(DateTime date)
@ -756,8 +760,7 @@ namespace Bit.Core.Utilities
SettingHasValue(globalSettings.Storage?.ConnectionString) && SettingHasValue(globalSettings.Storage?.ConnectionString) &&
SettingHasValue(globalSettings.IdentityServer.CertificatePassword)) SettingHasValue(globalSettings.IdentityServer.CertificatePassword))
{ {
var storageAccount = CloudStorageAccount.Parse(globalSettings.Storage.ConnectionString); return GetBlobCertificateAsync(globalSettings.Storage.ConnectionString, "certificates",
return GetBlobCertificateAsync(storageAccount, "certificates",
"identity.pfx", globalSettings.IdentityServer.CertificatePassword).GetAwaiter().GetResult(); "identity.pfx", globalSettings.IdentityServer.CertificatePassword).GetAwaiter().GetResult();
} }
return null; return null;

View File

@ -28,7 +28,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Localization; using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Azure.Storage;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -471,7 +470,7 @@ namespace Bit.Core.Utilities
public static void AddCustomDataProtectionServices( public static void AddCustomDataProtectionServices(
this IServiceCollection services, IWebHostEnvironment env, GlobalSettings globalSettings) this IServiceCollection services, IWebHostEnvironment env, GlobalSettings globalSettings)
{ {
var builder = services.AddDataProtection().SetApplicationName("Bitwarden"); var builder = services.AddDataProtection(options => options.ApplicationDiscriminator = "Bitwarden");
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
return; return;
@ -484,7 +483,6 @@ namespace Bit.Core.Utilities
if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Storage?.ConnectionString)) if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Storage?.ConnectionString))
{ {
var storageAccount = CloudStorageAccount.Parse(globalSettings.Storage.ConnectionString);
X509Certificate2 dataProtectionCert = null; X509Certificate2 dataProtectionCert = null;
if (CoreHelpers.SettingHasValue(globalSettings.DataProtection.CertificateThumbprint)) if (CoreHelpers.SettingHasValue(globalSettings.DataProtection.CertificateThumbprint))
{ {
@ -493,12 +491,13 @@ namespace Bit.Core.Utilities
} }
else if (CoreHelpers.SettingHasValue(globalSettings.DataProtection.CertificatePassword)) else if (CoreHelpers.SettingHasValue(globalSettings.DataProtection.CertificatePassword))
{ {
dataProtectionCert = CoreHelpers.GetBlobCertificateAsync(storageAccount, "certificates", dataProtectionCert = CoreHelpers.GetBlobCertificateAsync(globalSettings.Storage.ConnectionString, "certificates",
"dataprotection.pfx", globalSettings.DataProtection.CertificatePassword) "dataprotection.pfx", globalSettings.DataProtection.CertificatePassword)
.GetAwaiter().GetResult(); .GetAwaiter().GetResult();
} }
//TODO djsmith85 Check if this is the correct container name
builder builder
.PersistKeysToAzureBlobStorage(storageAccount, "aspnet-dataprotection/keys.xml") .PersistKeysToAzureBlobStorage(globalSettings.Storage.ConnectionString, "aspnet-dataprotection", "keys.xml")
.ProtectKeysWithCertificate(dataProtectionCert); .ProtectKeysWithCertificate(dataProtectionCert);
} }
} }