Files
server/src/Core/Services/Implementations/AzureSendFileStorageService.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

137 lines
4.8 KiB
C#
Raw Normal View History

using System;
using System.IO;
using System.Threading.Tasks;
Direct upload to Azure/Local (#1188) * Direct upload to azure To validate file sizes in the event of a rogue client, Azure event webhooks will be hooked up to AzureValidateFile. Sends outside of a grace size will be deleted as non-compliant. TODO: LocalSendFileStorageService direct upload method/endpoint. * Quick respond to no-body event calls These shouldn't happen, but might if some errant get requests occur * Event Grid only POSTS to webhook * Enable local storage direct file upload * Increase file size difference leeway * Upload through service * Fix LocalFileSendStorage It turns out that multipartHttpStreams do not have a length until read. this causes all long files to be "invalid". We need to write the entire stream, then validate length, just like Azure. the difference is, We can return an exception to local storage admonishing the client for lying * Update src/Api/Utilities/ApiHelpers.cs Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Do not delete directory if it has files * Allow large uploads for self hosted instances * Fix formatting * Re-verfiy access and increment access count on download of Send File * Update src/Core/Services/Implementations/SendService.cs Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Add back in original Send upload * Update size and mark as validated upon Send file validation * Log azure file validation errors * Lint fix Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-03-21 23:01:19 -05:00
using Bit.Core.Enums;
using Bit.Core.Models.Table;
using Bit.Core.Settings;
Direct upload to Azure/Local (#1188) * Direct upload to azure To validate file sizes in the event of a rogue client, Azure event webhooks will be hooked up to AzureValidateFile. Sends outside of a grace size will be deleted as non-compliant. TODO: LocalSendFileStorageService direct upload method/endpoint. * Quick respond to no-body event calls These shouldn't happen, but might if some errant get requests occur * Event Grid only POSTS to webhook * Enable local storage direct file upload * Increase file size difference leeway * Upload through service * Fix LocalFileSendStorage It turns out that multipartHttpStreams do not have a length until read. this causes all long files to be "invalid". We need to write the entire stream, then validate length, just like Azure. the difference is, We can return an exception to local storage admonishing the client for lying * Update src/Api/Utilities/ApiHelpers.cs Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Do not delete directory if it has files * Allow large uploads for self hosted instances * Fix formatting * Re-verfiy access and increment access count on download of Send File * Update src/Core/Services/Implementations/SendService.cs Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Add back in original Send upload * Update size and mark as validated upon Send file validation * Log azure file validation errors * Lint fix Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-03-21 23:01:19 -05:00
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
namespace Bit.Core.Services
{
public class AzureSendFileStorageService : ISendFileStorageService
{
public const string FilesContainerName = "sendfiles";
private static readonly TimeSpan _downloadLinkLiveTime = TimeSpan.FromMinutes(1);
private readonly CloudBlobClient _blobClient;
private CloudBlobContainer _sendFilesContainer;
Direct upload to Azure/Local (#1188) * Direct upload to azure To validate file sizes in the event of a rogue client, Azure event webhooks will be hooked up to AzureValidateFile. Sends outside of a grace size will be deleted as non-compliant. TODO: LocalSendFileStorageService direct upload method/endpoint. * Quick respond to no-body event calls These shouldn't happen, but might if some errant get requests occur * Event Grid only POSTS to webhook * Enable local storage direct file upload * Increase file size difference leeway * Upload through service * Fix LocalFileSendStorage It turns out that multipartHttpStreams do not have a length until read. this causes all long files to be "invalid". We need to write the entire stream, then validate length, just like Azure. the difference is, We can return an exception to local storage admonishing the client for lying * Update src/Api/Utilities/ApiHelpers.cs Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Do not delete directory if it has files * Allow large uploads for self hosted instances * Fix formatting * Re-verfiy access and increment access count on download of Send File * Update src/Core/Services/Implementations/SendService.cs Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Add back in original Send upload * Update size and mark as validated upon Send file validation * Log azure file validation errors * Lint fix Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-03-21 23:01:19 -05:00
public FileUploadType FileUploadType => FileUploadType.Azure;
public static string SendIdFromBlobName(string blobName) => blobName.Split('/')[0];
public static string BlobName(Send send, string fileId) => $"{send.Id}/{fileId}";
public AzureSendFileStorageService(
GlobalSettings globalSettings)
{
var storageAccount = CloudStorageAccount.Parse(globalSettings.Send.ConnectionString);
_blobClient = storageAccount.CreateCloudBlobClient();
}
public async Task UploadNewFileAsync(Stream stream, Send send, string fileId)
{
await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(BlobName(send, fileId));
if (send.UserId.HasValue)
{
blob.Metadata.Add("userId", send.UserId.Value.ToString());
}
else
{
blob.Metadata.Add("organizationId", send.OrganizationId.Value.ToString());
}
blob.Properties.ContentDisposition = $"attachment; filename=\"{fileId}\"";
await blob.UploadFromStreamAsync(stream);
}
public async Task DeleteFileAsync(Send send, string fileId) => await DeleteBlobAsync(BlobName(send, fileId));
public async Task DeleteBlobAsync(string blobName)
{
await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(blobName);
await blob.DeleteIfExistsAsync();
}
public async Task DeleteFilesForOrganizationAsync(Guid organizationId)
{
await InitAsync();
}
public async Task DeleteFilesForUserAsync(Guid userId)
{
await InitAsync();
}
public async Task<string> GetSendFileDownloadUrlAsync(Send send, string fileId)
{
await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(BlobName(send, fileId));
var accessPolicy = new SharedAccessBlobPolicy()
{
SharedAccessExpiryTime = DateTime.UtcNow.Add(_downloadLinkLiveTime),
Permissions = SharedAccessBlobPermissions.Read,
};
return blob.Uri + blob.GetSharedAccessSignature(accessPolicy);
}
Direct upload to Azure/Local (#1188) * Direct upload to azure To validate file sizes in the event of a rogue client, Azure event webhooks will be hooked up to AzureValidateFile. Sends outside of a grace size will be deleted as non-compliant. TODO: LocalSendFileStorageService direct upload method/endpoint. * Quick respond to no-body event calls These shouldn't happen, but might if some errant get requests occur * Event Grid only POSTS to webhook * Enable local storage direct file upload * Increase file size difference leeway * Upload through service * Fix LocalFileSendStorage It turns out that multipartHttpStreams do not have a length until read. this causes all long files to be "invalid". We need to write the entire stream, then validate length, just like Azure. the difference is, We can return an exception to local storage admonishing the client for lying * Update src/Api/Utilities/ApiHelpers.cs Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Do not delete directory if it has files * Allow large uploads for self hosted instances * Fix formatting * Re-verfiy access and increment access count on download of Send File * Update src/Core/Services/Implementations/SendService.cs Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Add back in original Send upload * Update size and mark as validated upon Send file validation * Log azure file validation errors * Lint fix Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-03-21 23:01:19 -05:00
public async Task<string> GetSendFileUploadUrlAsync(Send send, string fileId)
{
await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(BlobName(send, fileId));
var accessPolicy = new SharedAccessBlobPolicy()
{
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)
{
await InitAsync();
var blob = _sendFilesContainer.GetBlockBlobReference(BlobName(send, fileId));
if (!blob.Exists())
{
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()
{
if (_sendFilesContainer == null)
{
_sendFilesContainer = _blobClient.GetContainerReference(FilesContainerName);
await _sendFilesContainer.CreateIfNotExistsAsync(BlobContainerPublicAccessType.Off, null, null);
}
}
}
}