using System; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; namespace Bit.Core.Services { public enum PinLockType { Disabled, Persistent, Transient } public class VaultTimeoutService : IVaultTimeoutService { private readonly ICryptoService _cryptoService; private readonly IStateService _stateService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IFolderService _folderService; private readonly ICipherService _cipherService; private readonly ICollectionService _collectionService; private readonly ISearchService _searchService; private readonly ITokenService _tokenService; private readonly IUserVerificationService _userVerificationService; private readonly Func, Task> _lockedCallback; private readonly Func, Task> _loggedOutCallback; public VaultTimeoutService( ICryptoService cryptoService, IStateService stateService, IPlatformUtilsService platformUtilsService, IFolderService folderService, ICipherService cipherService, ICollectionService collectionService, ISearchService searchService, ITokenService tokenService, IUserVerificationService userVerificationService, Func, Task> lockedCallback, Func, Task> loggedOutCallback) { _cryptoService = cryptoService; _stateService = stateService; _platformUtilsService = platformUtilsService; _folderService = folderService; _cipherService = cipherService; _collectionService = collectionService; _searchService = searchService; _tokenService = tokenService; _userVerificationService = userVerificationService; _lockedCallback = lockedCallback; _loggedOutCallback = loggedOutCallback; } public long? DelayLockAndLogoutMs { get; set; } /// /// Determine if the current or provided account is locked. /// /// /// Optional specified user, must be provided if not the current account. /// public async Task IsLockedAsync(string userId = null) { // If biometrics are used, we can use the flag to determine locked state taking into account the auto unlock key for vault timeout never. var biometricSet = await IsBiometricLockSetAsync(userId); var hasAutoUnlockKey = await _cryptoService.HasAutoUnlockKeyAsync(userId); if (biometricSet && await _stateService.GetBiometricLockedAsync(userId) && !hasAutoUnlockKey) { return true; } if (!await _cryptoService.HasUserKeyAsync(userId)) { try { // Filter out accounts without auto key if (!hasAutoUnlockKey) { return true; } // Inactive accounts with an auto key aren't locked, but we shouldn't set user key if (userId != null && await _stateService.GetActiveUserIdAsync() != userId) { return false; } await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), userId); } catch (LegacyUserException) { // Legacy users must migrate on web vault before login await LogOutAsync(false, userId); } } // Check again to verify auto key was set var hasKey = await _cryptoService.HasUserKeyAsync(userId); return !hasKey; } public async Task ShouldLockAsync(string userId = null) { return await ShouldTimeoutAsync(userId) && await _stateService.GetVaultTimeoutActionAsync(userId) == VaultTimeoutAction.Lock; } public async Task IsLoggedOutByTimeoutAsync(string userId = null) { var authed = await _stateService.IsAuthenticatedAsync(userId); var email = await _stateService.GetEmailAsync(userId); return !authed && !string.IsNullOrWhiteSpace(email); } public async Task ShouldLogOutByTimeoutAsync(string userId = null) { return await ShouldTimeoutAsync(userId) && await _stateService.GetVaultTimeoutActionAsync(userId) == VaultTimeoutAction.Logout; } public async Task CheckVaultTimeoutAsync() { if (_platformUtilsService.IsViewOpen()) { return; } if (await ShouldTimeoutAsync()) { await ExecuteTimeoutActionAsync(); } } public async Task ShouldTimeoutAsync(string userId = null) { var authed = await _stateService.IsAuthenticatedAsync(userId); if (!authed) { return false; } var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync(userId); if (vaultTimeoutAction == VaultTimeoutAction.Lock && await IsLockedAsync(userId)) { return false; } var vaultTimeoutMinutes = await GetVaultTimeout(userId); if (vaultTimeoutMinutes < 0 || vaultTimeoutMinutes == null) { return false; } if (vaultTimeoutMinutes == 0 && !DelayLockAndLogoutMs.HasValue) { return true; } var lastActiveTime = await _stateService.GetLastActiveTimeAsync(userId); if (lastActiveTime == null) { return false; } var diffMs = _platformUtilsService.GetActiveTime() - lastActiveTime; if (DelayLockAndLogoutMs.HasValue && diffMs < DelayLockAndLogoutMs) { return false; } var vaultTimeoutMs = vaultTimeoutMinutes * 60000; return diffMs >= vaultTimeoutMs; } public async Task ExecuteTimeoutActionAsync(string userId = null) { var action = await _stateService.GetVaultTimeoutActionAsync(userId); if (action == VaultTimeoutAction.Logout) { await LogOutAsync(false, userId); } else { await LockAsync(true, false, userId); } } public async Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null) { var authed = await _stateService.IsAuthenticatedAsync(userId); if (!authed) { return; } var isActiveAccount = await _stateService.IsActiveAccountAsync(userId); if (userId == null) { userId = await _stateService.GetActiveUserIdAsync(); } if (!await _userVerificationService.HasMasterPasswordAsync()) { var pinStatus = await GetPinLockTypeAsync(userId); var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync() ?? await _stateService.GetPinProtectedKeyAsync(); var pinEnabled = (pinStatus == PinLockType.Transient && ephemeralPinSet != null) || pinStatus == PinLockType.Persistent; if (!pinEnabled && !await IsBiometricLockSetAsync()) { await LogOutAsync(userInitiated, userId); return; } } if (allowSoftLock) { var isBiometricLockSet = await IsBiometricLockSetAsync(userId); await _stateService.SetBiometricLockedAsync(isBiometricLockSet, userId); if (isBiometricLockSet) { _lockedCallback?.Invoke(new Tuple(userId, userInitiated)); return; } } await Task.WhenAll( _cryptoService.ClearUserKeyAsync(userId), _cryptoService.ClearMasterKeyAsync(userId), _stateService.SetUserKeyAutoUnlockAsync(null, userId), _cryptoService.ClearOrgKeysAsync(true, userId), _cryptoService.ClearKeyPairAsync(true, userId)); if (isActiveAccount) { _folderService.ClearCache(); await _cipherService.ClearCacheAsync(); _collectionService.ClearCache(); _searchService.ClearIndex(); } _lockedCallback?.Invoke(new Tuple(userId, userInitiated)); } public async Task LogOutAsync(bool userInitiated = true, string userId = null) { if (_loggedOutCallback != null) { await _loggedOutCallback.Invoke(new Tuple(userId, userInitiated, false)); } } public async Task SetVaultTimeoutOptionsAsync(int? timeout, VaultTimeoutAction? action) { await _stateService.SetVaultTimeoutAsync(timeout); await _stateService.SetVaultTimeoutActionAsync(action); await _cryptoService.RefreshKeysAsync(); await _tokenService.ToggleTokensAsync(); } public async Task GetPinLockTypeAsync(string userId = null) { // we can't depend on only the protected pin being set because old // versions only used it for MP on Restart var isPinEnabled = await _stateService.GetProtectedPinAsync(userId) != null; var hasUserKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync(userId) != null; var hasOldUserKeyPin = await _stateService.GetPinProtectedAsync(userId) != null; if (hasUserKeyPin || hasOldUserKeyPin) { return PinLockType.Persistent; } else if (isPinEnabled && !hasUserKeyPin && !hasOldUserKeyPin) { return PinLockType.Transient; } return PinLockType.Disabled; } public async Task IsBiometricLockSetAsync(string userId = null) { var biometricLock = await _stateService.GetBiometricUnlockAsync(userId); return biometricLock.GetValueOrDefault(); } public async Task ClearAsync(string userId = null) { await _cryptoService.ClearPinKeysAsync(userId); } public async Task GetVaultTimeout(string userId = null) { return await _stateService.GetVaultTimeoutAsync(userId); } public async Task GetVaultTimeoutAction(string userId = null) { return await _stateService.GetVaultTimeoutActionAsync(userId); } } }