diff --git a/src/Core/Abstractions/IApiService.cs b/src/Core/Abstractions/IApiService.cs index 19930ce55..2291eae2d 100644 --- a/src/Core/Abstractions/IApiService.cs +++ b/src/Core/Abstractions/IApiService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Bit.Core.Enums; using Bit.Core.Models.Domain; @@ -47,16 +48,16 @@ namespace Bit.Core.Abstractions Task RefreshIdentityTokenAsync(); Task PreValidateSso(string identifier); Task SendAsync(HttpMethod method, string path, - TRequest body, bool authed, bool hasResponse, Action alterRequest, bool logoutOnUnauthorized = true); + TRequest body, bool authed, bool hasResponse, Action alterRequest, bool logoutOnUnauthorized = true, CancellationToken cancellationToken = default); 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.")] Task PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data); - Task PostCipherAttachmentAsync(string id, AttachmentRequest request); + Task PostCipherAttachmentAsync(string id, AttachmentRequest request, CancellationToken cancellationToken); Task GetAttachmentData(string cipherId, string attachmentId); Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data, string organizationId); - Task RenewAttachmentUploadUrlAsync(string id, string attachmentId); - Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data); + Task RenewAttachmentUploadUrlAsync(string id, string attachmentId, CancellationToken cancellationToken); + Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data, CancellationToken cancellationToken); Task> GetHibpBreachAsync(string username); Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request); Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request); diff --git a/src/Core/Abstractions/IAzureFileUpoadService.cs b/src/Core/Abstractions/IAzureFileUpoadService.cs index 18f3e2ec0..32b7fdbc7 100644 --- a/src/Core/Abstractions/IAzureFileUpoadService.cs +++ b/src/Core/Abstractions/IAzureFileUpoadService.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Bit.Core.Models.Domain; @@ -6,6 +7,6 @@ namespace Bit.Core.Abstractions { public interface IAzureFileUploadService { - Task Upload(string uri, EncByteArray data, Func> renewalCallback); + Task Upload(string uri, EncByteArray data, Func> renewalCallback, CancellationToken cancellationToken); } } diff --git a/src/Core/Abstractions/ICipherService.cs b/src/Core/Abstractions/ICipherService.cs index e34aaa068..0987567d1 100644 --- a/src/Core/Abstractions/ICipherService.cs +++ b/src/Core/Abstractions/ICipherService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Bit.Core.Enums; using Bit.Core.Models.Data; @@ -27,7 +28,7 @@ namespace Bit.Core.Abstractions Task GetAsync(string id); Task GetLastUsedForUrlAsync(string url); Task ReplaceAsync(Dictionary ciphers); - Task SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data); + Task SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data, CancellationToken cancellationToken); Task SaveCollectionsWithServerAsync(Cipher cipher); Task SaveNeverDomainAsync(string domain); Task SaveWithServerAsync(Cipher cipher); diff --git a/src/Core/Abstractions/IFileUploadService.cs b/src/Core/Abstractions/IFileUploadService.cs index 75f566550..4962e0dfe 100644 --- a/src/Core/Abstractions/IFileUploadService.cs +++ b/src/Core/Abstractions/IFileUploadService.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; @@ -6,7 +7,7 @@ namespace Bit.Core.Abstractions { 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); } } diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs index d486ac2c4..5aabe4acc 100644 --- a/src/Core/Services/ApiService.cs +++ b/src/Core/Services/ApiService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; +using System.Threading; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; @@ -342,10 +343,10 @@ namespace Bit.Core.Services string.Concat("/ciphers/", id, "/attachment"), data, true, true); } - public Task PostCipherAttachmentAsync(string id, AttachmentRequest request) + public Task PostCipherAttachmentAsync(string id, AttachmentRequest request, CancellationToken cancellationToken) { return SendAsync(HttpMethod.Post, - $"/ciphers/{id}/attachment/v2", request, true, true); + $"/ciphers/{id}/attachment/v2", request, true, true, cancellationToken: cancellationToken); } public Task GetAttachmentData(string cipherId, string attachmentId) => @@ -365,12 +366,12 @@ namespace Bit.Core.Services data, true, false); } - public Task RenewAttachmentUploadUrlAsync(string cipherId, string attachmentId) => - SendAsync(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}/renew", true); + public Task RenewAttachmentUploadUrlAsync(string cipherId, string attachmentId, CancellationToken cancellationToken) => + SendAsync(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, - $"/ciphers/{cipherId}/attachment/{attachmentId}", data, true); + $"/ciphers/{cipherId}/attachment/{attachmentId}", data, true, cancellationToken); #endregion @@ -629,12 +630,12 @@ namespace Bit.Core.Services public Task SendAsync(HttpMethod method, string path, bool authed) => SendAsync(method, path, null, authed, false); - public Task SendAsync(HttpMethod method, string path, TRequest body, bool authed) => - SendAsync(method, path, body, authed, false); - public Task SendAsync(HttpMethod method, string path, bool authed) => - SendAsync(method, path, null, authed, true); + public Task SendAsync(HttpMethod method, string path, TRequest body, bool authed, CancellationToken cancellationToken = default) => + SendAsync(method, path, body, authed, false, cancellationToken: cancellationToken); + public Task SendAsync(HttpMethod method, string path, bool authed, CancellationToken cancellationToken = default) => + SendAsync(method, path, null, authed, true, cancellationToken: cancellationToken); public async Task SendAsync(HttpMethod method, string path, TRequest body, - bool authed, bool hasResponse, Action alterRequest = null, bool logoutOnUnauthorized = true) + bool authed, bool hasResponse, Action alterRequest = null, bool logoutOnUnauthorized = true, CancellationToken cancellationToken = default) { using (var requestMessage = new HttpRequestMessage()) { @@ -686,7 +687,7 @@ namespace Bit.Core.Services HttpResponseMessage response; try { - response = await _httpClient.SendAsync(requestMessage); + response = await _httpClient.SendAsync(requestMessage, cancellationToken); } catch (Exception e) { diff --git a/src/Core/Services/AzureFileUploadService.cs b/src/Core/Services/AzureFileUploadService.cs index 4d18c1246..9afd21b5f 100644 --- a/src/Core/Services/AzureFileUploadService.cs +++ b/src/Core/Services/AzureFileUploadService.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Web; using Bit.Core.Abstractions; @@ -29,19 +30,19 @@ namespace Bit.Core.Services }; } - public async Task Upload(string uri, EncByteArray data, Func> renewalCallback) + public async Task Upload(string uri, EncByteArray data, Func> renewalCallback, CancellationToken cancellationToken) { if (data?.Buffer?.Length <= MAX_SINGLE_BLOB_UPLOAD_SIZE) { - await AzureUploadBlob(uri, data); + await AzureUploadBlob(uri, data, cancellationToken); } 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()) { @@ -57,7 +58,7 @@ namespace Bit.Core.Services requestMessage.Method = HttpMethod.Put; requestMessage.RequestUri = uriBuilder.Uri; - var blobResponse = await _httpClient.SendAsync(requestMessage); + var blobResponse = await _httpClient.SendAsync(requestMessage, cancellationToken); if (blobResponse.StatusCode != HttpStatusCode.Created) { @@ -66,7 +67,7 @@ namespace Bit.Core.Services } } - private async Task AzureUploadBlocks(string uri, EncByteArray data, Func> renewalFunc) + private async Task AzureUploadBlocks(string uri, EncByteArray data, Func> renewalFunc, CancellationToken cancellationToken) { _httpClient.Timeout = TimeSpan.FromHours(3); var baseParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query); @@ -82,7 +83,7 @@ namespace Bit.Core.Services while (blockIndex < numBlocks) { - uri = await RenewUriIfNecessary(uri, renewalFunc); + uri = await RenewUriIfNecessary(uri, renewalFunc, cancellationToken); var blockUriBuilder = new UriBuilder(uri); var blockId = EncodeBlockId(blockIndex); var blockParams = HttpUtility.ParseQueryString(blockUriBuilder.Query); @@ -101,7 +102,7 @@ namespace Bit.Core.Services requestMessage.Method = HttpMethod.Put; requestMessage.RequestUri = blockUriBuilder.Uri; - var blockResponse = await _httpClient.SendAsync(requestMessage); + var blockResponse = await _httpClient.SendAsync(requestMessage, cancellationToken); if (blockResponse.StatusCode != HttpStatusCode.Created) { @@ -115,7 +116,7 @@ namespace Bit.Core.Services using (var requestMessage = new HttpRequestMessage()) { - uri = await RenewUriIfNecessary(uri, renewalFunc); + uri = await RenewUriIfNecessary(uri, renewalFunc, cancellationToken); var blockListXml = GenerateBlockListXml(blocksStaged); var blockListUriBuilder = new UriBuilder(uri); var blockListParams = HttpUtility.ParseQueryString(blockListUriBuilder.Query); @@ -130,7 +131,7 @@ namespace Bit.Core.Services requestMessage.Method = HttpMethod.Put; requestMessage.RequestUri = blockListUriBuilder.Uri; - var blockListResponse = await _httpClient.SendAsync(requestMessage); + var blockListResponse = await _httpClient.SendAsync(requestMessage, cancellationToken); if (blockListResponse.StatusCode != HttpStatusCode.Created) { @@ -139,13 +140,13 @@ namespace Bit.Core.Services } } - private async Task RenewUriIfNecessary(string uri, Func> renewalFunc) + private async Task RenewUriIfNecessary(string uri, Func> renewalFunc, CancellationToken cancellationToken) { var uriParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query); if (DateTime.TryParse(uriParams.Get("se") ?? "", out DateTime expiry) && expiry < DateTime.UtcNow.AddSeconds(1)) { - return await renewalFunc(); + return await renewalFunc(cancellationToken); } return uri; } diff --git a/src/Core/Services/BitwardenFileUploadService.cs b/src/Core/Services/BitwardenFileUploadService.cs index bb0c29073..d1aaa04b8 100644 --- a/src/Core/Services/BitwardenFileUploadService.cs +++ b/src/Core/Services/BitwardenFileUploadService.cs @@ -2,6 +2,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; +using System.Threading; using System.Threading.Tasks; using Bit.Core.Models.Domain; @@ -9,21 +10,21 @@ namespace Bit.Core.Services { public class BitwardenFileUploadService { + private readonly ApiService _apiService; + public BitwardenFileUploadService(ApiService apiService) { _apiService = apiService; } - private readonly ApiService _apiService; - - public async Task Upload(string encryptedFileName, EncByteArray encryptedFileData, Func apiCall) + public async Task Upload(string encryptedFileName, EncByteArray encryptedFileData, Func apiCall, CancellationToken cancellationToken) { var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}") { { new ByteArrayContent(encryptedFileData.Buffer) { Headers = { ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet) } }, "data", encryptedFileName } }; - await apiCall(fd); + await apiCall(fd, cancellationToken); } } } diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index e289a1ae6..3a6e53405 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; @@ -555,13 +556,15 @@ namespace Bit.Core.Services await UpsertAsync(data); } - public async Task SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data) + public async Task SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data, CancellationToken cancellationToken) { 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); + cancellationToken.ThrowIfCancellationRequested(); + CipherResponse response; try { @@ -572,9 +575,9 @@ namespace Bit.Core.Services FileSize = encFileData.Buffer.Length, }; - var uploadDataResponse = await _apiService.PostCipherAttachmentAsync(cipher.Id, request); + var uploadDataResponse = await _apiService.PostCipherAttachmentAsync(cipher.Id, request, cancellationToken); 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) { diff --git a/src/Core/Services/FileUploadService.cs b/src/Core/Services/FileUploadService.cs index fbc04acd7..2c46e6242 100644 --- a/src/Core/Services/FileUploadService.cs +++ b/src/Core/Services/FileUploadService.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; @@ -21,7 +22,7 @@ namespace Bit.Core.Services private readonly ApiService _apiService; public async Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, - EncString encryptedFileName, EncByteArray encryptedFileData) + EncString encryptedFileName, EncByteArray encryptedFileData, CancellationToken cancellationToken) { try { @@ -29,15 +30,15 @@ namespace Bit.Core.Services { case FileUploadType.Direct: 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; case FileUploadType.Azure: - Func> renewalCallback = async () => + Func> 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; }; - await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback); + await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback, cancellationToken); break; default: throw new Exception($"Unkown file upload type: {uploadData.FileUploadType}"); @@ -58,16 +59,16 @@ namespace Bit.Core.Services { case FileUploadType.Direct: 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; case FileUploadType.Azure: - Func> renewalCallback = async () => + Func> renewalCallback = async ct => { var response = await _apiService.RenewFileUploadUrlAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id); return response.Url; }; - await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback); + await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback, default); break; default: throw new Exception("Unknown file upload type");