mirror of
https://github.com/bitwarden/mobile
synced 2026-01-05 01:53:17 +00:00
support for per-user biometric state tracking (#1820)
This commit is contained in:
@@ -10,7 +10,6 @@ namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IStateService
|
||||
{
|
||||
bool BiometricLocked { get; set; }
|
||||
List<AccountView> AccountViews { get; }
|
||||
Task<string> GetActiveUserIdAsync();
|
||||
Task SetActiveUserAsync(string userId);
|
||||
@@ -24,6 +23,8 @@ namespace Bit.Core.Abstractions
|
||||
Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null);
|
||||
Task<bool?> GetBiometricUnlockAsync(string userId = null);
|
||||
Task SetBiometricUnlockAsync(bool? value, string userId = null);
|
||||
Task<bool> GetBiometricLockedAsync(string userId = null);
|
||||
Task SetBiometricLockedAsync(bool value, string userId = null);
|
||||
Task<bool> CanAccessPremiumAsync(string userId = null);
|
||||
Task<string> GetProtectedPinAsync(string userId = null);
|
||||
Task SetProtectedPinAsync(string value, string userId = null);
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Bit.Core.Models.Domain
|
||||
public AccountProfile Profile;
|
||||
public AccountTokens Tokens;
|
||||
public AccountSettings Settings;
|
||||
public AccountKeys Keys;
|
||||
public AccountVolatileData VolatileData;
|
||||
|
||||
public Account() { }
|
||||
|
||||
@@ -17,12 +17,12 @@ namespace Bit.Core.Models.Domain
|
||||
Profile = profile;
|
||||
Tokens = tokens;
|
||||
Settings = new AccountSettings();
|
||||
Keys = new AccountKeys();
|
||||
VolatileData = new AccountVolatileData();
|
||||
}
|
||||
|
||||
public Account(Account account)
|
||||
{
|
||||
// Copy constructor excludes Keys (for storage)
|
||||
// Copy constructor excludes VolatileData (for storage)
|
||||
Profile = new AccountProfile(account.Profile);
|
||||
Tokens = new AccountTokens(account.Tokens);
|
||||
Settings = new AccountSettings(account.Settings);
|
||||
@@ -101,10 +101,11 @@ namespace Bit.Core.Models.Domain
|
||||
public VaultTimeoutAction? VaultTimeoutAction;
|
||||
}
|
||||
|
||||
public class AccountKeys
|
||||
public class AccountVolatileData
|
||||
{
|
||||
public SymmetricCryptoKey Key;
|
||||
public EncString PinProtectedKey;
|
||||
public bool? BiometricLocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,7 +445,7 @@ namespace Bit.Core.Services
|
||||
|
||||
}
|
||||
|
||||
_stateService.BiometricLocked = false;
|
||||
await _stateService.SetBiometricLockedAsync(false);
|
||||
_messagingService.Send("loggedIn");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ namespace Bit.Core.Services
|
||||
private State _state;
|
||||
private bool _migrationChecked;
|
||||
|
||||
public bool BiometricLocked { get; set; } = true;
|
||||
|
||||
public List<AccountView> AccountViews { get; set; }
|
||||
|
||||
public StateService(IStorageService storageService, IStorageService secureStorageService)
|
||||
@@ -204,6 +202,22 @@ namespace Bit.Core.Services
|
||||
var key = Constants.BiometricUnlockKey(reconciledOptions.UserId);
|
||||
await SetValueAsync(key, value, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<bool> GetBiometricLockedAsync(string userId = null)
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.VolatileData?.BiometricLocked ?? true;
|
||||
}
|
||||
|
||||
public async Task SetBiometricLockedAsync(bool value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultInMemoryOptionsAsync());
|
||||
var account = await GetAccountAsync(reconciledOptions);
|
||||
account.VolatileData.BiometricLocked = value;
|
||||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<bool> CanAccessPremiumAsync(string userId = null)
|
||||
{
|
||||
@@ -264,7 +278,7 @@ namespace Bit.Core.Services
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.Keys?.PinProtectedKey;
|
||||
))?.VolatileData?.PinProtectedKey;
|
||||
}
|
||||
|
||||
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null)
|
||||
@@ -272,7 +286,7 @@ namespace Bit.Core.Services
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultInMemoryOptionsAsync());
|
||||
var account = await GetAccountAsync(reconciledOptions);
|
||||
account.Keys.PinProtectedKey = value;
|
||||
account.VolatileData.PinProtectedKey = value;
|
||||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
@@ -328,7 +342,7 @@ namespace Bit.Core.Services
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.Keys?.Key;
|
||||
))?.VolatileData?.Key;
|
||||
}
|
||||
|
||||
public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null)
|
||||
@@ -336,7 +350,7 @@ namespace Bit.Core.Services
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultInMemoryOptionsAsync());
|
||||
var account = await GetAccountAsync(reconciledOptions);
|
||||
account.Keys.Key = value;
|
||||
account.VolatileData.Key = value;
|
||||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
@@ -1207,9 +1221,9 @@ namespace Bit.Core.Services
|
||||
// Memory
|
||||
if (_state?.Accounts?.ContainsKey(options.UserId) ?? false)
|
||||
{
|
||||
if (_state.Accounts[options.UserId].Keys == null)
|
||||
if (_state.Accounts[options.UserId].VolatileData == null)
|
||||
{
|
||||
_state.Accounts[options.UserId].Keys = new Account.AccountKeys();
|
||||
_state.Accounts[options.UserId].VolatileData = new Account.AccountVolatileData();
|
||||
}
|
||||
return _state.Accounts[options.UserId];
|
||||
}
|
||||
@@ -1218,9 +1232,9 @@ namespace Bit.Core.Services
|
||||
_state = await GetStateFromStorageAsync();
|
||||
if (_state?.Accounts?.ContainsKey(options.UserId) ?? false)
|
||||
{
|
||||
if (_state.Accounts[options.UserId].Keys == null)
|
||||
if (_state.Accounts[options.UserId].VolatileData == null)
|
||||
{
|
||||
_state.Accounts[options.UserId].Keys = new Account.AccountKeys();
|
||||
_state.Accounts[options.UserId].VolatileData = new Account.AccountVolatileData();
|
||||
}
|
||||
return _state.Accounts[options.UserId];
|
||||
}
|
||||
@@ -1290,7 +1304,8 @@ namespace Bit.Core.Services
|
||||
{
|
||||
_state.Accounts[userId].Tokens.AccessToken = null;
|
||||
_state.Accounts[userId].Tokens.RefreshToken = null;
|
||||
_state.Accounts[userId].Keys.Key = null;
|
||||
_state.Accounts[userId].VolatileData.Key = null;
|
||||
_state.Accounts[userId].VolatileData.BiometricLocked = null;
|
||||
}
|
||||
}
|
||||
if (userInitiated && _state?.ActiveUserId == userId)
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Bit.Core.Services
|
||||
if (hasKey)
|
||||
{
|
||||
var biometricSet = await IsBiometricLockSetAsync(userId);
|
||||
if (biometricSet && _stateService.BiometricLocked)
|
||||
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -158,8 +158,9 @@ namespace Bit.Core.Services
|
||||
|
||||
if (allowSoftLock)
|
||||
{
|
||||
_stateService.BiometricLocked = await IsBiometricLockSetAsync();
|
||||
if (_stateService.BiometricLocked)
|
||||
var isBiometricLockSet = await IsBiometricLockSetAsync(userId);
|
||||
await _stateService.SetBiometricLockedAsync(isBiometricLockSet, userId);
|
||||
if (isBiometricLockSet)
|
||||
{
|
||||
_messagingService.Send("locked", userInitiated);
|
||||
_lockedCallback?.Invoke(userInitiated);
|
||||
|
||||
Reference in New Issue
Block a user