mirror of
https://github.com/bitwarden/mobile.git
synced 2025-03-12 13:19:10 +01: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
@ -227,7 +227,7 @@
|
|||||||
Clicked="ChooseFile_Clicked" />
|
Clicked="ChooseFile_Clicked" />
|
||||||
<Label
|
<Label
|
||||||
Margin="0, 5, 0, 0"
|
Margin="0, 5, 0, 0"
|
||||||
Text="{u:I18n MaxFileSizeSend}"
|
Text="{u:I18n MaxFileSize}"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center" />
|
||||||
|
@ -102,7 +102,7 @@ namespace Bit.App.Pages
|
|||||||
AppResources.AnErrorHasOccurred);
|
AppResources.AnErrorHasOccurred);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (FileData.Length > 104857600) // 100 MB
|
if (FileData.Length > 524288000) // 500 MB
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.MaxFileSize,
|
await _platformUtilsService.ShowDialogAsync(AppResources.MaxFileSize,
|
||||||
AppResources.AnErrorHasOccurred);
|
AppResources.AnErrorHasOccurred);
|
||||||
|
@ -477,7 +477,7 @@ namespace Bit.App.Pages
|
|||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(attachment, Cipher.OrganizationId);
|
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
if (data == null)
|
if (data == null)
|
||||||
{
|
{
|
||||||
|
@ -926,9 +926,6 @@
|
|||||||
<value>Feature Unavailable</value>
|
<value>Feature Unavailable</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MaxFileSize" xml:space="preserve">
|
<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>
|
<value>Maximum file size is 500 MB.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdateKey" xml:space="preserve">
|
<data name="UpdateKey" xml:space="preserve">
|
||||||
|
@ -46,9 +46,14 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse);
|
TRequest body, bool authed, bool hasResponse);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
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,
|
Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
||||||
string organizationId);
|
string organizationId);
|
||||||
|
Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string id, string attachmentId);
|
||||||
|
Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data);
|
||||||
Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username);
|
Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username);
|
||||||
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
|
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
|
||||||
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
||||||
|
@ -35,7 +35,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task UpdateLastUsedDateAsync(string id);
|
Task UpdateLastUsedDateAsync(string id);
|
||||||
Task UpsertAsync(CipherData cipher);
|
Task UpsertAsync(CipherData cipher);
|
||||||
Task UpsertAsync(List<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 SoftDeleteWithServerAsync(string id);
|
||||||
Task RestoreWithServerAsync(string id);
|
Task RestoreWithServerAsync(string id);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using Bit.Core.Models.Response;
|
|||||||
|
|
||||||
namespace Bit.Core.Abstractions {
|
namespace Bit.Core.Abstractions {
|
||||||
public interface IFileUploadService {
|
public interface IFileUploadService {
|
||||||
|
Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, string fileName, byte[] encryptedFileData);
|
||||||
Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, CipherString 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 FileName { get; set; }
|
||||||
public string Key { 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
|
#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,
|
return SendAsync<MultipartFormDataContent, CipherResponse>(HttpMethod.Post,
|
||||||
string.Concat("/ciphers/", id, "/attachment"), data, true, true);
|
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)
|
public Task DeleteCipherAttachmentAsync(string id, string attachmentId)
|
||||||
{
|
{
|
||||||
return SendAsync<object, object>(HttpMethod.Delete,
|
return SendAsync(HttpMethod.Delete,
|
||||||
string.Concat("/ciphers/", id, "/attachment/", attachmentId), null, true, false);
|
string.Concat("/ciphers/", id, "/attachment/", attachmentId), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
public Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
||||||
@ -323,6 +333,13 @@ namespace Bit.Core.Services
|
|||||||
data, true, false);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Sync APIs
|
#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,
|
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
|
||||||
bool authed, bool hasResponse)
|
bool authed, bool hasResponse)
|
||||||
{
|
{
|
||||||
|
@ -30,6 +30,7 @@ namespace Bit.Core.Services
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly IApiService _apiService;
|
private readonly IApiService _apiService;
|
||||||
|
private readonly IFileUploadService _fileUploadService;
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly Func<ISearchService> _searchService;
|
private readonly Func<ISearchService> _searchService;
|
||||||
@ -47,6 +48,7 @@ namespace Bit.Core.Services
|
|||||||
IUserService userService,
|
IUserService userService,
|
||||||
ISettingsService settingsService,
|
ISettingsService settingsService,
|
||||||
IApiService apiService,
|
IApiService apiService,
|
||||||
|
IFileUploadService fileUploadService,
|
||||||
IStorageService storageService,
|
IStorageService storageService,
|
||||||
II18nService i18nService,
|
II18nService i18nService,
|
||||||
Func<ISearchService> searchService,
|
Func<ISearchService> searchService,
|
||||||
@ -57,6 +59,7 @@ namespace Bit.Core.Services
|
|||||||
_userService = userService;
|
_userService = userService;
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_apiService = apiService;
|
_apiService = apiService;
|
||||||
|
_fileUploadService = fileUploadService;
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
_i18nService = i18nService;
|
_i18nService = i18nService;
|
||||||
_searchService = searchService;
|
_searchService = searchService;
|
||||||
@ -553,21 +556,47 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
|
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
|
||||||
{
|
{
|
||||||
var key = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
||||||
var encFileName = await _cryptoService.EncryptAsync(filename, key);
|
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
|
||||||
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key);
|
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
|
||||||
var encData = await _cryptoService.EncryptToBytesAsync(data, dataEncKey.Item1);
|
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
|
||||||
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
|
|
||||||
var fd = new MultipartFormDataContent(boundary);
|
CipherResponse response;
|
||||||
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
|
try
|
||||||
fd.Add(new StreamContent(new MemoryStream(encData)), "data", encFileName.EncryptedString);
|
{
|
||||||
var response = await _apiService.PostCipherAttachmentAsync(cipher.Id, fd);
|
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 userId = await _userService.GetUserIdAsync();
|
||||||
var cData = new CipherData(response, userId, cipher.CollectionIds);
|
var cData = new CipherData(response, userId, cipher.CollectionIds);
|
||||||
await UpsertAsync(cData);
|
await UpsertAsync(cData);
|
||||||
return new Cipher(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)
|
public async Task SaveCollectionsWithServerAsync(Cipher cipher)
|
||||||
{
|
{
|
||||||
var request = new CipherCollectionsRequest(cipher.CollectionIds?.ToList());
|
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
|
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)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -19,6 +19,35 @@ namespace Bit.Core.Services {
|
|||||||
private readonly AzureFileUploadService _azureFileUploadService;
|
private readonly AzureFileUploadService _azureFileUploadService;
|
||||||
private readonly ApiService _apiService;
|
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)
|
public async Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, CipherString fileName, byte[] encryptedFileData)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -41,10 +70,10 @@ namespace Bit.Core.Services {
|
|||||||
throw new Exception("Unknown file upload type");
|
throw new Exception("Unknown file upload type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
await _apiService.DeleteSendAsync(uploadData.SendResponse.Id);
|
await _apiService.DeleteSendAsync(uploadData.SendResponse.Id);
|
||||||
throw e;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ namespace Bit.Core.Utilities
|
|||||||
var userService = new UserService(storageService, tokenService);
|
var userService = new UserService(storageService, tokenService);
|
||||||
var settingsService = new SettingsService(userService, storageService);
|
var settingsService = new SettingsService(userService, storageService);
|
||||||
var fileUploadService = new FileUploadService(apiService);
|
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);
|
storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys);
|
||||||
var folderService = new FolderService(cryptoService, userService, apiService, storageService,
|
var folderService = new FolderService(cryptoService, userService, apiService, storageService,
|
||||||
i18nService, cipherService);
|
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 New Issue
Block a user