1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -05:00

Use sas token for attachment downloads (#1153)

* Get limited life attachment download URL

This change limits url download to a 1min lifetime.
This requires moving to a new container to allow for non-public blob
access.

Clients will have to call GetAttachmentData api function to receive the download
URL. For backwards compatibility, attachment URLs are still present, but will not
work for attachments stored in non-public access blobs.

* Make GlobalSettings interface for testing

* Test LocalAttachmentStorageService equivalence

* Remove comment

* Add missing globalSettings using

* Simplify default attachment container

* Default to attachments containe for existing methods

A new upload method will be made for uploading to attachments-v2.
For compatibility for clients which don't use these new methods, we need
to still use the old container. The new container will be used only for
new uploads

* Remove Default MetaData fixture.

* Keep attachments container blob-level security for all instances

* Close unclosed FileStream

* Favor default value for noop services
This commit is contained in:
Matt Gibson
2021-02-22 15:35:16 -06:00
committed by GitHub
parent 78606d5f13
commit 5537470703
177 changed files with 694 additions and 178 deletions

View File

@ -2,63 +2,73 @@
using System.IO;
using System;
using Bit.Core.Models.Table;
using Bit.Core.Models.Data;
using Bit.Core.Settings;
namespace Bit.Core.Services
{
public class LocalAttachmentStorageService : IAttachmentStorageService
{
private readonly string _baseAttachmentUrl;
private readonly string _baseDirPath;
private readonly string _baseTempDirPath;
public LocalAttachmentStorageService(
GlobalSettings globalSettings)
IGlobalSettings globalSettings)
{
_baseDirPath = globalSettings.Attachment.BaseDirectory;
_baseTempDirPath = $"{_baseDirPath}/temp";
_baseAttachmentUrl = globalSettings.Attachment.BaseUrl;
}
public async Task UploadNewAttachmentAsync(Stream stream, Cipher cipher, string attachmentId)
public async Task<string> GetAttachmentDownloadUrlAsync(Cipher cipher, CipherAttachment.MetaData attachmentData)
{
await InitAsync();
var cipherDirPath = $"{_baseDirPath}/{cipher.Id}";
return $"{_baseAttachmentUrl}/{cipher.Id}/{attachmentData.AttachmentId}";
}
public async Task UploadNewAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachmentData)
{
await InitAsync();
var cipherDirPath = CipherDirectoryPath(cipher.Id, temp: false);
CreateDirectoryIfNotExists(cipherDirPath);
using (var fs = File.Create($"{cipherDirPath}/{attachmentId}"))
using (var fs = File.Create(AttachmentFilePath(cipherDirPath, attachmentData.AttachmentId)))
{
stream.Seek(0, SeekOrigin.Begin);
await stream.CopyToAsync(fs);
}
}
public async Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, string attachmentId)
public async Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, CipherAttachment.MetaData attachmentData)
{
await InitAsync();
var tempCipherOrgDirPath = $"{_baseTempDirPath}/{cipherId}/{organizationId}";
var tempCipherOrgDirPath = OrganizationDirectoryPath(cipherId, organizationId, temp: true);
CreateDirectoryIfNotExists(tempCipherOrgDirPath);
using (var fs = File.Create($"{tempCipherOrgDirPath}/{attachmentId}"))
using (var fs = File.Create(AttachmentFilePath(tempCipherOrgDirPath, attachmentData.AttachmentId)))
{
stream.Seek(0, SeekOrigin.Begin);
await stream.CopyToAsync(fs);
}
}
public async Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
public async Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, CipherAttachment.MetaData attachmentData)
{
await InitAsync();
var sourceFilePath = $"{_baseTempDirPath}/{cipherId}/{organizationId}/{attachmentId}";
var sourceFilePath = AttachmentFilePath(attachmentData.AttachmentId, cipherId, organizationId, temp: true);
if (!File.Exists(sourceFilePath))
{
return;
}
var destFilePath = $"{_baseDirPath}/{cipherId}/{attachmentId}";
var destFilePath = AttachmentFilePath(attachmentData.AttachmentId, cipherId, temp: false);
if (!File.Exists(destFilePath))
{
return;
}
var originalFilePath = $"{_baseTempDirPath}/{cipherId}/{attachmentId}";
var originalFilePath = AttachmentFilePath(attachmentData.AttachmentId, cipherId, temp: true);
DeleteFileIfExists(originalFilePath);
File.Move(destFilePath, originalFilePath);
@ -67,40 +77,40 @@ namespace Bit.Core.Services
File.Move(sourceFilePath, destFilePath);
}
public async Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
public async Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, CipherAttachment.MetaData attachmentData, string originalContainer)
{
await InitAsync();
DeleteFileIfExists($"{_baseTempDirPath}/{cipherId}/{organizationId}/{attachmentId}");
DeleteFileIfExists(AttachmentFilePath(attachmentData.AttachmentId, cipherId, organizationId, temp: true));
var originalFilePath = $"{_baseTempDirPath}/{cipherId}/{attachmentId}";
var originalFilePath = AttachmentFilePath(attachmentData.AttachmentId, cipherId, temp: true);
if (!File.Exists(originalFilePath))
{
return;
}
var destFilePath = $"{_baseDirPath}/{cipherId}/{attachmentId}";
var destFilePath = AttachmentFilePath(attachmentData.AttachmentId, cipherId, temp: false);
DeleteFileIfExists(destFilePath);
File.Move(originalFilePath, destFilePath);
DeleteFileIfExists(originalFilePath);
}
public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId)
public async Task DeleteAttachmentAsync(Guid cipherId, CipherAttachment.MetaData attachmentData)
{
await InitAsync();
DeleteFileIfExists($"{_baseDirPath}/{cipherId}/{attachmentId}");
DeleteFileIfExists(AttachmentFilePath(attachmentData.AttachmentId, cipherId, temp: false));
}
public async Task CleanupAsync(Guid cipherId)
{
await InitAsync();
DeleteDirectoryIfExists($"{_baseTempDirPath}/{cipherId}");
DeleteDirectoryIfExists(CipherDirectoryPath(cipherId, temp: true));
}
public async Task DeleteAttachmentsForCipherAsync(Guid cipherId)
{
await InitAsync();
DeleteDirectoryIfExists($"{_baseDirPath}/{cipherId}");
DeleteDirectoryIfExists(CipherDirectoryPath(cipherId, temp: false));
}
public async Task DeleteAttachmentsForOrganizationAsync(Guid organizationId)
@ -151,5 +161,18 @@ namespace Bit.Core.Services
return Task.FromResult(0);
}
private string CipherDirectoryPath(Guid cipherId, bool temp = false) =>
Path.Combine(temp ? _baseTempDirPath : _baseDirPath, cipherId.ToString());
private string OrganizationDirectoryPath(Guid cipherId, Guid organizationId, bool temp = false) =>
Path.Combine(temp ? _baseTempDirPath : _baseDirPath, cipherId.ToString(), organizationId.ToString());
private string AttachmentFilePath(string dir, string attachmentId) => Path.Combine(dir, attachmentId);
private string AttachmentFilePath(string attachmentId, Guid cipherId, Guid? organizationId = null,
bool temp = false) =>
organizationId.HasValue ?
AttachmentFilePath(OrganizationDirectoryPath(cipherId, organizationId.Value, temp), attachmentId) :
AttachmentFilePath(CipherDirectoryPath(cipherId, temp), attachmentId);
}
}