mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-22 11:35:21 +01:00
support for per-user biometric state tracking (#1820)
This commit is contained in:
parent
2076c11cbd
commit
34d0ecf64b
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user