1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-09-28 03:57:43 +02:00

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

View File

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

View File

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

View File

@ -10,7 +10,6 @@ namespace Bit.Core.Abstractions
{ {
public interface IStateService public interface IStateService
{ {
bool BiometricLocked { get; set; }
List<AccountView> AccountViews { get; } List<AccountView> AccountViews { get; }
Task<string> GetActiveUserIdAsync(); Task<string> GetActiveUserIdAsync();
Task SetActiveUserAsync(string userId); Task SetActiveUserAsync(string userId);
@ -24,6 +23,8 @@ namespace Bit.Core.Abstractions
Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null); Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null);
Task<bool?> GetBiometricUnlockAsync(string userId = null); Task<bool?> GetBiometricUnlockAsync(string userId = null);
Task SetBiometricUnlockAsync(bool? value, 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<bool> CanAccessPremiumAsync(string userId = null);
Task<string> GetProtectedPinAsync(string userId = null); Task<string> GetProtectedPinAsync(string userId = null);
Task SetProtectedPinAsync(string value, 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 AccountProfile Profile;
public AccountTokens Tokens; public AccountTokens Tokens;
public AccountSettings Settings; public AccountSettings Settings;
public AccountKeys Keys; public AccountVolatileData VolatileData;
public Account() { } public Account() { }
@ -17,12 +17,12 @@ namespace Bit.Core.Models.Domain
Profile = profile; Profile = profile;
Tokens = tokens; Tokens = tokens;
Settings = new AccountSettings(); Settings = new AccountSettings();
Keys = new AccountKeys(); VolatileData = new AccountVolatileData();
} }
public Account(Account account) public Account(Account account)
{ {
// Copy constructor excludes Keys (for storage) // Copy constructor excludes VolatileData (for storage)
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);
@ -101,10 +101,11 @@ namespace Bit.Core.Models.Domain
public VaultTimeoutAction? VaultTimeoutAction; public VaultTimeoutAction? VaultTimeoutAction;
} }
public class AccountKeys public class AccountVolatileData
{ {
public SymmetricCryptoKey Key; public SymmetricCryptoKey Key;
public EncString PinProtectedKey; 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"); _messagingService.Send("loggedIn");
return result; return result;
} }

View File

@ -21,8 +21,6 @@ namespace Bit.Core.Services
private State _state; private State _state;
private bool _migrationChecked; private bool _migrationChecked;
public bool BiometricLocked { get; set; } = true;
public List<AccountView> AccountViews { get; set; } public List<AccountView> AccountViews { get; set; }
public StateService(IStorageService storageService, IStorageService secureStorageService) public StateService(IStorageService storageService, IStorageService secureStorageService)
@ -205,6 +203,22 @@ namespace Bit.Core.Services
await SetValueAsync(key, value, reconciledOptions); 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) public async Task<bool> CanAccessPremiumAsync(string userId = null)
{ {
if (userId == null) if (userId == null)
@ -264,7 +278,7 @@ namespace Bit.Core.Services
{ {
return (await GetAccountAsync( return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.Keys?.PinProtectedKey; ))?.VolatileData?.PinProtectedKey;
} }
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null) public async Task SetPinProtectedKeyAsync(EncString value, string userId = null)
@ -272,7 +286,7 @@ namespace Bit.Core.Services
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync()); await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions); var account = await GetAccountAsync(reconciledOptions);
account.Keys.PinProtectedKey = value; account.VolatileData.PinProtectedKey = value;
await SaveAccountAsync(account, reconciledOptions); await SaveAccountAsync(account, reconciledOptions);
} }
@ -328,7 +342,7 @@ namespace Bit.Core.Services
{ {
return (await GetAccountAsync( return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.Keys?.Key; ))?.VolatileData?.Key;
} }
public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null) public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null)
@ -336,7 +350,7 @@ namespace Bit.Core.Services
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync()); await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions); var account = await GetAccountAsync(reconciledOptions);
account.Keys.Key = value; account.VolatileData.Key = value;
await SaveAccountAsync(account, reconciledOptions); await SaveAccountAsync(account, reconciledOptions);
} }
@ -1207,9 +1221,9 @@ namespace Bit.Core.Services
// Memory // Memory
if (_state?.Accounts?.ContainsKey(options.UserId) ?? false) 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]; return _state.Accounts[options.UserId];
} }
@ -1218,9 +1232,9 @@ namespace Bit.Core.Services
_state = await GetStateFromStorageAsync(); _state = await GetStateFromStorageAsync();
if (_state?.Accounts?.ContainsKey(options.UserId) ?? false) 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]; return _state.Accounts[options.UserId];
} }
@ -1290,7 +1304,8 @@ namespace Bit.Core.Services
{ {
_state.Accounts[userId].Tokens.AccessToken = null; _state.Accounts[userId].Tokens.AccessToken = null;
_state.Accounts[userId].Tokens.RefreshToken = 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) if (userInitiated && _state?.ActiveUserId == userId)

View File

@ -60,7 +60,7 @@ namespace Bit.Core.Services
if (hasKey) if (hasKey)
{ {
var biometricSet = await IsBiometricLockSetAsync(userId); var biometricSet = await IsBiometricLockSetAsync(userId);
if (biometricSet && _stateService.BiometricLocked) if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
{ {
return true; return true;
} }
@ -158,8 +158,9 @@ namespace Bit.Core.Services
if (allowSoftLock) if (allowSoftLock)
{ {
_stateService.BiometricLocked = await IsBiometricLockSetAsync(); var isBiometricLockSet = await IsBiometricLockSetAsync(userId);
if (_stateService.BiometricLocked) await _stateService.SetBiometricLockedAsync(isBiometricLockSet, userId);
if (isBiometricLockSet)
{ {
_messagingService.Send("locked", userInitiated); _messagingService.Send("locked", userInitiated);
_lockedCallback?.Invoke(userInitiated); _lockedCallback?.Invoke(userInitiated);

View File

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