mirror of
https://github.com/bitwarden/mobile
synced 2025-12-26 21:23:46 +00:00
* 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>
266 lines
8.6 KiB
C#
266 lines
8.6 KiB
C#
using Bit.Core.Abstractions;
|
|
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 Bit.Core.Utilities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Bit.Core.Services
|
|
{
|
|
public class FolderService : IFolderService
|
|
{
|
|
private const char NestingDelimiter = '/';
|
|
|
|
private List<FolderView> _decryptedFolderCache;
|
|
private readonly ICryptoService _cryptoService;
|
|
private readonly IStateService _stateService;
|
|
private readonly IApiService _apiService;
|
|
private readonly II18nService _i18nService;
|
|
private readonly ICipherService _cipherService;
|
|
|
|
public FolderService(
|
|
ICryptoService cryptoService,
|
|
IStateService stateService,
|
|
IApiService apiService,
|
|
II18nService i18nService,
|
|
ICipherService cipherService)
|
|
{
|
|
_cryptoService = cryptoService;
|
|
_stateService = stateService;
|
|
_apiService = apiService;
|
|
_i18nService = i18nService;
|
|
_cipherService = cipherService;
|
|
}
|
|
|
|
public void ClearCache()
|
|
{
|
|
_decryptedFolderCache = null;
|
|
}
|
|
|
|
public async Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null)
|
|
{
|
|
var folder = new Folder
|
|
{
|
|
Id = model.Id,
|
|
Name = await _cryptoService.EncryptAsync(model.Name, key)
|
|
};
|
|
return folder;
|
|
}
|
|
|
|
public async Task<Folder> GetAsync(string id)
|
|
{
|
|
var folders = await _stateService.GetEncryptedFoldersAsync();
|
|
if (!folders?.ContainsKey(id) ?? true)
|
|
{
|
|
return null;
|
|
}
|
|
return new Folder(folders[id]);
|
|
}
|
|
|
|
public async Task<List<Folder>> GetAllAsync()
|
|
{
|
|
var folders = await _stateService.GetEncryptedFoldersAsync();
|
|
var response = folders?.Select(f => new Folder(f.Value));
|
|
return response?.ToList() ?? new List<Folder>();
|
|
}
|
|
|
|
// TODO: sequentialize?
|
|
public async Task<List<FolderView>> GetAllDecryptedAsync()
|
|
{
|
|
if (_decryptedFolderCache != null)
|
|
{
|
|
return _decryptedFolderCache;
|
|
}
|
|
var hasKey = await _cryptoService.HasKeyAsync();
|
|
if (!hasKey)
|
|
{
|
|
throw new Exception("No key.");
|
|
}
|
|
var decFolders = new List<FolderView>();
|
|
async Task decryptAndAddFolderAsync(Folder folder)
|
|
{
|
|
var f = await folder.DecryptAsync();
|
|
decFolders.Add(f);
|
|
}
|
|
var tasks = new List<Task>();
|
|
var folders = await GetAllAsync();
|
|
foreach (var folder in folders)
|
|
{
|
|
tasks.Add(decryptAndAddFolderAsync(folder));
|
|
}
|
|
await Task.WhenAll(tasks);
|
|
decFolders = decFolders.OrderBy(f => f, new FolderLocaleComparer(_i18nService)).ToList();
|
|
|
|
var noneFolder = new FolderView
|
|
{
|
|
Name = _i18nService.T("FolderNone")
|
|
};
|
|
decFolders.Add(noneFolder);
|
|
|
|
_decryptedFolderCache = decFolders;
|
|
return _decryptedFolderCache;
|
|
}
|
|
|
|
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync()
|
|
{
|
|
var folders = await GetAllDecryptedAsync();
|
|
var nodes = new List<TreeNode<FolderView>>();
|
|
foreach (var f in folders)
|
|
{
|
|
var folderCopy = new FolderView
|
|
{
|
|
Id = f.Id,
|
|
RevisionDate = f.RevisionDate
|
|
};
|
|
var parts = f.Name != null ?
|
|
Regex.Replace(f.Name, "^\\/+|\\/+$", string.Empty).Split(NestingDelimiter) : new string[] { };
|
|
CoreHelpers.NestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
public async Task<TreeNode<FolderView>> GetNestedAsync(string id)
|
|
{
|
|
var folders = await GetAllNestedAsync();
|
|
return CoreHelpers.GetTreeNodeObject(folders, id);
|
|
}
|
|
|
|
public async Task SaveWithServerAsync(Folder folder)
|
|
{
|
|
var request = new FolderRequest(folder);
|
|
FolderResponse response;
|
|
if (folder.Id == null)
|
|
{
|
|
response = await _apiService.PostFolderAsync(request);
|
|
folder.Id = response.Id;
|
|
}
|
|
else
|
|
{
|
|
response = await _apiService.PutFolderAsync(folder.Id, request);
|
|
}
|
|
var userId = await _stateService.GetActiveUserIdAsync();
|
|
var data = new FolderData(response, userId);
|
|
await UpsertAsync(data);
|
|
}
|
|
|
|
public async Task UpsertAsync(FolderData folder)
|
|
{
|
|
var folders = await _stateService.GetEncryptedFoldersAsync();
|
|
if (folders == null)
|
|
{
|
|
folders = new Dictionary<string, FolderData>();
|
|
}
|
|
if (!folders.ContainsKey(folder.Id))
|
|
{
|
|
folders.Add(folder.Id, null);
|
|
}
|
|
folders[folder.Id] = folder;
|
|
await _stateService.SetEncryptedFoldersAsync(folders);
|
|
_decryptedFolderCache = null;
|
|
}
|
|
|
|
public async Task UpsertAsync(List<FolderData> folder)
|
|
{
|
|
var folders = await _stateService.GetEncryptedFoldersAsync();
|
|
if (folders == null)
|
|
{
|
|
folders = new Dictionary<string, FolderData>();
|
|
}
|
|
foreach (var f in folder)
|
|
{
|
|
if (!folders.ContainsKey(f.Id))
|
|
{
|
|
folders.Add(f.Id, null);
|
|
}
|
|
folders[f.Id] = f;
|
|
}
|
|
await _stateService.SetEncryptedFoldersAsync(folders);
|
|
_decryptedFolderCache = null;
|
|
}
|
|
|
|
public async Task ReplaceAsync(Dictionary<string, FolderData> folders)
|
|
{
|
|
await _stateService.SetEncryptedFoldersAsync(folders);
|
|
_decryptedFolderCache = null;
|
|
}
|
|
|
|
public async Task ClearAsync(string userId)
|
|
{
|
|
await _stateService.SetEncryptedFoldersAsync(null, userId);
|
|
_decryptedFolderCache = null;
|
|
}
|
|
|
|
public async Task DeleteAsync(string id)
|
|
{
|
|
var folders = await _stateService.GetEncryptedFoldersAsync();
|
|
if (folders == null || !folders.ContainsKey(id))
|
|
{
|
|
return;
|
|
}
|
|
folders.Remove(id);
|
|
await _stateService.SetEncryptedFoldersAsync(folders);
|
|
_decryptedFolderCache = null;
|
|
|
|
// Items in a deleted folder are re-assigned to "No Folder"
|
|
var ciphers = await _stateService.GetEncryptedCiphersAsync();
|
|
if (ciphers != null)
|
|
{
|
|
var updates = new List<CipherData>();
|
|
foreach (var c in ciphers)
|
|
{
|
|
if (c.Value.FolderId == id)
|
|
{
|
|
c.Value.FolderId = null;
|
|
updates.Add(c.Value);
|
|
}
|
|
}
|
|
if (updates.Any())
|
|
{
|
|
await _cipherService.UpsertAsync(updates);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task DeleteWithServerAsync(string id)
|
|
{
|
|
await _apiService.DeleteFolderAsync(id);
|
|
await DeleteAsync(id);
|
|
}
|
|
|
|
private class FolderLocaleComparer : IComparer<FolderView>
|
|
{
|
|
private readonly II18nService _i18nService;
|
|
|
|
public FolderLocaleComparer(II18nService i18nService)
|
|
{
|
|
_i18nService = i18nService;
|
|
}
|
|
|
|
public int Compare(FolderView a, FolderView 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);
|
|
}
|
|
}
|
|
}
|
|
}
|