1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

...

3 Commits

15 changed files with 132 additions and 74 deletions

View File

@@ -83,7 +83,7 @@ namespace Bit.Droid.Services
return launchIntentSender != null; return launchIntentSender != null;
} }
public async Task ShowLoadingAsync(string text) public async Task ShowLoadingAsync(string text, System.Threading.CancellationTokenSource cts = null, string cancelButtonText = null)
{ {
if (_progressDialog != null) if (_progressDialog != null)
{ {
@@ -98,10 +98,16 @@ namespace Bit.Droid.Services
txtLoading.Text = text; txtLoading.Text = text;
txtLoading.SetTextColor(ThemeHelpers.TextColor); txtLoading.SetTextColor(ThemeHelpers.TextColor);
_progressDialog = new AlertDialog.Builder(activity) var progressDialogBuilder = new AlertDialog.Builder(activity)
.SetView(dialogView) .SetView(dialogView)
.SetCancelable(false) .SetCancelable(cts != null);
.Create();
if (cts != null)
{
progressDialogBuilder.SetNegativeButton(cancelButtonText ?? AppResources.Cancel, (sender, args) => cts?.Cancel());
}
_progressDialog = progressDialogBuilder.Create();
_progressDialog.Show(); _progressDialog.Show();
} }

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models;
@@ -13,7 +14,7 @@ namespace Bit.App.Abstractions
string GetBuildNumber(); string GetBuildNumber();
void Toast(string text, bool longDuration = false); void Toast(string text, bool longDuration = false);
Task ShowLoadingAsync(string text); Task ShowLoadingAsync(string text, CancellationTokenSource cts = null, string cancelButtonText = null);
Task HideLoadingAsync(); Task HideLoadingAsync();
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null, Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false, string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
@@ -31,6 +32,7 @@ namespace Bit.App.Pages
private bool _hasUpdatedKey; private bool _hasUpdatedKey;
private bool _canAccessAttachments; private bool _canAccessAttachments;
private string _fileName; private string _fileName;
private CancellationTokenSource _uploadCts;
public AttachmentsPageViewModel() public AttachmentsPageViewModel()
{ {
@@ -119,11 +121,15 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred); AppResources.AnErrorHasOccurred);
return false; return false;
} }
_uploadCts = new CancellationTokenSource();
var uploadCts = _uploadCts;
try try
{ {
await _deviceActionService.ShowLoadingAsync(AppResources.Saving); await _deviceActionService.ShowLoadingAsync(AppResources.Saving, uploadCts);
_cipherDomain = await _cipherService.SaveAttachmentRawWithServerAsync( _cipherDomain = await _cipherService.SaveAttachmentRawWithServerAsync(
_cipherDomain, FileName, FileData); _cipherDomain, FileName, FileData, uploadCts.Token);
Cipher = await _cipherDomain.DecryptAsync(); Cipher = await _cipherDomain.DecryptAsync();
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.AttachementAdded); _platformUtilsService.ShowToast("success", null, AppResources.AttachementAdded);
@@ -132,6 +138,11 @@ namespace Bit.App.Pages
FileName = null; FileName = null;
return true; return true;
} }
catch (OperationCanceledException)
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.UploadHasBeenCanceled, AppResources.Attachments);
}
catch (ApiException e) catch (ApiException e)
{ {
_logger.Exception(e); _logger.Exception(e);

View File

@@ -202,6 +202,24 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Biometric unlock for this account is disabled pending verification of master password..
/// </summary>
public static string AccountBiometricInvalidated {
get {
return ResourceManager.GetString("AccountBiometricInvalidated", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Autofill biometric unlock for this account is disabled pending verification of master password..
/// </summary>
public static string AccountBiometricInvalidatedExtension {
get {
return ResourceManager.GetString("AccountBiometricInvalidatedExtension", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Your new account has been created! You may now log in.. /// Looks up a localized string similar to Your new account has been created! You may now log in..
/// </summary> /// </summary>
@@ -967,24 +985,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Biometric unlock disabled pending verification of master password..
/// </summary>
public static string AccountBiometricInvalidated {
get {
return ResourceManager.GetString("AccountBiometricInvalidated", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Biometric unlock for autofill disabled pending verification of master password..
/// </summary>
public static string AccountBiometricInvalidatedExtension {
get {
return ResourceManager.GetString("AccountBiometricInvalidatedExtension", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Biometrics. /// Looks up a localized string similar to Biometrics.
/// </summary> /// </summary>
@@ -6515,6 +6515,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Upload has been canceled.
/// </summary>
public static string UploadHasBeenCanceled {
get {
return ResourceManager.GetString("UploadHasBeenCanceled", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Uppercase (A to Z). /// Looks up a localized string similar to Uppercase (A to Z).
/// </summary> /// </summary>

View File

@@ -2631,4 +2631,7 @@ Do you want to switch to this account?</value>
<data name="CurrentMasterPassword" xml:space="preserve"> <data name="CurrentMasterPassword" xml:space="preserve">
<value>Current master password</value> <value>Current master password</value>
</data> </data>
<data name="UploadHasBeenCanceled" xml:space="preserve">
<value>Upload has been canceled</value>
</data>
</root> </root>

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
@@ -47,16 +48,16 @@ namespace Bit.Core.Abstractions
Task RefreshIdentityTokenAsync(); Task RefreshIdentityTokenAsync();
Task<SsoPrevalidateResponse> PreValidateSso(string identifier); Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true); TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true, CancellationToken cancellationToken = default);
void SetUrls(EnvironmentUrls urls); void SetUrls(EnvironmentUrls urls);
[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.")] [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<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request); Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request, CancellationToken cancellationToken);
Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId); 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<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string id, string attachmentId, CancellationToken cancellationToken);
Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data); Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data, CancellationToken cancellationToken);
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);

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
@@ -6,6 +7,6 @@ namespace Bit.Core.Abstractions
{ {
public interface IAzureFileUploadService public interface IAzureFileUploadService
{ {
Task Upload(string uri, EncByteArray data, Func<Task<string>> renewalCallback); Task Upload(string uri, EncByteArray data, Func<CancellationToken, Task<string>> renewalCallback, CancellationToken cancellationToken);
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
@@ -27,7 +28,7 @@ namespace Bit.Core.Abstractions
Task<Cipher> GetAsync(string id); Task<Cipher> GetAsync(string id);
Task<CipherView> GetLastUsedForUrlAsync(string url); Task<CipherView> GetLastUsedForUrlAsync(string url);
Task ReplaceAsync(Dictionary<string, CipherData> ciphers); Task ReplaceAsync(Dictionary<string, CipherData> ciphers);
Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data); Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data, CancellationToken cancellationToken);
Task SaveCollectionsWithServerAsync(Cipher cipher); Task SaveCollectionsWithServerAsync(Cipher cipher);
Task SaveNeverDomainAsync(string domain); Task SaveNeverDomainAsync(string domain);
Task SaveWithServerAsync(Cipher cipher); Task SaveWithServerAsync(Cipher cipher);

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Response; using Bit.Core.Models.Response;
@@ -6,7 +7,7 @@ namespace Bit.Core.Abstractions
{ {
public interface IFileUploadService public interface IFileUploadService
{ {
Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData); Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData, CancellationToken cancellationToken);
Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData); Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData);
} }
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
@@ -342,10 +343,10 @@ namespace Bit.Core.Services
string.Concat("/ciphers/", id, "/attachment"), data, true, true); string.Concat("/ciphers/", id, "/attachment"), data, true, true);
} }
public Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request) public Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request, CancellationToken cancellationToken)
{ {
return SendAsync<AttachmentRequest, AttachmentUploadDataResponse>(HttpMethod.Post, return SendAsync<AttachmentRequest, AttachmentUploadDataResponse>(HttpMethod.Post,
$"/ciphers/{id}/attachment/v2", request, true, true); $"/ciphers/{id}/attachment/v2", request, true, true, cancellationToken: cancellationToken);
} }
public Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId) => public Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId) =>
@@ -365,12 +366,12 @@ namespace Bit.Core.Services
data, true, false); data, true, false);
} }
public Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string cipherId, string attachmentId) => public Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string cipherId, string attachmentId, CancellationToken cancellationToken) =>
SendAsync<AttachmentUploadDataResponse>(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}/renew", true); SendAsync<AttachmentUploadDataResponse>(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}/renew", true, cancellationToken);
public Task PostAttachmentFileAsync(string cipherId, string attachmentId, MultipartFormDataContent data) => public Task PostAttachmentFileAsync(string cipherId, string attachmentId, MultipartFormDataContent data, CancellationToken cancellationToken) =>
SendAsync(HttpMethod.Post, SendAsync(HttpMethod.Post,
$"/ciphers/{cipherId}/attachment/{attachmentId}", data, true); $"/ciphers/{cipherId}/attachment/{attachmentId}", data, true, cancellationToken);
#endregion #endregion
@@ -639,12 +640,12 @@ namespace Bit.Core.Services
public Task SendAsync(HttpMethod method, string path, bool authed) => public Task SendAsync(HttpMethod method, string path, bool authed) =>
SendAsync<object, object>(method, path, null, authed, false); SendAsync<object, object>(method, path, null, authed, false);
public Task SendAsync<TRequest>(HttpMethod method, string path, TRequest body, bool authed) => public Task SendAsync<TRequest>(HttpMethod method, string path, TRequest body, bool authed, CancellationToken cancellationToken = default) =>
SendAsync<TRequest, object>(method, path, body, authed, false); SendAsync<TRequest, object>(method, path, body, authed, false, cancellationToken: cancellationToken);
public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) => public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed, CancellationToken cancellationToken = default) =>
SendAsync<object, TResponse>(method, path, null, authed, true); SendAsync<object, TResponse>(method, path, null, authed, true, cancellationToken: cancellationToken);
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, Action<HttpRequestMessage> alterRequest = null, bool logoutOnUnauthorized = true) bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest = null, bool logoutOnUnauthorized = true, CancellationToken cancellationToken = default)
{ {
using (var requestMessage = new HttpRequestMessage()) using (var requestMessage = new HttpRequestMessage())
{ {
@@ -696,7 +697,15 @@ namespace Bit.Core.Services
HttpResponseMessage response; HttpResponseMessage response;
try try
{ {
response = await _httpClient.SendAsync(requestMessage); response = await _httpClient.SendAsync(requestMessage, cancellationToken);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex) when (ex.Message?.Contains("Socket closed") == true)
{
throw new OperationCanceledException();
} }
catch (Exception e) catch (Exception e)
{ {

View File

@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
@@ -29,19 +30,19 @@ namespace Bit.Core.Services
}; };
} }
public async Task Upload(string uri, EncByteArray data, Func<Task<string>> renewalCallback) public async Task Upload(string uri, EncByteArray data, Func<CancellationToken, Task<string>> renewalCallback, CancellationToken cancellationToken)
{ {
if (data?.Buffer?.Length <= MAX_SINGLE_BLOB_UPLOAD_SIZE) if (data?.Buffer?.Length <= MAX_SINGLE_BLOB_UPLOAD_SIZE)
{ {
await AzureUploadBlob(uri, data); await AzureUploadBlob(uri, data, cancellationToken);
} }
else else
{ {
await AzureUploadBlocks(uri, data, renewalCallback); await AzureUploadBlocks(uri, data, renewalCallback, cancellationToken);
} }
} }
private async Task AzureUploadBlob(string uri, EncByteArray data) private async Task AzureUploadBlob(string uri, EncByteArray data, CancellationToken cancellationToken)
{ {
using (var requestMessage = new HttpRequestMessage()) using (var requestMessage = new HttpRequestMessage())
{ {
@@ -57,7 +58,7 @@ namespace Bit.Core.Services
requestMessage.Method = HttpMethod.Put; requestMessage.Method = HttpMethod.Put;
requestMessage.RequestUri = uriBuilder.Uri; requestMessage.RequestUri = uriBuilder.Uri;
var blobResponse = await _httpClient.SendAsync(requestMessage); var blobResponse = await _httpClient.SendAsync(requestMessage, cancellationToken);
if (blobResponse.StatusCode != HttpStatusCode.Created) if (blobResponse.StatusCode != HttpStatusCode.Created)
{ {
@@ -66,7 +67,7 @@ namespace Bit.Core.Services
} }
} }
private async Task AzureUploadBlocks(string uri, EncByteArray data, Func<Task<string>> renewalFunc) private async Task AzureUploadBlocks(string uri, EncByteArray data, Func<CancellationToken, Task<string>> renewalFunc, CancellationToken cancellationToken)
{ {
_httpClient.Timeout = TimeSpan.FromHours(3); _httpClient.Timeout = TimeSpan.FromHours(3);
var baseParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query); var baseParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query);
@@ -82,7 +83,7 @@ namespace Bit.Core.Services
while (blockIndex < numBlocks) while (blockIndex < numBlocks)
{ {
uri = await RenewUriIfNecessary(uri, renewalFunc); uri = await RenewUriIfNecessary(uri, renewalFunc, cancellationToken);
var blockUriBuilder = new UriBuilder(uri); var blockUriBuilder = new UriBuilder(uri);
var blockId = EncodeBlockId(blockIndex); var blockId = EncodeBlockId(blockIndex);
var blockParams = HttpUtility.ParseQueryString(blockUriBuilder.Query); var blockParams = HttpUtility.ParseQueryString(blockUriBuilder.Query);
@@ -101,7 +102,7 @@ namespace Bit.Core.Services
requestMessage.Method = HttpMethod.Put; requestMessage.Method = HttpMethod.Put;
requestMessage.RequestUri = blockUriBuilder.Uri; requestMessage.RequestUri = blockUriBuilder.Uri;
var blockResponse = await _httpClient.SendAsync(requestMessage); var blockResponse = await _httpClient.SendAsync(requestMessage, cancellationToken);
if (blockResponse.StatusCode != HttpStatusCode.Created) if (blockResponse.StatusCode != HttpStatusCode.Created)
{ {
@@ -115,7 +116,7 @@ namespace Bit.Core.Services
using (var requestMessage = new HttpRequestMessage()) using (var requestMessage = new HttpRequestMessage())
{ {
uri = await RenewUriIfNecessary(uri, renewalFunc); uri = await RenewUriIfNecessary(uri, renewalFunc, cancellationToken);
var blockListXml = GenerateBlockListXml(blocksStaged); var blockListXml = GenerateBlockListXml(blocksStaged);
var blockListUriBuilder = new UriBuilder(uri); var blockListUriBuilder = new UriBuilder(uri);
var blockListParams = HttpUtility.ParseQueryString(blockListUriBuilder.Query); var blockListParams = HttpUtility.ParseQueryString(blockListUriBuilder.Query);
@@ -130,7 +131,7 @@ namespace Bit.Core.Services
requestMessage.Method = HttpMethod.Put; requestMessage.Method = HttpMethod.Put;
requestMessage.RequestUri = blockListUriBuilder.Uri; requestMessage.RequestUri = blockListUriBuilder.Uri;
var blockListResponse = await _httpClient.SendAsync(requestMessage); var blockListResponse = await _httpClient.SendAsync(requestMessage, cancellationToken);
if (blockListResponse.StatusCode != HttpStatusCode.Created) if (blockListResponse.StatusCode != HttpStatusCode.Created)
{ {
@@ -139,13 +140,13 @@ namespace Bit.Core.Services
} }
} }
private async Task<string> RenewUriIfNecessary(string uri, Func<Task<string>> renewalFunc) private async Task<string> RenewUriIfNecessary(string uri, Func<CancellationToken, Task<string>> renewalFunc, CancellationToken cancellationToken)
{ {
var uriParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query); var uriParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query);
if (DateTime.TryParse(uriParams.Get("se") ?? "", out DateTime expiry) && expiry < DateTime.UtcNow.AddSeconds(1)) if (DateTime.TryParse(uriParams.Get("se") ?? "", out DateTime expiry) && expiry < DateTime.UtcNow.AddSeconds(1))
{ {
return await renewalFunc(); return await renewalFunc(cancellationToken);
} }
return uri; return uri;
} }

View File

@@ -2,6 +2,7 @@
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Mime; using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
@@ -9,21 +10,21 @@ namespace Bit.Core.Services
{ {
public class BitwardenFileUploadService public class BitwardenFileUploadService
{ {
private readonly ApiService _apiService;
public BitwardenFileUploadService(ApiService apiService) public BitwardenFileUploadService(ApiService apiService)
{ {
_apiService = apiService; _apiService = apiService;
} }
private readonly ApiService _apiService; public async Task Upload(string encryptedFileName, EncByteArray encryptedFileData, Func<MultipartFormDataContent, CancellationToken, Task> apiCall, CancellationToken cancellationToken)
public async Task Upload(string encryptedFileName, EncByteArray encryptedFileData, Func<MultipartFormDataContent, Task> apiCall)
{ {
var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}") var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}")
{ {
{ new ByteArrayContent(encryptedFileData.Buffer) { Headers = { ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet) } }, "data", encryptedFileName } { new ByteArrayContent(encryptedFileData.Buffer) { Headers = { ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet) } }, "data", encryptedFileName }
}; };
await apiCall(fd); await apiCall(fd, cancellationToken);
} }
} }
} }

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
@@ -555,13 +556,15 @@ namespace Bit.Core.Services
await UpsertAsync(data); await UpsertAsync(data);
} }
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data) public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data, CancellationToken cancellationToken)
{ {
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId); var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey); var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey); var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey); var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
cancellationToken.ThrowIfCancellationRequested();
CipherResponse response; CipherResponse response;
try try
{ {
@@ -572,9 +575,9 @@ namespace Bit.Core.Services
FileSize = encFileData.Buffer.Length, FileSize = encFileData.Buffer.Length,
}; };
var uploadDataResponse = await _apiService.PostCipherAttachmentAsync(cipher.Id, request); var uploadDataResponse = await _apiService.PostCipherAttachmentAsync(cipher.Id, request, cancellationToken);
response = uploadDataResponse.CipherResponse; response = uploadDataResponse.CipherResponse;
await _fileUploadService.UploadCipherAttachmentFileAsync(uploadDataResponse, encFileName, encFileData); await _fileUploadService.UploadCipherAttachmentFileAsync(uploadDataResponse, encFileName, encFileData, cancellationToken);
} }
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed) catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
{ {

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
@@ -21,7 +22,7 @@ namespace Bit.Core.Services
private readonly ApiService _apiService; private readonly ApiService _apiService;
public async Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, public async Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData,
EncString encryptedFileName, EncByteArray encryptedFileData) EncString encryptedFileName, EncByteArray encryptedFileData, CancellationToken cancellationToken)
{ {
try try
{ {
@@ -29,15 +30,15 @@ namespace Bit.Core.Services
{ {
case FileUploadType.Direct: case FileUploadType.Direct:
await _bitwardenFileUploadService.Upload(encryptedFileName.EncryptedString, encryptedFileData, await _bitwardenFileUploadService.Upload(encryptedFileName.EncryptedString, encryptedFileData,
fd => _apiService.PostAttachmentFileAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId, fd)); (fd, ct) => _apiService.PostAttachmentFileAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId, fd, ct), cancellationToken);
break; break;
case FileUploadType.Azure: case FileUploadType.Azure:
Func<Task<string>> renewalCallback = async () => Func<CancellationToken, Task<string>> renewalCallback = async ct =>
{ {
var response = await _apiService.RenewAttachmentUploadUrlAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId); var response = await _apiService.RenewAttachmentUploadUrlAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId, ct);
return response.Url; return response.Url;
}; };
await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback); await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback, cancellationToken);
break; break;
default: default:
throw new Exception($"Unkown file upload type: {uploadData.FileUploadType}"); throw new Exception($"Unkown file upload type: {uploadData.FileUploadType}");
@@ -58,16 +59,16 @@ namespace Bit.Core.Services
{ {
case FileUploadType.Direct: case FileUploadType.Direct:
await _bitwardenFileUploadService.Upload(fileName.EncryptedString, encryptedFileData, await _bitwardenFileUploadService.Upload(fileName.EncryptedString, encryptedFileData,
fd => _apiService.PostSendFileAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id, fd)); (fd, _) => _apiService.PostSendFileAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id, fd), default);
break; break;
case FileUploadType.Azure: case FileUploadType.Azure:
Func<Task<string>> renewalCallback = async () => Func<CancellationToken, Task<string>> renewalCallback = async ct =>
{ {
var response = await _apiService.RenewFileUploadUrlAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id); var response = await _apiService.RenewFileUploadUrlAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id);
return response.Url; return response.Url;
}; };
await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback); await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback, default);
break; break;
default: default:
throw new Exception("Unknown file upload type"); throw new Exception("Unknown file upload type");

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
@@ -61,7 +62,7 @@ namespace Bit.iOS.Core.Services
}; };
} }
public Task ShowLoadingAsync(string text) public Task ShowLoadingAsync(string text, CancellationTokenSource cts = null, string cancelButtonText = null)
{ {
if (_progressAlert != null) if (_progressAlert != null)
{ {
@@ -84,6 +85,14 @@ namespace Bit.iOS.Core.Services
_progressAlert = UIAlertController.Create(null, text, UIAlertControllerStyle.Alert); _progressAlert = UIAlertController.Create(null, text, UIAlertControllerStyle.Alert);
_progressAlert.View.TintColor = UIColor.Black; _progressAlert.View.TintColor = UIColor.Black;
_progressAlert.View.Add(loadingIndicator); _progressAlert.View.Add(loadingIndicator);
if (cts != null)
{
_progressAlert.AddAction(UIAlertAction.Create(cancelButtonText ?? AppResources.Cancel, UIAlertActionStyle.Cancel, x =>
{
cts.Cancel();
result.TrySetResult(0);
}));
}
vc.PresentViewController(_progressAlert, false, () => result.TrySetResult(0)); vc.PresentViewController(_progressAlert, false, () => result.TrySetResult(0));
return result.Task; return result.Task;