1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-15 07:43:37 +00:00

vault timeout fixes

This commit is contained in:
Matt Portune
2022-01-18 22:55:22 -05:00
parent 59e7969856
commit 796cf6dc25
15 changed files with 61 additions and 49 deletions

View File

@@ -270,11 +270,18 @@ 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 () =>
{ {
await SetMainPageAsync(); if (shouldTimeout)
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await SetMainPageAsync();
}
UpdateTheme(); UpdateTheme();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
}); });
} }

View File

@@ -7,8 +7,7 @@
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="controls:AccountViewCellViewModel"> x:DataType="controls:AccountViewCellViewModel">
<Grid RowSpacing="0" <Grid RowSpacing="0"
ColumnSpacing="0" ColumnSpacing="0">
BackgroundColor="{DynamicResource BackgroundColor}">
<Grid.Resources> <Grid.Resources>
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />

View File

@@ -89,6 +89,7 @@
<ListView <ListView
ItemsSource="{Binding AccountViews}" ItemsSource="{Binding AccountViews}"
ItemSelected="AccountRow_Selected" ItemSelected="AccountRow_Selected"
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
RowHeight="60"> RowHeight="60">
<ListView.ItemTemplate> <ListView.ItemTemplate>

View File

@@ -183,6 +183,7 @@
<ListView <ListView
ItemsSource="{Binding AccountViews}" ItemsSource="{Binding AccountViews}"
ItemSelected="AccountRow_Selected" ItemSelected="AccountRow_Selected"
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
RowHeight="60"> RowHeight="60">
<ListView.ItemTemplate> <ListView.ItemTemplate>

View File

@@ -132,6 +132,7 @@
<ListView <ListView
ItemsSource="{Binding AccountViews}" ItemsSource="{Binding AccountViews}"
ItemSelected="AccountRow_Selected" ItemSelected="AccountRow_Selected"
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
RowHeight="60"> RowHeight="60">
<ListView.ItemTemplate> <ListView.ItemTemplate>

View File

@@ -182,6 +182,7 @@
<ListView <ListView
ItemsSource="{Binding AccountViews}" ItemsSource="{Binding AccountViews}"
ItemSelected="AccountRow_Selected" ItemSelected="AccountRow_Selected"
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
RowHeight="60"> RowHeight="60">
<ListView.ItemTemplate> <ListView.ItemTemplate>

View File

@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -137,11 +138,15 @@ namespace Bit.App.Pages
{ {
get get
{ {
// create a separate collection that includes the "add new" row if (_stateService.AccountViews.Count < Constants.MaxAccounts)
var accounts = new ExtendedObservableCollection<AccountView>(); {
accounts.AddRange(_stateService.AccountViews); // create a separate collection that includes the "add new" row
accounts.Add(new AccountView()); var accountViews = new ExtendedObservableCollection<AccountView>();
return accounts; accountViews.AddRange(_stateService.AccountViews);
accountViews.Add(new AccountView());
return accountViews;
}
return _stateService.AccountViews;
} }
} }

View File

@@ -25,7 +25,7 @@ namespace Bit.Core.Abstractions
Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null); Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null); Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
Task<SymmetricCryptoKey> GetKeyAsync(); Task<SymmetricCryptoKey> GetKeyAsync(string userId = null);
Task<string> GetKeyHashAsync(); Task<string> GetKeyHashAsync();
Task<SymmetricCryptoKey> GetOrgKeyAsync(string orgId); Task<SymmetricCryptoKey> GetOrgKeyAsync(string orgId);
Task<Dictionary<string, SymmetricCryptoKey>> GetOrgKeysAsync(); Task<Dictionary<string, SymmetricCryptoKey>> GetOrgKeysAsync();
@@ -34,7 +34,7 @@ namespace Bit.Core.Abstractions
Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key); Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key);
Task<bool> HasEncKeyAsync(); Task<bool> HasEncKeyAsync();
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization); Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task<bool> HasKeyAsync(); Task<bool> HasKeyAsync(string userId = null);
Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key); Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key);
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations); Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations);
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations, Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations,

View File

@@ -6,10 +6,12 @@ namespace Bit.Core.Abstractions
public interface IVaultTimeoutService public interface IVaultTimeoutService
{ {
Task CheckVaultTimeoutAsync(); Task CheckVaultTimeoutAsync();
Task<bool> ShouldTimeoutAsync(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<Tuple<bool, bool>> IsPinLockSetAsync(); Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null);
Task<bool> IsBiometricLockSetAsync(); 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);
Task LogOutAsync(bool userInitiated = true, string userId = null); Task LogOutAsync(bool userInitiated = true, string userId = null);
Task SetVaultTimeoutOptionsAsync(int? timeout, string action); Task SetVaultTimeoutOptionsAsync(int? timeout, string action);

View File

@@ -2,6 +2,7 @@
{ {
public static class Constants public static class Constants
{ {
public const int MaxAccounts = 5;
public const string AndroidAppProtocol = "androidapp://"; public const string AndroidAppProtocol = "androidapp://";
public const string iOSAppProtocol = "iosapp://"; public const string iOSAppProtocol = "iosapp://";
public static string StateKey = "state"; public static string StateKey = "state";

View File

@@ -5,7 +5,6 @@ namespace Bit.Core.Models.Domain
{ {
public class Account : Domain public class Account : Domain
{ {
public AuthenticationStatus? AuthStatus;
public AccountProfile Profile; public AccountProfile Profile;
public AccountTokens Tokens; public AccountTokens Tokens;
public AccountSettings Settings; public AccountSettings Settings;
@@ -24,7 +23,6 @@ namespace Bit.Core.Models.Domain
public Account(Account account) public Account(Account account)
{ {
// Copy constructor excludes Keys (for storage) // Copy constructor excludes Keys (for storage)
AuthStatus = account.AuthStatus;
Profile = new AccountProfile(account.Profile); Profile = new AccountProfile(account.Profile);
Tokens = new AccountTokens(account.Tokens); Tokens = new AccountTokens(account.Tokens);
Settings = new AccountSettings(account.Settings); Settings = new AccountSettings(account.Settings);

View File

@@ -15,7 +15,6 @@ namespace Bit.Core.Models.View
return; return;
} }
IsAccount = true; IsAccount = true;
AuthStatus = a.AuthStatus;
UserId = a.Profile?.UserId; UserId = a.Profile?.UserId;
Email = a.Profile?.Email; Email = a.Profile?.Email;
Hostname = a.Settings?.EnvironmentUrls?.Base; Hostname = a.Settings?.EnvironmentUrls?.Base;

View File

@@ -80,18 +80,18 @@ namespace Bit.Core.Services
await _stateService.SetOrgKeysEncryptedAsync(orgKeys); await _stateService.SetOrgKeysEncryptedAsync(orgKeys);
} }
public async Task<SymmetricCryptoKey> GetKeyAsync() public async Task<SymmetricCryptoKey> GetKeyAsync(string userId = null)
{ {
var inMemoryKey = await _stateService.GetKeyDecryptedAsync(); var inMemoryKey = await _stateService.GetKeyDecryptedAsync(userId);
if (inMemoryKey != null) if (inMemoryKey != null)
{ {
return inMemoryKey; return inMemoryKey;
} }
var key = await _stateService.GetKeyEncryptedAsync(); var key = await _stateService.GetKeyEncryptedAsync(userId);
if (key != null) if (key != null)
{ {
inMemoryKey = new SymmetricCryptoKey(Convert.FromBase64String(key)); inMemoryKey = new SymmetricCryptoKey(Convert.FromBase64String(key));
await _stateService.SetKeyDecryptedAsync(inMemoryKey); await _stateService.SetKeyDecryptedAsync(inMemoryKey, userId);
} }
return inMemoryKey; return inMemoryKey;
} }
@@ -295,9 +295,9 @@ namespace Bit.Core.Services
return false; return false;
} }
public async Task<bool> HasKeyAsync() public async Task<bool> HasKeyAsync(string userId = null)
{ {
var key = await GetKeyAsync(); var key = await GetKeyAsync(userId);
return key != null; return key != null;
} }

View File

@@ -89,12 +89,17 @@ namespace Bit.Core.Services
{ {
await CheckStateAsync(); await CheckStateAsync();
AccountViews = new ExtendedObservableCollection<AccountView>(); if (AccountViews == null)
{
AccountViews = new ExtendedObservableCollection<AccountView>();
}
AccountViews.Clear();
var accountList = _state?.Accounts?.Values.ToList(); var accountList = _state?.Accounts?.Values.ToList();
if (accountList == null) if (accountList == null)
{ {
return; return;
} }
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
foreach (var account in accountList) foreach (var account in accountList)
{ {
var accountView = new AccountView(account); var accountView = new AccountView(account);
@@ -104,7 +109,9 @@ namespace Bit.Core.Services
} }
else else
{ {
if (await IsLockedAsync(accountView.UserId)) var isLocked = await vaultTimeoutService.IsLockedAsync(accountView.UserId);
var shouldTimeout = await vaultTimeoutService.ShouldTimeoutAsync(accountView.UserId);
if (isLocked || shouldTimeout)
{ {
var action = account.Settings.VaultTimeoutAction; var action = account.Settings.VaultTimeoutAction;
accountView.AuthStatus = action == "logOut" ? AuthenticationStatus.LoggedOut accountView.AuthStatus = action == "logOut" ? AuthenticationStatus.LoggedOut

View File

@@ -54,10 +54,10 @@ namespace Bit.Core.Services
public async Task<bool> IsLockedAsync(string userId = null) public async Task<bool> IsLockedAsync(string userId = null)
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasKeyAsync(userId);
if (hasKey) if (hasKey)
{ {
var biometricSet = await IsBiometricLockSetAsync(); var biometricSet = await IsBiometricLockSetAsync(userId);
if (biometricSet && _stateService.BiometricLocked) if (biometricSet && _stateService.BiometricLocked)
{ {
return true; return true;
@@ -73,16 +73,13 @@ namespace Bit.Core.Services
return; return;
} }
foreach (var userId in await _stateService.GetUserIdsAsync()) if (await ShouldTimeoutAsync())
{ {
if (userId != null && await ShouldLockAsync(userId)) await ExecuteTimeoutActionAsync();
{
await ExecuteTimeoutActionAsync(userId);
}
} }
} }
private async Task<bool> ShouldLockAsync(string userId) public async Task<bool> ShouldTimeoutAsync(string userId = null)
{ {
var authed = await _stateService.IsAuthenticatedAsync(userId); var authed = await _stateService.IsAuthenticatedAsync(userId);
if (!authed) if (!authed)
@@ -108,7 +105,7 @@ namespace Bit.Core.Services
return diffMs >= vaultTimeoutMs; return diffMs >= vaultTimeoutMs;
} }
private async Task ExecuteTimeoutActionAsync(string userId) public async Task ExecuteTimeoutActionAsync(string userId = null)
{ {
var action = await _stateService.GetVaultTimeoutActionAsync(userId); var action = await _stateService.GetVaultTimeoutActionAsync(userId);
if (action == "logOut") if (action == "logOut")
@@ -130,8 +127,8 @@ namespace Bit.Core.Services
} }
if (await _keyConnectorService.GetUsesKeyConnector()) { if (await _keyConnectorService.GetUsesKeyConnector()) {
var pinSet = await IsPinLockSetAsync(); var pinSet = await IsPinLockSetAsync(userId);
var pinLock = (pinSet.Item1 && _stateService.GetPinProtectedAsync() != null) || pinSet.Item2; var pinLock = (pinSet.Item1 && _stateService.GetPinProtectedAsync(userId) != null) || pinSet.Item2;
if (!pinLock && !await IsBiometricLockSetAsync()) if (!pinLock && !await IsBiometricLockSetAsync())
{ {
@@ -142,21 +139,14 @@ namespace Bit.Core.Services
if (allowSoftLock) if (allowSoftLock)
{ {
var biometricLocked = await IsBiometricLockSetAsync(); _stateService.BiometricLocked = await IsBiometricLockSetAsync();
_stateService.BiometricLocked = biometricLocked; if (_stateService.BiometricLocked)
if (biometricLocked)
{ {
_messagingService.Send("locked", userInitiated); _messagingService.Send("locked", userInitiated);
_lockedCallback?.Invoke(userInitiated); _lockedCallback?.Invoke(userInitiated);
return; return;
} }
} }
if (userId == null || userId == await _stateService.GetActiveUserIdAsync())
{
_searchService.ClearIndex();
}
await Task.WhenAll( await Task.WhenAll(
_cryptoService.ClearKeyAsync(userId), _cryptoService.ClearKeyAsync(userId),
_cryptoService.ClearOrgKeysAsync(true, userId), _cryptoService.ClearOrgKeysAsync(true, userId),
@@ -187,16 +177,16 @@ namespace Bit.Core.Services
await _tokenService.ToggleTokensAsync(); await _tokenService.ToggleTokensAsync();
} }
public async Task<Tuple<bool, bool>> IsPinLockSetAsync() public async Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null)
{ {
var protectedPin = await _stateService.GetProtectedPinAsync(); var protectedPin = await _stateService.GetProtectedPinAsync(userId);
var pinProtectedKey = await _stateService.GetPinProtectedAsync(); var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId);
return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null); return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null);
} }
public async Task<bool> IsBiometricLockSetAsync() public async Task<bool> IsBiometricLockSetAsync(string userId = null)
{ {
var biometricLock = await _stateService.GetBiometricUnlockAsync(); var biometricLock = await _stateService.GetBiometricUnlockAsync(userId);
return biometricLock.GetValueOrDefault(); return biometricLock.GetValueOrDefault();
} }