using System; using Bit.Core.Abstractions; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; using Newtonsoft.Json; namespace Bit.Core.Services { public class StateService : IStateService { private readonly IStorageService _storageService; private readonly IStorageService _secureStorageService; private State _state; public bool BiometricLocked { get; set; } public ExtendedObservableCollection AccountViews { get; set; } public StateService(IStorageService storageService, IStorageService secureStorageService) { _storageService = storageService; _secureStorageService = secureStorageService; } public async Task> GetUserIdsAsync() { await CheckStateAsync(); return _state?.Accounts?.Keys.ToList(); } 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 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 var rememberEmail = await GetRememberEmailAsync(); if (rememberEmail.GetValueOrDefault(true)) { await SetRememberedEmailAsync(await GetEmailAsync()); } await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync()); } public async Task IsAuthenticatedAsync(string userId = null) { return await GetAccessTokenAsync(userId) != null; } public async Task HasMultipleAccountsAsync() { await CheckStateAsync(); return _state.Accounts?.Count > 1; } public async Task RefreshAccountViewsAsync(bool allowAddAccountRow) { await CheckStateAsync(); if (AccountViews == null) { AccountViews = new ExtendedObservableCollection(); } AccountViews.Clear(); var accountList = _state?.Accounts?.Values.ToList(); if (accountList == null) { return; } var vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); foreach (var account in accountList) { var accountView = new AccountView(account); if (accountView.UserId == _state.ActiveUserId) { accountView.AuthStatus = AuthenticationStatus.Active; } else { var isLocked = await vaultTimeoutService.IsLockedAsync(accountView.UserId); var shouldTimeout = await vaultTimeoutService.ShouldTimeoutAsync(accountView.UserId); if (isLocked || shouldTimeout) { var action = account.Settings.VaultTimeoutAction; accountView.AuthStatus = action == "logOut" ? AuthenticationStatus.LoggedOut : 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 ClearAsync(string userId) { if (userId == null) { throw new Exception("userId cannot be null"); } await RemoveAccountAsync(userId); // Find the next user to make active, if any if (_state?.Accounts != null) { foreach (var account in _state.Accounts) { var uid = account.Value?.Profile?.UserId; if (uid == null) { continue; } if (await IsAuthenticatedAsync(uid)) { 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()); var key = Constants.BiometricUnlockKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetBiometricUnlockAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.BiometricUnlockKey(reconciledOptions.UserId); await SetValueAsync(key, value, 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 GetProtectedPinAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultSecureStorageOptionsAsync()); var key = Constants.ProtectedPinKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetProtectedPinAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultSecureStorageOptionsAsync()); var key = Constants.ProtectedPinKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetPinProtectedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PinProtectedKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetPinProtectedAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PinProtectedKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetPinProtectedCachedAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Settings?.PinProtected; } public async Task SetPinProtectedCachedAsync(EncString value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Settings.PinProtected = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetKdfTypeAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.KdfType; } public async Task SetKdfTypeAsync(KdfType? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Profile.KdfType = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetKdfIterationsAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.KdfIterations; } public async Task SetKdfIterationsAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Profile.KdfIterations = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetKeyEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultSecureStorageOptionsAsync()); var key = Constants.KeyKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetKeyEncryptedAsync(string value, string userId) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultSecureStorageOptionsAsync()); var key = Constants.KeyKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetKeyDecryptedAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ))?.Keys?.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.Keys.Key = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetKeyHashAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.KeyHashKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetKeyHashAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.KeyHashKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetEncKeyEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.EncKeyKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetEncKeyEncryptedAsync(string value, string userId) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.EncKeyKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetOrgKeysEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.EncOrgKeysKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetOrgKeysEncryptedAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.EncOrgKeysKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetPrivateKeyEncryptedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.EncPrivateKeyKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetPrivateKeyEncryptedAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.EncPrivateKeyKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetAutofillBlacklistedUrisAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AutofillBlacklistedUrisKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetAutofillBlacklistedUrisAsync(List value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AutofillBlacklistedUrisKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetAutofillTileAddedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AutofillTileAdded; return await GetValueAsync(key, reconciledOptions); } public async Task SetAutofillTileAddedAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AutofillTileAdded; await SetValueAsync(key, value, reconciledOptions); } public async Task GetEmailAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Profile?.Email; } public async Task GetLastActiveTimeAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LastActiveTimeKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetLastActiveTimeAsync(long? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LastActiveTimeKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetVaultTimeoutAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Settings?.VaultTimeout; } public async Task SetVaultTimeoutAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Settings.VaultTimeout = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetVaultTimeoutActionAsync(string userId = null) { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) ))?.Settings?.VaultTimeoutAction; } public async Task SetVaultTimeoutActionAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); account.Settings.VaultTimeoutAction = value; await SaveAccountAsync(account, reconciledOptions); } public async Task GetLastFileCacheClearAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LastFileCacheClearKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetLastFileCacheClearAsync(DateTime? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LastFileCacheClearKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetPreviousPageInfoAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PreviousPageKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetPreviousPageInfoAsync(PreviousPageInfo value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PreviousPageKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetInvalidUnlockAttemptsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.InvalidUnlockAttemptsKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetInvalidUnlockAttemptsAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.InvalidUnlockAttemptsKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetLastBuildAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LastBuildKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetLastBuildAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LastBuildKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetDisableFaviconAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.DisableFaviconKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetDisableFaviconAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.DisableFaviconKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetDisableAutoTotpCopyAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetDisableAutoTotpCopyAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetInlineAutofillEnabledAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.InlineAutofillEnabledKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetInlineAutofillEnabledAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.InlineAutofillEnabledKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetAutofillDisableSavePromptAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AutofillDisableSavePromptKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetAutofillDisableSavePromptAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AutofillDisableSavePromptKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task>> GetLocalDataAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LocalDataKey(reconciledOptions.UserId); return await GetValueAsync>>(key, reconciledOptions); } public async Task SetLocalDataAsync(Dictionary> value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LocalDataKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetEncryptedCiphersAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.CiphersKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetEncryptedCiphersAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.CiphersKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetDefaultUriMatchAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.DefaultUriMatchKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetDefaultUriMatchAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.DefaultUriMatchKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetNeverDomainsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.NeverDomainsKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetNeverDomainsAsync(HashSet value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.NeverDomainsKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetClearClipboardAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.ClearClipboardKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetClearClipboardAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.ClearClipboardKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetEncryptedCollectionsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.CollectionsKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetEncryptedCollectionsAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.CollectionsKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetPasswordRepromptAutofillAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PasswordRepromptAutofillKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions) ?? false; } public async Task SetPasswordRepromptAutofillAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PasswordRepromptAutofillKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetPasswordVerifiedAutofillAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PasswordVerifiedAutofillKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions) ?? false; } public async Task SetPasswordVerifiedAutofillAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PasswordVerifiedAutofillKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetLastSyncAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LastSyncKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetLastSyncAsync(DateTime? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.LastSyncKey(reconciledOptions.UserId); await SetValueAsync(key, 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 GetForcePasswordReset(string userId) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.ForcePasswordResetKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions) ?? false; } public async Task SetForcePasswordResetAsync(bool? value, string userId) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.ForcePasswordResetKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetSyncOnRefreshAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.SyncOnRefreshKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions) ?? false; } public async Task SetSyncOnRefreshAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.SyncOnRefreshKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetRememberedEmailAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.RememberedEmailKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetRememberedEmailAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.RememberedEmailKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetRememberEmailAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.RememberEmailKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetRememberEmailAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.RememberEmailKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetRememberedOrgIdentifierAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.RememberedOrgIdentifierKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetRememberedOrgIdentifierAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.RememberedOrgIdentifierKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetRememberOrgIdentifierAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.RememberOrgIdentifierKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetRememberOrgIdentifierAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.RememberOrgIdentifierKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetThemeAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.ThemeKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetThemeAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.ThemeKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetAddSitePromptShownAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AddSitePromptShownKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetAddSitePromptShownAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AddSitePromptShownKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetMigratedFromV1Async(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.MigratedFromV1; return await GetValueAsync(key, reconciledOptions); } public async Task SetMigratedFromV1Async(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.MigratedFromV1; await SetValueAsync(key, value, reconciledOptions); } public async Task GetMigratedFromV1AutofillPromptShownAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.MigratedFromV1AutofillPromptShown; return await GetValueAsync(key, reconciledOptions); } public async Task SetMigratedFromV1AutofillPromptShownAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.MigratedFromV1AutofillPromptShown; await SetValueAsync(key, value, reconciledOptions); } public async Task GetTriedV1ResyncAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.TriedV1Resync; return await GetValueAsync(key, reconciledOptions); } public async Task SetTriedV1ResyncAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.TriedV1Resync; await SetValueAsync(key, value, reconciledOptions); } public async Task GetPushInitialPromptShownAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PushInitialPromptShownKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetPushInitialPromptShownAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PushInitialPromptShownKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetPushLastRegistrationDateAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PushLastRegistrationDateKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetPushLastRegistrationDateAsync(DateTime? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PushLastRegistrationDateKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetPushCurrentTokenAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PushCurrentTokenKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetPushCurrentTokenAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PushCurrentTokenKey; await SetValueAsync(key, value, reconciledOptions); } public async Task> GetEventCollectionAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.EventCollectionKey; return await GetValueAsync>(key, reconciledOptions); } public async Task SetEventCollectionAsync(List value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.EventCollectionKey; await SetValueAsync(key, value, reconciledOptions); } public async Task> GetEncryptedFoldersAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.FoldersKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetEncryptedFoldersAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.FoldersKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetEncryptedPoliciesAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PoliciesKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetEncryptedPoliciesAsync(Dictionary value, string userId) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PoliciesKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetPushRegisteredTokenAsync() { var options = await GetDefaultStorageOptionsAsync(); var key = Constants.PushRegisteredTokenKey; return await GetValueAsync(key, options); } public async Task SetPushRegisteredTokenAsync(string value) { var options = await GetDefaultStorageOptionsAsync(); var key = Constants.PushRegisteredTokenKey; await SetValueAsync(key, value, options); } public async Task GetAppExtensionStartedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AppExtensionStartedKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetAppExtensionStartedAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AppExtensionStartedKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetAppExtensionActivatedAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AppExtensionActivatedKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetAppExtensionActivatedAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AppExtensionActivatedKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetAppIdAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AppIdKey; return await GetValueAsync(key, reconciledOptions); } public async Task SetAppIdAsync(string value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.AppIdKey; await SetValueAsync(key, value, reconciledOptions); } public async Task GetUsesKeyConnectorAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.UsesKeyConnectorKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions) ?? false; } public async Task SetUsesKeyConnectorAsync(bool? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.UsesKeyConnectorKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetOrganizationsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.OrganizationsKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetOrganizationsAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.OrganizationsKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task GetPasswordGenerationOptionsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PassGenOptionsKey(reconciledOptions.UserId); return await GetValueAsync(key, reconciledOptions); } public async Task SetPasswordGenerationOptionsAsync(PasswordGenerationOptions value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PassGenOptionsKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetEncryptedPasswordGenerationHistory(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PassGenHistoryKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetEncryptedPasswordGenerationHistoryAsync(List value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.PassGenHistoryKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetEncryptedSendsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.SendsKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetEncryptedSendsAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.SendsKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } public async Task> GetSettingsAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.SettingsKey(reconciledOptions.UserId); return await GetValueAsync>(key, reconciledOptions); } public async Task SetSettingsAsync(Dictionary value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var key = Constants.SettingsKey(reconciledOptions.UserId); await SetValueAsync(key, 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()); var key = Constants.TwoFactorTokenKey(reconciledOptions.Email); return await GetValueAsync(key, reconciledOptions); } public async Task SetTwoFactorTokenAsync(string value, string email = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { Email = email }, await GetDefaultStorageOptionsAsync()); var key = Constants.TwoFactorTokenKey(reconciledOptions.Email); await SetValueAsync(key, value, reconciledOptions); } // Helpers private async Task GetValueAsync(string key, StorageOptions options) { var value = await GetStorageService(options).GetAsync(key); Log("GET", options, key, JsonConvert.SerializeObject(value)); return value; } private async Task SetValueAsync(string key, T value, StorageOptions options) { if (value == null) { Log("REMOVE", options, key, null); await GetStorageService(options).RemoveAsync(key); return; } Log("SET", options, key, JsonConvert.SerializeObject(value)); await GetStorageService(options).SaveAsync(key, value); } private IStorageService GetStorageService(StorageOptions options) { return options.UseSecureStorage.GetValueOrDefault(false) ? _secureStorageService : _storageService; } 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].Keys == null) { _state.Accounts[options.UserId].Keys = new Account.AccountKeys(); } return _state.Accounts[options.UserId]; } // Storage _state = await GetStateFromStorageAsync(); if (_state?.Accounts?.ContainsKey(options.UserId) ?? false) { if (_state.Accounts[options.UserId].Keys == null) { _state.Accounts[options.UserId].Keys = new Account.AccountKeys(); } 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) { if (userId == null) { throw new Exception("userId cannot be null"); } await CheckStateAsync(); // Memory if (_state?.Accounts?.ContainsKey(userId) ?? false) { _state?.Accounts?.Remove(userId); } if (_state?.ActiveUserId == userId) { _state.ActiveUserId = null; } // Storage var stateModified = false; var state = await GetStateFromStorageAsync(); if (state?.Accounts?.ContainsKey(userId) ?? false) { state.Accounts.Remove(userId); stateModified = true; } if (state?.ActiveUserId == userId) { state.ActiveUserId = null; stateModified = true; } if (stateModified) { await SaveStateToStorageAsync(state); } // Secure Storage await SetProtectedPinAsync(null, userId); await SetKeyEncryptedAsync(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(); } if (state.Accounts.ContainsKey(account.Profile.UserId)) { // Account data already exists, restore appropriate data account.Settings.VaultTimeout = state.Accounts[account.Profile.UserId].Settings?.VaultTimeout; account.Settings.VaultTimeoutAction = state.Accounts[account.Profile.UserId].Settings?.VaultTimeoutAction; } else { // New account, set defaults account.Settings.VaultTimeout = 15; account.Settings.VaultTimeoutAction = "lock"; } 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; } } 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; } 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() { var state = await _storageService.GetAsync(Constants.StateKey); // TODO Remove logging once all bugs are squished Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented), ">>> GetStateFromStorageAsync()"); return state; } private async Task SaveStateToStorageAsync(State state) { await _storageService.SaveAsync(Constants.StateKey, state); // TODO Remove logging once all bugs are squished Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented), ">>> SaveStateToStorageAsync()"); } private async Task CheckStateAsync() { // TODO perform migration if necessary if (_state == null) { _state = await GetStateFromStorageAsync() ?? new State(); } } private async Task IsLockedAsync(string userId) { if (userId == null) { userId = await GetActiveUserIdAsync(); } bool hasKey; var inMemoryKey = await GetKeyDecryptedAsync(userId); if (inMemoryKey != null) { hasKey = true; } else { var storedKey = await GetKeyEncryptedAsync(userId); hasKey = storedKey != null; } if (hasKey) { var biometricLock = await GetBiometricUnlockAsync(userId); var biometricSet = biometricLock.GetValueOrDefault(); if (biometricSet && BiometricLocked) { return true; } } return !hasKey; } private async Task ValidateUserAsync(string userId) { if (string.IsNullOrEmpty(userId)) { throw new Exception("userId cannot be null or empty"); } 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"); } private void Log(string tag, StorageOptions options, string key, string value) { // TODO Remove this once all bugs are squished var text = options?.UseSecureStorage ?? false ? "SECURE / " : ""; text += "Key: " + key + " / "; if (value != null) { text += "Value: " + value; } Debug.WriteLine(text, ">>> " + tag); } } }