Support for lock/logout/remove accounts from account list (#1826)
* Support for lock/logout/remove accounts via long-press * establish and set listview height before showing * undo modification
This commit is contained in:
parent
efd83d07dd
commit
79a76c4638
|
@ -84,7 +84,7 @@
|
||||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.7.0</Version>
|
<Version>1.7.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||||
<Version>122.0.0</Version>
|
<Version>122.0.0</Version>
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
|
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
|
||||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
|
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
||||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.2" />
|
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.0" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
||||||
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2291" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -73,7 +73,11 @@ namespace Bit.App
|
||||||
}
|
}
|
||||||
else if (message.Command == "locked")
|
else if (message.Command == "locked")
|
||||||
{
|
{
|
||||||
await LockedAsync(!(message.Data as bool?).GetValueOrDefault());
|
var extras = message.Data as Tuple<string, bool>;
|
||||||
|
var userId = extras?.Item1;
|
||||||
|
var userInitiated = extras?.Item2;
|
||||||
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
|
await LockedAsync(userId, userInitiated.GetValueOrDefault()));
|
||||||
}
|
}
|
||||||
else if (message.Command == "lockVault")
|
else if (message.Command == "lockVault")
|
||||||
{
|
{
|
||||||
|
@ -283,10 +287,9 @@ namespace Bit.App
|
||||||
private async Task SwitchedAccountAsync()
|
private async Task SwitchedAccountAsync()
|
||||||
{
|
{
|
||||||
await AppHelpers.OnAccountSwitchAsync();
|
await AppHelpers.OnAccountSwitchAsync();
|
||||||
var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync();
|
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
if (shouldTimeout)
|
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
||||||
{
|
{
|
||||||
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
|
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
|
||||||
}
|
}
|
||||||
|
@ -304,24 +307,20 @@ namespace Bit.App
|
||||||
var authed = await _stateService.IsAuthenticatedAsync();
|
var authed = await _stateService.IsAuthenticatedAsync();
|
||||||
if (authed)
|
if (authed)
|
||||||
{
|
{
|
||||||
var isLocked = await _vaultTimeoutService.IsLockedAsync();
|
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
|
||||||
var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync();
|
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
||||||
if (isLocked || shouldTimeout)
|
|
||||||
{
|
{
|
||||||
var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
||||||
if (vaultTimeoutAction == VaultTimeoutAction.Logout)
|
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
||||||
{
|
|
||||||
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
var email = await _stateService.GetEmailAsync();
|
||||||
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
||||||
|
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
|
||||||
var email = await _stateService.GetEmailAsync();
|
}
|
||||||
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
else if (await _vaultTimeoutService.IsLockedAsync() ||
|
||||||
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
|
await _vaultTimeoutService.ShouldLockAsync())
|
||||||
}
|
{
|
||||||
else
|
Current.MainPage = new NavigationPage(new LockPage(Options));
|
||||||
{
|
|
||||||
Current.MainPage = new NavigationPage(new LockPage(Options));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
|
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -343,7 +342,8 @@ namespace Bit.App
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
||||||
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync())
|
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
|
||||||
|
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
||||||
{
|
{
|
||||||
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
||||||
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
||||||
|
@ -430,8 +430,14 @@ namespace Bit.App
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LockedAsync(bool autoPromptBiometric)
|
private async Task LockedAsync(string userId, bool autoPromptBiometric)
|
||||||
{
|
{
|
||||||
|
if (!await _stateService.IsActiveAccount(userId))
|
||||||
|
{
|
||||||
|
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
|
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
|
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
|
||||||
|
|
|
@ -27,15 +27,17 @@
|
||||||
<ListView
|
<ListView
|
||||||
x:Name="_accountListView"
|
x:Name="_accountListView"
|
||||||
ItemsSource="{Binding BindingContext.AccountViews, Source={x:Reference _mainOverlay}}"
|
ItemsSource="{Binding BindingContext.AccountViews, Source={x:Reference _mainOverlay}}"
|
||||||
ItemSelected="AccountRow_Selected"
|
|
||||||
BackgroundColor="{DynamicResource BackgroundColor}"
|
BackgroundColor="{DynamicResource BackgroundColor}"
|
||||||
VerticalOptions="Start"
|
VerticalOptions="Start"
|
||||||
RowHeight="{OnPlatform 70, iOS=70, Android=74}"
|
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
|
||||||
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
|
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="view:AccountView">
|
<DataTemplate x:DataType="view:AccountView">
|
||||||
<controls:AccountViewCell
|
<controls:AccountViewCell
|
||||||
Account="{Binding .}" />
|
Account="{Binding .}"
|
||||||
|
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
|
||||||
|
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
|
||||||
|
/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
<ListView.Effects>
|
<ListView.Effects>
|
||||||
|
|
|
@ -10,12 +10,24 @@ namespace Bit.App.Controls
|
||||||
{
|
{
|
||||||
public partial class AccountSwitchingOverlayView : ContentView
|
public partial class AccountSwitchingOverlayView : ContentView
|
||||||
{
|
{
|
||||||
|
public static readonly BindableProperty MainPageProperty = BindableProperty.Create(
|
||||||
|
nameof(MainPage),
|
||||||
|
typeof(ContentPage),
|
||||||
|
typeof(AccountSwitchingOverlayView),
|
||||||
|
defaultBindingMode: BindingMode.OneWay);
|
||||||
|
|
||||||
public static readonly BindableProperty MainFabProperty = BindableProperty.Create(
|
public static readonly BindableProperty MainFabProperty = BindableProperty.Create(
|
||||||
nameof(MainFab),
|
nameof(MainFab),
|
||||||
typeof(View),
|
typeof(View),
|
||||||
typeof(AccountSwitchingOverlayView),
|
typeof(AccountSwitchingOverlayView),
|
||||||
defaultBindingMode: BindingMode.OneWay);
|
defaultBindingMode: BindingMode.OneWay);
|
||||||
|
|
||||||
|
public ContentPage MainPage
|
||||||
|
{
|
||||||
|
get => (ContentPage)GetValue(MainPageProperty);
|
||||||
|
set => SetValue(MainPageProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
public View MainFab
|
public View MainFab
|
||||||
{
|
{
|
||||||
get => (View)GetValue(MainFabProperty);
|
get => (View)GetValue(MainFabProperty);
|
||||||
|
@ -31,12 +43,26 @@ namespace Bit.App.Controls
|
||||||
ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync,
|
ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync,
|
||||||
onException: ex => _logger.Value.Exception(ex),
|
onException: ex => _logger.Value.Exception(ex),
|
||||||
allowsMultipleExecutions: false);
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
|
||||||
|
onException: ex => _logger.Value.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
LongPressAccountCommand = new AsyncCommand<AccountViewCellViewModel>(LongPressAccountAsync,
|
||||||
|
onException: ex => _logger.Value.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel;
|
public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel;
|
||||||
|
|
||||||
public ICommand ToggleVisibililtyCommand { get; }
|
public ICommand ToggleVisibililtyCommand { get; }
|
||||||
|
|
||||||
|
public ICommand SelectAccountCommand { get; }
|
||||||
|
|
||||||
|
public ICommand LongPressAccountCommand { get; }
|
||||||
|
|
||||||
|
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
|
||||||
|
|
||||||
public async Task ToggleVisibilityAsync()
|
public async Task ToggleVisibilityAsync()
|
||||||
{
|
{
|
||||||
if (IsVisible)
|
if (IsVisible)
|
||||||
|
@ -51,13 +77,24 @@ namespace Bit.App.Controls
|
||||||
|
|
||||||
public async Task ShowAsync()
|
public async Task ShowAsync()
|
||||||
{
|
{
|
||||||
await ViewModel?.RefreshAccountViewsAsync();
|
if (ViewModel == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ViewModel.RefreshAccountViewsAsync();
|
||||||
|
|
||||||
await Device.InvokeOnMainThreadAsync(async () =>
|
await Device.InvokeOnMainThreadAsync(async () =>
|
||||||
{
|
{
|
||||||
// start listView in default (off-screen) position
|
// start listView in default (off-screen) position
|
||||||
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0);
|
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0);
|
||||||
|
|
||||||
|
// re-measure in case accounts have been removed without changing screens
|
||||||
|
if (ViewModel.AccountViews != null)
|
||||||
|
{
|
||||||
|
_accountListView.HeightRequest = AccountListRowHeight * ViewModel.AccountViews.Count;
|
||||||
|
}
|
||||||
|
|
||||||
// set overlay opacity to zero before making visible and start fade-in
|
// set overlay opacity to zero before making visible and start fade-in
|
||||||
Opacity = 0;
|
Opacity = 0;
|
||||||
IsVisible = true;
|
IsVisible = true;
|
||||||
|
@ -113,16 +150,10 @@ namespace Bit.App.Controls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async void AccountRow_Selected(object sender, SelectedItemChangedEventArgs e)
|
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!(e.SelectedItem is AccountViewCellViewModel item))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
((ListView)sender).SelectedItem = null;
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
await HideAsync();
|
await HideAsync();
|
||||||
|
|
||||||
|
@ -133,5 +164,25 @@ namespace Bit.App.Controls
|
||||||
_logger.Value.Exception(ex);
|
_logger.Value.Exception(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
|
||||||
|
{
|
||||||
|
if (!item.IsAccount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
await HideAsync();
|
||||||
|
|
||||||
|
ViewModel?.LongPressAccountCommand?.Execute(
|
||||||
|
new Tuple<ContentPage, AccountViewCellViewModel>(MainPage, item));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
@ -24,6 +26,10 @@ namespace Bit.App.Controls
|
||||||
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
|
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
|
||||||
onException: ex => logger.Exception(ex),
|
onException: ex => logger.Exception(ex),
|
||||||
allowsMultipleExecutions: false);
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
LongPressAccountCommand = new AsyncCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
|
||||||
|
onException: ex => logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this needs to be a new list every time for the binding to get updated,
|
// this needs to be a new list every time for the binding to get updated,
|
||||||
|
@ -37,6 +43,8 @@ namespace Bit.App.Controls
|
||||||
|
|
||||||
public ICommand SelectAccountCommand { get; }
|
public ICommand SelectAccountCommand { get; }
|
||||||
|
|
||||||
|
public ICommand LongPressAccountCommand { get; }
|
||||||
|
|
||||||
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||||
{
|
{
|
||||||
if (item.AccountView.IsAccount)
|
if (item.AccountView.IsAccount)
|
||||||
|
@ -57,6 +65,15 @@ namespace Bit.App.Controls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LongPressAccountAsync(Tuple<ContentPage, AccountViewCellViewModel> item)
|
||||||
|
{
|
||||||
|
var (page, account) = item;
|
||||||
|
if (account.AccountView.IsAccount)
|
||||||
|
{
|
||||||
|
await AppHelpers.AccountListOptions(page, account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RefreshAccountViewsAsync()
|
public async Task RefreshAccountViewsAsync()
|
||||||
{
|
{
|
||||||
await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow);
|
await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow);
|
||||||
|
|
|
@ -5,9 +5,15 @@
|
||||||
x:Class="Bit.App.Controls.AccountViewCell"
|
x:Class="Bit.App.Controls.AccountViewCell"
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
x:Name="_accountView"
|
||||||
x:DataType="controls:AccountViewCellViewModel">
|
x:DataType="controls:AccountViewCellViewModel">
|
||||||
<Grid RowSpacing="0"
|
<Grid RowSpacing="0"
|
||||||
ColumnSpacing="0">
|
ColumnSpacing="0"
|
||||||
|
xct:TouchEffect.NativeAnimation="True"
|
||||||
|
xct:TouchEffect.Command="{Binding SelectAccountCommand, Source={x:Reference _accountView}}"
|
||||||
|
xct:TouchEffect.CommandParameter="{Binding .}"
|
||||||
|
xct:TouchEffect.LongPressCommand="{Binding LongPressAccountCommand, Source={x:Reference _accountView}}"
|
||||||
|
xct:TouchEffect.LongPressCommandParameter="{Binding .}">
|
||||||
|
|
||||||
<Grid.Resources>
|
<Grid.Resources>
|
||||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
@ -71,7 +77,7 @@
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountUnlocked}"
|
Text="{u:I18n AccountUnlocked}"
|
||||||
IsVisible="{Binding IsUnlocked}"
|
IsVisible="{Binding IsUnlockedAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="list-sub"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
|
@ -79,7 +85,7 @@
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLocked}"
|
Text="{u:I18n AccountLocked}"
|
||||||
IsVisible="{Binding IsLocked}"
|
IsVisible="{Binding IsLockedAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="list-sub"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
|
@ -87,7 +93,7 @@
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLoggedOut}"
|
Text="{u:I18n AccountLoggedOut}"
|
||||||
IsVisible="{Binding IsLoggedOut}"
|
IsVisible="{Binding IsLoggedOutAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="list-sub"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
|
using System.Windows.Input;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Controls
|
namespace Bit.App.Controls
|
||||||
|
@ -6,7 +7,13 @@ namespace Bit.App.Controls
|
||||||
public partial class AccountViewCell : ViewCell
|
public partial class AccountViewCell : ViewCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty AccountProperty = BindableProperty.Create(
|
public static readonly BindableProperty AccountProperty = BindableProperty.Create(
|
||||||
nameof(Account), typeof(AccountView), typeof(AccountViewCell), default(AccountView), BindingMode.OneWay);
|
nameof(Account), typeof(AccountView), typeof(AccountViewCell));
|
||||||
|
|
||||||
|
public static readonly BindableProperty SelectAccountCommandProperty = BindableProperty.Create(
|
||||||
|
nameof(SelectAccountCommand), typeof(ICommand), typeof(AccountViewCell));
|
||||||
|
|
||||||
|
public static readonly BindableProperty LongPressAccountCommandProperty = BindableProperty.Create(
|
||||||
|
nameof(LongPressAccountCommand), typeof(ICommand), typeof(AccountViewCell));
|
||||||
|
|
||||||
public AccountViewCell()
|
public AccountViewCell()
|
||||||
{
|
{
|
||||||
|
@ -19,6 +26,18 @@ namespace Bit.App.Controls
|
||||||
set => SetValue(AccountProperty, value);
|
set => SetValue(AccountProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ICommand SelectAccountCommand
|
||||||
|
{
|
||||||
|
get => GetValue(SelectAccountCommandProperty) as ICommand;
|
||||||
|
set => SetValue(SelectAccountCommandProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand LongPressAccountCommand
|
||||||
|
{
|
||||||
|
get => GetValue(LongPressAccountCommandProperty) as ICommand;
|
||||||
|
set => SetValue(LongPressAccountCommandProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnPropertyChanged(string propertyName = null)
|
protected override void OnPropertyChanged(string propertyName = null)
|
||||||
{
|
{
|
||||||
base.OnPropertyChanged(propertyName);
|
base.OnPropertyChanged(propertyName);
|
||||||
|
|
|
@ -48,16 +48,31 @@ namespace Bit.App.Controls
|
||||||
get => AccountView.AuthStatus == AuthenticationStatus.Unlocked;
|
get => AccountView.AuthStatus == AuthenticationStatus.Unlocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsUnlockedAndNotActive
|
||||||
|
{
|
||||||
|
get => IsUnlocked && !IsActive;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsLocked
|
public bool IsLocked
|
||||||
{
|
{
|
||||||
get => AccountView.AuthStatus == AuthenticationStatus.Locked;
|
get => AccountView.AuthStatus == AuthenticationStatus.Locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsLockedAndNotActive
|
||||||
|
{
|
||||||
|
get => IsLocked && !IsActive;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsLoggedOut
|
public bool IsLoggedOut
|
||||||
{
|
{
|
||||||
get => AccountView.AuthStatus == AuthenticationStatus.LoggedOut;
|
get => AccountView.AuthStatus == AuthenticationStatus.LoggedOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsLoggedOutAndNotActive
|
||||||
|
{
|
||||||
|
get => IsLoggedOut && !IsActive;
|
||||||
|
}
|
||||||
|
|
||||||
public string AuthStatusIconActive
|
public string AuthStatusIconActive
|
||||||
{
|
{
|
||||||
get => BitwardenIcons.CheckCircle;
|
get => BitwardenIcons.CheckCircle;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
x:DataType="pages:HomeViewModel"
|
x:DataType="pages:HomeViewModel"
|
||||||
|
x:Name="_page"
|
||||||
Title="{Binding PageTitle}">
|
Title="{Binding PageTitle}">
|
||||||
<ContentPage.BindingContext>
|
<ContentPage.BindingContext>
|
||||||
<pages:HomeViewModel />
|
<pages:HomeViewModel />
|
||||||
|
@ -73,6 +74,7 @@
|
||||||
x:Name="_accountListOverlay"
|
x:Name="_accountListOverlay"
|
||||||
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
AbsoluteLayout.LayoutFlags="All"
|
AbsoluteLayout.LayoutFlags="All"
|
||||||
|
MainPage="{Binding Source={x:Reference _page}}"
|
||||||
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
||||||
</AbsoluteLayout>
|
</AbsoluteLayout>
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
x:DataType="pages:LockPageViewModel"
|
x:DataType="pages:LockPageViewModel"
|
||||||
|
x:Name="_page"
|
||||||
Title="{Binding PageTitle}">
|
Title="{Binding PageTitle}">
|
||||||
|
|
||||||
<ContentPage.BindingContext>
|
<ContentPage.BindingContext>
|
||||||
|
@ -165,6 +166,7 @@
|
||||||
x:Name="_accountListOverlay"
|
x:Name="_accountListOverlay"
|
||||||
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
AbsoluteLayout.LayoutFlags="All"
|
AbsoluteLayout.LayoutFlags="All"
|
||||||
|
MainPage="{Binding Source={x:Reference _page}}"
|
||||||
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
||||||
|
|
||||||
</AbsoluteLayout>
|
</AbsoluteLayout>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
x:DataType="pages:LoginPageViewModel"
|
x:DataType="pages:LoginPageViewModel"
|
||||||
|
x:Name="_page"
|
||||||
Title="{Binding PageTitle}">
|
Title="{Binding PageTitle}">
|
||||||
|
|
||||||
<ContentPage.BindingContext>
|
<ContentPage.BindingContext>
|
||||||
|
@ -130,6 +131,7 @@
|
||||||
x:Name="_accountListOverlay"
|
x:Name="_accountListOverlay"
|
||||||
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
AbsoluteLayout.LayoutFlags="All"
|
AbsoluteLayout.LayoutFlags="All"
|
||||||
|
MainPage="{Binding Source={x:Reference _page}}"
|
||||||
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
||||||
</AbsoluteLayout>
|
</AbsoluteLayout>
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,7 @@
|
||||||
x:Name="_accountListOverlay"
|
x:Name="_accountListOverlay"
|
||||||
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
AbsoluteLayout.LayoutFlags="All"
|
AbsoluteLayout.LayoutFlags="All"
|
||||||
|
MainPage="{Binding Source={x:Reference _page}}"
|
||||||
MainFab="{Binding Source={x:Reference _fab}, Path=.}"
|
MainFab="{Binding Source={x:Reference _fab}, Path=.}"
|
||||||
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
||||||
</AbsoluteLayout>
|
</AbsoluteLayout>
|
||||||
|
|
|
@ -3773,6 +3773,24 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string AccountLockedSuccessfully {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AccountLockedSuccessfully", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AccountLoggedOutSuccessfully {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AccountLoggedOutSuccessfully", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AccountRemovedSuccessfully {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AccountRemovedSuccessfully", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string DeleteAccount {
|
public static string DeleteAccount {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("DeleteAccount", resourceCulture);
|
return ResourceManager.GetString("DeleteAccount", resourceCulture);
|
||||||
|
@ -3809,6 +3827,12 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string RequestOTP {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RequestOTP", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string SendCode {
|
public static string SendCode {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("SendCode", resourceCulture);
|
return ResourceManager.GetString("SendCode", resourceCulture);
|
||||||
|
|
|
@ -2120,6 +2120,15 @@
|
||||||
<data name="AccountSwitchedAutomatically" xml:space="preserve">
|
<data name="AccountSwitchedAutomatically" xml:space="preserve">
|
||||||
<value>Switched to next available account</value>
|
<value>Switched to next available account</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AccountLockedSuccessfully" xml:space="preserve">
|
||||||
|
<value>Account Locked</value>
|
||||||
|
</data>
|
||||||
|
<data name="AccountLoggedOutSuccessfully" xml:space="preserve">
|
||||||
|
<value>Account logged out successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="AccountRemovedSuccessfully" xml:space="preserve">
|
||||||
|
<value>Account removed successfully</value>
|
||||||
|
</data>
|
||||||
<data name="DeleteAccount" xml:space="preserve">
|
<data name="DeleteAccount" xml:space="preserve">
|
||||||
<value>Delete Account</value>
|
<value>Delete Account</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Controls;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
@ -196,6 +197,55 @@ namespace Bit.App.Utilities
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<string> AccountListOptions(ContentPage page, AccountViewCellViewModel accountViewCell)
|
||||||
|
{
|
||||||
|
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
|
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
|
||||||
|
var userId = accountViewCell.AccountView.UserId;
|
||||||
|
|
||||||
|
List<string> options;
|
||||||
|
if (await vaultTimeoutService.IsLoggedOutByTimeoutAsync(userId) ||
|
||||||
|
await vaultTimeoutService.ShouldLogOutByTimeoutAsync(userId))
|
||||||
|
{
|
||||||
|
options = new List<string> { AppResources.RemoveAccount };
|
||||||
|
}
|
||||||
|
else if (await vaultTimeoutService.IsLockedAsync(userId) ||
|
||||||
|
await vaultTimeoutService.ShouldLockAsync(userId))
|
||||||
|
{
|
||||||
|
options = new List<string> { AppResources.LogOut };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
options = new List<string> { AppResources.Lock, AppResources.LogOut };
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountSummary = accountViewCell.AccountView.Email;
|
||||||
|
if (!string.IsNullOrWhiteSpace(accountViewCell.AccountView.Hostname))
|
||||||
|
{
|
||||||
|
accountSummary += "\n" + accountViewCell.AccountView.Hostname;
|
||||||
|
}
|
||||||
|
var selection = await page.DisplayActionSheet(accountSummary, AppResources.Cancel, null, options.ToArray());
|
||||||
|
|
||||||
|
if (selection == AppResources.Lock)
|
||||||
|
{
|
||||||
|
await vaultTimeoutService.LockAsync(true, true, userId);
|
||||||
|
}
|
||||||
|
else if (selection == AppResources.LogOut || selection == AppResources.RemoveAccount)
|
||||||
|
{
|
||||||
|
var title = selection == AppResources.LogOut ? AppResources.LogOut : AppResources.RemoveAccount;
|
||||||
|
var text = (selection == AppResources.LogOut ? AppResources.LogoutConfirmation
|
||||||
|
: AppResources.RemoveAccountConfirmation) + "\n\n" + accountSummary;
|
||||||
|
var confirmed =
|
||||||
|
await platformUtilsService.ShowDialogAsync(text, title, AppResources.Yes, AppResources.Cancel);
|
||||||
|
if (confirmed)
|
||||||
|
{
|
||||||
|
await LogOutAsync(userId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task CopySendUrlAsync(SendView send)
|
public static async Task CopySendUrlAsync(SendView send)
|
||||||
{
|
{
|
||||||
if (await IsSendDisabledByPolicyAsync())
|
if (await IsSendDisabledByPolicyAsync())
|
||||||
|
@ -459,6 +509,8 @@ namespace Bit.App.Utilities
|
||||||
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
var searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
var searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
||||||
|
|
||||||
|
var isActiveAccount = await stateService.IsActiveAccount(userId);
|
||||||
|
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
userId = await stateService.GetActiveUserIdAsync();
|
userId = await stateService.GetActiveUserIdAsync();
|
||||||
|
@ -479,14 +531,33 @@ namespace Bit.App.Utilities
|
||||||
|
|
||||||
searchService.ClearIndex();
|
searchService.ClearIndex();
|
||||||
|
|
||||||
// check if we switched accounts automatically
|
if (!userInitiated)
|
||||||
if (userInitiated && await stateService.GetActiveUserIdAsync() != null)
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we switched active accounts automatically
|
||||||
|
if (isActiveAccount && await stateService.GetActiveUserIdAsync() != null)
|
||||||
{
|
{
|
||||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
messagingService.Send("switchedAccount");
|
messagingService.Send("switchedAccount");
|
||||||
|
|
||||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
platformUtilsService.ShowToast("info", null, AppResources.AccountSwitchedAutomatically);
|
platformUtilsService.ShowToast("info", null, AppResources.AccountSwitchedAutomatically);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we logged out a non-active account
|
||||||
|
if (!isActiveAccount)
|
||||||
|
{
|
||||||
|
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
if (await vaultTimeoutService.IsLoggedOutByTimeoutAsync(userId) ||
|
||||||
|
await vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
||||||
|
{
|
||||||
|
platformUtilsService.ShowToast("info", null, AppResources.AccountRemovedSuccessfully);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
platformUtilsService.ShowToast("info", null, AppResources.AccountLoggedOutSuccessfully);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
List<AccountView> AccountViews { get; }
|
List<AccountView> AccountViews { get; }
|
||||||
Task<string> GetActiveUserIdAsync();
|
Task<string> GetActiveUserIdAsync();
|
||||||
|
Task<bool> IsActiveAccount(string userId = null);
|
||||||
Task SetActiveUserAsync(string userId);
|
Task SetActiveUserAsync(string userId);
|
||||||
Task<bool> IsAuthenticatedAsync(string userId = null);
|
Task<bool> IsAuthenticatedAsync(string userId = null);
|
||||||
Task<string> GetUserIdAsync(string email);
|
Task<string> GetUserIdAsync(string email);
|
||||||
|
|
|
@ -13,7 +13,9 @@ namespace Bit.Core.Abstractions
|
||||||
Task ExecuteTimeoutActionAsync(string userId = null);
|
Task ExecuteTimeoutActionAsync(string userId = null);
|
||||||
Task ClearAsync(string userId = null);
|
Task ClearAsync(string userId = null);
|
||||||
Task<bool> IsLockedAsync(string userId = null);
|
Task<bool> IsLockedAsync(string userId = null);
|
||||||
|
Task<bool> ShouldLockAsync(string userId = null);
|
||||||
Task<bool> IsLoggedOutByTimeoutAsync(string userId = null);
|
Task<bool> IsLoggedOutByTimeoutAsync(string userId = null);
|
||||||
|
Task<bool> ShouldLogOutByTimeoutAsync(string userId = null);
|
||||||
Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null);
|
Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null);
|
||||||
Task<bool> IsBiometricLockSetAsync(string userId = null);
|
Task<bool> IsBiometricLockSetAsync(string userId = null);
|
||||||
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null);
|
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null);
|
||||||
|
|
|
@ -68,6 +68,7 @@ namespace Bit.Core.Services
|
||||||
_apiService.SetUrls(envUrls);
|
_apiService.SetUrls(envUrls);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
BaseUrl = urls.Base;
|
||||||
WebVaultUrl = urls.WebVault;
|
WebVaultUrl = urls.WebVault;
|
||||||
ApiUrl = envUrls.Api = urls.Api;
|
ApiUrl = envUrls.Api = urls.Api;
|
||||||
IdentityUrl = envUrls.Identity = urls.Identity;
|
IdentityUrl = envUrls.Identity = urls.Identity;
|
||||||
|
|
|
@ -42,6 +42,16 @@ namespace Bit.Core.Services
|
||||||
return activeUserId;
|
return activeUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsActiveAccount(string userId = null)
|
||||||
|
{
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
await CheckStateAsync();
|
||||||
|
return userId == await GetActiveUserIdAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SetActiveUserAsync(string userId)
|
public async Task SetActiveUserAsync(string userId)
|
||||||
{
|
{
|
||||||
if (userId != null)
|
if (userId != null)
|
||||||
|
@ -110,18 +120,16 @@ namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
var isActiveAccount = account.Profile.UserId == _state.ActiveUserId;
|
var isActiveAccount = account.Profile.UserId == _state.ActiveUserId;
|
||||||
var accountView = new AccountView(account, isActiveAccount);
|
var accountView = new AccountView(account, isActiveAccount);
|
||||||
if (isActiveAccount)
|
|
||||||
|
if (await vaultTimeoutService.IsLoggedOutByTimeoutAsync(accountView.UserId) ||
|
||||||
|
await vaultTimeoutService.ShouldLogOutByTimeoutAsync(accountView.UserId))
|
||||||
{
|
{
|
||||||
AccountViews.Add(accountView);
|
accountView.AuthStatus = AuthenticationStatus.LoggedOut;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
var isLocked = await vaultTimeoutService.IsLockedAsync(accountView.UserId);
|
else if (await vaultTimeoutService.IsLockedAsync(accountView.UserId) ||
|
||||||
var shouldTimeout = await vaultTimeoutService.ShouldTimeoutAsync(accountView.UserId);
|
await vaultTimeoutService.ShouldLockAsync(accountView.UserId))
|
||||||
if (isLocked || shouldTimeout)
|
|
||||||
{
|
{
|
||||||
var action = account.Settings.VaultTimeoutAction;
|
accountView.AuthStatus = AuthenticationStatus.Locked;
|
||||||
accountView.AuthStatus = action == VaultTimeoutAction.Logout ? AuthenticationStatus.LoggedOut
|
|
||||||
: AuthenticationStatus.Locked;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Bit.Core.Services
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly Action<bool> _lockedCallback;
|
private readonly Func<Tuple<string, bool>, Task> _lockedCallback;
|
||||||
private readonly Func<Tuple<string, bool, bool>, Task> _loggedOutCallback;
|
private readonly Func<Tuple<string, bool, bool>, Task> _loggedOutCallback;
|
||||||
|
|
||||||
public VaultTimeoutService(
|
public VaultTimeoutService(
|
||||||
|
@ -34,7 +34,7 @@ namespace Bit.Core.Services
|
||||||
ITokenService tokenService,
|
ITokenService tokenService,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
IKeyConnectorService keyConnectorService,
|
IKeyConnectorService keyConnectorService,
|
||||||
Action<bool> lockedCallback,
|
Func<Tuple<string, bool>, Task> lockedCallback,
|
||||||
Func<Tuple<string, bool, bool>, Task> loggedOutCallback)
|
Func<Tuple<string, bool, bool>, Task> loggedOutCallback)
|
||||||
{
|
{
|
||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
|
@ -67,7 +67,17 @@ namespace Bit.Core.Services
|
||||||
}
|
}
|
||||||
return !hasKey;
|
return !hasKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ShouldLockAsync(string userId = null)
|
||||||
|
{
|
||||||
|
if (await ShouldTimeoutAsync(userId))
|
||||||
|
{
|
||||||
|
var action = await _stateService.GetVaultTimeoutActionAsync(userId);
|
||||||
|
return action == VaultTimeoutAction.Lock;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> IsLoggedOutByTimeoutAsync(string userId = null)
|
public async Task<bool> IsLoggedOutByTimeoutAsync(string userId = null)
|
||||||
{
|
{
|
||||||
var authed = await _stateService.IsAuthenticatedAsync(userId);
|
var authed = await _stateService.IsAuthenticatedAsync(userId);
|
||||||
|
@ -75,6 +85,16 @@ namespace Bit.Core.Services
|
||||||
return !authed && !string.IsNullOrWhiteSpace(email);
|
return !authed && !string.IsNullOrWhiteSpace(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ShouldLogOutByTimeoutAsync(string userId = null)
|
||||||
|
{
|
||||||
|
if (await ShouldTimeoutAsync(userId))
|
||||||
|
{
|
||||||
|
var action = await _stateService.GetVaultTimeoutActionAsync(userId);
|
||||||
|
return action == VaultTimeoutAction.Logout;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task CheckVaultTimeoutAsync()
|
public async Task CheckVaultTimeoutAsync()
|
||||||
{
|
{
|
||||||
if (_platformUtilsService.IsViewOpen())
|
if (_platformUtilsService.IsViewOpen())
|
||||||
|
@ -144,6 +164,13 @@ namespace Bit.Core.Services
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isActiveAccount = await _stateService.IsActiveAccount(userId);
|
||||||
|
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
userId = await _stateService.GetActiveUserIdAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (await _keyConnectorService.GetUsesKeyConnector()) {
|
if (await _keyConnectorService.GetUsesKeyConnector()) {
|
||||||
var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId);
|
var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId);
|
||||||
var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) ||
|
var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) ||
|
||||||
|
@ -162,8 +189,7 @@ namespace Bit.Core.Services
|
||||||
await _stateService.SetBiometricLockedAsync(isBiometricLockSet, userId);
|
await _stateService.SetBiometricLockedAsync(isBiometricLockSet, userId);
|
||||||
if (isBiometricLockSet)
|
if (isBiometricLockSet)
|
||||||
{
|
{
|
||||||
_messagingService.Send("locked", userInitiated);
|
_lockedCallback?.Invoke(new Tuple<string, bool>(userId, userInitiated));
|
||||||
_lockedCallback?.Invoke(userInitiated);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,12 +199,14 @@ namespace Bit.Core.Services
|
||||||
_cryptoService.ClearKeyPairAsync(true, userId),
|
_cryptoService.ClearKeyPairAsync(true, userId),
|
||||||
_cryptoService.ClearEncKeyAsync(true, userId));
|
_cryptoService.ClearEncKeyAsync(true, userId));
|
||||||
|
|
||||||
_folderService.ClearCache();
|
if (isActiveAccount)
|
||||||
await _cipherService.ClearCacheAsync();
|
{
|
||||||
_collectionService.ClearCache();
|
_folderService.ClearCache();
|
||||||
_searchService.ClearIndex();
|
await _cipherService.ClearCacheAsync();
|
||||||
_messagingService.Send("locked", userInitiated);
|
_collectionService.ClearCache();
|
||||||
_lockedCallback?.Invoke(userInitiated);
|
_searchService.ClearIndex();
|
||||||
|
}
|
||||||
|
_lockedCallback?.Invoke(new Tuple<string, bool>(userId, userInitiated));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogOutAsync(bool userInitiated = true, string userId = null)
|
public async Task LogOutAsync(bool userInitiated = true, string userId = null)
|
||||||
|
|
|
@ -52,7 +52,13 @@ namespace Bit.Core.Utilities
|
||||||
organizationService);
|
organizationService);
|
||||||
var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService,
|
var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService,
|
||||||
folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
||||||
policyService, keyConnectorService, null, (extras) =>
|
policyService, keyConnectorService,
|
||||||
|
(extras) =>
|
||||||
|
{
|
||||||
|
messagingService.Send("locked", extras);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
},
|
||||||
|
(extras) =>
|
||||||
{
|
{
|
||||||
messagingService.Send("logout", extras);
|
messagingService.Send("logout", extras);
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
|
|
|
@ -167,7 +167,7 @@
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
|
||||||
<PackageReference Include="Microsoft.AppCenter.Crashes">
|
<PackageReference Include="Microsoft.AppCenter.Crashes">
|
||||||
<Version>4.4.0</Version>
|
<Version>4.4.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
@ -183,7 +183,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.7.0</Version>
|
<Version>1.7.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||||
|
|
Loading…
Reference in New Issue