1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-26 21:23:46 +00:00
Files
mobile/src/Core/Services/FolderService.cs
Matt Portune 2e8824ce05 Account Switching (#1807)
* 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>
2022-02-23 12:40:17 -05:00

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);
}
}
}
}