mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
vault timeout fixes
This commit is contained in:
@@ -270,11 +270,18 @@ namespace Bit.App
|
||||
private async Task SwitchedAccountAsync()
|
||||
{
|
||||
await AppHelpers.OnAccountSwitchAsync();
|
||||
var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync();
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await SetMainPageAsync();
|
||||
if (shouldTimeout)
|
||||
{
|
||||
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetMainPageAsync();
|
||||
}
|
||||
UpdateTheme();
|
||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="controls:AccountViewCellViewModel">
|
||||
<Grid RowSpacing="0"
|
||||
ColumnSpacing="0"
|
||||
BackgroundColor="{DynamicResource BackgroundColor}">
|
||||
ColumnSpacing="0">
|
||||
|
||||
<Grid.Resources>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
<ListView
|
||||
ItemsSource="{Binding AccountViews}"
|
||||
ItemSelected="AccountRow_Selected"
|
||||
BackgroundColor="Transparent"
|
||||
VerticalOptions="FillAndExpand"
|
||||
RowHeight="60">
|
||||
<ListView.ItemTemplate>
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
<ListView
|
||||
ItemsSource="{Binding AccountViews}"
|
||||
ItemSelected="AccountRow_Selected"
|
||||
BackgroundColor="Transparent"
|
||||
VerticalOptions="FillAndExpand"
|
||||
RowHeight="60">
|
||||
<ListView.ItemTemplate>
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
<ListView
|
||||
ItemsSource="{Binding AccountViews}"
|
||||
ItemSelected="AccountRow_Selected"
|
||||
BackgroundColor="Transparent"
|
||||
VerticalOptions="FillAndExpand"
|
||||
RowHeight="60">
|
||||
<ListView.ItemTemplate>
|
||||
|
||||
@@ -182,6 +182,7 @@
|
||||
<ListView
|
||||
ItemsSource="{Binding AccountViews}"
|
||||
ItemSelected="AccountRow_Selected"
|
||||
BackgroundColor="Transparent"
|
||||
VerticalOptions="FillAndExpand"
|
||||
RowHeight="60">
|
||||
<ListView.ItemTemplate>
|
||||
|
||||
@@ -10,6 +10,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
@@ -137,11 +138,15 @@ namespace Bit.App.Pages
|
||||
{
|
||||
get
|
||||
{
|
||||
// create a separate collection that includes the "add new" row
|
||||
var accounts = new ExtendedObservableCollection<AccountView>();
|
||||
accounts.AddRange(_stateService.AccountViews);
|
||||
accounts.Add(new AccountView());
|
||||
return accounts;
|
||||
if (_stateService.AccountViews.Count < Constants.MaxAccounts)
|
||||
{
|
||||
// create a separate collection that includes the "add new" row
|
||||
var accountViews = new ExtendedObservableCollection<AccountView>();
|
||||
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<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
|
||||
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
|
||||
Task<SymmetricCryptoKey> GetKeyAsync();
|
||||
Task<SymmetricCryptoKey> GetKeyAsync(string userId = null);
|
||||
Task<string> GetKeyHashAsync();
|
||||
Task<SymmetricCryptoKey> GetOrgKeyAsync(string orgId);
|
||||
Task<Dictionary<string, SymmetricCryptoKey>> GetOrgKeysAsync();
|
||||
@@ -34,7 +34,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key);
|
||||
Task<bool> HasEncKeyAsync();
|
||||
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<SymmetricCryptoKey> MakeKeyAsync(string password, 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
|
||||
{
|
||||
Task CheckVaultTimeoutAsync();
|
||||
Task<bool> ShouldTimeoutAsync(string userId = null);
|
||||
Task ExecuteTimeoutActionAsync(string userId = null);
|
||||
Task ClearAsync(string userId = null);
|
||||
Task<bool> IsLockedAsync(string userId = null);
|
||||
Task<Tuple<bool, bool>> IsPinLockSetAsync();
|
||||
Task<bool> IsBiometricLockSetAsync();
|
||||
Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null);
|
||||
Task<bool> IsBiometricLockSetAsync(string userId = null);
|
||||
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null);
|
||||
Task LogOutAsync(bool userInitiated = true, string userId = null);
|
||||
Task SetVaultTimeoutOptionsAsync(int? timeout, string action);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const int MaxAccounts = 5;
|
||||
public const string AndroidAppProtocol = "androidapp://";
|
||||
public const string iOSAppProtocol = "iosapp://";
|
||||
public static string StateKey = "state";
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace Bit.Core.Models.Domain
|
||||
{
|
||||
public class Account : Domain
|
||||
{
|
||||
public AuthenticationStatus? AuthStatus;
|
||||
public AccountProfile Profile;
|
||||
public AccountTokens Tokens;
|
||||
public AccountSettings Settings;
|
||||
@@ -24,7 +23,6 @@ namespace Bit.Core.Models.Domain
|
||||
public Account(Account account)
|
||||
{
|
||||
// Copy constructor excludes Keys (for storage)
|
||||
AuthStatus = account.AuthStatus;
|
||||
Profile = new AccountProfile(account.Profile);
|
||||
Tokens = new AccountTokens(account.Tokens);
|
||||
Settings = new AccountSettings(account.Settings);
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Bit.Core.Models.View
|
||||
return;
|
||||
}
|
||||
IsAccount = true;
|
||||
AuthStatus = a.AuthStatus;
|
||||
UserId = a.Profile?.UserId;
|
||||
Email = a.Profile?.Email;
|
||||
Hostname = a.Settings?.EnvironmentUrls?.Base;
|
||||
|
||||
@@ -80,18 +80,18 @@ namespace Bit.Core.Services
|
||||
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)
|
||||
{
|
||||
return inMemoryKey;
|
||||
}
|
||||
var key = await _stateService.GetKeyEncryptedAsync();
|
||||
var key = await _stateService.GetKeyEncryptedAsync(userId);
|
||||
if (key != null)
|
||||
{
|
||||
inMemoryKey = new SymmetricCryptoKey(Convert.FromBase64String(key));
|
||||
await _stateService.SetKeyDecryptedAsync(inMemoryKey);
|
||||
await _stateService.SetKeyDecryptedAsync(inMemoryKey, userId);
|
||||
}
|
||||
return inMemoryKey;
|
||||
}
|
||||
@@ -295,9 +295,9 @@ namespace Bit.Core.Services
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,12 +89,17 @@ namespace Bit.Core.Services
|
||||
{
|
||||
await CheckStateAsync();
|
||||
|
||||
AccountViews = new ExtendedObservableCollection<AccountView>();
|
||||
if (AccountViews == null)
|
||||
{
|
||||
AccountViews = new ExtendedObservableCollection<AccountView>();
|
||||
}
|
||||
AccountViews.Clear();
|
||||
var accountList = _state?.Accounts?.Values.ToList();
|
||||
if (accountList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
foreach (var account in accountList)
|
||||
{
|
||||
var accountView = new AccountView(account);
|
||||
@@ -104,7 +109,9 @@ namespace Bit.Core.Services
|
||||
}
|
||||
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;
|
||||
accountView.AuthStatus = action == "logOut" ? AuthenticationStatus.LoggedOut
|
||||
|
||||
@@ -54,10 +54,10 @@ namespace Bit.Core.Services
|
||||
|
||||
public async Task<bool> IsLockedAsync(string userId = null)
|
||||
{
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasKeyAsync(userId);
|
||||
if (hasKey)
|
||||
{
|
||||
var biometricSet = await IsBiometricLockSetAsync();
|
||||
var biometricSet = await IsBiometricLockSetAsync(userId);
|
||||
if (biometricSet && _stateService.BiometricLocked)
|
||||
{
|
||||
return true;
|
||||
@@ -73,16 +73,13 @@ namespace Bit.Core.Services
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var userId in await _stateService.GetUserIdsAsync())
|
||||
if (await ShouldTimeoutAsync())
|
||||
{
|
||||
if (userId != null && await ShouldLockAsync(userId))
|
||||
{
|
||||
await ExecuteTimeoutActionAsync(userId);
|
||||
}
|
||||
await ExecuteTimeoutActionAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ShouldLockAsync(string userId)
|
||||
public async Task<bool> ShouldTimeoutAsync(string userId = null)
|
||||
{
|
||||
var authed = await _stateService.IsAuthenticatedAsync(userId);
|
||||
if (!authed)
|
||||
@@ -108,7 +105,7 @@ namespace Bit.Core.Services
|
||||
return diffMs >= vaultTimeoutMs;
|
||||
}
|
||||
|
||||
private async Task ExecuteTimeoutActionAsync(string userId)
|
||||
public async Task ExecuteTimeoutActionAsync(string userId = null)
|
||||
{
|
||||
var action = await _stateService.GetVaultTimeoutActionAsync(userId);
|
||||
if (action == "logOut")
|
||||
@@ -130,8 +127,8 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
if (await _keyConnectorService.GetUsesKeyConnector()) {
|
||||
var pinSet = await IsPinLockSetAsync();
|
||||
var pinLock = (pinSet.Item1 && _stateService.GetPinProtectedAsync() != null) || pinSet.Item2;
|
||||
var pinSet = await IsPinLockSetAsync(userId);
|
||||
var pinLock = (pinSet.Item1 && _stateService.GetPinProtectedAsync(userId) != null) || pinSet.Item2;
|
||||
|
||||
if (!pinLock && !await IsBiometricLockSetAsync())
|
||||
{
|
||||
@@ -142,21 +139,14 @@ namespace Bit.Core.Services
|
||||
|
||||
if (allowSoftLock)
|
||||
{
|
||||
var biometricLocked = await IsBiometricLockSetAsync();
|
||||
_stateService.BiometricLocked = biometricLocked;
|
||||
if (biometricLocked)
|
||||
_stateService.BiometricLocked = await IsBiometricLockSetAsync();
|
||||
if (_stateService.BiometricLocked)
|
||||
{
|
||||
_messagingService.Send("locked", userInitiated);
|
||||
_lockedCallback?.Invoke(userInitiated);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (userId == null || userId == await _stateService.GetActiveUserIdAsync())
|
||||
{
|
||||
_searchService.ClearIndex();
|
||||
}
|
||||
|
||||
await Task.WhenAll(
|
||||
_cryptoService.ClearKeyAsync(userId),
|
||||
_cryptoService.ClearOrgKeysAsync(true, userId),
|
||||
@@ -187,16 +177,16 @@ namespace Bit.Core.Services
|
||||
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 pinProtectedKey = await _stateService.GetPinProtectedAsync();
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync(userId);
|
||||
var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId);
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user