using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; using Bit.Core.Models.View; using Bit.Core.Utilities; using BwRegion = Bit.Core.Enums.Region; namespace Bit.Core.Services { public class StateService : IStateService { // TODO: Refactor this removing all storage services and use the IStorageMediatorService instead private readonly IStorageService _storageService; private readonly IStorageService _secureStorageService; private readonly IStorageMediatorService _storageMediatorService; private readonly IMessagingService _messagingService; private State _state; private bool _migrationChecked; public List AccountViews { get; set; } public StateService(IStorageService storageService, IStorageService secureStorageService, IStorageMediatorService storageMediatorService, IMessagingService messagingService) { _storageService = storageService; _secureStorageService = secureStorageService; _storageMediatorService = storageMediatorService; _messagingService = messagingService; } public async Task GetActiveUserIdAsync() { await CheckStateAsync(); var activeUserId = _state?.ActiveUserId; if (activeUserId == null) { var state = await GetStateFromStorageAsync(); activeUserId = state?.ActiveUserId; } return activeUserId; } public async Task GetActiveUserEmailAsync() { var activeUserId = await GetActiveUserIdAsync(); return await GetEmailAsync(activeUserId); } public async Task GetActiveUserCustomDataAsync(Func dataMapper) { var userId = await GetActiveUserIdAsync(); var account = await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ); return dataMapper(account); } public async Task IsActiveAccountAsync(string userId = null) { if (userId == null) { return true; } return userId == await GetActiveUserIdAsync(); } public async Task SetActiveUserAsync(string userId) { if (userId != null) { await ValidateUserAsync(userId); } await CheckStateAsync(); var state = await GetStateFromStorageAsync(); state.ActiveUserId = userId; await SaveStateToStorageAsync(state); _state.ActiveUserId = userId; // Update pre-auth settings based on now-active user await SetRememberedOrgIdentifierAsync(await GetRememberedOrgIdentifierAsync()); await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync()); await SetLastUserShouldConnectToWatchAsync(); } public async Task CheckExtensionActiveUserAndSwitchIfNeededAsync() { var extensionUserId = await GetExtensionActiveUserIdFromStorageAsync(); if (string.IsNullOrEmpty(extensionUserId)) { return; } if (_state?.ActiveUserId == extensionUserId) { // Clear the value in case the user changes the active user from the app // so if that happens and the user sends the app to background and comes back // the user is not changed again. await SaveExtensionActiveUserIdToStorageAsync(null); return; } await SetActiveUserAsync(extensionUserId); await SaveExtensionActiveUserIdToStorageAsync(null); _messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT); } public async Task IsAuthenticatedAsync(string userId = null) { return await GetAccessTokenAsync(userId) != null; } public async Task GetUserIdAsync(string email) { if (string.IsNullOrWhiteSpace(email)) { throw new ArgumentNullException(nameof(email)); } await CheckStateAsync(); if (_state?.Accounts != null) { foreach (var account in _state.Accounts) { var accountEmail = account.Value?.Profile?.Email; if (accountEmail == email) { return account.Value.Profile.UserId; } } } return null; } public async Task RefreshAccountViewsAsync(bool allowAddAccountRow) { await CheckStateAsync(); if (AccountViews == null) { AccountViews = new List(); } else { AccountViews.Clear(); } var accountList = _state?.Accounts?.Values.ToList(); if (accountList == null) { return; } var vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); foreach (var account in accountList) { var isActiveAccount = account.Profile.UserId == _state.ActiveUserId; var accountView = new AccountView(account, isActiveAccount); if (await vaultTimeoutService.IsLoggedOutByTimeoutAsync(accountView.UserId) || await vaultTimeoutService.ShouldLogOutByTimeoutAsync(accountView.UserId)) { accountView.AuthStatus = AuthenticationStatus.LoggedOut; } else if (await vaultTimeoutService.IsLockedAsync(accountView.UserId) || await vaultTimeoutService.ShouldLockAsync(accountView.UserId)) { accountView.AuthStatus = AuthenticationStatus.Locked; } else { accountView.AuthStatus = AuthenticationStatus.Unlocked; } AccountViews.Add(accountView); } if (allowAddAccountRow && AccountViews.Count < Constants.MaxAccounts) { AccountViews.Add(new AccountView()); } } public async Task AddAccountAsync(Account account) { await ScaffoldNewAccountAsync(account); await SetActiveUserAsync(account.Profile.UserId); await RefreshAccountViewsAsync(true); } public async Task LogoutAccountAsync(string userId, bool userInitiated) { if (string.IsNullOrWhiteSpace(userId)) { throw new ArgumentNullException(nameof(userId)); } await CheckStateAsync(); await RemoveAccountAsync(userId, userInitiated); // If user initiated logout (not vault timeout) and ActiveUserId is null after account removal, find the // next user to make active, if any if (userInitiated && _state?.ActiveUserId == null && _state?.Accounts != null) { foreach (var account in _state.Accounts) { var uid = account.Value?.Profile?.UserId; if (uid == null) { continue; } await SetActiveUserAsync(uid); break; } } } public async Task GetPreAuthEnvironmentUrlsAsync() { return await GetValueAsync( Constants.PreAuthEnvironmentUrlsKey, await GetDefaultStorageOptionsAsync()); } public async Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value) { await SetValueAsync( Constants.PreAuthEnvironmentUrlsKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetEnvironmentUrlsAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Settings?.EnvironmentUrls; } public async Task GetUserKeyBiometricUnlockAsync(string userId = null) { var keyB64 = await _storageMediatorService.GetAsync( await ComposeKeyAsync(Constants.UserKeyBiometricUnlockKey, userId), true); return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64)); } public async Task SetUserKeyBiometricUnlockAsync(UserKey value, string userId = null) { await _storageMediatorService.SaveAsync( await ComposeKeyAsync(Constants.UserKeyBiometricUnlockKey, userId), value?.KeyB64, true); } public async Task GetBiometricUnlockAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.BiometricUnlockKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetBiometricUnlockAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.BiometricUnlockKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task 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 GetSystemBiometricIntegrityState(string bioIntegritySrcKey) { return await GetValueAsync(bioIntegritySrcKey, await GetDefaultStorageOptionsAsync()); } public async Task SetSystemBiometricIntegrityState(string bioIntegritySrcKey, string systemBioIntegrityState) { await SetValueAsync(bioIntegritySrcKey, systemBioIntegrityState, await GetDefaultStorageOptionsAsync()); } public async Task IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null) { var systemBioIntegrityState = await GetSystemBiometricIntegrityState(bioIntegritySrcKey); var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync( Constants.AccountBiometricIntegrityValidKey(reconciledOptions.UserId, systemBioIntegrityState), reconciledOptions) ?? false; } public async Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null) { var systemBioIntegrityState = await GetSystemBiometricIntegrityState(bioIntegritySrcKey); var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync( Constants.AccountBiometricIntegrityValidKey(reconciledOptions.UserId, systemBioIntegrityState), true, reconciledOptions); } public async Task GetUserKeyAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ))?.VolatileData?.UserKey; } public async Task SetUserKeyAsync(UserKey value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.VolatileData.UserKey = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetMasterKeyAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ))?.VolatileData?.MasterKey; } public async Task SetMasterKeyAsync(MasterKey value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.VolatileData.MasterKey = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetMasterKeyEncryptedUserKeyAsync(string userId = null) { return await _storageMediatorService.GetAsync( await ComposeKeyAsync(Constants.MasterKeyEncryptedUserKeyKey, userId), false); } public async Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null) { await _storageMediatorService.SaveAsync( await ComposeKeyAsync(Constants.MasterKeyEncryptedUserKeyKey, userId), value, false); } public async Task GetUserKeyAutoUnlockAsync(string userId = null) { var keyB64 = await _storageMediatorService.GetAsync( await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), true); return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64)); } public async Task SetUserKeyAutoUnlockAsync(UserKey value, string userId = null) { await _storageMediatorService.SaveAsync( await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), value?.KeyB64, true); } public async Task CanAccessPremiumAsync(string userId = null) { if (userId == null) { userId = await GetActiveUserIdAsync(); } if (!await IsAuthenticatedAsync(userId)) { return false; } var account = await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())); if (account?.Profile?.HasPremiumPersonally.GetValueOrDefault() ?? false) { return true; } var organizationService = ServiceContainer.Resolve("organizationService"); var organizations = await organizationService.GetAllAsync(userId); return organizations?.Any(o => o.UsersGetPremium && o.Enabled) ?? false; } public async Task SetPersonalPremiumAsync(bool value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); if (account?.Profile == null || account.Profile.HasPremiumPersonally.GetValueOrDefault() == value) { return; } account.Profile.HasPremiumPersonally = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetProtectedPinAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetProtectedPinAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetPinKeyEncryptedUserKeyAsync(string userId = null) { var key = await _storageMediatorService.GetAsync( await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), false); return key != null ? new EncString(key) : null; } public async Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null) { await _storageMediatorService.SaveAsync( await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), value?.EncryptedString, false); } public async Task GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ))?.VolatileData?.PinKeyEncryptedUserKeyEphemeral; } public async Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.VolatileData.PinKeyEncryptedUserKeyEphemeral = value; await SaveAccountAsync(account, reconciledOptions); } public async Task SetKdfConfigurationAsync(KdfConfig config, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Profile.KdfType = config.Type; account.Profile.KdfIterations = config.Iterations; account.Profile.KdfMemory = config.Memory; account.Profile.KdfParallelism = config.Parallelism; await SaveAccountAsync(account, reconciledOptions); } public async Task GetKeyHashAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.KeyHashKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetKeyHashAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.KeyHashKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task> GetOrgKeysEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>(Constants.EncOrgKeysKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetOrgKeysEncryptedAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.EncOrgKeysKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetPrivateKeyEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetPrivateKeyEncryptedAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetDeviceKeyAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var deviceKeyB64 = await _storageMediatorService.GetAsync(Constants.DeviceKeyKey(reconciledOptions.UserId), true); if (string.IsNullOrEmpty(deviceKeyB64)) { return null; } return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64)); } public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(reconciledOptions.UserId), value?.KeyB64, true); } public async Task> GetAutofillBlacklistedUrisAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>(Constants.AutofillBlacklistedUrisKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetAutofillBlacklistedUrisAsync(List value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.AutofillBlacklistedUrisKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetAutofillTileAddedAsync() { return await GetValueAsync(Constants.AutofillTileAddedKey, await GetDefaultStorageOptionsAsync()); } public async Task SetAutofillTileAddedAsync(bool? value) { await SetValueAsync(Constants.AutofillTileAddedKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetEmailAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.Email; } public async Task GetNameAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.Name; } public async Task SetNameAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Profile.Name = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetOrgIdentifierAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.OrgIdentifier; } public async Task GetLastActiveTimeAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.LastActiveTimeKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetLastActiveTimeAsync(long? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.LastActiveTimeKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetVaultTimeoutAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.VaultTimeoutKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetVaultTimeoutAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.VaultTimeoutKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetVaultTimeoutActionAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.VaultTimeoutActionKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.VaultTimeoutActionKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetScreenCaptureAllowedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.ScreenCaptureAllowedKey(reconciledOptions.UserId), reconciledOptions) ?? CoreHelpers.InDebugMode(); } public async Task SetScreenCaptureAllowedAsync(bool value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.ScreenCaptureAllowedKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetLastFileCacheClearAsync() { return await GetValueAsync(Constants.LastFileCacheClearKey, await GetDefaultStorageOptionsAsync()); } public async Task SetLastFileCacheClearAsync(DateTime? value) { await SetValueAsync(Constants.LastFileCacheClearKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetPreviousPageInfoAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.PreviousPageKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetPreviousPageInfoAsync(PreviousPageInfo value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PreviousPageKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetInvalidUnlockAttemptsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.InvalidUnlockAttemptsKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetInvalidUnlockAttemptsAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.InvalidUnlockAttemptsKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetLastBuildAsync() { return await GetValueAsync(Constants.LastBuildKey, await GetDefaultStorageOptionsAsync()); } public async Task SetLastBuildAsync(string value) { await SetValueAsync(Constants.LastBuildKey, value, await GetDefaultStorageOptionsAsync()); } // TODO: [PS-961] Fix negative function names public async Task GetDisableFaviconAsync() { return await GetValueAsync(Constants.DisableFaviconKey, await GetDefaultStorageOptionsAsync()); } // TODO: [PS-961] Fix negative function names public async Task SetDisableFaviconAsync(bool? value) { await SetValueAsync(Constants.DisableFaviconKey, value, await GetDefaultStorageOptionsAsync()); } // TODO: [PS-961] Fix negative function names public async Task GetDisableAutoTotpCopyAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId), reconciledOptions); } // TODO: [PS-961] Fix negative function names public async Task SetDisableAutoTotpCopyAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetInlineAutofillEnabledAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.InlineAutofillEnabledKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetInlineAutofillEnabledAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.InlineAutofillEnabledKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetAutofillDisableSavePromptAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.AutofillDisableSavePromptKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetAutofillDisableSavePromptAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.AutofillDisableSavePromptKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task>> GetCiphersLocalDataAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>>( Constants.CiphersLocalDataKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetCiphersLocalDataAsync(Dictionary> value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.CiphersLocalDataKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task> GetEncryptedCiphersAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>(Constants.CiphersKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetEncryptedCiphersAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.CiphersKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetDefaultUriMatchAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.DefaultUriMatchKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetDefaultUriMatchAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.DefaultUriMatchKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetClearClipboardAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.ClearClipboardKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetClearClipboardAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.ClearClipboardKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task> GetEncryptedCollectionsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>( Constants.CollectionsKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetEncryptedCollectionsAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.CollectionsKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetPasswordRepromptAutofillAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.PasswordRepromptAutofillKey(reconciledOptions.UserId), reconciledOptions) ?? false; } public async Task SetPasswordRepromptAutofillAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PasswordRepromptAutofillKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetPasswordVerifiedAutofillAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.PasswordVerifiedAutofillKey(reconciledOptions.UserId), reconciledOptions) ?? false; } public async Task SetPasswordVerifiedAutofillAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PasswordVerifiedAutofillKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetLastSyncAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.LastSyncKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetLastSyncAsync(DateTime? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.LastSyncKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetSecurityStampAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.Stamp; } public async Task SetSecurityStampAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Profile.Stamp = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetEmailVerifiedAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.EmailVerified ?? false; } public async Task SetEmailVerifiedAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Profile.EmailVerified = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetSyncOnRefreshAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.SyncOnRefreshKey(reconciledOptions.UserId), reconciledOptions) ?? false; } public async Task SetSyncOnRefreshAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.SyncOnRefreshKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetRememberedEmailAsync() { return await GetValueAsync(Constants.RememberedEmailKey, await GetDefaultStorageOptionsAsync()); } public async Task SetRememberedEmailAsync(string value) { await SetValueAsync(Constants.RememberedEmailKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetRememberedOrgIdentifierAsync() { return await GetValueAsync(Constants.RememberedOrgIdentifierKey, await GetDefaultStorageOptionsAsync()); } public async Task SetRememberedOrgIdentifierAsync(string value) { await SetValueAsync(Constants.RememberedOrgIdentifierKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetThemeAsync() { return await GetValueAsync(Constants.ThemeKey, await GetDefaultStorageOptionsAsync()); } public async Task SetThemeAsync(string value) { await SetValueAsync(Constants.ThemeKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetAutoDarkThemeAsync() { return await GetValueAsync(Constants.AutoDarkThemeKey, await GetDefaultStorageOptionsAsync()); } public async Task SetAutoDarkThemeAsync(string value) { await SetValueAsync(Constants.AutoDarkThemeKey, value, await GetDefaultStorageOptionsAsync()); } public string GetLocale() { return _storageMediatorService.Get(Constants.AppLocaleKey); } public void SetLocale(string locale) { _storageMediatorService.Save(Constants.AppLocaleKey, locale); } public async Task GetAddSitePromptShownAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.AddSitePromptShownKey, reconciledOptions); } public async Task SetAddSitePromptShownAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.AddSitePromptShownKey, value, reconciledOptions); } public async Task GetPushInitialPromptShownAsync() { return await GetValueAsync(Constants.PushInitialPromptShownKey, await GetDefaultStorageOptionsAsync()); } public async Task SetPushInitialPromptShownAsync(bool? value) { await SetValueAsync(Constants.PushInitialPromptShownKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetPushLastRegistrationDateAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.PushLastRegistrationDateKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetPushLastRegistrationDateAsync(DateTime? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PushLastRegistrationDateKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetPushInstallationRegistrationErrorAsync() { return await GetValueAsync(Constants.PushInstallationRegistrationErrorKey, await GetDefaultStorageOptionsAsync()); } public async Task SetPushInstallationRegistrationErrorAsync(string value) { await SetValueAsync(Constants.PushInstallationRegistrationErrorKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetPushCurrentTokenAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.PushCurrentTokenKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetPushCurrentTokenAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PushCurrentTokenKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task> GetEventCollectionAsync() { return await GetValueAsync>(Constants.EventCollectionKey, await GetDefaultStorageOptionsAsync()); } public async Task SetEventCollectionAsync(List value) { await SetValueAsync(Constants.EventCollectionKey, value, await GetDefaultStorageOptionsAsync()); } public async Task> GetEncryptedFoldersAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>(Constants.FoldersKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetEncryptedFoldersAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.FoldersKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task> GetEncryptedPoliciesAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>(Constants.PoliciesKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetEncryptedPoliciesAsync(Dictionary value, string userId) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PoliciesKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetPushRegisteredTokenAsync() { return await GetValueAsync(Constants.PushRegisteredTokenKey, await GetDefaultStorageOptionsAsync()); } public async Task SetPushRegisteredTokenAsync(string value) { await SetValueAsync(Constants.PushRegisteredTokenKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetUsesKeyConnectorAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.UsesKeyConnectorKey(reconciledOptions.UserId), reconciledOptions) ?? false; } public async Task SetUsesKeyConnectorAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.UsesKeyConnectorKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetForcePasswordResetReasonAsync(string userId = null) { var reconcileOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return (await GetAccountAsync(reconcileOptions))?.Profile?.ForcePasswordResetReason; } public async Task SetForcePasswordResetReasonAsync(ForcePasswordResetReason? value, string userId = null) { var reconcileOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconcileOptions); account.Profile.ForcePasswordResetReason = value; await SaveAccountAsync(account, reconcileOptions); } public async Task> GetOrganizationsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>( Constants.OrganizationsKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetOrganizationsAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.OrganizationsKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetPasswordGenerationOptionsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.PassGenOptionsKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetPasswordGenerationOptionsAsync(PasswordGenerationOptions value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PassGenOptionsKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetUsernameGenerationOptionsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync( Constants.UsernameGenOptionsKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetUsernameGenerationOptionsAsync(UsernameGenerationOptions value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.UsernameGenOptionsKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task> GetEncryptedPasswordGenerationHistory(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>( Constants.PassGenHistoryKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetEncryptedPasswordGenerationHistoryAsync(List value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PassGenHistoryKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task> GetEncryptedSendsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>(Constants.SendsKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetEncryptedSendsAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.SendsKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task> GetSettingsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>(Constants.SettingsKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetSettingsAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.SettingsKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetAccessTokenAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Tokens?.AccessToken; } public async Task SetAccessTokenAsync(string value, bool skipTokenStorage, string userId = null) { var reconciledOptions = ReconcileOptions( new StorageOptions { UserId = userId, SkipTokenStorage = skipTokenStorage }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Tokens.AccessToken = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetRefreshTokenAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Tokens?.RefreshToken; } public async Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null) { var reconciledOptions = ReconcileOptions( new StorageOptions { UserId = userId, SkipTokenStorage = skipTokenStorage }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Tokens.RefreshToken = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetTwoFactorTokenAsync(string email = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { Email = email }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.TwoFactorTokenKey(reconciledOptions.Email), reconciledOptions); } public async Task SetTwoFactorTokenAsync(string value, string email = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { Email = email }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.TwoFactorTokenKey(reconciledOptions.Email), value, reconciledOptions); } public async Task GetApprovePasswordlessLoginsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.ApprovePasswordlessLoginsKey(reconciledOptions.UserId), reconciledOptions) ?? false; } public async Task SetApprovePasswordlessLoginsAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.ApprovePasswordlessLoginsKey(reconciledOptions.UserId), value, reconciledOptions); } public async Task GetPasswordlessLoginNotificationAsync() { return await GetValueAsync(Constants.PasswordlessLoginNotificationKey, await GetDefaultStorageOptionsAsync()); } public async Task SetPasswordlessLoginNotificationAsync(PasswordlessRequestNotification value) { await SetValueAsync(Constants.PasswordlessLoginNotificationKey, value, await GetDefaultStorageOptionsAsync()); } public async Task SetAvatarColorAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Profile.AvatarColor = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetAvatarColorAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.AvatarColor; } public async Task GetPreLoginEmailAsync() { var options = await GetDefaultStorageOptionsAsync(); return await GetValueAsync(Constants.PreLoginEmailKey, options); } public async Task SetPreLoginEmailAsync(string value) { var options = await GetDefaultStorageOptionsAsync(); await SetValueAsync(Constants.PreLoginEmailKey, value, options); } public async Task GetAccountDecryptionOptions(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.UserDecryptionOptions; } public async Task GetShouldTrustDeviceAsync() { return await _storageMediatorService.GetAsync(Constants.ShouldTrustDevice); } public async Task SetShouldTrustDeviceAsync(bool value) { await _storageMediatorService.SaveAsync(Constants.ShouldTrustDevice, value); } public async Task GetPendingAdminAuthRequestAsync(string userId = null) { try { // GetAsync will throw an ArgumentException exception if there isn't a value to deserialize return await _storageMediatorService.GetAsync(await ComposeKeyAsync(Constants.PendingAdminAuthRequest, userId), true); } catch (ArgumentException) { return null; } } public async Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null) { await _storageMediatorService.SaveAsync(await ComposeKeyAsync(Constants.PendingAdminAuthRequest, userId), value, true); } public ConfigResponse GetConfigs() { return _storageMediatorService.Get(Constants.ConfigsKey); } public void SetConfigs(ConfigResponse value) { _storageMediatorService.Save(Constants.ConfigsKey, value); } public async Task SetUserHasMasterPasswordAsync(bool value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Profile.UserDecryptionOptions.HasMasterPassword = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetActiveUserRegionAsync() { return await GetActiveUserCustomDataAsync(a => a?.Settings?.Region); } public async Task GetPreAuthRegionAsync() { return await _storageMediatorService.GetAsync(Constants.RegionEnvironment); } public async Task SetPreAuthRegionAsync(BwRegion value) { await _storageMediatorService.SaveAsync(Constants.RegionEnvironment, value); } public async Task GetShouldCheckOrganizationUnassignedItemsAsync(string userId = null) { return await _storageMediatorService.GetAsync(await ComposeKeyAsync(Constants.ShouldCheckOrganizationUnassignedItemsKey, userId)) ?? true; } public async Task SetShouldCheckOrganizationUnassignedItemsAsync(bool shouldCheck, string userId = null) { await _storageMediatorService.SaveAsync(await ComposeKeyAsync(Constants.ShouldCheckOrganizationUnassignedItemsKey, userId), shouldCheck); } // Helpers [Obsolete("Use IStorageMediatorService instead")] private async Task GetValueAsync(string key, StorageOptions options) { return await GetStorageService(options).GetAsync(key); } [Obsolete("Use IStorageMediatorService instead")] private async Task SetValueAsync(string key, T value, StorageOptions options) { if (value == null) { await GetStorageService(options).RemoveAsync(key); return; } await GetStorageService(options).SaveAsync(key, value); } [Obsolete("Use IStorageMediatorService instead")] private IStorageService GetStorageService(StorageOptions options) { return options.UseSecureStorage.GetValueOrDefault(false) ? _secureStorageService : _storageService; } private async Task ComposeKeyAsync(Func userDependantKey, string userId = null) { return userDependantKey(userId ?? await GetActiveUserIdAsync()); } private async Task GetAccountAsync(StorageOptions options) { await CheckStateAsync(); if (options?.UserId == null) { return null; } // Memory if (_state?.Accounts?.ContainsKey(options.UserId) ?? false) { if (_state.Accounts[options.UserId].VolatileData == null) { _state.Accounts[options.UserId].VolatileData = new Account.AccountVolatileData(); } return _state.Accounts[options.UserId]; } // Storage var state = await GetStateFromStorageAsync(); if (state?.Accounts?.ContainsKey(options.UserId) ?? false) { state.Accounts[options.UserId].VolatileData = new Account.AccountVolatileData(); return state.Accounts[options.UserId]; } return null; } private async Task SaveAccountAsync(Account account, StorageOptions options = null) { if (account?.Profile?.UserId == null) { throw new Exception("account?.Profile?.UserId cannot be null"); } await CheckStateAsync(); // Memory if (UseMemory(options)) { if (_state.Accounts == null) { _state.Accounts = new Dictionary(); } _state.Accounts[account.Profile.UserId] = account; } // Storage if (UseDisk(options)) { var state = await GetStateFromStorageAsync() ?? new State(); if (state.Accounts == null) { state.Accounts = new Dictionary(); } // Use Account copy constructor to clone with keys excluded (for storage) state.Accounts[account.Profile.UserId] = new Account(account); // If we have a vault timeout and the action is log out, don't store token if (options?.SkipTokenStorage.GetValueOrDefault() ?? false) { state.Accounts[account.Profile.UserId].Tokens.AccessToken = null; state.Accounts[account.Profile.UserId].Tokens.RefreshToken = null; } await SaveStateToStorageAsync(state); } } private async Task RemoveAccountAsync(string userId, bool userInitiated) { if (string.IsNullOrWhiteSpace(userId)) { throw new ArgumentNullException(nameof(userId)); } var email = await GetEmailAsync(userId); // Memory if (_state?.Accounts?.ContainsKey(userId) ?? false) { if (userInitiated) { _state.Accounts.Remove(userId); } else { _state.Accounts[userId].Tokens.AccessToken = null; _state.Accounts[userId].Tokens.RefreshToken = null; _state.Accounts[userId].VolatileData = null; } } if (userInitiated && _state?.ActiveUserId == userId) { _state.ActiveUserId = null; } // Storage var stateModified = false; var state = await GetStateFromStorageAsync(); if (state?.Accounts?.ContainsKey(userId) ?? false) { if (userInitiated) { state.Accounts.Remove(userId); } else { state.Accounts[userId].Tokens.AccessToken = null; state.Accounts[userId].Tokens.RefreshToken = null; } stateModified = true; } if (userInitiated && state?.ActiveUserId == userId) { state.ActiveUserId = null; stateModified = true; } if (stateModified) { await SaveStateToStorageAsync(state); } // Non-state storage await Task.WhenAll( SetUserKeyAutoUnlockAsync(null, userId), SetUserKeyBiometricUnlockAsync(null, userId), SetProtectedPinAsync(null, userId), SetPinKeyEncryptedUserKeyAsync(null, userId), SetKeyHashAsync(null, userId), SetOrgKeysEncryptedAsync(null, userId), SetPrivateKeyEncryptedAsync(null, userId), SetLastActiveTimeAsync(null, userId), SetPreviousPageInfoAsync(null, userId), SetInvalidUnlockAttemptsAsync(null, userId), SetCiphersLocalDataAsync(null, userId), SetEncryptedCiphersAsync(null, userId), SetEncryptedCollectionsAsync(null, userId), SetLastSyncAsync(null, userId), SetEncryptedFoldersAsync(null, userId), SetEncryptedPoliciesAsync(null, userId), SetUsesKeyConnectorAsync(null, userId), SetOrganizationsAsync(null, userId), SetEncryptedPasswordGenerationHistoryAsync(null, userId), SetEncryptedSendsAsync(null, userId), SetSettingsAsync(null, userId), SetEncKeyEncryptedAsync(null, userId), SetKeyEncryptedAsync(null, userId), SetPinProtectedAsync(null, userId)); } private async Task ScaffoldNewAccountAsync(Account account) { await CheckStateAsync(); account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync(); account.Settings.Region = await GetPreAuthRegionAsync(); // Storage var state = await GetStateFromStorageAsync() ?? new State(); if (state.Accounts == null) { state.Accounts = new Dictionary(); } state.Accounts[account.Profile.UserId] = account; await SaveStateToStorageAsync(state); // Memory if (_state == null) { _state = state; } else { if (_state.Accounts == null) { _state.Accounts = new Dictionary(); } _state.Accounts[account.Profile.UserId] = account; } // Check if account has logged in before by checking a guaranteed non-null pref if (await GetVaultTimeoutActionAsync(account.Profile.UserId) == null) { // Account has never logged in, set defaults await SetVaultTimeoutAsync(Constants.VaultTimeoutDefault, account.Profile.UserId); await SetVaultTimeoutActionAsync(VaultTimeoutAction.Lock, account.Profile.UserId); } } private StorageOptions ReconcileOptions(StorageOptions requestedOptions, StorageOptions defaultOptions) { if (requestedOptions == null) { return defaultOptions; } requestedOptions.StorageLocation = requestedOptions.StorageLocation ?? defaultOptions.StorageLocation; requestedOptions.UseSecureStorage = requestedOptions.UseSecureStorage ?? defaultOptions.UseSecureStorage; requestedOptions.UserId = requestedOptions.UserId ?? defaultOptions.UserId; requestedOptions.Email = requestedOptions.Email ?? defaultOptions.Email; requestedOptions.SkipTokenStorage = requestedOptions.SkipTokenStorage ?? defaultOptions.SkipTokenStorage; return requestedOptions; } /// /// Gets the default options for storage. /// If it's only used for composing the constant key with the user id /// then use instead /// which saves time if the user id is already known /// private async Task GetDefaultStorageOptionsAsync() { return new StorageOptions() { StorageLocation = StorageLocation.Both, UserId = await GetActiveUserIdAsync(), }; } private async Task GetDefaultSecureStorageOptionsAsync() { return new StorageOptions() { StorageLocation = StorageLocation.Disk, UseSecureStorage = true, UserId = await GetActiveUserIdAsync(), }; } private async Task GetDefaultInMemoryOptionsAsync() { return new StorageOptions() { StorageLocation = StorageLocation.Memory, UserId = await GetActiveUserIdAsync(), }; } private bool UseMemory(StorageOptions options) { return options?.StorageLocation == StorageLocation.Memory || options?.StorageLocation == StorageLocation.Both; } private bool UseDisk(StorageOptions options) { return options?.StorageLocation == StorageLocation.Disk || options?.StorageLocation == StorageLocation.Both; } private async Task GetStateFromStorageAsync() { return await _storageService.GetAsync(Constants.StateKey); } private async Task SaveStateToStorageAsync(State state) { await _storageService.SaveAsync(Constants.StateKey, state); } private async Task GetExtensionActiveUserIdFromStorageAsync() { return await _storageService.GetAsync(Constants.iOSExtensionActiveUserIdKey); } public async Task SaveExtensionActiveUserIdToStorageAsync(string userId) { await _storageService.SaveAsync(Constants.iOSExtensionActiveUserIdKey, userId); } public async Task ReloadStateAsync() { _state = await GetStateFromStorageAsync() ?? new State(); } private async Task CheckStateAsync() { if (!_migrationChecked) { var migrationService = ServiceContainer.Resolve(); await migrationService.MigrateIfNeededAsync(); _migrationChecked = true; } if (_state == null) { await ReloadStateAsync(); } } private async Task ValidateUserAsync(string userId) { if (string.IsNullOrWhiteSpace(userId)) { throw new ArgumentNullException(nameof(userId)); } await CheckStateAsync(); var accounts = _state?.Accounts; if (accounts == null || !accounts.Any()) { throw new Exception("At least one account required to validate user"); } foreach (var account in accounts) { if (account.Key == userId) { // found match, user is valid return; } } throw new Exception("User does not exist in account list"); } public async Task GetShouldConnectToWatchAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.ShouldConnectToWatchKey(reconciledOptions.UserId), reconciledOptions) ?? false; } public async Task SetShouldConnectToWatchAsync(bool shouldConnect, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.ShouldConnectToWatchKey(reconciledOptions.UserId), shouldConnect, reconciledOptions); await SetLastUserShouldConnectToWatchAsync(shouldConnect); } public async Task GetLastUserShouldConnectToWatchAsync() { return await GetValueAsync(Constants.LastUserShouldConnectToWatchKey, await GetDefaultStorageOptionsAsync()) ?? false; } private async Task SetLastUserShouldConnectToWatchAsync(bool? shouldConnect = null) { await SetValueAsync(Constants.LastUserShouldConnectToWatchKey, shouldConnect ?? await GetShouldConnectToWatchAsync(), await GetDefaultStorageOptionsAsync()); } [Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")] public async Task GetPinProtectedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions); } [Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead")] public async Task SetPinProtectedAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions); } [Obsolete("Use GetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")] public async Task GetPinProtectedKeyAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ))?.VolatileData?.PinProtectedKey; } [Obsolete("Use SetPinKeyEncryptedUserKeyEphemeralAsync instead")] public async Task SetPinProtectedKeyAsync(EncString value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.VolatileData.PinProtectedKey = value; await SaveAccountAsync(account, reconciledOptions); } [Obsolete("Use GetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")] public async Task GetEncKeyEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.EncKeyKey(reconciledOptions.UserId), reconciledOptions); } [Obsolete("Use SetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")] public async Task SetEncKeyEncryptedAsync(string value, string userId) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.EncKeyKey(reconciledOptions.UserId), value, reconciledOptions); } [Obsolete("Left for migration purposes")] public async Task SetKeyEncryptedAsync(string value, string userId) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultSecureStorageOptionsAsync()); await SetValueAsync(Constants.KeyKey(reconciledOptions.UserId), value, reconciledOptions); } [Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")] public async Task GetKeyEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultSecureStorageOptionsAsync()); return await GetValueAsync(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions); } [Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")] public async Task GetKeyDecryptedAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ))?.VolatileData?.Key; } } }