2019-05-21 04:18:34 +02:00
|
|
|
|
using System;
|
2019-05-07 04:35:42 +02:00
|
|
|
|
using System.Collections.Generic;
|
2019-05-21 04:18:34 +02:00
|
|
|
|
using System.Linq;
|
2019-05-07 04:35:42 +02:00
|
|
|
|
using System.Threading;
|
2019-05-03 03:25:26 +02:00
|
|
|
|
using System.Threading.Tasks;
|
2019-05-21 04:18:34 +02:00
|
|
|
|
using Bit.App.Abstractions;
|
|
|
|
|
using Bit.App.Resources;
|
2019-05-03 03:25:26 +02:00
|
|
|
|
using Bit.Core.Abstractions;
|
2019-05-21 04:18:34 +02:00
|
|
|
|
using Bit.Core.Enums;
|
|
|
|
|
using Bit.Core.Exceptions;
|
2019-05-03 03:25:26 +02:00
|
|
|
|
using Bit.Core.Models.View;
|
|
|
|
|
using Bit.Core.Utilities;
|
|
|
|
|
using Xamarin.Forms;
|
|
|
|
|
|
|
|
|
|
namespace Bit.App.Pages
|
|
|
|
|
{
|
2022-06-14 19:02:03 +02:00
|
|
|
|
public class CiphersPageViewModel : VaultFilterViewModel
|
2019-05-03 03:25:26 +02:00
|
|
|
|
{
|
|
|
|
|
private readonly IPlatformUtilsService _platformUtilsService;
|
|
|
|
|
private readonly ICipherService _cipherService;
|
2019-05-07 04:35:42 +02:00
|
|
|
|
private readonly ISearchService _searchService;
|
2019-05-21 04:18:34 +02:00
|
|
|
|
private readonly IDeviceActionService _deviceActionService;
|
2022-10-11 23:19:32 +02:00
|
|
|
|
private readonly IAutofillHandler _autofillHandler;
|
2019-06-08 18:18:49 +02:00
|
|
|
|
private readonly IStateService _stateService;
|
2021-05-21 15:13:54 +02:00
|
|
|
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
2022-06-08 15:39:53 +02:00
|
|
|
|
private readonly IOrganizationService _organizationService;
|
|
|
|
|
private readonly IPolicyService _policyService;
|
2019-05-07 04:35:42 +02:00
|
|
|
|
private CancellationTokenSource _searchCancellationTokenSource;
|
2022-06-08 15:39:53 +02:00
|
|
|
|
private readonly ILogger _logger;
|
2019-05-03 03:25:26 +02:00
|
|
|
|
|
|
|
|
|
private bool _showNoData;
|
2019-05-07 04:35:42 +02:00
|
|
|
|
private bool _showList;
|
2019-06-08 18:18:49 +02:00
|
|
|
|
private bool _websiteIconsEnabled;
|
2019-05-03 03:25:26 +02:00
|
|
|
|
|
|
|
|
|
public CiphersPageViewModel()
|
|
|
|
|
{
|
|
|
|
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
|
|
|
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
2019-05-07 04:35:42 +02:00
|
|
|
|
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
2019-05-21 04:18:34 +02:00
|
|
|
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
2022-10-11 23:19:32 +02:00
|
|
|
|
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
2019-06-08 18:18:49 +02:00
|
|
|
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
2021-05-21 15:13:54 +02:00
|
|
|
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
2022-06-08 15:39:53 +02:00
|
|
|
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
|
|
|
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
|
|
|
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
2019-05-21 04:18:34 +02:00
|
|
|
|
|
2019-05-03 03:25:26 +02:00
|
|
|
|
Ciphers = new ExtendedObservableCollection<CipherView>();
|
|
|
|
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Command CipherOptionsCommand { get; set; }
|
|
|
|
|
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
2019-05-07 04:35:42 +02:00
|
|
|
|
public Func<CipherView, bool> Filter { get; set; }
|
2019-05-21 04:18:34 +02:00
|
|
|
|
public string AutofillUrl { get; set; }
|
2020-05-20 19:35:20 +02:00
|
|
|
|
public bool Deleted { get; set; }
|
2019-05-03 03:25:26 +02:00
|
|
|
|
|
2022-06-14 19:02:03 +02:00
|
|
|
|
protected override ICipherService cipherService => _cipherService;
|
|
|
|
|
protected override IPolicyService policyService => _policyService;
|
|
|
|
|
protected override IOrganizationService organizationService => _organizationService;
|
|
|
|
|
protected override ILogger logger => _logger;
|
|
|
|
|
|
2019-05-03 03:25:26 +02:00
|
|
|
|
public bool ShowNoData
|
|
|
|
|
{
|
|
|
|
|
get => _showNoData;
|
2019-05-30 18:04:31 +02:00
|
|
|
|
set => SetProperty(ref _showNoData, value, additionalPropertyNames: new string[]
|
|
|
|
|
{
|
|
|
|
|
nameof(ShowSearchDirection)
|
|
|
|
|
});
|
2019-05-03 03:25:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-07 04:35:42 +02:00
|
|
|
|
public bool ShowList
|
|
|
|
|
{
|
|
|
|
|
get => _showList;
|
2019-05-30 18:04:31 +02:00
|
|
|
|
set => SetProperty(ref _showList, value, additionalPropertyNames: new string[]
|
|
|
|
|
{
|
|
|
|
|
nameof(ShowSearchDirection)
|
|
|
|
|
});
|
2019-05-07 04:35:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 18:04:31 +02:00
|
|
|
|
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
|
|
|
|
|
2019-06-08 18:18:49 +02:00
|
|
|
|
public bool WebsiteIconsEnabled
|
|
|
|
|
{
|
|
|
|
|
get => _websiteIconsEnabled;
|
|
|
|
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task InitAsync()
|
|
|
|
|
{
|
2022-06-15 21:18:30 +02:00
|
|
|
|
await InitVaultFilterAsync(true);
|
2022-06-08 15:39:53 +02:00
|
|
|
|
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
|
|
|
|
PerformSearchIfPopulated();
|
2019-06-08 18:18:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-07 04:35:42 +02:00
|
|
|
|
public void Search(string searchText, int? timeout = null)
|
|
|
|
|
{
|
|
|
|
|
var previousCts = _searchCancellationTokenSource;
|
|
|
|
|
var cts = new CancellationTokenSource();
|
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
{
|
|
|
|
|
List<CipherView> ciphers = null;
|
|
|
|
|
var searchable = !string.IsNullOrWhiteSpace(searchText) && searchText.Length > 1;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (searchable)
|
2019-05-07 04:35:42 +02:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (timeout != null)
|
2019-05-07 04:35:42 +02:00
|
|
|
|
{
|
|
|
|
|
await Task.Delay(timeout.Value);
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (searchText != (Page as CiphersPage).SearchBar.Text)
|
2019-05-07 04:35:42 +02:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
previousCts?.Cancel();
|
|
|
|
|
}
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-06-08 15:39:53 +02:00
|
|
|
|
var vaultFilteredCiphers = await GetAllCiphersAsync();
|
2020-05-20 19:35:20 +02:00
|
|
|
|
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
2022-06-08 15:39:53 +02:00
|
|
|
|
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
|
2019-05-07 04:35:42 +02:00
|
|
|
|
cts.Token.ThrowIfCancellationRequested();
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
catch (OperationCanceledException)
|
2019-05-07 04:35:42 +02:00
|
|
|
|
{
|
2020-03-09 23:43:28 +01:00
|
|
|
|
return;
|
2019-05-07 04:35:42 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (ciphers == null)
|
2019-05-07 04:35:42 +02:00
|
|
|
|
{
|
|
|
|
|
ciphers = new List<CipherView>();
|
|
|
|
|
}
|
2020-03-09 23:43:28 +01:00
|
|
|
|
Device.BeginInvokeOnMainThread(() =>
|
|
|
|
|
{
|
|
|
|
|
Ciphers.ResetWithRange(ciphers);
|
|
|
|
|
ShowNoData = searchable && Ciphers.Count == 0;
|
|
|
|
|
ShowList = searchable && !ShowNoData;
|
|
|
|
|
});
|
2019-05-07 04:35:42 +02:00
|
|
|
|
}, cts.Token);
|
|
|
|
|
_searchCancellationTokenSource = cts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task SelectCipherAsync(CipherView cipher)
|
2019-05-03 03:25:26 +02:00
|
|
|
|
{
|
2019-05-21 04:18:34 +02:00
|
|
|
|
string selection = null;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(AutofillUrl))
|
2019-05-21 04:18:34 +02:00
|
|
|
|
{
|
|
|
|
|
var options = new List<string> { AppResources.Autofill };
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (cipher.Type == CipherType.Login &&
|
2019-06-04 04:43:52 +02:00
|
|
|
|
Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
|
2019-05-21 04:18:34 +02:00
|
|
|
|
{
|
|
|
|
|
options.Add(AppResources.AutofillAndSave);
|
|
|
|
|
}
|
|
|
|
|
options.Add(AppResources.View);
|
|
|
|
|
selection = await Page.DisplayActionSheet(AppResources.AutofillOrView, AppResources.Cancel, null,
|
|
|
|
|
options.ToArray());
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (selection == AppResources.View || string.IsNullOrWhiteSpace(AutofillUrl))
|
2019-05-21 04:18:34 +02:00
|
|
|
|
{
|
2022-07-27 18:46:56 +02:00
|
|
|
|
var page = new CipherDetailsPage(cipher.Id);
|
2019-05-21 04:18:34 +02:00
|
|
|
|
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
|
2019-05-21 04:18:34 +02:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (selection == AppResources.AutofillAndSave)
|
2019-05-21 04:18:34 +02:00
|
|
|
|
{
|
|
|
|
|
var uris = cipher.Login?.Uris?.ToList();
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (uris == null)
|
2019-05-21 04:18:34 +02:00
|
|
|
|
{
|
|
|
|
|
uris = new List<LoginUriView>();
|
|
|
|
|
}
|
|
|
|
|
uris.Add(new LoginUriView
|
|
|
|
|
{
|
|
|
|
|
Uri = AutofillUrl,
|
|
|
|
|
Match = null
|
|
|
|
|
});
|
|
|
|
|
cipher.Login.Uris = uris;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
|
|
|
|
await _cipherService.SaveWithServerAsync(await _cipherService.EncryptAsync(cipher));
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
catch (ApiException e)
|
2019-05-21 04:18:34 +02:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (e?.Error != null)
|
2019-10-22 22:37:40 +02:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
|
|
|
|
|
AppResources.AnErrorHasOccurred);
|
|
|
|
|
}
|
2019-05-21 04:18:34 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (_deviceActionService.SystemMajorVersion() < 21)
|
2019-05-21 04:18:34 +02:00
|
|
|
|
{
|
2021-05-21 15:13:54 +02:00
|
|
|
|
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
2019-05-21 04:18:34 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-10-11 23:19:32 +02:00
|
|
|
|
_autofillHandler.Autofill(cipher);
|
2019-05-21 04:18:34 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-03 03:25:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 15:39:53 +02:00
|
|
|
|
private void PerformSearchIfPopulated()
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
|
|
|
|
|
{
|
|
|
|
|
Search((Page as CiphersPage).SearchBar.Text, 200);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-14 19:02:03 +02:00
|
|
|
|
protected override async Task OnVaultFilterSelectedAsync()
|
2022-06-08 15:39:53 +02:00
|
|
|
|
{
|
|
|
|
|
PerformSearchIfPopulated();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-03 03:25:26 +02:00
|
|
|
|
private async void CipherOptionsAsync(CipherView cipher)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if ((Page as BaseContentPage).DoOnce())
|
2019-05-07 05:07:47 +02:00
|
|
|
|
{
|
2021-05-21 15:13:54 +02:00
|
|
|
|
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
2019-05-07 05:07:47 +02:00
|
|
|
|
}
|
2019-05-03 03:25:26 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|