support for per-user biometric state tracking (#1820)

This commit is contained in:
Matt Portune 2022-03-01 14:04:17 -05:00 committed by GitHub
parent 2076c11cbd
commit 34d0ecf64b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 43 additions and 26 deletions

View File

@ -374,7 +374,7 @@ namespace Bit.App.Pages
page.MasterPasswordEntry.Focus();
}
});
_stateService.BiometricLocked = !success;
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{
await DoContinueAsync();
@ -393,7 +393,7 @@ namespace Bit.App.Pages
private async Task DoContinueAsync()
{
_stateService.BiometricLocked = false;
await _stateService.SetBiometricLockedAsync(false);
_messagingService.Send("unlocked");
UnlockedAction?.Invoke();
}

View File

@ -393,7 +393,7 @@ namespace Bit.App.Pages
{
await _stateService.SetBiometricUnlockAsync(null);
}
_stateService.BiometricLocked = false;
await _stateService.SetBiometricLockedAsync(false);
await _cryptoService.ToggleKeyAsync();
BuildList();
}

View File

@ -476,7 +476,6 @@ namespace Bit.App.Utilities
policyService.ClearAsync(userId),
stateService.LogoutAccountAsync(userId, userInitiated));
stateService.BiometricLocked = true;
searchService.ClearIndex();
// check if we switched accounts automatically

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -445,7 +445,7 @@ namespace Bit.Core.Services
}
_stateService.BiometricLocked = false;
await _stateService.SetBiometricLockedAsync(false);
_messagingService.Send("loggedIn");
return result;
}

View File

@ -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)

View File

@ -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);

View File

@ -314,7 +314,7 @@ namespace Bit.iOS.Core.Controllers
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder());
_stateService.BiometricLocked = !success;
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{
DoContinue();
@ -356,7 +356,7 @@ namespace Bit.iOS.Core.Controllers
await _stateService.SetPasswordVerifiedAutofillAsync(true);
}
await EnableBiometricsIfNeeded();
_stateService.BiometricLocked = false;
await _stateService.SetBiometricLockedAsync(false);
MasterPasswordCell.TextField.ResignFirstResponder();
Success();
}