using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; 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; 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 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 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 GetPinProtectedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions); } 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); } public async Task GetPinProtectedKeyAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ))?.VolatileData?.PinProtectedKey; } 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); } 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 GetKeyEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultSecureStorageOptionsAsync()); return await GetValueAsync(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions); } 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); } public async Task GetKeyDecryptedAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ))?.VolatileData?.Key; } public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.VolatileData.Key = value; 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 GetEncKeyEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.EncKeyKey(reconciledOptions.UserId), reconciledOptions); } 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); } 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> 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.AutofillTileAdded, await GetDefaultStorageOptionsAsync()); } public async Task SetAutofillTileAddedAsync(bool? value) { await SetValueAsync(Constants.AutofillTileAdded, 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()); } public async Task GetDisableFaviconAsync() { return await GetValueAsync(Constants.DisableFaviconKey, await GetDefaultStorageOptionsAsync()); } public async Task SetDisableFaviconAsync(bool? value) { await SetValueAsync(Constants.DisableFaviconKey, value, await GetDefaultStorageOptionsAsync()); } public async Task GetDisableAutoTotpCopyAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync(Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId), reconciledOptions); } 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>> GetLocalDataAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>>( Constants.LocalDataKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetLocalDataAsync(Dictionary> value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.LocalDataKey(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> GetNeverDomainsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); return await GetValueAsync>(Constants.NeverDomainsKey(reconciledOptions.UserId), reconciledOptions); } public async Task SetNeverDomainsAsync(HashSet value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); await SetValueAsync(Constants.NeverDomainsKey(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); } // 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 SetProtectedPinAsync(null, userId); await SetPinProtectedAsync(null, userId); await SetKeyEncryptedAsync(null, userId); await SetKeyHashAsync(null, userId); await SetEncKeyEncryptedAsync(null, userId); await SetOrgKeysEncryptedAsync(null, userId); await SetPrivateKeyEncryptedAsync(null, userId); await SetLastActiveTimeAsync(null, userId); await SetPreviousPageInfoAsync(null, userId); await SetInvalidUnlockAttemptsAsync(null, userId); await SetLocalDataAsync(null, userId); await SetEncryptedCiphersAsync(null, userId); await SetEncryptedCollectionsAsync(null, userId); await SetLastSyncAsync(null, userId); await SetEncryptedFoldersAsync(null, userId); await SetEncryptedPoliciesAsync(null, userId); await SetUsesKeyConnectorAsync(null, userId); await SetOrganizationsAsync(null, userId); await SetEncryptedPasswordGenerationHistoryAsync(null, userId); await SetEncryptedSendsAsync(null, userId); await SetSettingsAsync(null, userId); await SetApprovePasswordlessLoginsAsync(null, userId); } private async Task ScaffoldNewAccountAsync(Account account) { await CheckStateAsync(); account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync(); // 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); } private async Task CheckStateAsync() { if (!_migrationChecked) { var migrationService = ServiceContainer.Resolve(); await migrationService.MigrateIfNeededAsync(); _migrationChecked = true; } if (_state == null) { _state = await GetStateFromStorageAsync() ?? new State(); } } 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()); } } }