mirror of
https://github.com/bitwarden/android.git
synced 2025-01-11 18:57:39 +03:00
Attachment azure upload blobs (#1345)
* Update Size limits * Add new Api paths for direct upload of Cipher Attachments * Add Attachment upload to fileUploadService * Save with direct upload and fallback to legacy uplaod CipherID is required for direct upload to request an upload URL * Inform on when to remove legacy code * Test Attachment upload
This commit is contained in:
parent
04aeddc5de
commit
ce0b8bc62d
15 changed files with 292 additions and 25 deletions
|
@ -227,7 +227,7 @@
|
|||
Clicked="ChooseFile_Clicked" />
|
||||
<Label
|
||||
Margin="0, 5, 0, 0"
|
||||
Text="{u:I18n MaxFileSizeSend}"
|
||||
Text="{u:I18n MaxFileSize}"
|
||||
StyleClass="text-sm, text-muted"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
HorizontalTextAlignment="Center" />
|
||||
|
|
|
@ -102,7 +102,7 @@ namespace Bit.App.Pages
|
|||
AppResources.AnErrorHasOccurred);
|
||||
return false;
|
||||
}
|
||||
if (FileData.Length > 104857600) // 100 MB
|
||||
if (FileData.Length > 524288000) // 500 MB
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.MaxFileSize,
|
||||
AppResources.AnErrorHasOccurred);
|
||||
|
|
|
@ -477,7 +477,7 @@ namespace Bit.App.Pages
|
|||
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
|
||||
try
|
||||
{
|
||||
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(attachment, Cipher.OrganizationId);
|
||||
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
if (data == null)
|
||||
{
|
||||
|
|
|
@ -926,9 +926,6 @@
|
|||
<value>Feature Unavailable</value>
|
||||
</data>
|
||||
<data name="MaxFileSize" xml:space="preserve">
|
||||
<value>Maximum file size is 100 MB.</value>
|
||||
</data>
|
||||
<data name="MaxFileSizeSend" xml:space="preserve">
|
||||
<value>Maximum file size is 500 MB.</value>
|
||||
</data>
|
||||
<data name="UpdateKey" xml:space="preserve">
|
||||
|
|
|
@ -46,9 +46,14 @@ namespace Bit.Core.Abstractions
|
|||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||
TRequest body, bool authed, bool hasResponse);
|
||||
void SetUrls(EnvironmentUrls urls);
|
||||
Task<CipherResponse> PostCipherAttachmentAsync(string id, MultipartFormDataContent data);
|
||||
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||
Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
|
||||
Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request);
|
||||
Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId);
|
||||
Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
||||
string organizationId);
|
||||
Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string id, string attachmentId);
|
||||
Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data);
|
||||
Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username);
|
||||
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
|
||||
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace Bit.Core.Abstractions
|
|||
Task UpdateLastUsedDateAsync(string id);
|
||||
Task UpsertAsync(CipherData cipher);
|
||||
Task UpsertAsync(List<CipherData> cipher);
|
||||
Task<byte[]> DownloadAndDecryptAttachmentAsync(AttachmentView attachment, string organizationId);
|
||||
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
|
||||
Task SoftDeleteWithServerAsync(string id);
|
||||
Task RestoreWithServerAsync(string id);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using Bit.Core.Models.Response;
|
|||
|
||||
namespace Bit.Core.Abstractions {
|
||||
public interface IFileUploadService {
|
||||
Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, string fileName, byte[] encryptedFileData);
|
||||
Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, CipherString fileName, byte[] encryptedFileData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ namespace Bit.Core.Models.Request
|
|||
{
|
||||
public string FileName { get; set; }
|
||||
public string Key { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
12
src/Core/Models/Response/AttachmentUploadDataReponse.cs
Normal file
12
src/Core/Models/Response/AttachmentUploadDataReponse.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Response
|
||||
{
|
||||
public class AttachmentUploadDataResponse
|
||||
{
|
||||
public string AttachmentId { get; set; }
|
||||
public FileUploadType FileUploadType { get; set; }
|
||||
public CipherResponse CipherResponse { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
|
@ -303,16 +303,26 @@ namespace Bit.Core.Services
|
|||
|
||||
#region Attachments APIs
|
||||
|
||||
public Task<CipherResponse> PostCipherAttachmentAsync(string id, MultipartFormDataContent data)
|
||||
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||
public Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data)
|
||||
{
|
||||
return SendAsync<MultipartFormDataContent, CipherResponse>(HttpMethod.Post,
|
||||
string.Concat("/ciphers/", id, "/attachment"), data, true, true);
|
||||
}
|
||||
|
||||
public Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request)
|
||||
{
|
||||
return SendAsync<AttachmentRequest, AttachmentUploadDataResponse>(HttpMethod.Post,
|
||||
$"/ciphers/{id}/attachment/v2", request, true, true);
|
||||
}
|
||||
|
||||
public Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId) =>
|
||||
SendAsync<AttachmentResponse>(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}", true);
|
||||
|
||||
public Task DeleteCipherAttachmentAsync(string id, string attachmentId)
|
||||
{
|
||||
return SendAsync<object, object>(HttpMethod.Delete,
|
||||
string.Concat("/ciphers/", id, "/attachment/", attachmentId), null, true, false);
|
||||
return SendAsync(HttpMethod.Delete,
|
||||
string.Concat("/ciphers/", id, "/attachment/", attachmentId), true);
|
||||
}
|
||||
|
||||
public Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
||||
|
@ -323,6 +333,13 @@ namespace Bit.Core.Services
|
|||
data, true, false);
|
||||
}
|
||||
|
||||
public Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string cipherId, string attachmentId) =>
|
||||
SendAsync<AttachmentUploadDataResponse>(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}", true);
|
||||
|
||||
public Task PostAttachmentFileAsync(string cipherId, string attachmentId, MultipartFormDataContent data) =>
|
||||
SendAsync(HttpMethod.Post,
|
||||
$"/ciphers/{cipherId}/attachment/{attachmentId}", data, true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sync APIs
|
||||
|
@ -437,6 +454,12 @@ namespace Bit.Core.Services
|
|||
}
|
||||
}
|
||||
|
||||
public Task SendAsync(HttpMethod method, string path, bool authed) =>
|
||||
SendAsync<object, object>(method, path, null, authed, false);
|
||||
public Task SendAsync<TRequest>(HttpMethod method, string path, TRequest body, bool authed) =>
|
||||
SendAsync<TRequest, object>(method, path, body, authed, false);
|
||||
public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) =>
|
||||
SendAsync<object, TResponse>(method, path, null, authed, true);
|
||||
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
|
||||
bool authed, bool hasResponse)
|
||||
{
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace Bit.Core.Services
|
|||
private readonly IUserService _userService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IApiService _apiService;
|
||||
private readonly IFileUploadService _fileUploadService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly Func<ISearchService> _searchService;
|
||||
|
@ -47,6 +48,7 @@ namespace Bit.Core.Services
|
|||
IUserService userService,
|
||||
ISettingsService settingsService,
|
||||
IApiService apiService,
|
||||
IFileUploadService fileUploadService,
|
||||
IStorageService storageService,
|
||||
II18nService i18nService,
|
||||
Func<ISearchService> searchService,
|
||||
|
@ -57,6 +59,7 @@ namespace Bit.Core.Services
|
|||
_userService = userService;
|
||||
_settingsService = settingsService;
|
||||
_apiService = apiService;
|
||||
_fileUploadService = fileUploadService;
|
||||
_storageService = storageService;
|
||||
_i18nService = i18nService;
|
||||
_searchService = searchService;
|
||||
|
@ -553,21 +556,47 @@ namespace Bit.Core.Services
|
|||
|
||||
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
|
||||
{
|
||||
var key = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
||||
var encFileName = await _cryptoService.EncryptAsync(filename, key);
|
||||
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||
var encData = await _cryptoService.EncryptToBytesAsync(data, dataEncKey.Item1);
|
||||
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
|
||||
var fd = new MultipartFormDataContent(boundary);
|
||||
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
|
||||
fd.Add(new StreamContent(new MemoryStream(encData)), "data", encFileName.EncryptedString);
|
||||
var response = await _apiService.PostCipherAttachmentAsync(cipher.Id, fd);
|
||||
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
||||
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
|
||||
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
|
||||
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
|
||||
|
||||
CipherResponse response;
|
||||
try
|
||||
{
|
||||
var request = new AttachmentRequest
|
||||
{
|
||||
Key = orgEncAttachmentKey.EncryptedString,
|
||||
FileName = encFileName.EncryptedString,
|
||||
FileSize = encFileData.Length,
|
||||
};
|
||||
|
||||
var uploadDataResponse = await _apiService.PostCipherAttachmentAsync(cipher.Id, request);
|
||||
response = uploadDataResponse.CipherResponse;
|
||||
await _fileUploadService.UploadCipherAttachmentFileAsync(uploadDataResponse, encFileName.EncryptedString, encFileData);
|
||||
}
|
||||
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
|
||||
{
|
||||
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey);
|
||||
}
|
||||
|
||||
var userId = await _userService.GetUserIdAsync();
|
||||
var cData = new CipherData(response, userId, cipher.CollectionIds);
|
||||
await UpsertAsync(cData);
|
||||
return new Cipher(cData);
|
||||
}
|
||||
|
||||
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||
private async Task<CipherResponse> LegacyServerAttachmentFileUploadAsync(string cipherId,
|
||||
CipherString encFileName, byte[] encFileData, CipherString key)
|
||||
{
|
||||
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
|
||||
var fd = new MultipartFormDataContent(boundary);
|
||||
fd.Add(new StringContent(key.EncryptedString), "key");
|
||||
fd.Add(new StreamContent(new MemoryStream(encFileData)), "data", encFileName.EncryptedString);
|
||||
return await _apiService.PostCipherAttachmentLegacyAsync(cipherId, fd);
|
||||
}
|
||||
|
||||
public async Task SaveCollectionsWithServerAsync(Cipher cipher)
|
||||
{
|
||||
var request = new CipherCollectionsRequest(cipher.CollectionIds?.ToList());
|
||||
|
@ -706,11 +735,23 @@ namespace Bit.Core.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> DownloadAndDecryptAttachmentAsync(AttachmentView attachment, string organizationId)
|
||||
public async Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId)
|
||||
{
|
||||
string url;
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(new Uri(attachment.Url));
|
||||
var attachmentDownloadResponse = await _apiService.GetAttachmentData(cipherId, attachment.Id);
|
||||
url = attachmentDownloadResponse.Url;
|
||||
}
|
||||
// TODO: Delete this catch when all Servers are updated to respond to the above method
|
||||
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
url = attachment.Url;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(new Uri(url));
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return null;
|
||||
|
|
|
@ -19,6 +19,35 @@ namespace Bit.Core.Services {
|
|||
private readonly AzureFileUploadService _azureFileUploadService;
|
||||
private readonly ApiService _apiService;
|
||||
|
||||
public async Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData,
|
||||
string encryptedFileName, byte[] encryptedFileData)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (uploadData.FileUploadType)
|
||||
{
|
||||
case FileUploadType.Direct:
|
||||
await _bitwardenFileUploadService.Upload(encryptedFileName, encryptedFileData,
|
||||
fd => _apiService.PostAttachmentFileAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId, fd));
|
||||
break;
|
||||
case FileUploadType.Azure:
|
||||
Func<Task<string>> renewalCallback = async () =>
|
||||
{
|
||||
var response = await _apiService.RenewAttachmentUploadUrlAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId);
|
||||
return response.Url;
|
||||
};
|
||||
await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unkown file upload type: {uploadData.FileUploadType}");
|
||||
}
|
||||
} catch
|
||||
{
|
||||
await _apiService.DeleteCipherAttachmentAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, CipherString fileName, byte[] encryptedFileData)
|
||||
{
|
||||
try
|
||||
|
@ -41,10 +70,10 @@ namespace Bit.Core.Services {
|
|||
throw new Exception("Unknown file upload type");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
await _apiService.DeleteSendAsync(uploadData.SendResponse.Id);
|
||||
throw e;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Bit.Core.Utilities
|
|||
var userService = new UserService(storageService, tokenService);
|
||||
var settingsService = new SettingsService(userService, storageService);
|
||||
var fileUploadService = new FileUploadService(apiService);
|
||||
var cipherService = new CipherService(cryptoService, userService, settingsService, apiService,
|
||||
var cipherService = new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService,
|
||||
storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys);
|
||||
var folderService = new FolderService(cryptoService, userService, apiService, storageService,
|
||||
i18nService, cipherService);
|
||||
|
|
62
test/Core.Test/AutoFixture/Cipher/CipherCustomizations.cs
Normal file
62
test/Core.Test/AutoFixture/Cipher/CipherCustomizations.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture
|
||||
|
||||
{
|
||||
internal class OrganizationCipher : ICustomization
|
||||
{
|
||||
public string OrganizationId { get; set; }
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<Cipher>(composer => composer
|
||||
.With(c => c.OrganizationId, OrganizationId ?? Guid.NewGuid().ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
internal class UserCipher : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<Cipher>(composer => composer
|
||||
.Without(c => c.OrganizationId));
|
||||
}
|
||||
}
|
||||
|
||||
internal class UserCipherAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public UserCipherAutoDataAttribute() : base(new SutProviderCustomization(),
|
||||
new UserCipher())
|
||||
{ }
|
||||
}
|
||||
internal class InlineUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineUserCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
typeof(UserCipher) }, values)
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class InlineKnownUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineKnownUserCipherAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
|
||||
{ new SutProviderCustomization(), new UserCipher() }, values)
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class OrganizationCipherAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public OrganizationCipherAutoDataAttribute(string organizationId = null) : base(new SutProviderCustomization(),
|
||||
new OrganizationCipher { OrganizationId = organizationId ?? null })
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class InlineOrganizationCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineOrganizationCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
typeof(OrganizationCipher) }, values)
|
||||
{ }
|
||||
}
|
||||
}
|
96
test/Core.Test/Services/CipherServiceTests.cs
Normal file
96
test/Core.Test/Services/CipherServiceTests.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
public class CipherServiceTests
|
||||
{
|
||||
[Theory, UserCipherAutoData]
|
||||
public async Task SaveWithServerAsync_PrefersFileUploadService(SutProvider<CipherService> sutProvider,
|
||||
Cipher cipher, string fileName, byte[] data, AttachmentUploadDataResponse uploadDataResponse, CipherString encKey)
|
||||
{
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(new CipherString(fileName));
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(data);
|
||||
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()).Returns(new Tuple<SymmetricCryptoKey, CipherString>(null, encKey));
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||
.Returns(uploadDataResponse);
|
||||
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data);
|
||||
|
||||
await sutProvider.GetDependency<IFileUploadService>().Received(1)
|
||||
.UploadCipherAttachmentFileAsync(uploadDataResponse, fileName, data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineUserCipherAutoData(HttpStatusCode.NotFound)]
|
||||
[InlineUserCipherAutoData(HttpStatusCode.MethodNotAllowed)]
|
||||
public async Task SaveWithServerAsync_FallsBackToLegacyFormData(HttpStatusCode statusCode,
|
||||
SutProvider<CipherService> sutProvider, Cipher cipher, string fileName, byte[] data,
|
||||
CipherResponse response, CipherString encKey)
|
||||
{
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(new CipherString(fileName));
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(data);
|
||||
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()).Returns(new Tuple<SymmetricCryptoKey, CipherString>(null, encKey));
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||
.Throws(new ApiException(new ErrorResponse {StatusCode = statusCode}));
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>())
|
||||
.Returns(response);
|
||||
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data);
|
||||
|
||||
await sutProvider.GetDependency<IApiService>().Received(1)
|
||||
.PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>());
|
||||
}
|
||||
|
||||
[Theory, UserCipherAutoData]
|
||||
public async Task SaveWithServerAsync_ThrowsOnBadRequestApiException(SutProvider<CipherService> sutProvider,
|
||||
Cipher cipher, string fileName, byte[] data, CipherString encKey)
|
||||
{
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(new CipherString(fileName));
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(data);
|
||||
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(new Tuple<SymmetricCryptoKey, CipherString>(null, encKey));
|
||||
var expectedException = new ApiException(new ErrorResponse { StatusCode = HttpStatusCode.BadRequest });
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||
.Throws(expectedException);
|
||||
|
||||
var actualException = await Assert.ThrowsAsync<ApiException>(async () =>
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data));
|
||||
|
||||
Assert.Equal(expectedException.Error.StatusCode, actualException.Error.StatusCode);
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(SutProviderCustomization), typeof(SymmetricCryptoKeyCustomization))]
|
||||
public async Task DownloadAndDecryptAttachmentAsync_RequestsTimeLimitedUrl(SutProvider<CipherService> sutProvider,
|
||||
string cipherId, AttachmentView attachment, AttachmentResponse response)
|
||||
{
|
||||
sutProvider.GetDependency<IApiService>().GetAttachmentData(cipherId, attachment.Id)
|
||||
.Returns(response);
|
||||
|
||||
await sutProvider.Sut.DownloadAndDecryptAttachmentAsync(cipherId, attachment, null);
|
||||
|
||||
sutProvider.GetDependency<IApiService>().Received(1).GetAttachmentData(cipherId, attachment.Id);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue