mirror of
https://github.com/bitwarden/mobile
synced 2025-12-15 07:43:37 +00:00
vault timeout fixes
This commit is contained in:
@@ -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();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user