mirror of
https://github.com/bitwarden/mobile.git
synced 2024-10-01 04:27:39 +02:00
2e8824ce05
* Account Switching (#1720) * Account switching * WIP * wip * wip * updates to send test logic * fixed Send tests * fixes for theme handling on account switching and re-adding existing account * switch fixes * fixes * fixes * cleanup * vault timeout fixes * account list status enhancements * logout fixes and token handling improvements * merge latest (#1727) * remove duplicate dependency * fix for initial login token storage paradox (#1730) * Fix avatar color update toolbar item issue on iOS for account switching (#1735) * Updated account switching menu UI (#1733) * updated account switching menu UI * additional changes * add key suffix to constant * GetFirstLetters method tweaks * Fix crash on account switching when logging out when having more than user at a time (#1740) * single account migration to multi-account on app update (#1741) * Account Switching Tap to dismiss (#1743) * Added tap to dismiss on the Account switching overlay and improved a bit the code * Fix account switching overlay background transparent on the proper place * Fixed transparent background and the shadow on the account switching overlay * Fix iOS top space on Account switching list overlay after modal (#1746) * Fix top space added to Account switching list overlay after closing modal * Fix top space added to Account switching list overlay after closing modal on lock, login and home views just in case we add modals in the future there as well * Usability: dismiss account list on certain events (#1748) * dismiss account list on certain events * use new FireAndForget method for back button logic * Create and use Account Switching overlay control (#1753) * Added Account switching overlay control and its own ViewModel and refactored accordingly * Fix account switching Accounts list binding update * Implemented dismiss account switching overlay when changing tabs and when selecting the same tab. Also updated the deprecated listener on CustomTabbedRenderer on Android (#1755) * Overriden Equals on AvatarImageSource so it doesn't get set multiple times when it's the same image thus producing blinking on tab chaged (#1756) * Usability improvements for logout on vault timeout (#1781) * accountswitching fixes (#1784) * Fix for invalid PIN lock state when switching accounts (#1792) * fix for pin lock flow * named tuple values and updated async * clear send service cache on account switch (#1796) * Global theme and account removal (#1793) * Global theme and account removal * remove redundant call to hide account list overlay * cleanup and additional tweaks * add try/catch to remove account dialog flow Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
298 lines
11 KiB
C#
298 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Threading.Tasks;
|
|
using Bit.Core.Abstractions;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Models.Data;
|
|
using Bit.Core.Models.Domain;
|
|
using Bit.Core.Models.Request;
|
|
using Bit.Core.Models.Response;
|
|
using Bit.Core.Models.View;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace Bit.Core.Services
|
|
{
|
|
public class SendService : ISendService
|
|
{
|
|
private List<SendView> _decryptedSendsCache;
|
|
private readonly ICryptoService _cryptoService;
|
|
private readonly IStateService _stateService;
|
|
private readonly IApiService _apiService;
|
|
private readonly II18nService _i18nService;
|
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
|
private Task<List<SendView>> _getAllDecryptedTask;
|
|
private readonly IFileUploadService _fileUploadService;
|
|
|
|
public SendService(
|
|
ICryptoService cryptoService,
|
|
IStateService stateService,
|
|
IApiService apiService,
|
|
IFileUploadService fileUploadService,
|
|
II18nService i18nService,
|
|
ICryptoFunctionService cryptoFunctionService)
|
|
{
|
|
_cryptoService = cryptoService;
|
|
_stateService = stateService;
|
|
_apiService = apiService;
|
|
_fileUploadService = fileUploadService;
|
|
_i18nService = i18nService;
|
|
_cryptoFunctionService = cryptoFunctionService;
|
|
}
|
|
|
|
public async Task ClearAsync(string userId)
|
|
{
|
|
await _stateService.SetEncryptedSendsAsync(null, userId);
|
|
ClearCache();
|
|
}
|
|
|
|
public void ClearCache() => _decryptedSendsCache = null;
|
|
|
|
public async Task DeleteAsync(params string[] ids)
|
|
{
|
|
var sends = await _stateService.GetEncryptedSendsAsync();
|
|
|
|
if (sends == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var id in ids)
|
|
{
|
|
sends.Remove(id);
|
|
}
|
|
|
|
await _stateService.SetEncryptedSendsAsync(sends);
|
|
ClearCache();
|
|
}
|
|
|
|
public async Task DeleteWithServerAsync(string id)
|
|
{
|
|
await _apiService.DeleteSendAsync(id);
|
|
await DeleteAsync(id);
|
|
}
|
|
|
|
public async Task<(Send send, EncByteArray encryptedFileData)> EncryptAsync(SendView model, byte[] fileData,
|
|
string password, SymmetricCryptoKey key = null)
|
|
{
|
|
if (model.Key == null)
|
|
{
|
|
model.Key = _cryptoFunctionService.RandomBytes(16);
|
|
model.CryptoKey = await _cryptoService.MakeSendKeyAsync(model.Key);
|
|
}
|
|
|
|
var send = new Send
|
|
{
|
|
Id = model.Id,
|
|
Type = model.Type,
|
|
Disabled = model.Disabled,
|
|
DeletionDate = model.DeletionDate,
|
|
ExpirationDate = model.ExpirationDate,
|
|
MaxAccessCount = model.MaxAccessCount,
|
|
Key = await _cryptoService.EncryptAsync(model.Key, key),
|
|
Name = await _cryptoService.EncryptAsync(model.Name, model.CryptoKey),
|
|
Notes = await _cryptoService.EncryptAsync(model.Notes, model.CryptoKey),
|
|
HideEmail = model.HideEmail
|
|
};
|
|
EncByteArray encryptedFileData = null;
|
|
|
|
if (password != null)
|
|
{
|
|
var passwordHash = await _cryptoFunctionService.Pbkdf2Async(password, model.Key,
|
|
CryptoHashAlgorithm.Sha256, 100000);
|
|
send.Password = Convert.ToBase64String(passwordHash);
|
|
}
|
|
|
|
switch (send.Type)
|
|
{
|
|
case SendType.Text:
|
|
send.Text = new SendText
|
|
{
|
|
Text = await _cryptoService.EncryptAsync(model.Text.Text, model.CryptoKey),
|
|
Hidden = model.Text.Hidden
|
|
};
|
|
break;
|
|
case SendType.File:
|
|
send.File = new SendFile();
|
|
if (fileData != null)
|
|
{
|
|
send.File.FileName = await _cryptoService.EncryptAsync(model.File.FileName, model.CryptoKey);
|
|
encryptedFileData = await _cryptoService.EncryptToBytesAsync(fileData, model.CryptoKey);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (send, encryptedFileData);
|
|
}
|
|
|
|
public async Task<List<Send>> GetAllAsync()
|
|
{
|
|
var sends = await _stateService.GetEncryptedSendsAsync();
|
|
return sends?.Select(kvp => new Send(kvp.Value)).ToList() ?? new List<Send>();
|
|
}
|
|
|
|
public async Task<List<SendView>> GetAllDecryptedAsync()
|
|
{
|
|
if (_decryptedSendsCache != null)
|
|
{
|
|
return _decryptedSendsCache;
|
|
}
|
|
|
|
var hasKey = await _cryptoService.HasKeyAsync();
|
|
if (!hasKey)
|
|
{
|
|
throw new Exception("No Key.");
|
|
}
|
|
|
|
if (_getAllDecryptedTask != null && !_getAllDecryptedTask.IsCompleted && !_getAllDecryptedTask.IsFaulted)
|
|
{
|
|
return await _getAllDecryptedTask;
|
|
}
|
|
|
|
async Task<List<SendView>> doTask()
|
|
{
|
|
var decSends = new List<SendView>();
|
|
|
|
async Task decryptAndAddSendAsync(Send send) => decSends.Add(await send.DecryptAsync());
|
|
await Task.WhenAll((await GetAllAsync()).Select(s => decryptAndAddSendAsync(s)));
|
|
|
|
decSends = decSends.OrderBy(s => s, new SendLocaleComparer(_i18nService)).ToList();
|
|
_decryptedSendsCache = decSends;
|
|
return _decryptedSendsCache;
|
|
}
|
|
|
|
_getAllDecryptedTask = doTask();
|
|
return await _getAllDecryptedTask;
|
|
}
|
|
|
|
public async Task<Send> GetAsync(string id)
|
|
{
|
|
var sends = await _stateService.GetEncryptedSendsAsync();
|
|
|
|
if (sends == null || !sends.ContainsKey(id))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new Send(sends[id]);
|
|
}
|
|
|
|
public async Task ReplaceAsync(Dictionary<string, SendData> sends)
|
|
{
|
|
await _stateService.SetEncryptedSendsAsync(sends);
|
|
_decryptedSendsCache = null;
|
|
}
|
|
|
|
public async Task<string> SaveWithServerAsync(Send send, EncByteArray encryptedFileData)
|
|
{
|
|
var request = new SendRequest(send, encryptedFileData?.Buffer?.LongLength);
|
|
SendResponse response = default;
|
|
if (send.Id == null)
|
|
{
|
|
switch (send.Type)
|
|
{
|
|
case SendType.Text:
|
|
response = await _apiService.PostSendAsync(request);
|
|
break;
|
|
case SendType.File:
|
|
try{
|
|
var uploadDataResponse = await _apiService.PostFileTypeSendAsync(request);
|
|
response = uploadDataResponse.SendResponse;
|
|
|
|
await _fileUploadService.UploadSendFileAsync(uploadDataResponse, send.File.FileName, encryptedFileData);
|
|
}
|
|
catch (ApiException e) when (e.Error.StatusCode == HttpStatusCode.NotFound)
|
|
{
|
|
response = await LegacyServerSendFileUpload(request, send, encryptedFileData);
|
|
}
|
|
catch
|
|
{
|
|
if (response != default){
|
|
await _apiService.DeleteSendAsync(response.Id);
|
|
}
|
|
throw;
|
|
}
|
|
break;
|
|
default:
|
|
throw new NotImplementedException($"Cannot save unknown Send type {send.Type}");
|
|
}
|
|
send.Id = response.Id;
|
|
}
|
|
else
|
|
{
|
|
response = await _apiService.PutSendAsync(send.Id, request);
|
|
}
|
|
|
|
var userId = await _stateService.GetActiveUserIdAsync();
|
|
await UpsertAsync(new SendData(response, userId));
|
|
return response.Id;
|
|
}
|
|
|
|
[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<SendResponse> LegacyServerSendFileUpload(SendRequest request, Send send, EncByteArray encryptedFileData) {
|
|
var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}")
|
|
{
|
|
{ new StringContent(JsonConvert.SerializeObject(request)), "model" },
|
|
{ new ByteArrayContent(encryptedFileData.Buffer), "data", send.File.FileName.EncryptedString }
|
|
};
|
|
|
|
return await _apiService.PostSendFileAsync(fd);
|
|
}
|
|
|
|
public async Task UpsertAsync(params SendData[] sends)
|
|
{
|
|
var knownSends = await _stateService.GetEncryptedSendsAsync() ??
|
|
new Dictionary<string, SendData>();
|
|
|
|
foreach (var send in sends)
|
|
{
|
|
knownSends[send.Id] = send;
|
|
}
|
|
|
|
await _stateService.SetEncryptedSendsAsync(knownSends);
|
|
_decryptedSendsCache = null;
|
|
}
|
|
|
|
public async Task RemovePasswordWithServerAsync(string id)
|
|
{
|
|
var response = await _apiService.PutSendRemovePasswordAsync(id);
|
|
var userId = await _stateService.GetActiveUserIdAsync();
|
|
await UpsertAsync(new SendData(response, userId));
|
|
}
|
|
|
|
private class SendLocaleComparer : IComparer<SendView>
|
|
{
|
|
private readonly II18nService _i18nService;
|
|
|
|
public SendLocaleComparer(II18nService i18nService)
|
|
{
|
|
_i18nService = i18nService;
|
|
}
|
|
|
|
public int Compare(SendView a, SendView b)
|
|
{
|
|
var aName = a?.Name;
|
|
var bName = b?.Name;
|
|
if (aName == null && bName != null)
|
|
{
|
|
return -1;
|
|
}
|
|
if (aName != null && bName == null)
|
|
{
|
|
return 1;
|
|
}
|
|
if (aName == null && bName == null)
|
|
{
|
|
return 0;
|
|
}
|
|
return _i18nService.StringComparer.Compare(aName, bName);
|
|
}
|
|
}
|
|
}
|
|
}
|