From 448758a697b3287bef3a84cdde14d4f934ded250 Mon Sep 17 00:00:00 2001 From: mp-bw <59324545+mp-bw@users.noreply.github.com> Date: Tue, 14 Jun 2022 13:02:03 -0400 Subject: [PATCH] Additional logic around filter display (#1951) --- src/App/Pages/Vault/CiphersPageViewModel.cs | 82 ++---------- .../GroupingsPage/GroupingsPageViewModel.cs | 92 ++------------ src/App/Pages/VaultFilterViewModel.cs | 118 ++++++++++++++++++ src/Core/Services/PolicyService.cs | 6 + 4 files changed, 144 insertions(+), 154 deletions(-) create mode 100644 src/App/Pages/VaultFilterViewModel.cs diff --git a/src/App/Pages/Vault/CiphersPageViewModel.cs b/src/App/Pages/Vault/CiphersPageViewModel.cs index bc318de2e..1a4be4164 100644 --- a/src/App/Pages/Vault/CiphersPageViewModel.cs +++ b/src/App/Pages/Vault/CiphersPageViewModel.cs @@ -3,14 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Resources; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; using Xamarin.CommunityToolkit.ObjectModel; @@ -18,7 +15,7 @@ using Xamarin.Forms; namespace Bit.App.Pages { - public class CiphersPageViewModel : BaseViewModel + public class CiphersPageViewModel : VaultFilterViewModel { private readonly IPlatformUtilsService _platformUtilsService; private readonly ICipherService _cipherService; @@ -31,12 +28,9 @@ namespace Bit.App.Pages private CancellationTokenSource _searchCancellationTokenSource; private readonly ILogger _logger; - private bool _showVaultFilter; - private string _vaultFilterSelection; private bool _showNoData; private bool _showList; private bool _websiteIconsEnabled; - private List _organizations; public CiphersPageViewModel() { @@ -52,18 +46,19 @@ namespace Bit.App.Pages Ciphers = new ExtendedObservableCollection(); CipherOptionsCommand = new Command(CipherOptionsAsync); - VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, - onException: ex => _logger.Exception(ex), - allowsMultipleExecutions: false); } public Command CipherOptionsCommand { get; set; } - public ICommand VaultFilterCommand { get; } public ExtendedObservableCollection Ciphers { get; set; } public Func Filter { get; set; } public string AutofillUrl { get; set; } public bool Deleted { get; set; } + protected override ICipherService cipherService => _cipherService; + protected override IPolicyService policyService => _policyService; + protected override IOrganizationService organizationService => _organizationService; + protected override ILogger logger => _logger; + public bool ShowNoData { get => _showNoData; @@ -81,23 +76,6 @@ namespace Bit.App.Pages nameof(ShowSearchDirection) }); } - public bool ShowVaultFilter - { - get => _showVaultFilter; - set => SetProperty(ref _showVaultFilter, value); - } - public string VaultFilterDescription - { - get - { - if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults) - { - return string.Format(AppResources.VaultFilterDescription, AppResources.All); - } - return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection); - } - set => SetProperty(ref _vaultFilterSelection, value); - } public bool ShowSearchDirection => !ShowList && !ShowNoData; @@ -109,12 +87,7 @@ namespace Bit.App.Pages public async Task InitAsync() { - _organizations = await _organizationService.GetAllAsync(); - ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync(); - if (ShowVaultFilter && _vaultFilterSelection == null) - { - _vaultFilterSelection = AppResources.AllVaults; - } + await InitVaultFilterAsync(); WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); PerformSearchIfPopulated(); } @@ -237,50 +210,11 @@ namespace Bit.App.Pages } } - private async Task VaultFilterOptionsAsync() + protected override async Task OnVaultFilterSelectedAsync() { - var options = new List { AppResources.AllVaults, AppResources.MyVault }; - if (_organizations.Any()) - { - options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name)); - } - var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null, - options.ToArray()); - if (selection == null || selection == AppResources.Cancel || - (_vaultFilterSelection == null && selection == AppResources.AllVaults) || - (_vaultFilterSelection != null && _vaultFilterSelection == selection)) - { - return; - } - VaultFilterDescription = selection; PerformSearchIfPopulated(); } - private async Task> GetAllCiphersAsync() - { - var decCiphers = await _cipherService.GetAllDecryptedAsync(); - if (IsVaultFilterMyVault) - { - return decCiphers.Where(c => c.OrganizationId == null).ToList(); - } - if (IsVaultFilterOrgVault) - { - var orgId = GetVaultFilterOrgId(); - return decCiphers.Where(c => c.OrganizationId == orgId).ToList(); - } - return decCiphers; - } - - private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault; - - private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults && - _vaultFilterSelection != AppResources.MyVault; - - private string GetVaultFilterOrgId() - { - return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id; - } - private async void CipherOptionsAsync(CipherView cipher) { if ((Page as BaseContentPage).DoOnce()) diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index ac3200aca..9cc52dc8a 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Controls; using Bit.App.Resources; @@ -17,7 +16,7 @@ using Xamarin.Forms; namespace Bit.App.Pages { - public class GroupingsPageViewModel : BaseViewModel + public class GroupingsPageViewModel : VaultFilterViewModel { private const int NoFolderListSize = 100; @@ -30,10 +29,7 @@ namespace Bit.App.Pages private bool _showList; private bool _websiteIconsEnabled; private bool _syncRefreshing; - private bool _showVaultFilter; - private string _vaultFilterSelection; private string _noDataText; - private List _organizations; private List _allCiphers; private Dictionary _folderCounts = new Dictionary(); private Dictionary _collectionCounts = new Dictionary(); @@ -78,9 +74,6 @@ namespace Bit.App.Pages await LoadAsync(); }); CipherOptionsCommand = new Command(CipherOptionsAsync); - VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, - onException: ex => _logger.Exception(ex), - allowsMultipleExecutions: false); AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) { @@ -108,6 +101,11 @@ namespace Bit.App.Pages public List Collections { get; set; } public List> NestedCollections { get; set; } + protected override ICipherService cipherService => _cipherService; + protected override IPolicyService policyService => _policyService; + protected override IOrganizationService organizationService => _organizationService; + protected override ILogger logger => _logger; + public bool Refreshing { get => _refreshing; @@ -153,30 +151,12 @@ namespace Bit.App.Pages get => _websiteIconsEnabled; set => SetProperty(ref _websiteIconsEnabled, value); } - public bool ShowVaultFilter - { - get => _showVaultFilter; - set => SetProperty(ref _showVaultFilter, value); - } - public string VaultFilterDescription - { - get - { - if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults) - { - return string.Format(AppResources.VaultFilterDescription, AppResources.All); - } - return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection); - } - set => SetProperty(ref _vaultFilterSelection, value); - } public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; } public ObservableRangeCollection GroupedItems { get; set; } public Command RefreshCommand { get; set; } public Command CipherOptionsCommand { get; set; } - public ICommand VaultFilterCommand { get; } public bool LoadedOnce { get; set; } public async Task LoadAsync() @@ -201,14 +181,9 @@ namespace Bit.App.Pages return; } - _organizations = await _organizationService.GetAllAsync(); if (MainPage) { - ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync(); - if (ShowVaultFilter && _vaultFilterSelection == null) - { - _vaultFilterSelection = AppResources.AllVaults; - } + await InitVaultFilterAsync(); PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault; } @@ -394,30 +369,11 @@ namespace Bit.App.Pages SyncRefreshing = false; } - public async Task VaultFilterOptionsAsync() + protected override async Task OnVaultFilterSelectedAsync() { - var options = new List { AppResources.AllVaults, AppResources.MyVault }; - if (_organizations.Any()) - { - options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name)); - } - var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null, - options.ToArray()); - if (selection == null || selection == AppResources.Cancel || - (_vaultFilterSelection == null && selection == AppResources.AllVaults) || - (_vaultFilterSelection != null && _vaultFilterSelection == selection)) - { - return; - } - VaultFilterDescription = selection; await LoadAsync(); } - public string GetVaultFilterOrgId() - { - return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id; - } - public async Task SelectCipherAsync(CipherView cipher) { var page = new ViewPage(cipher.Id); @@ -501,8 +457,8 @@ namespace Bit.App.Pages private async Task LoadDataAsync() { - var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync(); NoDataText = AppResources.NoItems; + _allCiphers = await GetAllCiphersAsync(); HasCiphers = _allCiphers.Any(); FavoriteCiphers?.Clear(); NoFolderCiphers?.Clear(); @@ -516,7 +472,7 @@ namespace Bit.App.Pages if (MainPage) { - await FillFoldersAndCollectionsAsync(orgId); + await FillFoldersAndCollectionsAsync(); NestedFolders = await _folderService.GetAllNestedAsync(Folders); HasFolders = NestedFolders.Any(f => f.Node?.Id != null); NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null; @@ -640,28 +596,9 @@ namespace Bit.App.Pages } } - private async Task FillAllCiphersAndGetOrgIdIfNeededAsync() - { - string orgId = null; - var decCiphers = await _cipherService.GetAllDecryptedAsync(); - if (IsVaultFilterMyVault) - { - _allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList(); - } - else if (IsVaultFilterOrgVault) - { - orgId = GetVaultFilterOrgId(); - _allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList(); - } - else - { - _allCiphers = decCiphers; - } - return orgId; - } - - private async Task FillFoldersAndCollectionsAsync(string orgId) + private async Task FillFoldersAndCollectionsAsync() { + var orgId = GetVaultFilterOrgId(); var decFolders = await _folderService.GetAllDecryptedAsync(); var decCollections = await _collectionService.GetAllDecryptedAsync(); if (IsVaultFilterMyVault) @@ -681,11 +618,6 @@ namespace Bit.App.Pages } } - private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault; - - private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults && - _vaultFilterSelection != AppResources.MyVault; - private List BuildFolders(List decFolders) { var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList(); diff --git a/src/App/Pages/VaultFilterViewModel.cs b/src/App/Pages/VaultFilterViewModel.cs new file mode 100644 index 000000000..0a801a97c --- /dev/null +++ b/src/App/Pages/VaultFilterViewModel.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Models.Domain; +using Bit.Core.Models.View; +using Xamarin.CommunityToolkit.ObjectModel; + +namespace Bit.App.Pages +{ + public abstract class VaultFilterViewModel : BaseViewModel + { + protected abstract ICipherService cipherService { get; } + protected abstract IPolicyService policyService { get; } + protected abstract IOrganizationService organizationService { get; } + protected abstract ILogger logger { get; } + + protected bool _showVaultFilter; + protected bool _hideMyVaultFilterOption; + protected string _vaultFilterSelection; + protected List _organizations; + + public VaultFilterViewModel() + { + VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, + onException: ex => logger.Exception(ex), + allowsMultipleExecutions: false); + } + + public ICommand VaultFilterCommand { get; set; } + + public bool ShowVaultFilter + { + get => _showVaultFilter; + set => SetProperty(ref _showVaultFilter, value); + } + + public string VaultFilterDescription + { + get + { + if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults) + { + return string.Format(AppResources.VaultFilterDescription, AppResources.All); + } + return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection); + } + set => SetProperty(ref _vaultFilterSelection, value); + } + + public string GetVaultFilterOrgId() + { + return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id; + } + + protected bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault; + + protected bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults && + _vaultFilterSelection != AppResources.MyVault; + + protected async Task InitVaultFilterAsync() + { + _organizations = await organizationService.GetAllAsync(); + ShowVaultFilter = await policyService.ShouldShowVaultFilterAsync(); + if (ShowVaultFilter) + { + _hideMyVaultFilterOption = await policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership); + if (_vaultFilterSelection == null) + { + _vaultFilterSelection = AppResources.AllVaults; + } + } + } + + protected async Task> GetAllCiphersAsync() + { + var decCiphers = await cipherService.GetAllDecryptedAsync(); + if (IsVaultFilterMyVault) + { + return decCiphers.Where(c => c.OrganizationId == null).ToList(); + } + if (IsVaultFilterOrgVault) + { + var orgId = GetVaultFilterOrgId(); + return decCiphers.Where(c => c.OrganizationId == orgId).ToList(); + } + return decCiphers; + } + + protected async Task VaultFilterOptionsAsync() + { + var options = new List { AppResources.AllVaults }; + if (!_hideMyVaultFilterOption) + { + options.Add(AppResources.MyVault); + } + if (_organizations.Any()) + { + options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name)); + } + var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null, + options.ToArray()); + if (selection == null || selection == AppResources.Cancel || + (_vaultFilterSelection == null && selection == AppResources.AllVaults) || + (_vaultFilterSelection != null && _vaultFilterSelection == selection)) + { + return; + } + VaultFilterDescription = selection; + await OnVaultFilterSelectedAsync(); + } + + protected abstract Task OnVaultFilterSelectedAsync(); + } +} diff --git a/src/Core/Services/PolicyService.cs b/src/Core/Services/PolicyService.cs index c0984d635..c7b374643 100644 --- a/src/Core/Services/PolicyService.cs +++ b/src/Core/Services/PolicyService.cs @@ -249,6 +249,12 @@ namespace Bit.Core.Services public async Task ShouldShowVaultFilterAsync() { + var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership); + var singleOrgPolicyApplies = await PolicyAppliesToUser(PolicyType.OnlyOrg); + if (personalOwnershipPolicyApplies && singleOrgPolicyApplies) + { + return false; + } var organizations = await _organizationService.GetAllAsync(); return organizations?.Any() ?? false; }