From ebae2585f6364ae4d7fe06802c65eae9f2557e56 Mon Sep 17 00:00:00 2001 From: Matt Portune Date: Sun, 16 Jan 2022 22:43:37 -0500 Subject: [PATCH] Account switching --- .../Accessibility/AccessibilityService.cs | 11 +- src/Android/Android.csproj | 3 +- src/Android/Autofill/AutofillService.cs | 17 +- src/Android/Autofill/Parser.cs | 4 +- src/Android/MainActivity.cs | 8 +- src/Android/MainApplication.cs | 14 +- .../Receivers/PackageReplacedReceiver.cs | 10 +- .../drawable/{cog.xml => cog_environment.xml} | 0 .../Resources/drawable/cog_settings.xml | 9 + .../AndroidPushNotificationService.cs | 13 +- src/Android/Services/ClipboardService.cs | 8 +- src/Android/Services/DeviceActionService.cs | 13 +- src/Android/Tiles/AutofillTileService.cs | 9 +- src/Android/Utilities/AppCenterHelper.cs | 8 +- src/App/App.csproj | 6 +- src/App/App.xaml.cs | 74 +- .../AccountViewCell/AccountViewCell.xaml | 101 ++ .../AccountViewCell/AccountViewCell.xaml.cs | 36 + .../AccountViewCellViewModel.cs | 31 + src/App/Controls/AvatarImageSource.cs | 113 ++ src/App/Controls/ExtendedToolbarItem.cs | 9 + src/App/Models/AppOptions.cs | 2 + .../Accounts/BaseChangePasswordViewModel.cs | 6 +- .../Pages/Accounts/EnvironmentPage.xaml.cs | 4 - src/App/Pages/Accounts/HomePage.xaml | 119 +- src/App/Pages/Accounts/HomePage.xaml.cs | 38 +- src/App/Pages/Accounts/HomePageViewModel.cs | 14 +- src/App/Pages/Accounts/LockPage.xaml | 270 +-- src/App/Pages/Accounts/LockPage.xaml.cs | 29 +- src/App/Pages/Accounts/LockPageViewModel.cs | 36 +- src/App/Pages/Accounts/LoginPage.xaml | 172 +- src/App/Pages/Accounts/LoginPage.xaml.cs | 36 +- src/App/Pages/Accounts/LoginPageViewModel.cs | 22 +- src/App/Pages/Accounts/LoginSsoPage.xaml.cs | 6 - .../Pages/Accounts/LoginSsoPageViewModel.cs | 16 +- src/App/Pages/Accounts/RegisterPage.xaml.cs | 8 +- .../Pages/Accounts/SetPasswordPage.xaml.cs | 8 +- .../Accounts/SetPasswordPageViewModel.cs | 15 +- src/App/Pages/Accounts/TwoFactorPage.xaml.cs | 4 - .../Pages/Accounts/TwoFactorPageViewModel.cs | 5 - .../Accounts/UpdateTempPasswordPage.xaml.cs | 3 - .../UpdateTempPasswordPageViewModel.cs | 10 +- src/App/Pages/BaseContentPage.cs | 102 +- src/App/Pages/BaseViewModel.cs | 10 +- .../Pages/Send/SendAddEditPageViewModel.cs | 8 +- .../SendGroupingsPageViewModel.cs | 11 +- .../Settings/AutofillServicesPageViewModel.cs | 9 +- .../Pages/Settings/ExtensionPageViewModel.cs | 13 +- .../Pages/Settings/OptionsPageViewModel.cs | 44 +- .../SettingsPage/SettingsPageViewModel.cs | 29 +- src/App/Pages/Settings/SyncPageViewModel.cs | 9 +- src/App/Pages/TabsPage.cs | 4 +- src/App/Pages/Vault/AddEditPage.xaml.cs | 9 +- src/App/Pages/Vault/AddEditPageViewModel.cs | 10 +- .../Pages/Vault/AttachmentsPageViewModel.cs | 6 +- .../Vault/AutofillCiphersPageViewModel.cs | 5 +- src/App/Pages/Vault/CiphersPageViewModel.cs | 3 +- .../Vault/GroupingsPage/GroupingsPage.xaml | 45 + .../Vault/GroupingsPage/GroupingsPage.xaml.cs | 52 +- .../GroupingsPage/GroupingsPageViewModel.cs | 25 +- src/App/Pages/Vault/SharePageViewModel.cs | 8 +- src/App/Pages/Vault/ViewPageViewModel.cs | 6 +- src/App/Services/MobileStorageService.cs | 24 +- .../PushNotificationListenerService.cs | 16 +- src/App/Utilities/AppHelpers.cs | 108 +- src/App/Utilities/ThemeManager.cs | 13 +- src/Core/Abstractions/IAppIdService.cs | 1 - src/Core/Abstractions/ICollectionService.cs | 2 +- src/Core/Abstractions/ICryptoService.cs | 14 +- src/Core/Abstractions/IEventService.cs | 2 +- src/Core/Abstractions/IFolderService.cs | 2 +- src/Core/Abstractions/IOrganizationService.cs | 16 + .../IPasswordGenerationService.cs | 3 +- src/Core/Abstractions/ISettingsService.cs | 2 +- src/Core/Abstractions/IStateService.cs | 173 +- src/Core/Abstractions/ITokenService.cs | 5 +- src/Core/Abstractions/IVaultTimeoutService.cs | 14 +- src/Core/Constants.cs | 72 +- src/Core/Enums/AuthenticationStatus.cs | 10 + src/Core/Enums/DiskStorageLocation.cs | 9 + src/Core/Enums/StorageLocation.cs | 9 + .../Models/Data}/PreviousPageInfo.cs | 2 +- src/Core/Models/Domain/Account.cs | 99 ++ src/Core/Models/Domain/GlobalState.cs | 10 + src/Core/Models/Domain/State.cs | 10 + src/Core/Models/Domain/StorageOptions.cs | 12 + src/Core/Models/View/AccountView.cs | 31 + src/Core/Services/AppIdService.cs | 35 +- src/Core/Services/AuthService.cs | 29 +- src/Core/Services/CipherService.cs | 90 +- src/Core/Services/CollectionService.cs | 41 +- src/Core/Services/CryptoService.cs | 173 +- src/Core/Services/EnvironmentService.cs | 14 +- src/Core/Services/EventService.cs | 26 +- src/Core/Services/FolderService.cs | 47 +- src/Core/Services/KeyConnectorService.cs | 31 +- src/Core/Services/OrganizationService.cs | 55 + .../Services/PasswordGenerationService.cs | 28 +- src/Core/Services/PolicyService.cs | 25 +- src/Core/Services/SendService.cs | 36 +- src/Core/Services/SettingsService.cs | 22 +- src/Core/Services/StateService.cs | 1515 ++++++++++++++++- src/Core/Services/SyncService.cs | 50 +- src/Core/Services/TokenService.cs | 48 +- src/Core/Services/TotpService.cs | 8 +- src/Core/Services/VaultTimeoutService.cs | 144 +- src/Core/Utilities/ServiceContainer.cs | 61 +- .../CredentialProviderViewController.cs | 46 +- src/iOS.Autofill/Utilities/AutofillHelpers.cs | 9 +- .../Controllers/LockPasswordViewController.cs | 34 +- .../Renderers/CustomNavigationRenderer.cs | 54 + src/iOS.Core/Services/ClipboardService.cs | 8 +- src/iOS.Core/Services/DeviceActionService.cs | 8 +- src/iOS.Core/Utilities/ASHelpers.cs | 7 +- src/iOS.Core/Utilities/AppCenterHelper.cs | 8 +- src/iOS.Core/Utilities/iOSCoreHelpers.cs | 16 +- src/iOS.Core/Views/ExtensionTableSource.cs | 6 +- src/iOS.Core/iOS.Core.csproj | 1 + src/iOS.Extension/LoadingViewController.cs | 25 +- src/iOS.Extension/LoginListViewController.cs | 3 +- src/iOS/AppDelegate.cs | 15 +- src/iOS/Resources/cog_environment.png | Bin 0 -> 1711 bytes src/iOS/Resources/cog_environment@2x.png | Bin 0 -> 2277 bytes src/iOS/Resources/cog_environment@3x.png | Bin 0 -> 2922 bytes .../Resources/{cog.png => cog_settings.png} | Bin .../{cog@2x.png => cog_settings@2x.png} | Bin .../{cog@3x.png => cog_settings@3x.png} | Bin src/iOS/iOS.csproj | 15 +- test/Core.Test/Services/SendServiceTests.cs | 28 +- 129 files changed, 3883 insertions(+), 1265 deletions(-) rename src/Android/Resources/drawable/{cog.xml => cog_environment.xml} (100%) create mode 100644 src/Android/Resources/drawable/cog_settings.xml create mode 100644 src/App/Controls/AccountViewCell/AccountViewCell.xaml create mode 100644 src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs create mode 100644 src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs create mode 100644 src/App/Controls/AvatarImageSource.cs create mode 100644 src/App/Controls/ExtendedToolbarItem.cs create mode 100644 src/Core/Abstractions/IOrganizationService.cs create mode 100644 src/Core/Enums/AuthenticationStatus.cs create mode 100644 src/Core/Enums/DiskStorageLocation.cs create mode 100644 src/Core/Enums/StorageLocation.cs rename src/{App/Models => Core/Models/Data}/PreviousPageInfo.cs (86%) create mode 100644 src/Core/Models/Domain/Account.cs create mode 100644 src/Core/Models/Domain/GlobalState.cs create mode 100644 src/Core/Models/Domain/State.cs create mode 100644 src/Core/Models/Domain/StorageOptions.cs create mode 100644 src/Core/Models/View/AccountView.cs create mode 100644 src/Core/Services/OrganizationService.cs create mode 100644 src/iOS.Core/Renderers/CustomNavigationRenderer.cs create mode 100644 src/iOS/Resources/cog_environment.png create mode 100644 src/iOS/Resources/cog_environment@2x.png create mode 100644 src/iOS/Resources/cog_environment@3x.png rename src/iOS/Resources/{cog.png => cog_settings.png} (100%) rename src/iOS/Resources/{cog@2x.png => cog_settings@2x.png} (100%) rename src/iOS/Resources/{cog@3x.png => cog_settings@3x.png} (100%) diff --git a/src/Android/Accessibility/AccessibilityService.cs b/src/Android/Accessibility/AccessibilityService.cs index 7f6aad01b..1345a5cef 100644 --- a/src/Android/Accessibility/AccessibilityService.cs +++ b/src/Android/Accessibility/AccessibilityService.cs @@ -10,7 +10,6 @@ using Android.Views; using Android.Views.Accessibility; using Android.Widget; using Bit.App.Resources; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Utilities; @@ -25,7 +24,7 @@ namespace Bit.Droid.Accessibility private const string BitwardenPackage = "com.x8bit.bitwarden"; private const string BitwardenWebsite = "vault.bitwarden.com"; - private IStorageService _storageService; + private IStateService _stateService; private IBroadcasterService _broadcasterService; private DateTime? _lastSettingsReload = null; private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1); @@ -444,9 +443,9 @@ namespace Bit.Droid.Accessibility private void LoadServices() { - if (_storageService == null) + if (_stateService == null) { - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); } if (_broadcasterService == null) { @@ -460,12 +459,12 @@ namespace Bit.Droid.Accessibility if (_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan) { _lastSettingsReload = now; - var uris = await _storageService.GetAsync>(Constants.AutofillBlacklistedUrisKey); + var uris = await _stateService.GetAutofillBlacklistedUrisAsync(); if (uris != null) { _blacklistedUris = new HashSet(uris); } - var isAutoFillTileAdded = await _storageService.GetAsync(Constants.AutofillTileAdded); + var isAutoFillTileAdded = await _stateService.GetAutofillTileAddedAsync(); AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault(); } } diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 5112861b7..851ec0937 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -171,7 +171,8 @@ - + + diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs index 38ecbe4a9..a2ac6b4c8 100644 --- a/src/Android/Autofill/AutofillService.cs +++ b/src/Android/Autofill/AutofillService.cs @@ -22,9 +22,8 @@ namespace Bit.Droid.Autofill { private ICipherService _cipherService; private IVaultTimeoutService _vaultTimeoutService; - private IStorageService _storageService; private IPolicyService _policyService; - private IUserService _userService; + private IStateService _stateService; public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) @@ -38,18 +37,18 @@ namespace Bit.Droid.Autofill var parser = new Parser(structure, ApplicationContext); parser.Parse(); - if (_storageService == null) + if (_stateService == null) { - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); } - var shouldAutofill = await parser.ShouldAutofillAsync(_storageService); + var shouldAutofill = await parser.ShouldAutofillAsync(_stateService); if (!shouldAutofill) { return; } - var inlineAutofillEnabled = await _storageService.GetAsync(Constants.InlineAutofillEnabledKey) ?? true; + var inlineAutofillEnabled = await _stateService.GetInlineAutofillEnabledAsync() ?? true; if (_vaultTimeoutService == null) { @@ -81,12 +80,12 @@ namespace Bit.Droid.Autofill return; } - if (_storageService == null) + if (_stateService == null) { - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); } - var disableSavePrompt = await _storageService.GetAsync(Constants.AutofillDisableSavePromptKey); + var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync(); if (disableSavePrompt.GetValueOrDefault()) { return; diff --git a/src/Android/Autofill/Parser.cs b/src/Android/Autofill/Parser.cs index d097d4d1f..45a2fdb0b 100644 --- a/src/Android/Autofill/Parser.cs +++ b/src/Android/Autofill/Parser.cs @@ -80,13 +80,13 @@ namespace Bit.Droid.Autofill } } - public async Task ShouldAutofillAsync(IStorageService storageService) + public async Task ShouldAutofillAsync(IStateService stateService) { var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) && FieldCollection != null && FieldCollection.Fillable; if (fillable) { - var blacklistedUris = await storageService.GetAsync>(Constants.AutofillBlacklistedUrisKey); + var blacklistedUris = await stateService.GetAutofillBlacklistedUrisAsync(); if (blacklistedUris != null && blacklistedUris.Count > 0) { fillable = !new HashSet(blacklistedUris).Contains(Uri); diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index b73e42dc2..cac699fd5 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -33,7 +33,7 @@ namespace Bit.Droid private IDeviceActionService _deviceActionService; private IMessagingService _messagingService; private IBroadcasterService _broadcasterService; - private IUserService _userService; + private IStateService _stateService; private IAppIdService _appIdService; private IEventService _eventService; private PendingIntent _eventUploadPendingIntent; @@ -54,7 +54,7 @@ namespace Bit.Droid _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _messagingService = ServiceContainer.Resolve("messagingService"); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _appIdService = ServiceContainer.Resolve("appIdService"); _eventService = ServiceContainer.Resolve("eventService"); @@ -71,7 +71,7 @@ namespace Bit.Droid } #if !FDROID - var appCenterHelper = new AppCenterHelper(_appIdService, _userService); + var appCenterHelper = new AppCenterHelper(_appIdService, _stateService); var appCenterTask = appCenterHelper.InitAsync(); #endif @@ -373,7 +373,7 @@ namespace Bit.Droid { Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor); Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor); - ThemeHelpers.SetAppearance(ThemeManager.GetTheme(true), ThemeManager.OsDarkModeEnabled()); + ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled()); } private void ExitApp() diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs index b2854aebe..9909db9df 100644 --- a/src/Android/MainApplication.cs +++ b/src/Android/MainApplication.cs @@ -97,13 +97,14 @@ namespace Bit.Droid var secureStorageService = new SecureStorageService(); var cryptoPrimitiveService = new CryptoPrimitiveService(); var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); - var deviceActionService = new DeviceActionService(mobileStorageService, messagingService, + var stateService = new StateService(mobileStorageService, secureStorageService); + var deviceActionService = new DeviceActionService(stateService, messagingService, broadcasterService, () => ServiceContainer.Resolve("eventService")); var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService, broadcasterService); var biometricService = new BiometricService(); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); - var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService); + var cryptoService = new CryptoService(stateService, cryptoFunctionService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); ServiceContainer.Register("broadcasterService", broadcasterService); @@ -113,7 +114,8 @@ namespace Bit.Droid ServiceContainer.Register("cryptoPrimitiveService", cryptoPrimitiveService); ServiceContainer.Register("storageService", mobileStorageService); ServiceContainer.Register("secureStorageService", secureStorageService); - ServiceContainer.Register("clipboardService", new ClipboardService(mobileStorageService)); + ServiceContainer.Register("stateService", stateService); + ServiceContainer.Register("clipboardService", new ClipboardService(stateService)); ServiceContainer.Register("deviceActionService", deviceActionService); ServiceContainer.Register("platformUtilsService", platformUtilsService); ServiceContainer.Register("biometricService", biometricService); @@ -132,7 +134,7 @@ namespace Bit.Droid ServiceContainer.Register( "pushNotificationListenerService", notificationListenerService); var androidPushNotificationService = new AndroidPushNotificationService( - mobileStorageService, notificationListenerService); + stateService, notificationListenerService); ServiceContainer.Register( "pushNotificationService", androidPushNotificationService); #endif @@ -148,10 +150,6 @@ namespace Bit.Droid private async Task BootstrapAsync() { - var disableFavicon = await ServiceContainer.Resolve("storageService") - .GetAsync(Constants.DisableFaviconKey); - await ServiceContainer.Resolve("stateService").SaveAsync( - Constants.DisableFaviconKey, disableFavicon); await ServiceContainer.Resolve("environmentService").SetUrlsFromStorageAsync(); } } diff --git a/src/Android/Receivers/PackageReplacedReceiver.cs b/src/Android/Receivers/PackageReplacedReceiver.cs index 2af350320..a4a03f9c1 100644 --- a/src/Android/Receivers/PackageReplacedReceiver.cs +++ b/src/Android/Receivers/PackageReplacedReceiver.cs @@ -1,5 +1,4 @@ -using System; -using Android.App; +using Android.App; using Android.Content; using Bit.App.Abstractions; using Bit.App.Utilities; @@ -14,9 +13,10 @@ namespace Bit.Droid.Receivers { public override async void OnReceive(Context context, Intent intent) { - var storageService = ServiceContainer.Resolve("storageService"); - await AppHelpers.PerformUpdateTasksAsync(ServiceContainer.Resolve("syncService"), - ServiceContainer.Resolve("deviceActionService"), storageService); + await AppHelpers.PerformUpdateTasksAsync( + ServiceContainer.Resolve("syncService"), + ServiceContainer.Resolve("deviceActionService"), + ServiceContainer.Resolve("stateService")); } } } diff --git a/src/Android/Resources/drawable/cog.xml b/src/Android/Resources/drawable/cog_environment.xml similarity index 100% rename from src/Android/Resources/drawable/cog.xml rename to src/Android/Resources/drawable/cog_environment.xml diff --git a/src/Android/Resources/drawable/cog_settings.xml b/src/Android/Resources/drawable/cog_settings.xml new file mode 100644 index 000000000..a7a5a37ce --- /dev/null +++ b/src/Android/Resources/drawable/cog_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/Android/Services/AndroidPushNotificationService.cs b/src/Android/Services/AndroidPushNotificationService.cs index 975cbd2db..3c5b344fc 100644 --- a/src/Android/Services/AndroidPushNotificationService.cs +++ b/src/Android/Services/AndroidPushNotificationService.cs @@ -2,7 +2,6 @@ using System; using System.Threading.Tasks; using Bit.App.Abstractions; -using Bit.Core; using Bit.Core.Abstractions; using Xamarin.Forms; @@ -10,25 +9,25 @@ namespace Bit.Droid.Services { public class AndroidPushNotificationService : IPushNotificationService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly IPushNotificationListenerService _pushNotificationListenerService; public AndroidPushNotificationService( - IStorageService storageService, + IStateService stateService, IPushNotificationListenerService pushNotificationListenerService) { - _storageService = storageService; + _stateService = stateService; _pushNotificationListenerService = pushNotificationListenerService; } public async Task GetTokenAsync() { - return await _storageService.GetAsync(Constants.PushCurrentTokenKey); + return await _stateService.GetPushCurrentTokenAsync(); } public async Task RegisterAsync() { - var registeredToken = await _storageService.GetAsync(Constants.PushRegisteredTokenKey); + var registeredToken = await _stateService.GetPushRegisteredTokenAsync(); var currentToken = await GetTokenAsync(); if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken) { @@ -36,7 +35,7 @@ namespace Bit.Droid.Services } else { - await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow); + await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow); } } diff --git a/src/Android/Services/ClipboardService.cs b/src/Android/Services/ClipboardService.cs index 82f1c21db..07e720c57 100644 --- a/src/Android/Services/ClipboardService.cs +++ b/src/Android/Services/ClipboardService.cs @@ -12,12 +12,12 @@ namespace Bit.Droid.Services { public class ClipboardService : IClipboardService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly Lazy _clearClipboardPendingIntent; - public ClipboardService(IStorageService storageService) + public ClipboardService(IStateService stateService) { - _storageService = storageService; + _stateService = stateService; _clearClipboardPendingIntent = new Lazy(() => PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity, @@ -39,7 +39,7 @@ namespace Bit.Droid.Services if (clearMs < 0) { // if not set then we need to check if the user set this config - var clearSeconds = await _storageService.GetAsync(Constants.ClearClipboardKey); + var clearSeconds = await _stateService.GetClearClipboardAsync(); if (clearSeconds != null) { clearMs = clearSeconds.Value * 1000; diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index 9bb004161..638e542a3 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -33,7 +33,7 @@ namespace Bit.Droid.Services { public class DeviceActionService : IDeviceActionService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly IMessagingService _messagingService; private readonly IBroadcasterService _broadcasterService; private readonly Func _eventServiceFunc; @@ -43,12 +43,12 @@ namespace Bit.Droid.Services private string _userAgent; public DeviceActionService( - IStorageService storageService, + IStateService stateService, IMessagingService messagingService, IBroadcasterService broadcasterService, Func eventServiceFunc) { - _storageService = storageService; + _stateService = stateService; _messagingService = messagingService; _broadcasterService = broadcasterService; _eventServiceFunc = eventServiceFunc; @@ -250,7 +250,7 @@ namespace Bit.Droid.Services try { DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir); - await _storageService.SaveAsync(Constants.LastFileCacheClearKey, DateTime.UtcNow); + await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow); } catch (Exception) { } } @@ -833,9 +833,8 @@ namespace Bit.Droid.Services { if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp)) { - var userService = ServiceContainer.Resolve("userService"); - var autoCopyDisabled = await _storageService.GetAsync(Constants.DisableAutoTotpCopyKey); - var canAccessPremium = await userService.CanAccessPremiumAsync(); + var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync(); + var canAccessPremium = await _stateService.CanAccessPremiumAsync(); if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault()) { var totpService = ServiceContainer.Resolve("totpService"); diff --git a/src/Android/Tiles/AutofillTileService.cs b/src/Android/Tiles/AutofillTileService.cs index 305afce37..7f9af7d8b 100644 --- a/src/Android/Tiles/AutofillTileService.cs +++ b/src/Android/Tiles/AutofillTileService.cs @@ -4,7 +4,6 @@ using Android.Content; using Android.Runtime; using Android.Service.QuickSettings; using Bit.App.Resources; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Utilities; using Bit.Droid.Accessibility; @@ -18,7 +17,7 @@ namespace Bit.Droid.Tile [Register("com.x8bit.bitwarden.AutofillTileService")] public class AutofillTileService : TileService { - private IStorageService _storageService; + private IStateService _stateService; public override void OnTileAdded() { @@ -59,11 +58,11 @@ namespace Bit.Droid.Tile private void SetTileAdded(bool isAdded) { AccessibilityHelpers.IsAutofillTileAdded = isAdded; - if (_storageService == null) + if (_stateService == null) { - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); } - _storageService.SaveAsync(Constants.AutofillTileAdded, isAdded); + _stateService.SetAutofillTileAddedAsync(isAdded); } private void ScanAndFill() diff --git a/src/Android/Utilities/AppCenterHelper.cs b/src/Android/Utilities/AppCenterHelper.cs index d580fbbc6..aa3313a3e 100644 --- a/src/Android/Utilities/AppCenterHelper.cs +++ b/src/Android/Utilities/AppCenterHelper.cs @@ -12,22 +12,22 @@ namespace Bit.Droid.Utilities private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42"; private readonly IAppIdService _appIdService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private string _userId; private string _appId; public AppCenterHelper( IAppIdService appIdService, - IUserService userService) + IStateService stateService) { _appIdService = appIdService; - _userService = userService; + _stateService = stateService; } public async Task InitAsync() { - _userId = await _userService.GetUserIdAsync(); + _userId = await _stateService.GetActiveUserIdAsync(); _appId = await _appIdService.GetAppIdAsync(); AppCenter.Start(AppSecret, typeof(Crashes)); diff --git a/src/App/App.csproj b/src/App/App.csproj index c9ab94f02..6f66ba844 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -15,9 +15,11 @@ + + - + @@ -120,6 +122,7 @@ SendGroupingsPage.xaml Code + @@ -131,6 +134,7 @@ MSBuild:UpdateDesignTimeXaml + diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index ab31a0818..93efef606 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -4,8 +4,8 @@ using Bit.App.Pages; using Bit.App.Resources; using Bit.App.Services; using Bit.App.Utilities; -using Bit.Core; using Bit.Core.Abstractions; +using Bit.Core.Models.Data; using Bit.Core.Utilities; using System; using System.Threading.Tasks; @@ -17,7 +17,6 @@ namespace Bit.App { public partial class App : Application { - private readonly IUserService _userService; private readonly IBroadcasterService _broadcasterService; private readonly IMessagingService _messagingService; private readonly IStateService _stateService; @@ -25,7 +24,6 @@ namespace Bit.App private readonly ISyncService _syncService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IAuthService _authService; - private readonly IStorageService _storageService; private readonly IStorageService _secureStorageService; private readonly IDeviceActionService _deviceActionService; @@ -39,7 +37,6 @@ namespace Bit.App Current = this; return; } - _userService = ServiceContainer.Resolve("userService"); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _messagingService = ServiceContainer.Resolve("messagingService"); _stateService = ServiceContainer.Resolve("stateService"); @@ -47,7 +44,6 @@ namespace Bit.App _syncService = ServiceContainer.Resolve("syncService"); _authService = ServiceContainer.Resolve("authService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - _storageService = ServiceContainer.Resolve("storageService"); _secureStorageService = ServiceContainer.Resolve("secureStorageService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); @@ -84,8 +80,11 @@ namespace Bit.App } else if (message.Command == "logout") { + var extras = message.Data as Tuple; + var expired = extras?.Item1; + var userId = extras?.Item2; Device.BeginInvokeOnMainThread(async () => - await LogOutAsync((message.Data as bool?).GetValueOrDefault())); + await LogOutAsync(expired.GetValueOrDefault(), userId)); } else if (message.Command == "loggedOut") { @@ -106,6 +105,14 @@ namespace Bit.App await SleptAsync(); } } + else if (message.Command == "addAccount") + { + await AddAccount(); + } + else if (message.Command == "switchedAccount") + { + await SwitchedAccount(); + } else if (message.Command == "migrated") { await Task.Delay(1000); @@ -167,7 +174,7 @@ namespace Bit.App if (string.IsNullOrWhiteSpace(Options.Uri)) { var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService, - _storageService); + _stateService); if (!updated) { SyncIfNeeded(); @@ -189,7 +196,7 @@ namespace Bit.App var isLocked = await _vaultTimeoutService.IsLockedAsync(); if (!isLocked) { - await _storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime()); + await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); } SetTabsPageFromAutofill(isLocked); await SleptAsync(); @@ -234,12 +241,12 @@ namespace Bit.App new System.Globalization.UmAlQuraCalendar(); } - private async Task LogOutAsync(bool expired) + private async Task LogOutAsync(bool expired, string userId) { - await AppHelpers.LogOutAsync(); + await AppHelpers.LogOutAsync(userId); _authService.LogOut(() => { - Current.MainPage = new HomePage(); + Current.MainPage = new NavigationPage(new HomePage(Options)); if (expired) { _platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired); @@ -247,9 +254,31 @@ namespace Bit.App }); } + private async Task AddAccount() + { + Device.BeginInvokeOnMainThread(async () => + { + Options.ShowAccountSwitcher = true; + Current.MainPage = new NavigationPage(new HomePage(Options)); + }); + } + + private async Task SwitchedAccount() + { + await AppHelpers.ClearServiceCache(); + Device.BeginInvokeOnMainThread(async () => + { + await SetMainPageAsync(); + }); + } + private async Task SetMainPageAsync() { - var authed = await _userService.IsAuthenticatedAsync(); + if (await _stateService.HasMultipleAccountsAsync()) + { + Options.ShowAccountSwitcher = true; + } + var authed = await _stateService.IsAuthenticatedAsync(); if (authed) { if (await _vaultTimeoutService.IsLockedAsync()) @@ -275,7 +304,7 @@ namespace Bit.App } else { - Current.MainPage = new HomePage(Options); + Current.MainPage = new NavigationPage(new HomePage(Options)); } } @@ -285,16 +314,16 @@ namespace Bit.App { return; } - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(); if (!authed) { return; } - var vaultTimeout = await _storageService.GetAsync(Constants.VaultTimeoutKey); + var vaultTimeout = await _stateService.GetVaultTimeoutAsync(); vaultTimeout = vaultTimeout.GetValueOrDefault(-1); if (vaultTimeout == 0) { - var action = await _storageService.GetAsync(Constants.VaultTimeoutActionKey); + var action = await _stateService.GetVaultTimeoutActionAsync(); if (action == "logOut") { await _vaultTimeoutService.LogOutAsync(); @@ -308,7 +337,7 @@ namespace Bit.App private async Task ClearCacheIfNeededAsync() { - var lastClear = await _storageService.GetAsync(Constants.LastFileCacheClearKey); + var lastClear = await _stateService.GetLastFileCacheClearAsync(); if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1) { var task = Task.Run(() => _deviceActionService.ClearCacheAsync()); @@ -351,12 +380,12 @@ namespace Bit.App { InitializeComponent(); SetCulture(); - ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources); + ThemeManager.SetTheme(Current.Resources); Current.RequestedThemeChanged += (s, a) => { UpdateTheme(); }; - Current.MainPage = new HomePage(); + Current.MainPage = new NavigationPage(new HomePage(Options)); var mainPageTask = SetMainPageAsync(); ServiceContainer.Resolve("platformUtilsService").Init(); } @@ -382,17 +411,16 @@ namespace Bit.App { Device.BeginInvokeOnMainThread(() => { - ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources); + ThemeManager.SetTheme(Current.Resources); _messagingService.Send("updatedTheme"); }); } private async Task LockedAsync(bool autoPromptBiometric) { - await _stateService.PurgeAsync(); if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS) { - var vaultTimeout = await _storageService.GetAsync(Constants.VaultTimeoutKey); + var vaultTimeout = await _stateService.GetVaultTimeoutAsync(); if (vaultTimeout == 0) { autoPromptBiometric = false; @@ -422,7 +450,7 @@ namespace Bit.App } } } - await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock); + await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock); var lockPage = new LockPage(Options, autoPromptBiometric); Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage)); } diff --git a/src/App/Controls/AccountViewCell/AccountViewCell.xaml b/src/App/Controls/AccountViewCell/AccountViewCell.xaml new file mode 100644 index 000000000..ada1faa4c --- /dev/null +++ b/src/App/Controls/AccountViewCell/AccountViewCell.xaml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs b/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs new file mode 100644 index 000000000..573f17ef0 --- /dev/null +++ b/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs @@ -0,0 +1,36 @@ +using Bit.Core.Models.View; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public partial class AccountViewCell : ViewCell + { + public static readonly BindableProperty AccountProperty = BindableProperty.Create( + nameof(Account), typeof(AccountView), typeof(AccountViewCell), default(AccountView), BindingMode.OneWay); + + public AccountViewCell() + { + InitializeComponent(); + } + + public AccountView Account + { + get => GetValue(AccountProperty) as AccountView; + set => SetValue(AccountProperty, value); + } + + protected override void OnPropertyChanged(string propertyName = null) + { + base.OnPropertyChanged(propertyName); + if (propertyName == AccountProperty.PropertyName) + { + if (Account == null) + { + return; + } + BindingContext = new AccountViewCellViewModel(Account); + } + } + } +} + diff --git a/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs b/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs new file mode 100644 index 000000000..97f371d3a --- /dev/null +++ b/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs @@ -0,0 +1,31 @@ +using Bit.Core.Models.View; +using Bit.Core.Utilities; + +namespace Bit.App.Controls +{ + public class AccountViewCellViewModel : ExtendedViewModel + { + private AccountView _account; + + public AccountViewCellViewModel(AccountView accountView) + { + Account = accountView; + } + + public AccountView Account + { + get => _account; + set => SetProperty(ref _account, value); + } + + public bool IsAccount + { + get => Account.IsAccount; + } + + public string AuthStatusText + { + get => Account.AuthStatus.ToString(); + } + } +} diff --git a/src/App/Controls/AvatarImageSource.cs b/src/App/Controls/AvatarImageSource.cs new file mode 100644 index 000000000..04781086c --- /dev/null +++ b/src/App/Controls/AvatarImageSource.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SkiaSharp; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class AvatarImageSource : StreamImageSource + { + private string _data; + + public AvatarImageSource(string data = null) + { + _data = data; + } + + public override Func> Stream => GetStreamAsync; + + private Task GetStreamAsync(CancellationToken userToken = new CancellationToken()) + { + OnLoadingStarted(); + userToken.Register(CancellationTokenSource.Cancel); + var result = Draw(); + OnLoadingCompleted(CancellationTokenSource.IsCancellationRequested); + return Task.FromResult(result); + } + + private Stream Draw() + { + string chars = null; + string upperData = null; + + if (string.IsNullOrEmpty(_data)) + { + chars = ".."; + } + else if (_data?.Length > 2) + { + upperData = _data.ToUpper(); + chars = upperData.Substring(0, 2).ToUpper(); + } + + var bgColor = StringToColor(upperData); + var textColor = Color.White;; + var size = 50; + + var bitmap = new SKBitmap( + size * 2, + size * 2, + SKImageInfo.PlatformColorType, + SKAlphaType.Premul); + var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.Transparent); + + var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2; + var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2; + var radius = midX - midX / 5; + + var circlePaint = new SKPaint + { + IsAntialias = true, + Style = SKPaintStyle.Fill, + StrokeJoin = SKStrokeJoin.Miter, + Color = SKColor.Parse(bgColor.ToHex()) + }; + canvas.DrawCircle(midX, midY, radius, circlePaint); + + var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal); + var textSize = midX / 1.3f; + var textPaint = new SKPaint + { + IsAntialias = true, + Style = SKPaintStyle.Fill, + Color = SKColor.Parse(textColor.ToHex()), + TextSize = textSize, + TextAlign = SKTextAlign.Center, + Typeface = typeface + }; + var rect = new SKRect(); + textPaint.MeasureText(chars, ref rect); + canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint); + + return SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100).AsStream(); + } + + private Color StringToColor(string str) + { + if (str == null) + { + return Color.FromHex("#33ffffff"); + } + var hash = 0; + for (var i = 0; i < str.Length; i++) + { + hash = str[i] + ((hash << 5) - hash); + } + var color = "#FF"; + for (var i = 0; i < 3; i++) + { + var value = (hash >> (i * 8)) & 0xff; + color += Convert.ToString(value, 16); + } + if (Device.RuntimePlatform == Device.iOS) + { + // TODO remove this once iOS ToolbarItem tint issue is solved + return Color.FromHex("#33ffffff"); + } + return Color.FromHex(color); + } + } +} diff --git a/src/App/Controls/ExtendedToolbarItem.cs b/src/App/Controls/ExtendedToolbarItem.cs new file mode 100644 index 000000000..a7b0ad56e --- /dev/null +++ b/src/App/Controls/ExtendedToolbarItem.cs @@ -0,0 +1,9 @@ +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class ExtendedToolbarItem : ToolbarItem + { + public bool UseOriginalImage { get; set; } + } +} diff --git a/src/App/Models/AppOptions.cs b/src/App/Models/AppOptions.cs index a28a76855..55d226c90 100644 --- a/src/App/Models/AppOptions.cs +++ b/src/App/Models/AppOptions.cs @@ -21,6 +21,7 @@ namespace Bit.App.Models public string SaveCardCode { get; set; } public bool IosExtension { get; set; } public Tuple CreateSend { get; set; } + public bool ShowAccountSwitcher { get; set; } public void SetAllFrom(AppOptions o) { @@ -44,6 +45,7 @@ namespace Bit.App.Models SaveCardCode = o.SaveCardCode; IosExtension = o.IosExtension; CreateSend = o.CreateSend; + ShowAccountSwitcher = o.ShowAccountSwitcher; } } } diff --git a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs index b8ed2a230..9a6896428 100644 --- a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs +++ b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs @@ -14,7 +14,7 @@ namespace Bit.App.Pages public class BaseChangePasswordViewModel : BaseViewModel { protected readonly IPlatformUtilsService _platformUtilsService; - protected readonly IUserService _userService; + protected readonly IStateService _stateService; protected readonly IPolicyService _policyService; protected readonly IPasswordGenerationService _passwordGenerationService; protected readonly II18nService _i18nService; @@ -31,7 +31,7 @@ namespace Bit.App.Pages protected BaseChangePasswordViewModel() { _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _policyService = ServiceContainer.Resolve("policyService"); _passwordGenerationService = ServiceContainer.Resolve("passwordGenerationService"); @@ -172,7 +172,7 @@ namespace Bit.App.Pages private async Task> GetPasswordStrengthUserInput() { - var email = await _userService.GetEmailAsync(); + var email = await _stateService.GetEmailAsync(); List userInput = null; var atPosition = email.IndexOf('@'); if (atPosition > -1) diff --git a/src/App/Pages/Accounts/EnvironmentPage.xaml.cs b/src/App/Pages/Accounts/EnvironmentPage.xaml.cs index d6a916131..1f631af71 100644 --- a/src/App/Pages/Accounts/EnvironmentPage.xaml.cs +++ b/src/App/Pages/Accounts/EnvironmentPage.xaml.cs @@ -10,14 +10,11 @@ namespace Bit.App.Pages public partial class EnvironmentPage : BaseContentPage { private readonly IPlatformUtilsService _platformUtilsService; - private readonly IMessagingService _messagingService; private readonly EnvironmentPageViewModel _vm; public EnvironmentPage() { _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - _messagingService = ServiceContainer.Resolve("messagingService"); - _messagingService.Send("showStatusBar", true); InitializeComponent(); _vm = BindingContext as EnvironmentPageViewModel; _vm.Page = this; @@ -35,7 +32,6 @@ namespace Bit.App.Pages _vm.SubmitSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync()); _vm.CloseAction = async () => { - _messagingService.Send("showStatusBar", false); await Navigation.PopModalAsync(); }; } diff --git a/src/App/Pages/Accounts/HomePage.xaml b/src/App/Pages/Accounts/HomePage.xaml index 1040914ad..4c729f41b 100644 --- a/src/App/Pages/Accounts/HomePage.xaml +++ b/src/App/Pages/Accounts/HomePage.xaml @@ -1,53 +1,104 @@  - + - + - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs index 13bf9f6ec..e9afc50f0 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs @@ -1,7 +1,5 @@ using Bit.App.Abstractions; -using Bit.App.Models; using Bit.App.Resources; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; @@ -9,6 +7,7 @@ using System; using System.Linq; using System.Threading.Tasks; using Bit.App.Controls; +using Bit.Core.Models.Data; using Xamarin.Forms; namespace Bit.App.Pages @@ -18,7 +17,7 @@ namespace Bit.App.Pages private readonly IBroadcasterService _broadcasterService; private readonly ISyncService _syncService; private readonly IPushNotificationService _pushNotificationService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly IVaultTimeoutService _vaultTimeoutService; private readonly ICipherService _cipherService; private readonly IDeviceActionService _deviceActionService; @@ -37,7 +36,7 @@ namespace Bit.App.Pages _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _syncService = ServiceContainer.Resolve("syncService"); _pushNotificationService = ServiceContainer.Resolve("pushNotificationService"); - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _cipherService = ServiceContainer.Resolve("cipherService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); @@ -70,6 +69,10 @@ namespace Bit.App.Pages _absLayout.Children.Remove(_fab); ToolbarItems.Remove(_addItem); } + if (!mainPage) + { + ToolbarItems.Remove(_accountAvatar); + } } protected async override void OnAppearing() @@ -80,6 +83,11 @@ namespace Bit.App.Pages IsBusy = true; } + if (_vm.MainPage) + { + _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); + } + _broadcasterService.Subscribe(_pageName, async (message) => { if (message.Command == "syncStarted") @@ -100,7 +108,7 @@ namespace Bit.App.Pages } }); - var migratedFromV1 = await _storageService.GetAsync(Constants.MigratedFromV1); + var migratedFromV1 = await _stateService.GetMigratedFromV1Async(); await LoadOnAppearedAsync(_mainLayout, false, async () => { if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any()) @@ -128,10 +136,10 @@ namespace Bit.App.Pages !_vm.HasCiphers && Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None) { - var triedV1ReSync = await _storageService.GetAsync(Constants.TriedV1Resync); + var triedV1ReSync = await _stateService.GetTriedV1ResyncAsync(); if (!triedV1ReSync.GetValueOrDefault()) { - await _storageService.SaveAsync(Constants.TriedV1Resync, true); + await _stateService.SetTriedV1ResyncAsync(true); await _syncService.FullSyncAsync(true); } } @@ -145,14 +153,14 @@ namespace Bit.App.Pages } // Push registration - var lastPushRegistration = await _storageService.GetAsync(Constants.PushLastRegistrationDateKey); + var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync(); lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue); if (Device.RuntimePlatform == Device.iOS) { - var pushPromptShow = await _storageService.GetAsync(Constants.PushInitialPromptShownKey); + var pushPromptShow = await _stateService.GetPushInitialPromptShownAsync(); if (!pushPromptShow.GetValueOrDefault(false)) { - await _storageService.SaveAsync(Constants.PushInitialPromptShownKey, true); + await _stateService.SetPushInitialPromptShownAsync(true); await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert, AppResources.OkGotIt); } @@ -173,8 +181,8 @@ namespace Bit.App.Pages { if (migratedFromV1.GetValueOrDefault()) { - var migratedFromV1AutofillPromptShown = await _storageService.GetAsync( - Constants.MigratedFromV1AutofillPromptShown); + var migratedFromV1AutofillPromptShown = + await _stateService.GetMigratedFromV1AutofillPromptShownAsync(); if (!migratedFromV1AutofillPromptShown.GetValueOrDefault()) { await DisplayAlert(AppResources.Autofill, @@ -182,7 +190,7 @@ namespace Bit.App.Pages } } } - await _storageService.SaveAsync(Constants.MigratedFromV1AutofillPromptShown, true); + await _stateService.SetMigratedFromV1AutofillPromptShownAsync(true); } } @@ -284,5 +292,23 @@ namespace Bit.App.Pages _addItem.IsEnabled = !_vm.Deleted; _addItem.IconImageSource = _vm.Deleted ? null : "plus.png"; } + + private async void AccountSwitch_Clicked(object sender, EventArgs e) + { + + if (_accountListOverlay.IsVisible) + { + await ShowAccountListAsync(false, _accountListView, _accountListOverlay, _fab); + } + else + { + await ShowAccountListAsync(true, _accountListView, _accountListOverlay, _fab); + } + } + + private async void AccountRow_Selected(object sender, SelectedItemChangedEventArgs e) + { + await AccountRowSelectedAsync(sender, e, _accountListView, _accountListOverlay, _fab); + } } } diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index 228c94fb6..45b633cb4 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -1,7 +1,6 @@ using Bit.App.Abstractions; using Bit.App.Resources; using Bit.App.Utilities; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; @@ -39,13 +38,11 @@ namespace Bit.App.Pages private readonly IFolderService _folderService; private readonly ICollectionService _collectionService; private readonly ISyncService _syncService; - private readonly IUserService _userService; private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IDeviceActionService _deviceActionService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IMessagingService _messagingService; private readonly IStateService _stateService; - private readonly IStorageService _storageService; private readonly IPasswordRepromptService _passwordRepromptService; public GroupingsPageViewModel() @@ -54,13 +51,11 @@ namespace Bit.App.Pages _folderService = ServiceContainer.Resolve("folderService"); _collectionService = ServiceContainer.Resolve("collectionService"); _syncService = ServiceContainer.Resolve("syncService"); - _userService = ServiceContainer.Resolve("userService"); _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _messagingService = ServiceContainer.Resolve("messagingService"); _stateService = ServiceContainer.Resolve("stateService"); - _storageService = ServiceContainer.Resolve("storageService"); _passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); Loading = true; @@ -80,7 +75,6 @@ namespace Bit.App.Pages public string CollectionId { get; set; } public Func Filter { get; set; } public bool Deleted { get; set; } - public bool HasCiphers { get; set; } public bool HasFolders { get; set; } public bool HasCollections { get; set; } @@ -139,6 +133,18 @@ namespace Bit.App.Pages get => _websiteIconsEnabled; set => SetProperty(ref _websiteIconsEnabled, value); } + public ExtendedObservableCollection Accounts + { + get + { + // create a separate collection that includes the "add new" row + var accounts = new ExtendedObservableCollection(); + accounts.AddRange(_stateService.Accounts); + accounts.Add(new AccountView()); + return accounts; + } + } + public ExtendedObservableCollection GroupedItems { get; set; } public Command RefreshCommand { get; set; } public Command CipherOptionsCommand { get; set; } @@ -150,7 +156,7 @@ namespace Bit.App.Pages { return; } - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(); if (!authed) { return; @@ -159,7 +165,7 @@ namespace Bit.App.Pages { return; } - if (await _storageService.GetAsync(Constants.SyncOnRefreshKey) && Refreshing && !SyncRefreshing) + if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing) { SyncRefreshing = true; await _syncService.FullSyncAsync(false); @@ -175,8 +181,7 @@ namespace Bit.App.Pages var groupedItems = new List(); var page = Page as GroupingsPage; - WebsiteIconsEnabled = !(await _stateService.GetAsync(Constants.DisableFaviconKey)) - .GetValueOrDefault(); + WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); try { await LoadDataAsync(); diff --git a/src/App/Pages/Vault/SharePageViewModel.cs b/src/App/Pages/Vault/SharePageViewModel.cs index 1f37f088a..0c8d0fd9d 100644 --- a/src/App/Pages/Vault/SharePageViewModel.cs +++ b/src/App/Pages/Vault/SharePageViewModel.cs @@ -16,7 +16,7 @@ namespace Bit.App.Pages private readonly IDeviceActionService _deviceActionService; private readonly ICipherService _cipherService; private readonly ICollectionService _collectionService; - private readonly IUserService _userService; + private readonly IOrganizationService _organizationService; private readonly IPlatformUtilsService _platformUtilsService; private CipherView _cipher; private int _organizationSelectedIndex; @@ -28,7 +28,7 @@ namespace Bit.App.Pages { _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _cipherService = ServiceContainer.Resolve("cipherService"); - _userService = ServiceContainer.Resolve("userService"); + _organizationService = ServiceContainer.Resolve("organizationService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _collectionService = ServiceContainer.Resolve("collectionService"); Collections = new ExtendedObservableCollection(); @@ -67,7 +67,7 @@ namespace Bit.App.Pages var allCollections = await _collectionService.GetAllDecryptedAsync(); _writeableCollections = allCollections.Where(c => !c.ReadOnly).ToList(); - var orgs = await _userService.GetAllOrganizationAsync(); + var orgs = await _organizationService.GetAllAsync(); OrganizationOptions = orgs.OrderBy(o => o.Name) .Where(o => o.Enabled && o.Status == OrganizationUserStatusType.Confirmed) .Select(o => new KeyValuePair(o.Name, o.Id)).ToList(); @@ -110,7 +110,7 @@ namespace Bit.App.Pages await _cipherService.ShareWithServerAsync(cipherView, OrganizationId, checkedCollectionIds); await _deviceActionService.HideLoadingAsync(); var movedItemToOrgText = string.Format(AppResources.MovedItemToOrg, cipherView.Name, - (await _userService.GetOrganizationAsync(OrganizationId)).Name); + (await _organizationService.GetAsync(OrganizationId)).Name); _platformUtilsService.ShowToast("success", null, movedItemToOrgText); await Page.Navigation.PopModalAsync(); return true; diff --git a/src/App/Pages/Vault/ViewPageViewModel.cs b/src/App/Pages/Vault/ViewPageViewModel.cs index 5ea45d1b9..4cebc4b06 100644 --- a/src/App/Pages/Vault/ViewPageViewModel.cs +++ b/src/App/Pages/Vault/ViewPageViewModel.cs @@ -18,7 +18,7 @@ namespace Bit.App.Pages { private readonly IDeviceActionService _deviceActionService; private readonly ICipherService _cipherService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly ITotpService _totpService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IAuditService _auditService; @@ -48,7 +48,7 @@ namespace Bit.App.Pages { _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _cipherService = ServiceContainer.Resolve("cipherService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _totpService = ServiceContainer.Resolve("totpService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _auditService = ServiceContainer.Resolve("auditService"); @@ -248,7 +248,7 @@ namespace Bit.App.Pages return false; } Cipher = await cipher.DecryptAsync(); - CanAccessPremium = await _userService.CanAccessPremiumAsync(); + CanAccessPremium = await _stateService.CanAccessPremiumAsync(); Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(this, Cipher, f)).ToList(); if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) && diff --git a/src/App/Services/MobileStorageService.cs b/src/App/Services/MobileStorageService.cs index d1fb15cc5..60fd1fccc 100644 --- a/src/App/Services/MobileStorageService.cs +++ b/src/App/Services/MobileStorageService.cs @@ -13,15 +13,10 @@ namespace Bit.App.Services private readonly HashSet _preferenceStorageKeys = new HashSet { - Constants.VaultTimeoutKey, - Constants.VaultTimeoutActionKey, - Constants.ThemeKey, - Constants.DefaultUriMatch, - Constants.DisableAutoTotpCopyKey, - Constants.DisableFaviconKey, - Constants.ClearClipboardKey, - Constants.AutofillDisableSavePromptKey, - Constants.LastActiveTimeKey, + Constants.AppIdKey, + Constants.PreAuthEnvironmentUrlsKey, + Constants.AutofillTileAdded, + Constants.AddSitePromptShownKey, Constants.PushInitialPromptShownKey, Constants.LastFileCacheClearKey, Constants.PushLastRegistrationDateKey, @@ -37,14 +32,17 @@ namespace Bit.App.Services Constants.iOSAutoFillBiometricIntegrityKey, Constants.iOSExtensionClearCiphersCacheKey, Constants.iOSExtensionBiometricIntegrityKey, - Constants.EnvironmentUrlsKey, - Constants.InlineAutofillEnabledKey, - Constants.InvalidUnlockAttempts, + Constants.RememberEmailKey, + Constants.RememberedEmailKey, + Constants.RememberOrgIdentifierKey, + Constants.RememberedOrgIdentifierKey, + Constants.AppExtensionStartedKey, + Constants.AppExtensionActivatedKey, }; private readonly HashSet _migrateToPreferences = new HashSet { - Constants.EnvironmentUrlsKey, + "environmentUrls", }; private readonly HashSet _haveMigratedToPreferences = new HashSet(); diff --git a/src/App/Services/PushNotificationListenerService.cs b/src/App/Services/PushNotificationListenerService.cs index dd6aa73c1..9d9934cb3 100644 --- a/src/App/Services/PushNotificationListenerService.cs +++ b/src/App/Services/PushNotificationListenerService.cs @@ -19,9 +19,8 @@ namespace Bit.App.Services { private bool _showNotification; private bool _resolved; - private IStorageService _storageService; private ISyncService _syncService; - private IUserService _userService; + private IStateService _stateService; private IAppIdService _appIdService; private IApiService _apiService; private IMessagingService _messagingService; @@ -58,8 +57,8 @@ namespace Bit.App.Services return; } - var myUserId = await _userService.GetUserIdAsync(); - var isAuthenticated = await _userService.IsAuthenticatedAsync(); + var myUserId = await _stateService.GetActiveUserIdAsync(); + var isAuthenticated = await _stateService.IsAuthenticatedAsync(); switch (notification.Type) { case NotificationType.SyncCipherUpdate: @@ -129,7 +128,7 @@ namespace Bit.App.Services { Resolve(); Debug.WriteLine(string.Format("Push Notification - Device Registered - Token : {0}", token)); - var isAuthenticated = await _userService.IsAuthenticatedAsync(); + var isAuthenticated = await _stateService.IsAuthenticatedAsync(); if (!isAuthenticated) { return; @@ -141,10 +140,10 @@ namespace Bit.App.Services await _apiService.PutDeviceTokenAsync(appId, new Core.Models.Request.DeviceTokenRequest { PushToken = token }); Debug.WriteLine("Registered device with server."); - await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow); + await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow); if (deviceType == Device.Android) { - await _storageService.SaveAsync(Constants.PushCurrentTokenKey, token); + await _stateService.SetPushCurrentTokenAsync(token); } } catch (ApiException) @@ -174,9 +173,8 @@ namespace Bit.App.Services { return; } - _storageService = ServiceContainer.Resolve("storageService"); _syncService = ServiceContainer.Resolve("syncService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _appIdService = ServiceContainer.Resolve("appIdService"); _apiService = ServiceContainer.Resolve("apiService"); _messagingService = ServiceContainer.Resolve("messagingService"); diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index bf11e143a..947012569 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -14,6 +14,8 @@ using System.Threading.Tasks; using Bit.App.Models; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; using Newtonsoft.Json; using Xamarin.Essentials; using Xamarin.Forms; @@ -46,8 +48,8 @@ namespace Bit.App.Utilities } if (!string.IsNullOrWhiteSpace(cipher.Login.Totp)) { - var userService = ServiceContainer.Resolve("userService"); - var canAccessPremium = await userService.CanAccessPremiumAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + var canAccessPremium = await stateService.CanAccessPremiumAsync(); if (canAccessPremium || cipher.OrganizationUseTotp) { options.Add(AppResources.CopyTotp); @@ -330,33 +332,33 @@ namespace Bit.App.Utilities } public static async Task PerformUpdateTasksAsync(ISyncService syncService, - IDeviceActionService deviceActionService, IStorageService storageService) + IDeviceActionService deviceActionService, IStateService stateService) { var currentBuild = deviceActionService.GetBuildNumber(); - var lastBuild = await storageService.GetAsync(Constants.LastBuildKey); - if (lastBuild == null) - { - // Installed - var currentTimeout = await storageService.GetAsync(Constants.VaultTimeoutKey); - if (currentTimeout == null) - { - await storageService.SaveAsync(Constants.VaultTimeoutKey, 15); - } - - var currentAction = await storageService.GetAsync(Constants.VaultTimeoutActionKey); - if (currentAction == null) - { - await storageService.SaveAsync(Constants.VaultTimeoutActionKey, "lock"); - } - } - else if (lastBuild != currentBuild) + var lastBuild = await stateService.GetLastBuildAsync(); + // if (lastBuild == null) + // { + // // Installed + // var currentTimeout = await stateService.GetVaultTimeoutAsync(); + // if (currentTimeout == null) + // { + // await stateService.SetVaultTimeoutAsync(15); + // } + // + // var currentAction = await stateService.GetVaultTimeoutActionAsync(); + // if (currentAction == null) + // { + // await stateService.SetVaultTimeoutActionAsync("lock"); + // } + // } + if (lastBuild != currentBuild) { // Updated var tasks = Task.Run(() => syncService.FullSyncAsync(true)); } if (lastBuild != currentBuild) { - await storageService.SaveAsync(Constants.LastBuildKey, currentBuild); + await stateService.SetLastBuildAsync(currentBuild); return true; } return false; @@ -418,35 +420,34 @@ namespace Bit.App.Utilities public static async Task ClearPreviousPage() { - var storageService = ServiceContainer.Resolve("storageService"); - var previousPage = await storageService.GetAsync(Constants.PreviousPageKey); + var stateService = ServiceContainer.Resolve("stateService"); + var previousPage = await stateService.GetPreviousPageInfoAsync(); if (previousPage != null) { - await storageService.RemoveAsync(Constants.PreviousPageKey); + await stateService.SetPreviousPageInfoAsync(null); } return previousPage; } public static async Task IncrementInvalidUnlockAttemptsAsync() { - var storageService = ServiceContainer.Resolve("storageService"); - var invalidUnlockAttempts = await storageService.GetAsync(Constants.InvalidUnlockAttempts); + var stateService = ServiceContainer.Resolve("stateService"); + var invalidUnlockAttempts = await stateService.GetInvalidUnlockAttemptsAsync(); invalidUnlockAttempts++; - await storageService.SaveAsync(Constants.InvalidUnlockAttempts, invalidUnlockAttempts); + await stateService.SetInvalidUnlockAttemptsAsync(invalidUnlockAttempts); return invalidUnlockAttempts; } public static async Task ResetInvalidUnlockAttemptsAsync() { - var storageService = ServiceContainer.Resolve("storageService"); - await storageService.RemoveAsync(Constants.InvalidUnlockAttempts); + var stateService = ServiceContainer.Resolve("stateService"); + await stateService.SetInvalidUnlockAttemptsAsync(null); } public static async Task IsVaultTimeoutImmediateAsync() { - var storageService = ServiceContainer.Resolve("storageService"); - - var vaultTimeoutMinutes = await storageService.GetAsync(Constants.VaultTimeoutKey); + var stateService = ServiceContainer.Resolve("stateService"); + var vaultTimeoutMinutes = await stateService.GetVaultTimeoutAsync(); if (vaultTimeoutMinutes.GetValueOrDefault(-1) == 0) { return true; @@ -466,9 +467,8 @@ namespace Bit.App.Utilities return Convert.ToBase64String(Encoding.UTF8.GetBytes(multiByteEscaped)); } - public static async Task LogOutAsync() + public static async Task LogOutAsync(string userId) { - var userService = ServiceContainer.Resolve("userService"); var syncService = ServiceContainer.Resolve("syncService"); var tokenService = ServiceContainer.Resolve("tokenService"); var cryptoService = ServiceContainer.Resolve("cryptoService"); @@ -483,21 +483,45 @@ namespace Bit.App.Utilities var deviceActionService = ServiceContainer.Resolve("deviceActionService"); var searchService = ServiceContainer.Resolve("searchService"); - var userId = await userService.GetUserIdAsync(); + if (userId == null) + { + userId = await stateService.GetActiveUserIdAsync(); + } + await Task.WhenAll( syncService.SetLastSyncAsync(DateTime.MinValue), - tokenService.ClearTokenAsync(), - cryptoService.ClearKeysAsync(), - userService.ClearAsync(), + tokenService.ClearTokenAsync(userId), + cryptoService.ClearKeysAsync(userId), + stateService.CleanAsync(userId), settingsService.ClearAsync(userId), cipherService.ClearAsync(userId), folderService.ClearAsync(userId), collectionService.ClearAsync(userId), - passwordGenerationService.ClearAsync(), - vaultTimeoutService.ClearAsync(), - stateService.PurgeAsync(), + passwordGenerationService.ClearAsync(userId), + vaultTimeoutService.ClearAsync(userId), deviceActionService.ClearCacheAsync()); - vaultTimeoutService.BiometricLocked = true; + stateService.BiometricLocked = true; + searchService.ClearIndex(); + } + + public static async Task ClearServiceCache() + { + var tokenService = ServiceContainer.Resolve("tokenService"); + var cipherService = ServiceContainer.Resolve("cipherService"); + var folderService = ServiceContainer.Resolve("folderService"); + var collectionService = ServiceContainer.Resolve("collectionService"); + var passwordGenerationService = ServiceContainer.Resolve( + "passwordGenerationService"); + var deviceActionService = ServiceContainer.Resolve("deviceActionService"); + var searchService = ServiceContainer.Resolve("searchService"); + + await Task.WhenAll( + cipherService.ClearCacheAsync(), + deviceActionService.ClearCacheAsync()); + tokenService.ClearCache(); + folderService.ClearCache(); + collectionService.ClearCache(); + passwordGenerationService.ClearCache(); searchService.ClearIndex(); } } diff --git a/src/App/Utilities/ThemeManager.cs b/src/App/Utilities/ThemeManager.cs index fbce368ab..86b99fe3b 100644 --- a/src/App/Utilities/ThemeManager.cs +++ b/src/App/Utilities/ThemeManager.cs @@ -3,6 +3,8 @@ using Bit.App.Models; using Bit.App.Services; using Bit.App.Styles; using Bit.Core; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; using Xamarin.Forms; #if !FDROID using Microsoft.AppCenter.Crashes; @@ -93,16 +95,15 @@ namespace Bit.App.Utilities } } - public static void SetTheme(bool android, ResourceDictionary resources) + public static void SetTheme(ResourceDictionary resources) { - SetThemeStyle(GetTheme(android), resources); + SetThemeStyle(GetTheme(), resources); } - public static string GetTheme(bool android) + public static string GetTheme() { - return Xamarin.Essentials.Preferences.Get( - string.Format(PreferencesStorageService.KeyFormat, Constants.ThemeKey), default(string), - !android ? "group.com.8bit.bitwarden" : default(string)); + var stateService = ServiceContainer.Resolve("stateService"); + return stateService.GetThemeAsync().GetAwaiter().GetResult(); } public static bool OsDarkModeEnabled() diff --git a/src/Core/Abstractions/IAppIdService.cs b/src/Core/Abstractions/IAppIdService.cs index 6c3bd6d30..c5f7c0909 100644 --- a/src/Core/Abstractions/IAppIdService.cs +++ b/src/Core/Abstractions/IAppIdService.cs @@ -5,6 +5,5 @@ namespace Bit.Core.Abstractions public interface IAppIdService { Task GetAppIdAsync(); - Task GetAnonymousAppIdAsync(); } } diff --git a/src/Core/Abstractions/ICollectionService.cs b/src/Core/Abstractions/ICollectionService.cs index 1f649dba4..21035b421 100644 --- a/src/Core/Abstractions/ICollectionService.cs +++ b/src/Core/Abstractions/ICollectionService.cs @@ -22,4 +22,4 @@ namespace Bit.Core.Abstractions Task UpsertAsync(CollectionData collection); Task UpsertAsync(List collection); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index fc9376e63..fb4a56585 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -9,13 +9,13 @@ namespace Bit.Core.Abstractions { public interface ICryptoService { - Task ClearEncKeyAsync(bool memoryOnly = false); - Task ClearKeyAsync(); - Task ClearKeyHashAsync(); - Task ClearKeyPairAsync(bool memoryOnly = false); - Task ClearKeysAsync(); - Task ClearOrgKeysAsync(bool memoryOnly = false); - Task ClearPinProtectedKeyAsync(); + Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null); + Task ClearKeyAsync(string userId = null); + Task ClearKeyHashAsync(string userId = null); + Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null); + Task ClearKeysAsync(string userId = null); + Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null); + Task ClearPinProtectedKeyAsync(string userId = null); Task DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key); Task DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null); Task DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null); diff --git a/src/Core/Abstractions/IEventService.cs b/src/Core/Abstractions/IEventService.cs index 0f0a51bf0..6a8f8f42e 100644 --- a/src/Core/Abstractions/IEventService.cs +++ b/src/Core/Abstractions/IEventService.cs @@ -9,4 +9,4 @@ namespace Bit.Core.Abstractions Task CollectAsync(EventType eventType, string cipherId = null, bool uploadImmediately = false); Task UploadEventsAsync(); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IFolderService.cs b/src/Core/Abstractions/IFolderService.cs index 818921fa6..4fe215d1a 100644 --- a/src/Core/Abstractions/IFolderService.cs +++ b/src/Core/Abstractions/IFolderService.cs @@ -23,4 +23,4 @@ namespace Bit.Core.Abstractions Task UpsertAsync(FolderData folder); Task UpsertAsync(List folder); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IOrganizationService.cs b/src/Core/Abstractions/IOrganizationService.cs new file mode 100644 index 000000000..07cab5ac0 --- /dev/null +++ b/src/Core/Abstractions/IOrganizationService.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; + +namespace Bit.Core.Abstractions +{ + public interface IOrganizationService + { + Task GetAsync(string id); + Task GetByIdentifierAsync(string identifier); + Task> GetAllAsync(string userId = null); + Task ReplaceAsync(Dictionary organizations); + Task ClearAllAsync(string userId); + } +} diff --git a/src/Core/Abstractions/IPasswordGenerationService.cs b/src/Core/Abstractions/IPasswordGenerationService.cs index 0bdc80b05..4e52f60a3 100644 --- a/src/Core/Abstractions/IPasswordGenerationService.cs +++ b/src/Core/Abstractions/IPasswordGenerationService.cs @@ -8,7 +8,8 @@ namespace Bit.Core.Abstractions public interface IPasswordGenerationService { Task AddHistoryAsync(string password, CancellationToken token = default(CancellationToken)); - Task ClearAsync(); + Task ClearAsync(string userId = null); + void ClearCache(); Task GeneratePassphraseAsync(PasswordGenerationOptions options); Task GeneratePasswordAsync(PasswordGenerationOptions options); Task> GetHistoryAsync(); diff --git a/src/Core/Abstractions/ISettingsService.cs b/src/Core/Abstractions/ISettingsService.cs index a5b780e8d..8ad100f38 100644 --- a/src/Core/Abstractions/ISettingsService.cs +++ b/src/Core/Abstractions/ISettingsService.cs @@ -10,4 +10,4 @@ namespace Bit.Core.Abstractions Task>> GetEquivalentDomainsAsync(); Task SetEquivalentDomainsAsync(List> equivalentDomains); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index 484ed6d52..15c7c5ba1 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -1,12 +1,173 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +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; namespace Bit.Core.Abstractions { public interface IStateService { - Task GetAsync(string key); - Task RemoveAsync(string key); - Task SaveAsync(string key, T obj); - Task PurgeAsync(); + bool BiometricLocked { get; set; } + ExtendedObservableCollection Accounts { get; set; } + Task GetActiveUserIdAsync(StorageOptions options = null); + Task SetActiveUserAsync(string userId); + Task IsAuthenticatedAsync(StorageOptions options = null); + Task HasMultipleAccountsAsync(); + Task AddAccountAsync(Account account); + // Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations); + Task CleanAsync(string userId); + Task GetPreAuthEnvironmentUrlsAsync(); + Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value); + Task GetEnvironmentUrlsAsync(StorageOptions options = null); + Task SetEnvironmentUrlsAsync(EnvironmentUrlData value, StorageOptions options = null); + Task GetBiometricUnlockAsync(StorageOptions options = null); + Task SetBiometricUnlockAsync(bool? value, StorageOptions options = null); + Task CanAccessPremiumAsync(StorageOptions options = null); + Task GetProtectedPinAsync(StorageOptions options = null); + Task SetProtectedPinAsync(string value, StorageOptions options = null); + Task GetPinProtectedAsync(StorageOptions options = null); + Task SetPinProtectedAsync(string value, StorageOptions options = null); + Task GetPinProtectedCachedAsync(StorageOptions options = null); + Task SetPinProtectedCachedAsync(EncString value, StorageOptions options = null); + Task GetKdfTypeAsync(StorageOptions options = null); + Task SetKdfTypeAsync(KdfType? value, StorageOptions options = null); + Task GetKdfIterationsAsync(StorageOptions options = null); + Task SetKdfIterationsAsync(int? value, StorageOptions options = null); + Task GetKeyEncryptedAsync(StorageOptions options = null); + Task SetKeyEncryptedAsync(string value, StorageOptions options = null); + Task GetKeyDecryptedAsync(StorageOptions options = null); + Task SetKeyDecryptedAsync(SymmetricCryptoKey value, StorageOptions options = null); + Task GetKeyHashAsync(StorageOptions options = null); + Task SetKeyHashAsync(string value, StorageOptions options = null); + Task GetKeyHashCachedAsync(StorageOptions options = null); + Task SetKeyHashCachedAsync(string value, StorageOptions options = null); + Task GetEncKeyEncryptedAsync(StorageOptions options = null); + Task SetEncKeyEncryptedAsync(string value, StorageOptions options = null); + Task GetEncKeyDecryptedAsync(StorageOptions options = null); + Task SetEncKeyDecryptedAsync(SymmetricCryptoKey value, StorageOptions options = null); + Task> GetOrgKeysEncryptedAsync(StorageOptions options = null); + Task SetOrgKeysEncryptedAsync(Dictionary value, StorageOptions options = null); + Task> GetOrgKeysDecryptedAsync(StorageOptions options = null); + Task SetOrgKeysDecryptedAsync(Dictionary value, StorageOptions options = null); + Task GetPublicKeyAsync(StorageOptions options = null); + Task SetPublicKeyAsync(byte[] value, StorageOptions options = null); + Task GetPrivateKeyEncryptedAsync(StorageOptions options = null); + Task SetPrivateKeyEncryptedAsync(string value, StorageOptions options = null); + Task GetPrivateKeyDecryptedAsync(StorageOptions options = null); + Task SetPrivateKeyDecryptedAsync(byte[] value, StorageOptions options = null); + Task> GetAutofillBlacklistedUrisAsync(StorageOptions options = null); + Task SetAutofillBlacklistedUrisAsync(List value, StorageOptions options = null); + Task GetAutofillTileAddedAsync(StorageOptions options = null); + Task SetAutofillTileAddedAsync(bool? value, StorageOptions options = null); + Task GetEmailAsync(StorageOptions options = null); + // Task SetEmailAsync(string value, StorageOptions options = null); + Task GetLastActiveTimeAsync(StorageOptions options = null); + Task SetLastActiveTimeAsync(long? value, StorageOptions options = null); + Task GetVaultTimeoutAsync(StorageOptions options = null); + Task SetVaultTimeoutAsync(int? value, StorageOptions options = null); + Task GetVaultTimeoutActionAsync(StorageOptions options = null); + Task SetVaultTimeoutActionAsync(string value, StorageOptions options = null); + Task GetLastFileCacheClearAsync(StorageOptions options = null); + Task SetLastFileCacheClearAsync(DateTime? value, StorageOptions options = null); + Task GetPreviousPageInfoAsync(StorageOptions options = null); + Task SetPreviousPageInfoAsync(PreviousPageInfo value, StorageOptions options = null); + Task GetInvalidUnlockAttemptsAsync(StorageOptions options = null); + Task SetInvalidUnlockAttemptsAsync(int? value, StorageOptions options = null); + Task GetLastBuildAsync(StorageOptions options = null); + Task SetLastBuildAsync(string value, StorageOptions options = null); + Task GetDisableFaviconAsync(StorageOptions options = null); + Task SetDisableFaviconAsync(bool? value, StorageOptions options = null); + Task GetDisableAutoTotpCopyAsync(StorageOptions options = null); + Task SetDisableAutoTotpCopyAsync(bool? value, StorageOptions options = null); + Task GetInlineAutofillEnabledAsync(StorageOptions options = null); + Task SetInlineAutofillEnabledAsync(bool? value, StorageOptions options = null); + Task GetAutofillDisableSavePromptAsync(StorageOptions options = null); + Task SetAutofillDisableSavePromptAsync(bool? value, StorageOptions options = null); + Task>> GetLocalDataAsync(StorageOptions options = null); + Task SetLocalDataAsync(Dictionary> value, StorageOptions options = null); + Task> GetEncryptedCiphersAsync(StorageOptions options = null); + Task SetEncryptedCiphersAsync(Dictionary value, StorageOptions options = null); + Task GetDefaultUriMatchAsync(StorageOptions options = null); + Task SetDefaultUriMatchAsync(int? value, StorageOptions options = null); + Task> GetNeverDomainsAsync(StorageOptions options = null); + Task SetNeverDomainsAsync(HashSet value, StorageOptions options = null); + Task GetClearClipboardAsync(StorageOptions options = null); + Task SetClearClipboardAsync(int? value, StorageOptions options = null); + Task> GetEncryptedCollectionsAsync(StorageOptions options = null); + Task SetEncryptedCollectionsAsync(Dictionary value, StorageOptions options = null); + Task GetPasswordRepromptAutofillAsync(StorageOptions options = null); + Task SetPasswordRepromptAutofillAsync(bool? value, StorageOptions options = null); + Task GetPasswordVerifiedAutofillAsync(StorageOptions options = null); + Task SetPasswordVerifiedAutofillAsync(bool? value, StorageOptions options = null); + Task GetLastSyncAsync(StorageOptions options = null); + Task SetLastSyncAsync(DateTime? value, StorageOptions options = null); + Task GetSecurityStampAsync(StorageOptions options = null); + Task SetSecurityStampAsync(string value, StorageOptions options = null); + Task GetEmailVerifiedAsync(StorageOptions options = null); + Task SetEmailVerifiedAsync(bool? value, StorageOptions options = null); + Task GetForcePasswordReset(StorageOptions options = null); + Task SetForcePasswordResetAsync(bool? value, StorageOptions options = null); + Task GetSyncOnRefreshAsync(StorageOptions options = null); + Task SetSyncOnRefreshAsync(bool? value, StorageOptions options = null); + Task GetRememberedEmailAsync(StorageOptions options = null); + Task SetRememberedEmailAsync(string value, StorageOptions options = null); + Task GetRememberEmailAsync(StorageOptions options = null); + Task SetRememberEmailAsync(bool? value, StorageOptions options = null); + Task GetRememberedOrgIdentifierAsync(StorageOptions options = null); + Task SetRememberedOrgIdentifierAsync(string value, StorageOptions options = null); + Task GetRememberOrgIdentifierAsync(StorageOptions options = null); + Task SetRememberOrgIdentifierAsync(bool? value, StorageOptions options = null); + Task GetThemeAsync(StorageOptions options = null); + Task SetThemeAsync(string value, StorageOptions options = null); + Task GetAddSitePromptShownAsync(StorageOptions options = null); + Task SetAddSitePromptShownAsync(bool? value, StorageOptions options = null); + Task GetMigratedFromV1Async(StorageOptions options = null); + Task SetMigratedFromV1Async(bool? value, StorageOptions options = null); + Task GetMigratedFromV1AutofillPromptShownAsync(StorageOptions options = null); + Task SetMigratedFromV1AutofillPromptShownAsync(bool? value, StorageOptions options = null); + Task GetTriedV1ResyncAsync(StorageOptions options = null); + Task SetTriedV1ResyncAsync(bool? value, StorageOptions options = null); + Task GetPushInitialPromptShownAsync(StorageOptions options = null); + Task SetPushInitialPromptShownAsync(bool? value, StorageOptions options = null); + Task GetPushLastRegistrationDateAsync(StorageOptions options = null); + Task SetPushLastRegistrationDateAsync(DateTime? value, StorageOptions options = null); + Task GetPushCurrentTokenAsync(StorageOptions options = null); + Task SetPushCurrentTokenAsync(string value, StorageOptions options = null); + Task> GetEventCollectionAsync(StorageOptions options = null); + Task SetEventCollectionAsync(List value, StorageOptions options = null); + Task> GetEncryptedFoldersAsync(StorageOptions options = null); + Task SetEncryptedFoldersAsync(Dictionary value, StorageOptions options = null); + Task> GetEncryptedPoliciesAsync(StorageOptions options = null); + Task SetEncryptedPoliciesAsync(Dictionary value, StorageOptions options = null); + Task GetPushRegisteredTokenAsync(StorageOptions options = null); + Task SetPushRegisteredTokenAsync(string value, StorageOptions options = null); + Task GetAppExtensionStartedAsync(StorageOptions options = null); + Task SetAppExtensionStartedAsync(bool? value, StorageOptions options = null); + Task GetAppExtensionActivatedAsync(StorageOptions options = null); + Task SetAppExtensionActivatedAsync(bool? value, StorageOptions options = null); + Task GetAppIdAsync(StorageOptions options = null); + Task SetAppIdAsync(string value, StorageOptions options = null); + Task GetUsesKeyConnectorAsync(StorageOptions options = null); + Task SetUsesKeyConnectorAsync(bool? value, StorageOptions options = null); + Task> GetOrganizationsAsync(StorageOptions options = null); + Task SetOrganizationsAsync(Dictionary organizations, StorageOptions options = null); + Task GetPasswordGenerationOptionsAsync(StorageOptions options = null); + Task SetPasswordGenerationOptionsAsync(PasswordGenerationOptions value, StorageOptions options = null); + Task> GetEncryptedPasswordGenerationHistory(StorageOptions options = null); + Task SetEncryptedPasswordGenerationHistoryAsync(List value, StorageOptions options = null); + Task> GetEncryptedSendsAsync(StorageOptions options = null); + Task SetEncryptedSendsAsync(Dictionary value, StorageOptions options = null); + Task> GetSettingsAsync(StorageOptions options = null); + Task SetSettingsAsync(Dictionary value, StorageOptions options = null); + Task GetAccessTokenAsync(StorageOptions options = null); + Task SetAccessTokenAsync(string value, StorageOptions options = null); + Task GetRefreshTokenAsync(StorageOptions options = null); + Task SetRefreshTokenAsync(string value, StorageOptions options = null); + Task GetTwoFactorTokenAsync(StorageOptions options = null); + Task SetTwoFactorTokenAsync(string value, StorageOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/ITokenService.cs b/src/Core/Abstractions/ITokenService.cs index b08391865..34febcc3a 100644 --- a/src/Core/Abstractions/ITokenService.cs +++ b/src/Core/Abstractions/ITokenService.cs @@ -6,15 +6,16 @@ namespace Bit.Core.Abstractions { public interface ITokenService { - Task ClearTokenAsync(); + Task ClearTokenAsync(string userId = null); Task ClearTwoFactorTokenAsync(string email); + void ClearCache(); JObject DecodeToken(); string GetEmail(); bool GetEmailVerified(); string GetIssuer(); string GetName(); bool GetPremium(); - bool GetIsExternal(); + Task GetIsExternal(); Task GetRefreshTokenAsync(); Task GetTokenAsync(); Task ToggleTokensAsync(); diff --git a/src/Core/Abstractions/IVaultTimeoutService.cs b/src/Core/Abstractions/IVaultTimeoutService.cs index 1c780a169..c817bdb56 100644 --- a/src/Core/Abstractions/IVaultTimeoutService.cs +++ b/src/Core/Abstractions/IVaultTimeoutService.cs @@ -1,22 +1,18 @@ using System; using System.Threading.Tasks; -using Bit.Core.Models.Domain; namespace Bit.Core.Abstractions { public interface IVaultTimeoutService { - EncString PinProtectedKey { get; set; } - bool BiometricLocked { get; set; } - Task CheckVaultTimeoutAsync(); - Task ClearAsync(); - Task IsLockedAsync(); + Task ClearAsync(string userId = null); + Task IsLockedAsync(string userId = null); Task> IsPinLockSetAsync(); Task IsBiometricLockSetAsync(); - Task LockAsync(bool allowSoftLock = false, bool userInitiated = false); - Task LogOutAsync(); + Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null); + Task LogOutAsync(string userId = null); Task SetVaultTimeoutOptionsAsync(int? timeout, string action); - Task GetVaultTimeout(); + Task GetVaultTimeout(string userId = null); } } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 90cd12e22..83a4d524c 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -4,29 +4,16 @@ { public const string AndroidAppProtocol = "androidapp://"; public const string iOSAppProtocol = "iosapp://"; - public static string SyncOnRefreshKey = "syncOnRefresh"; - public static string VaultTimeoutKey = "lockOption"; - public static string VaultTimeoutActionKey = "vaultTimeoutAction"; - public static string LastActiveTimeKey = "lastActiveTime"; - public static string BiometricUnlockKey = "fingerprintUnlock"; - public static string ProtectedPin = "protectedPin"; - public static string PinProtectedKey = "pinProtectedKey"; - public static string DefaultUriMatch = "defaultUriMatch"; - public static string DisableAutoTotpCopyKey = "disableAutoTotpCopy"; - public static string EnvironmentUrlsKey = "environmentUrls"; + public static string StateKey = "state"; + public static string AppIdKey = "appId"; + public static string PreAuthEnvironmentUrlsKey = "preAuthEnvironmentUrls"; public static string LastFileCacheClearKey = "lastFileCacheClear"; - public static string AutofillDisableSavePromptKey = "autofillDisableSavePrompt"; - public static string AutofillBlacklistedUrisKey = "autofillBlacklistedUris"; public static string AutofillTileAdded = "autofillTileAdded"; - public static string DisableFaviconKey = "disableFavicon"; public static string PushRegisteredTokenKey = "pushRegisteredToken"; public static string PushCurrentTokenKey = "pushCurrentToken"; public static string PushLastRegistrationDateKey = "pushLastRegistrationDate"; public static string PushInitialPromptShownKey = "pushInitialPromptShown"; - public static string ThemeKey = "theme"; - public static string ClearClipboardKey = "clearClipboard"; public static string LastBuildKey = "lastBuild"; - public static string OldUserIdKey = "userId"; public static string AddSitePromptShownKey = "addSitePromptShown"; public static string ClearCiphersCacheKey = "clearCiphersCache"; public static string BiometricIntegrityKey = "biometricIntegrityState"; @@ -38,11 +25,12 @@ public static string MigratedFromV1AutofillPromptShown = "migratedV1AutofillPromptShown"; public static string TriedV1Resync = "triedV1Resync"; public static string EventCollectionKey = "eventCollection"; - public static string PreviousPageKey = "previousPage"; - public static string InlineAutofillEnabledKey = "inlineAutofillEnabled"; - public static string InvalidUnlockAttempts = "invalidUnlockAttempts"; - public static string PasswordRepromptAutofillKey = "passwordRepromptAutofillKey"; - public static string PasswordVerifiedAutofillKey = "passwordVerifiedAutofillKey"; + public static string RememberEmailKey = "rememberEmail"; + public static string RememberedEmailKey = "rememberedEmail"; + public static string RememberOrgIdentifierKey = "rememberOrgIdentifier"; + public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier"; + public static string AppExtensionStartedKey = "appExtensionStarted"; + public static string AppExtensionActivatedKey = "appExtensionActivated"; public const int SelectFileRequestCode = 42; public const int SelectFilePermissionRequestCode = 43; public const int SaveFileRequestCode = 44; @@ -58,5 +46,47 @@ iOSAutoFillClearCiphersCacheKey, iOSExtensionClearCiphersCacheKey }; + + public static string CiphersKey(string userId) => $"ciphers_{userId}"; + public static string FoldersKey(string userId) => $"folders_{userId}"; + public static string CollectionsKey(string userId) => $"collections_{userId}"; + public static string OrganizationsKey(string userId) => $"organizations_{userId}"; + public static string LocalDataKey(string userId) => $"ciphersLocalData_{userId}"; + public static string NeverDomainsKey(string userId) => $"neverDomains_{userId}"; + public static string SendsKey(string userId) => $"sends_{userId}"; + public static string PoliciesKey(string userId) => $"policies_{userId}"; + public static string KeyKey(string userId) => $"key_{userId}"; + public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}"; + public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{userId}"; + public static string EncKeyKey(string userId) => $"encKey_{userId}"; + public static string KeyHashKey(string userId) => $"keyHash_{userId}"; + public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}"; + public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}"; + public static string PassGenHistoryKey(string userId) => $"generatedPasswordHistory_{userId}"; + public static string TwoFactorTokenKey(string email) => $"twoFactorToken_{email}"; + public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}"; + public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}"; + public static string LastActiveTimeKey(string userId) => $"lastActiveTime_{userId}"; + public static string InvalidUnlockAttemptsKey(string userId) => $"invalidUnlockAttempts_{userId}"; + public static string InlineAutofillEnabledKey(string userId) => $"inlineAutofillEnabled_{userId}"; + public static string AutofillDisableSavePromptKey(string userId) => $"autofillDisableSavePrompt_{userId}"; + public static string AutofillBlacklistedUrisKey(string userId) => $"autofillBlacklistedUris_{userId}"; + public static string ClearClipboardKey(string userId) => $"clearClipboard_{userId}"; + public static string SyncOnRefreshKey(string userId) => $"syncOnRefresh_{userId}"; + public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}"; + public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}"; + public static string ThemeKey(string userId) => $"theme_{userId}"; + public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}"; + public static string PreviousPageKey(string userId) => $"previousPage_{userId}"; + public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}"; + public static string PasswordVerifiedAutofillKey(string userId) => $"passwordVerifiedAutofillKey_{userId}"; + public static string SettingsKey(string userId) => $"settings_{userId}"; + public static string UsesKeyConnectorKey(string userId) => $"usesKeyConnector_{userId}"; + public static string ProtectedPinKey(string userId) => $"protectedPin_{userId}"; + public static string SecurityStampKey(string userId) => $"securityStamp_{userId}"; + public static string EmailVerifiedKey(string userId) => $"emailVerified_{userId}"; + public static string ForcePasswordResetKey(string userId) => $"forcePasswordReset_{userId}"; + public static string LastSyncKey(string userId) => $"lastSync_{userId}"; + public static string BiometricUnlockKey(string userId) => $"biometricUnlock_{userId}"; } } diff --git a/src/Core/Enums/AuthenticationStatus.cs b/src/Core/Enums/AuthenticationStatus.cs new file mode 100644 index 000000000..feef7e588 --- /dev/null +++ b/src/Core/Enums/AuthenticationStatus.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.Enums +{ + public enum AuthenticationStatus : byte + { + LoggedOut = 0, + Locked = 1, + Unlocked = 2, + Active = 3, + } +} diff --git a/src/Core/Enums/DiskStorageLocation.cs b/src/Core/Enums/DiskStorageLocation.cs new file mode 100644 index 000000000..e84a238c9 --- /dev/null +++ b/src/Core/Enums/DiskStorageLocation.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Enums +{ + public enum DiskStorageLocation + { + Default = 1, + Preferences = 2, + SecureStorage = 3 + } +} diff --git a/src/Core/Enums/StorageLocation.cs b/src/Core/Enums/StorageLocation.cs new file mode 100644 index 000000000..faa9476c4 --- /dev/null +++ b/src/Core/Enums/StorageLocation.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Enums +{ + public enum StorageLocation + { + Both = 0, + Disk = 1, + Memory = 2 + } +} diff --git a/src/App/Models/PreviousPageInfo.cs b/src/Core/Models/Data/PreviousPageInfo.cs similarity index 86% rename from src/App/Models/PreviousPageInfo.cs rename to src/Core/Models/Data/PreviousPageInfo.cs index 3f6bba7de..bcf8e29c3 100644 --- a/src/App/Models/PreviousPageInfo.cs +++ b/src/Core/Models/Data/PreviousPageInfo.cs @@ -1,4 +1,4 @@ -namespace Bit.App.Models +namespace Bit.Core.Models.Data { public class PreviousPageInfo { diff --git a/src/Core/Models/Domain/Account.cs b/src/Core/Models/Domain/Account.cs new file mode 100644 index 000000000..0638ec728 --- /dev/null +++ b/src/Core/Models/Domain/Account.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using Bit.Core.Enums; +using Bit.Core.Models.Data; + +namespace Bit.Core.Models.Domain +{ + public class Account : Domain + { + public AccountProfile Profile; + public AccountTokens Tokens; + public AccountSettings Settings; + public AccountKeys Keys; + + public Account() { } + + public Account(AccountProfile profile, AccountTokens tokens) + { + Profile = profile; + Tokens = tokens; + Settings = new AccountSettings(); + Keys = new AccountKeys(); + } + + public Account(Account account) + { + // Copy constructor excludes Keys (for storage) + Profile = new AccountProfile(account.Profile); + Tokens = new AccountTokens(account.Tokens); + Settings = new AccountSettings(account.Settings); + } + + public class AccountProfile + { + public AccountProfile() { } + + public AccountProfile(AccountProfile copy) + { + if (copy == null) { return;} + + UserId = copy.UserId; + Email = copy.Email; + AuthStatus = copy.AuthStatus; + HasPremiumPersonally = copy.HasPremiumPersonally; + KdfType = copy.KdfType; + KdfIterations = copy.KdfIterations; + } + + public string UserId; + public string Email; + public AuthenticationStatus? AuthStatus; + public bool? HasPremiumPersonally; + public KdfType? KdfType; + public int? KdfIterations; + } + + public class AccountTokens + { + public AccountTokens() {} + + public AccountTokens(AccountTokens copy) + { + if (copy == null) { return;} + + AccessToken = copy.AccessToken; + RefreshToken = copy.RefreshToken; + } + + public string AccessToken; + public string RefreshToken; + } + + public class AccountSettings + { + public AccountSettings() {} + + public AccountSettings(AccountSettings copy) + { + if (copy == null) { return;} + + EnvironmentUrls = copy.EnvironmentUrls; + PinProtected = copy.PinProtected; + } + + public EnvironmentUrlData EnvironmentUrls; + public EncString PinProtected; + } + + public class AccountKeys + { + public SymmetricCryptoKey Key; + public string KeyHash; + public SymmetricCryptoKey EncKey; + public Dictionary + OrganizationKeys = new Dictionary(); + public byte[] PrivateKey; + public byte[] PublicKey; + } + } +} diff --git a/src/Core/Models/Domain/GlobalState.cs b/src/Core/Models/Domain/GlobalState.cs new file mode 100644 index 000000000..20c80d7e1 --- /dev/null +++ b/src/Core/Models/Domain/GlobalState.cs @@ -0,0 +1,10 @@ +using System; + +namespace Bit.Core.Models.Domain +{ + public class GlobalState : Domain + { + public int? StateVersion { get; set; } + public bool EnableBiometrics { get; set; } + } +} diff --git a/src/Core/Models/Domain/State.cs b/src/Core/Models/Domain/State.cs new file mode 100644 index 000000000..f8180fab8 --- /dev/null +++ b/src/Core/Models/Domain/State.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Bit.Core.Models.Domain +{ + public class State : Domain + { + public Dictionary Accounts { get; set; } + public string ActiveUserId { get; set; } + } +} diff --git a/src/Core/Models/Domain/StorageOptions.cs b/src/Core/Models/Domain/StorageOptions.cs new file mode 100644 index 000000000..709f89b1b --- /dev/null +++ b/src/Core/Models/Domain/StorageOptions.cs @@ -0,0 +1,12 @@ +using Bit.Core.Enums; + +namespace Bit.Core.Models.Domain +{ + public class StorageOptions : Domain + { + public StorageLocation? StorageLocation { get; set; } + public bool? UseSecureStorage { get; set; } + public string UserId { get; set; } + public string Email { get; set; } + } +} diff --git a/src/Core/Models/View/AccountView.cs b/src/Core/Models/View/AccountView.cs new file mode 100644 index 000000000..279bcd016 --- /dev/null +++ b/src/Core/Models/View/AccountView.cs @@ -0,0 +1,31 @@ +using Bit.Core.Enums; +using Bit.Core.Models.Domain; + +namespace Bit.Core.Models.View +{ + public class AccountView : View + { + public AccountView() { } + + public AccountView(Account a = null) + { + if (a == null) + { + // null will render as "Add Account" row + return; + } + IsAccount = true; + + UserId = a.Profile.UserId; + Email = a.Profile.Email; + Hostname = a.Settings.EnvironmentUrls.Base; + AuthStatus = a.Profile.AuthStatus; + } + + public bool IsAccount { get; set; } + public string UserId { get; set; } + public string Email { get; set; } + public string Hostname { get; set; } + public AuthenticationStatus? AuthStatus { get; set; } + } +} diff --git a/src/Core/Services/AppIdService.cs b/src/Core/Services/AppIdService.cs index ada27d857..ba19779fe 100644 --- a/src/Core/Services/AppIdService.cs +++ b/src/Core/Services/AppIdService.cs @@ -6,33 +6,28 @@ namespace Bit.Core.Services { public class AppIdService : IAppIdService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; - public AppIdService(IStorageService storageService) + public AppIdService(IStateService stateService) { - _storageService = storageService; + _stateService = stateService; } - public Task GetAppIdAsync() + public async Task GetAppIdAsync() { - return MakeAndGetAppIdAsync("appId"); - } - - public Task GetAnonymousAppIdAsync() - { - return MakeAndGetAppIdAsync("anonymousAppId"); - } - - private async Task MakeAndGetAppIdAsync(string key) - { - var existingId = await _storageService.GetAsync(key); - if (existingId != null) + var appId = await _stateService.GetAppIdAsync(); + if (appId != null) { - return existingId; + return appId; } - var guid = Guid.NewGuid().ToString(); - await _storageService.SaveAsync(key, guid); - return guid; + appId = MakeAppId(); + await _stateService.SetAppIdAsync(appId); + return appId; + } + + private string MakeAppId() + { + return Guid.NewGuid().ToString(); } } } diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index ca76c437f..ae8ea5e95 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -14,13 +14,12 @@ namespace Bit.Core.Services private readonly ICryptoService _cryptoService; private readonly ICryptoFunctionService _cryptoFunctionService; private readonly IApiService _apiService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly ITokenService _tokenService; private readonly IAppIdService _appIdService; private readonly II18nService _i18nService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IMessagingService _messagingService; - private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IKeyConnectorService _keyConnectorService; private readonly bool _setCryptoKeys; @@ -30,7 +29,7 @@ namespace Bit.Core.Services ICryptoService cryptoService, ICryptoFunctionService cryptoFunctionService, IApiService apiService, - IUserService userService, + IStateService stateService, ITokenService tokenService, IAppIdService appIdService, II18nService i18nService, @@ -43,13 +42,12 @@ namespace Bit.Core.Services _cryptoService = cryptoService; _cryptoFunctionService = cryptoFunctionService; _apiService = apiService; - _userService = userService; + _stateService = stateService; _tokenService = tokenService; _appIdService = appIdService; _i18nService = i18nService; _platformUtilsService = platformUtilsService; _messagingService = messagingService; - _vaultTimeoutService = vaultTimeoutService; _keyConnectorService = keyConnectorService; _setCryptoKeys = setCryptoKeys; @@ -355,8 +353,23 @@ namespace Bit.Core.Services await _tokenService.SetTwoFactorTokenAsync(tokenResponse.TwoFactorToken, email); } await _tokenService.SetTokensAsync(tokenResponse.AccessToken, tokenResponse.RefreshToken); - await _userService.SetInformationAsync(_tokenService.GetUserId(), _tokenService.GetEmail(), - tokenResponse.Kdf, tokenResponse.KdfIterations); + await _stateService.AddAccountAsync( + new Account( + new Account.AccountProfile() + { + UserId = _tokenService.GetUserId(), + Email = _tokenService.GetEmail(), + HasPremiumPersonally = _tokenService.GetPremium(), + KdfType = tokenResponse.Kdf, + KdfIterations = tokenResponse.KdfIterations, + }, + new Account.AccountTokens() + { + AccessToken = tokenResponse.AccessToken, + RefreshToken = tokenResponse.RefreshToken, + } + ) + ); if (_setCryptoKeys) { if (key != null) @@ -430,7 +443,7 @@ namespace Bit.Core.Services } - _vaultTimeoutService.BiometricLocked = false; + _stateService.BiometricLocked = false; _messagingService.Send("loggedIn"); return result; } diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index 5f644472d..f95eaa992 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -19,15 +19,11 @@ namespace Bit.Core.Services { public class CipherService : ICipherService { - private const string Keys_CiphersFormat = "ciphers_{0}"; - private const string Keys_LocalData = "ciphersLocalData"; - private const string Keys_NeverDomains = "neverDomains"; - private readonly string[] _ignoredSearchTerms = new string[] { "com", "net", "org", "android", "io", "co", "uk", "au", "nz", "fr", "de", "tv", "info", "app", "apps", "eu", "me", "dev", "jp", "mobile" }; private List _decryptedCipherCache; private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly ISettingsService _settingsService; private readonly IApiService _apiService; private readonly IFileUploadService _fileUploadService; @@ -45,7 +41,7 @@ namespace Bit.Core.Services public CipherService( ICryptoService cryptoService, - IUserService userService, + IStateService stateService, ISettingsService settingsService, IApiService apiService, IFileUploadService fileUploadService, @@ -56,7 +52,7 @@ namespace Bit.Core.Services string[] allClearCipherCacheKeys) { _cryptoService = cryptoService; - _userService = userService; + _stateService = stateService; _settingsService = settingsService; _apiService = apiService; _fileUploadService = fileUploadService; @@ -211,11 +207,8 @@ namespace Bit.Core.Services public async Task GetAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var localData = await _storageService.GetAsync>>( - Keys_LocalData); - var ciphers = await _storageService.GetAsync>( - string.Format(Keys_CiphersFormat, userId)); + var localData = await _stateService.GetLocalDataAsync(); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (!ciphers?.ContainsKey(id) ?? true) { return null; @@ -226,11 +219,8 @@ namespace Bit.Core.Services public async Task> GetAllAsync() { - var userId = await _userService.GetUserIdAsync(); - var localData = await _storageService.GetAsync>>( - Keys_LocalData); - var ciphers = await _storageService.GetAsync>( - string.Format(Keys_CiphersFormat, userId)); + var localData = await _stateService.GetLocalDataAsync(); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); var response = ciphers?.Select(c => new Cipher(c.Value, false, localData?.ContainsKey(c.Key) ?? false ? localData[c.Key] : null)); return response?.ToList() ?? new List(); @@ -347,7 +337,7 @@ namespace Bit.Core.Services var others = new List(); var ciphers = await ciphersTask; - var defaultMatch = (UriMatchType?)(await _storageService.GetAsync(Constants.DefaultUriMatch)); + var defaultMatch = (UriMatchType?)(await _stateService.GetDefaultUriMatchAsync()); if (defaultMatch == null) { defaultMatch = UriMatchType.Domain; @@ -457,8 +447,7 @@ namespace Bit.Core.Services public async Task UpdateLastUsedDateAsync(string id) { - var ciphersLocalData = await _storageService.GetAsync>>( - Keys_LocalData); + var ciphersLocalData = await _stateService.GetLocalDataAsync(); if (ciphersLocalData == null) { ciphersLocalData = new Dictionary>(); @@ -476,7 +465,7 @@ namespace Bit.Core.Services ciphersLocalData[id].Add("lastUsedDate", DateTime.UtcNow); } - await _storageService.SaveAsync(Keys_LocalData, ciphersLocalData); + await _stateService.SetLocalDataAsync(ciphersLocalData); // Update cache if (DecryptedCipherCache == null) { @@ -495,13 +484,13 @@ namespace Bit.Core.Services { return; } - var domains = await _storageService.GetAsync>(Keys_NeverDomains); + var domains = await _stateService.GetNeverDomainsAsync(); if (domains == null) { domains = new HashSet(); } domains.Add(domain); - await _storageService.SaveAsync(Keys_NeverDomains, domains); + await _stateService.SetNeverDomainsAsync(domains); } public async Task SaveWithServerAsync(Cipher cipher) @@ -526,7 +515,7 @@ namespace Bit.Core.Services var request = new CipherRequest(cipher); response = await _apiService.PutCipherAsync(cipher.Id, request); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var data = new CipherData(response, userId, cipher.CollectionIds); await UpsertAsync(data); } @@ -550,7 +539,7 @@ namespace Bit.Core.Services var encCipher = await EncryptAsync(cipher); var request = new CipherShareRequest(encCipher); var response = await _apiService.PutShareCipherAsync(cipher.Id, request); - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var data = new CipherData(response, userId, collectionIds); await UpsertAsync(data); } @@ -581,7 +570,7 @@ namespace Bit.Core.Services response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var cData = new CipherData(response, userId, cipher.CollectionIds); await UpsertAsync(cData); return new Cipher(cData); @@ -602,16 +591,14 @@ namespace Bit.Core.Services { var request = new CipherCollectionsRequest(cipher.CollectionIds?.ToList()); await _apiService.PutCipherCollectionsAsync(cipher.Id, request); - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var data = cipher.ToCipherData(userId); await UpsertAsync(data); } public async Task UpsertAsync(CipherData cipher) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(storageKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { ciphers = new Dictionary(); @@ -621,15 +608,13 @@ namespace Bit.Core.Services ciphers.Add(cipher.Id, null); } ciphers[cipher.Id] = cipher; - await _storageService.SaveAsync(storageKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task UpsertAsync(List cipher) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(storageKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { ciphers = new Dictionary(); @@ -642,28 +627,25 @@ namespace Bit.Core.Services } ciphers[c.Id] = c; } - await _storageService.SaveAsync(storageKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task ReplaceAsync(Dictionary ciphers) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_CiphersFormat, userId), ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_CiphersFormat, userId)); + await _stateService.SetEncryptedCiphersAsync(null, new StorageOptions { UserId = userId }); await ClearCacheAsync(); } public async Task DeleteAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { return; @@ -673,15 +655,13 @@ namespace Bit.Core.Services return; } ciphers.Remove(id); - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task DeleteAsync(List ids) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { return; @@ -694,7 +674,7 @@ namespace Bit.Core.Services } ciphers.Remove(id); } - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } @@ -706,9 +686,7 @@ namespace Bit.Core.Services public async Task DeleteAttachmentAsync(string id, string attachmentId) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null || !ciphers.ContainsKey(id) || ciphers[id].Attachments == null) { return; @@ -718,7 +696,7 @@ namespace Bit.Core.Services { ciphers[id].Attachments.Remove(attachment); } - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } @@ -771,9 +749,7 @@ namespace Bit.Core.Services public async Task SoftDeleteWithServerAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { return; @@ -785,15 +761,13 @@ namespace Bit.Core.Services await _apiService.PutDeleteCipherAsync(id); ciphers[id].DeletedDate = DateTime.UtcNow; - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task RestoreWithServerAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { return; @@ -805,7 +779,7 @@ namespace Bit.Core.Services var response = await _apiService.PutRestoreCipherAsync(id); ciphers[id].DeletedDate = null; ciphers[id].RevisionDate = response.RevisionDate; - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } diff --git a/src/Core/Services/CollectionService.cs b/src/Core/Services/CollectionService.cs index aadce803e..c4143e19d 100644 --- a/src/Core/Services/CollectionService.cs +++ b/src/Core/Services/CollectionService.cs @@ -13,24 +13,20 @@ namespace Bit.Core.Services { public class CollectionService : ICollectionService { - private const string Keys_CollectionsFormat = "collections_{0}"; private const char NestingDelimiter = '/'; private List _decryptedCollectionCache; private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly II18nService _i18nService; public CollectionService( ICryptoService cryptoService, - IUserService userService, - IStorageService storageService, + IStateService stateService, II18nService i18nService) { _cryptoService = cryptoService; - _userService = userService; - _storageService = storageService; + _stateService = stateService; _i18nService = i18nService; } @@ -83,9 +79,7 @@ namespace Bit.Core.Services public async Task GetAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var collections = await _storageService.GetAsync>( - string.Format(Keys_CollectionsFormat, userId)); + var collections = await _stateService.GetEncryptedCollectionsAsync(); if (!collections?.ContainsKey(id) ?? true) { return null; @@ -95,9 +89,7 @@ namespace Bit.Core.Services public async Task> GetAllAsync() { - var userId = await _userService.GetUserIdAsync(); - var collections = await _storageService.GetAsync>( - string.Format(Keys_CollectionsFormat, userId)); + var collections = await _stateService.GetEncryptedCollectionsAsync(); var response = collections?.Select(c => new Collection(c.Value)); return response?.ToList() ?? new List(); } @@ -148,9 +140,7 @@ namespace Bit.Core.Services public async Task UpsertAsync(CollectionData collection) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_CollectionsFormat, userId); - var collections = await _storageService.GetAsync>(storageKey); + var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null) { collections = new Dictionary(); @@ -160,15 +150,13 @@ namespace Bit.Core.Services collections.Add(collection.Id, null); } collections[collection.Id] = collection; - await _storageService.SaveAsync(storageKey, collections); + await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task UpsertAsync(List collection) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_CollectionsFormat, userId); - var collections = await _storageService.GetAsync>(storageKey); + var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null) { collections = new Dictionary(); @@ -181,34 +169,31 @@ namespace Bit.Core.Services } collections[c.Id] = c; } - await _storageService.SaveAsync(storageKey, collections); + await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task ReplaceAsync(Dictionary collections) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_CollectionsFormat, userId), collections); + await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_CollectionsFormat, userId)); + await _stateService.SetEncryptedCollectionsAsync(null, new StorageOptions { UserId = userId }); _decryptedCollectionCache = null; } public async Task DeleteAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var collectionKey = string.Format(Keys_CollectionsFormat, userId); - var collections = await _storageService.GetAsync>(collectionKey); + var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null || !collections.ContainsKey(id)) { return; } collections.Remove(id); - await _storageService.SaveAsync(collectionKey, collections); + await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 375f6d467..1e75127f2 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -14,53 +14,38 @@ namespace Bit.Core.Services { public class CryptoService : ICryptoService { - private readonly IStorageService _storageService; - private readonly IStorageService _secureStorageService; + private readonly IStateService _stateService; private readonly ICryptoFunctionService _cryptoFunctionService; - private SymmetricCryptoKey _key; - private SymmetricCryptoKey _encKey; private SymmetricCryptoKey _legacyEtmKey; - private string _keyHash; - private byte[] _publicKey; - private byte[] _privateKey; - private Dictionary _orgKeys; private Task _getEncKeysTask; private Task> _getOrgKeysTask; - private const string Keys_Key = "key"; - private const string Keys_EncOrgKeys = "encOrgKeys"; - private const string Keys_EncPrivateKey = "encPrivateKey"; - private const string Keys_EncKey = "encKey"; - private const string Keys_KeyHash = "keyHash"; - public CryptoService( - IStorageService storageService, - IStorageService secureStorageService, + IStateService stateService, ICryptoFunctionService cryptoFunctionService) { - _storageService = storageService; - _secureStorageService = secureStorageService; + _stateService = stateService; _cryptoFunctionService = cryptoFunctionService; } public async Task SetKeyAsync(SymmetricCryptoKey key) { - _key = key; - var option = await _storageService.GetAsync(Constants.VaultTimeoutKey); - var biometric = await _storageService.GetAsync(Constants.BiometricUnlockKey); + await _stateService.SetKeyDecryptedAsync(key); + var option = await _stateService.GetVaultTimeoutAsync(); + var biometric = await _stateService.GetBiometricUnlockAsync(); if (option.HasValue && !biometric.GetValueOrDefault()) { // If we have a lock option set, we do not store the key return; } - await _secureStorageService.SaveAsync(Keys_Key, key?.KeyB64); + await _stateService.SetKeyEncryptedAsync(key?.KeyB64); } public async Task SetKeyHashAsync(string keyHash) { - _keyHash = keyHash; - await _storageService.SaveAsync(Keys_KeyHash, keyHash); + await _stateService.SetKeyHashCachedAsync(keyHash); + await _stateService.SetKeyHashAsync(keyHash); } public async Task SetEncKeyAsync(string encKey) @@ -69,8 +54,8 @@ namespace Bit.Core.Services { return; } - await _storageService.SaveAsync(Keys_EncKey, encKey); - _encKey = null; + await _stateService.SetEncKeyEncryptedAsync(encKey); + await _stateService.SetEncKeyDecryptedAsync(null); } public async Task SetEncPrivateKeyAsync(string encPrivateKey) @@ -79,50 +64,54 @@ namespace Bit.Core.Services { return; } - await _storageService.SaveAsync(Keys_EncPrivateKey, encPrivateKey); - _privateKey = null; + await _stateService.SetPrivateKeyEncryptedAsync(encPrivateKey); + await _stateService.SetPrivateKeyDecryptedAsync(null); } public async Task SetOrgKeysAsync(IEnumerable orgs) { var orgKeys = orgs.ToDictionary(org => org.Id, org => org.Key); - _orgKeys = null; - await _storageService.SaveAsync(Keys_EncOrgKeys, orgKeys); + await _stateService.SetOrgKeysDecryptedAsync(null); + await _stateService.SetOrgKeysEncryptedAsync(orgKeys); } public async Task GetKeyAsync() { - if (_key != null) + var inMemoryKey = await _stateService.GetKeyDecryptedAsync(); + if (inMemoryKey != null) { - return _key; + return inMemoryKey; } - var key = await _secureStorageService.GetAsync(Keys_Key); + var key = await _stateService.GetKeyEncryptedAsync(); if (key != null) { - _key = new SymmetricCryptoKey(Convert.FromBase64String(key)); + inMemoryKey = new SymmetricCryptoKey(Convert.FromBase64String(key)); + await _stateService.SetKeyDecryptedAsync(inMemoryKey); } - return _key; + return inMemoryKey; } public async Task GetKeyHashAsync() { - if (_keyHash != null) + var inMemoryKeyHash = await _stateService.GetKeyHashCachedAsync(); + if (inMemoryKeyHash != null) { - return _keyHash; + return inMemoryKeyHash; } - var keyHash = await _storageService.GetAsync(Keys_KeyHash); + var keyHash = await _stateService.GetKeyHashAsync(); if (keyHash != null) { - _keyHash = keyHash; + await _stateService.SetKeyHashCachedAsync(keyHash); } - return _keyHash; + return keyHash; } public Task GetEncKeyAsync(SymmetricCryptoKey key = null) { - if (_encKey != null) + var inMemoryKey = _stateService.GetEncKeyDecryptedAsync().GetAwaiter().GetResult(); + if (inMemoryKey != null) { - return Task.FromResult(_encKey); + return Task.FromResult(inMemoryKey); } if (_getEncKeysTask != null && !_getEncKeysTask.IsCompleted && !_getEncKeysTask.IsFaulted) { @@ -132,7 +121,7 @@ namespace Bit.Core.Services { try { - var encKey = await _storageService.GetAsync(Keys_EncKey); + var encKey = await _stateService.GetEncKeyEncryptedAsync(); if (encKey == null) { return null; @@ -167,8 +156,9 @@ namespace Bit.Core.Services { return null; } - _encKey = new SymmetricCryptoKey(decEncKey); - return _encKey; + var newEncKey = new SymmetricCryptoKey(decEncKey); + await _stateService.SetEncKeyDecryptedAsync(newEncKey); + return newEncKey; } finally { @@ -181,32 +171,36 @@ namespace Bit.Core.Services public async Task GetPublicKeyAsync() { - if (_publicKey != null) + var inMemoryKey = await _stateService.GetPublicKeyAsync(); + if (inMemoryKey != null) { - return _publicKey; + return inMemoryKey; } var privateKey = await GetPrivateKeyAsync(); if (privateKey == null) { return null; } - _publicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(privateKey); - return _publicKey; + inMemoryKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(privateKey); + await _stateService.SetPublicKeyAsync(inMemoryKey); + return inMemoryKey; } public async Task GetPrivateKeyAsync() { - if (_privateKey != null) + var inMemoryKey = await _stateService.GetPrivateKeyDecryptedAsync(); + if (inMemoryKey != null) { - return _privateKey; + return inMemoryKey; } - var encPrivateKey = await _storageService.GetAsync(Keys_EncPrivateKey); + var encPrivateKey = await _stateService.GetPrivateKeyEncryptedAsync(); if (encPrivateKey == null) { return null; } - _privateKey = await DecryptToBytesAsync(new EncString(encPrivateKey), null); - return _privateKey; + inMemoryKey = await DecryptToBytesAsync(new EncString(encPrivateKey), null); + await _stateService.SetPrivateKeyDecryptedAsync(inMemoryKey); + return inMemoryKey; } public async Task> GetFingerprintAsync(string userId, byte[] publicKey = null) @@ -226,9 +220,10 @@ namespace Bit.Core.Services public Task> GetOrgKeysAsync() { - if (_orgKeys != null && _orgKeys.Count > 0) + var inMemoryKeys = _stateService.GetOrgKeysDecryptedAsync(); + if (inMemoryKeys != null && inMemoryKeys.Result.Count > 0) { - return Task.FromResult(_orgKeys); + return inMemoryKeys; } if (_getOrgKeysTask != null && !_getOrgKeysTask.IsCompleted && !_getOrgKeysTask.IsFaulted) { @@ -238,7 +233,7 @@ namespace Bit.Core.Services { try { - var encOrgKeys = await _storageService.GetAsync>(Keys_EncOrgKeys); + var encOrgKeys = await _stateService.GetOrgKeysEncryptedAsync(); if (encOrgKeys == null) { return null; @@ -254,9 +249,9 @@ namespace Bit.Core.Services if (setKey) { - _orgKeys = orgKeys; + await _stateService.SetOrgKeysDecryptedAsync(orgKeys); } - return _orgKeys; + return orgKeys; } finally { @@ -311,76 +306,78 @@ namespace Bit.Core.Services public async Task HasEncKeyAsync() { - var encKey = await _storageService.GetAsync(Keys_EncKey); + var encKey = await _stateService.GetEncKeyEncryptedAsync(); return encKey != null; } - public async Task ClearKeyAsync() + public async Task ClearKeyAsync(string userId = null) { - _key = _legacyEtmKey = null; - await _secureStorageService.RemoveAsync(Keys_Key); + await _stateService.SetKeyDecryptedAsync(null, new StorageOptions { UserId = userId }); + _legacyEtmKey = null; + await _stateService.SetKeyEncryptedAsync(null, new StorageOptions { UserId = userId }); } - public async Task ClearKeyHashAsync() + public async Task ClearKeyHashAsync(string userId = null) { - _keyHash = null; - await _storageService.RemoveAsync(Keys_KeyHash); + await _stateService.SetKeyHashCachedAsync(null, new StorageOptions { UserId = userId }); + await _stateService.SetKeyHashAsync(null, new StorageOptions { UserId = userId }); } - public async Task ClearEncKeyAsync(bool memoryOnly = false) + public async Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null) { - _encKey = null; + await _stateService.SetEncKeyDecryptedAsync(null, new StorageOptions { UserId = userId }); if (!memoryOnly) { - await _storageService.RemoveAsync(Keys_EncKey); + await _stateService.SetEncKeyEncryptedAsync(null, new StorageOptions { UserId = userId }); } } - public async Task ClearKeyPairAsync(bool memoryOnly = false) + public async Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null) { - _publicKey = _privateKey = null; + await _stateService.SetPublicKeyAsync(null, new StorageOptions { UserId = userId }); + await _stateService.SetPrivateKeyDecryptedAsync(null, new StorageOptions { UserId = userId }); if (!memoryOnly) { - await _storageService.RemoveAsync(Keys_EncPrivateKey); + await _stateService.SetPrivateKeyEncryptedAsync(null, new StorageOptions { UserId = userId }); } } - public async Task ClearOrgKeysAsync(bool memoryOnly = false) + public async Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null) { - _orgKeys = null; + await _stateService.SetOrgKeysDecryptedAsync(null, new StorageOptions { UserId = userId }); if (!memoryOnly) { - await _storageService.RemoveAsync(Keys_EncOrgKeys); + await _stateService.SetOrgKeysEncryptedAsync(null, new StorageOptions { UserId = userId }); } } - public async Task ClearPinProtectedKeyAsync() + public async Task ClearPinProtectedKeyAsync(string userId = null) { - await _storageService.RemoveAsync(Constants.PinProtectedKey); + await _stateService.SetPinProtectedAsync(null, new StorageOptions { UserId = userId }); } - public async Task ClearKeysAsync() + public async Task ClearKeysAsync(string userId = null) { await Task.WhenAll(new Task[] { - ClearKeyAsync(), - ClearKeyHashAsync(), - ClearOrgKeysAsync(), - ClearEncKeyAsync(), - ClearKeyPairAsync(), - ClearPinProtectedKeyAsync() + ClearKeyAsync(userId), + ClearKeyHashAsync(userId), + ClearOrgKeysAsync(false, userId), + ClearEncKeyAsync(false, userId), + ClearKeyPairAsync(false, userId), + ClearPinProtectedKeyAsync(userId) }); } public async Task ToggleKeyAsync() { var key = await GetKeyAsync(); - var option = await _storageService.GetAsync(Constants.VaultTimeoutKey); - var biometric = await _storageService.GetAsync(Constants.BiometricUnlockKey); + var option = await _stateService.GetVaultTimeoutAsync(); + var biometric = await _stateService.GetBiometricUnlockAsync(); if (!biometric.GetValueOrDefault() && (option != null || option == 0)) { await ClearKeyAsync(); - _key = key; + await _stateService.SetKeyDecryptedAsync(key); return; } await SetKeyAsync(key); @@ -415,7 +412,7 @@ namespace Bit.Core.Services { if (protectedKeyCs == null) { - var pinProtectedKey = await _storageService.GetAsync(Constants.PinProtectedKey); + var pinProtectedKey = await _stateService.GetPinProtectedAsync(); if (pinProtectedKey == null) { throw new Exception("No PIN protected key found."); diff --git a/src/Core/Services/EnvironmentService.cs b/src/Core/Services/EnvironmentService.cs index 315e27126..b5a6e4511 100644 --- a/src/Core/Services/EnvironmentService.cs +++ b/src/Core/Services/EnvironmentService.cs @@ -9,14 +9,14 @@ namespace Bit.Core.Services public class EnvironmentService : IEnvironmentService { private readonly IApiService _apiService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; public EnvironmentService( IApiService apiService, - IStorageService storageService) + IStateService stateService) { _apiService = apiService; - _storageService = storageService; + _stateService = stateService; } public string BaseUrl { get; set; } @@ -42,7 +42,11 @@ namespace Bit.Core.Services public async Task SetUrlsFromStorageAsync() { - var urls = await _storageService.GetAsync(Constants.EnvironmentUrlsKey); + var urls = await _stateService.GetEnvironmentUrlsAsync(); + if (urls == null) + { + urls = await _stateService.GetPreAuthEnvironmentUrlsAsync(); + } if (urls == null) { urls = new EnvironmentUrlData(); @@ -72,7 +76,7 @@ namespace Bit.Core.Services urls.Icons = FormatUrl(urls.Icons); urls.Notifications = FormatUrl(urls.Notifications); urls.Events = FormatUrl(urls.Events); - await _storageService.SaveAsync(Constants.EnvironmentUrlsKey, urls); + await _stateService.SetPreAuthEnvironmentUrlsAsync(urls); BaseUrl = urls.Base; WebVaultUrl = urls.WebVault; ApiUrl = urls.Api; diff --git a/src/Core/Services/EventService.cs b/src/Core/Services/EventService.cs index 3a599d17a..179c689c2 100644 --- a/src/Core/Services/EventService.cs +++ b/src/Core/Services/EventService.cs @@ -12,31 +12,31 @@ namespace Bit.Core.Services { public class EventService : IEventService { - private readonly IStorageService _storageService; private readonly IApiService _apiService; - private readonly IUserService _userService; + private readonly IStateService _stateService; + private readonly IOrganizationService _organizationService; private readonly ICipherService _cipherService; public EventService( - IStorageService storageService, IApiService apiService, - IUserService userService, + IStateService stateService, + IOrganizationService organizationService, ICipherService cipherService) { - _storageService = storageService; _apiService = apiService; - _userService = userService; + _stateService = stateService; + _organizationService = organizationService; _cipherService = cipherService; } public async Task CollectAsync(EventType eventType, string cipherId = null, bool uploadImmediately = false) { - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(); if (!authed) { return; } - var organizations = await _userService.GetAllOrganizationAsync(); + var organizations = await _organizationService.GetAllAsync(); if (organizations == null) { return; @@ -54,7 +54,7 @@ namespace Bit.Core.Services return; } } - var eventCollection = await _storageService.GetAsync>(Constants.EventCollectionKey); + var eventCollection = await _stateService.GetEventCollectionAsync(); if (eventCollection == null) { eventCollection = new List(); @@ -65,7 +65,7 @@ namespace Bit.Core.Services CipherId = cipherId, Date = DateTime.UtcNow }); - await _storageService.SaveAsync(Constants.EventCollectionKey, eventCollection); + await _stateService.SetEventCollectionAsync(eventCollection); if (uploadImmediately) { await UploadEventsAsync(); @@ -74,12 +74,12 @@ namespace Bit.Core.Services public async Task UploadEventsAsync() { - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(); if (!authed) { return; } - var eventCollection = await _storageService.GetAsync>(Constants.EventCollectionKey); + var eventCollection = await _stateService.GetEventCollectionAsync(); if (eventCollection == null || !eventCollection.Any()) { return; @@ -100,7 +100,7 @@ namespace Bit.Core.Services public async Task ClearEventsAsync() { - await _storageService.RemoveAsync(Constants.EventCollectionKey); + await _stateService.SetEventCollectionAsync(null); } } } diff --git a/src/Core/Services/FolderService.cs b/src/Core/Services/FolderService.cs index 6eb38ef23..549b28849 100644 --- a/src/Core/Services/FolderService.cs +++ b/src/Core/Services/FolderService.cs @@ -15,30 +15,25 @@ namespace Bit.Core.Services { public class FolderService : IFolderService { - private const string Keys_CiphersFormat = "ciphers_{0}"; - private const string Keys_FoldersFormat = "folders_{0}"; private const char NestingDelimiter = '/'; private List _decryptedFolderCache; private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly IApiService _apiService; - private readonly IStorageService _storageService; private readonly II18nService _i18nService; private readonly ICipherService _cipherService; public FolderService( ICryptoService cryptoService, - IUserService userService, + IStateService stateService, IApiService apiService, - IStorageService storageService, II18nService i18nService, ICipherService cipherService) { _cryptoService = cryptoService; - _userService = userService; + _stateService = stateService; _apiService = apiService; - _storageService = storageService; _i18nService = i18nService; _cipherService = cipherService; } @@ -60,9 +55,7 @@ namespace Bit.Core.Services public async Task GetAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var folders = await _storageService.GetAsync>( - string.Format(Keys_FoldersFormat, userId)); + var folders = await _stateService.GetEncryptedFoldersAsync(); if (!folders?.ContainsKey(id) ?? true) { return null; @@ -72,9 +65,7 @@ namespace Bit.Core.Services public async Task> GetAllAsync() { - var userId = await _userService.GetUserIdAsync(); - var folders = await _storageService.GetAsync>( - string.Format(Keys_FoldersFormat, userId)); + var folders = await _stateService.GetEncryptedFoldersAsync(); var response = folders?.Select(f => new Folder(f.Value)); return response?.ToList() ?? new List(); } @@ -153,16 +144,14 @@ namespace Bit.Core.Services { response = await _apiService.PutFolderAsync(folder.Id, request); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var data = new FolderData(response, userId); await UpsertAsync(data); } public async Task UpsertAsync(FolderData folder) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_FoldersFormat, userId); - var folders = await _storageService.GetAsync>(storageKey); + var folders = await _stateService.GetEncryptedFoldersAsync(); if (folders == null) { folders = new Dictionary(); @@ -172,15 +161,13 @@ namespace Bit.Core.Services folders.Add(folder.Id, null); } folders[folder.Id] = folder; - await _storageService.SaveAsync(storageKey, folders); + await _stateService.SetEncryptedFoldersAsync(folders); _decryptedFolderCache = null; } public async Task UpsertAsync(List folder) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_FoldersFormat, userId); - var folders = await _storageService.GetAsync>(storageKey); + var folders = await _stateService.GetEncryptedFoldersAsync(); if (folders == null) { folders = new Dictionary(); @@ -193,39 +180,35 @@ namespace Bit.Core.Services } folders[f.Id] = f; } - await _storageService.SaveAsync(storageKey, folders); + await _stateService.SetEncryptedFoldersAsync(folders); _decryptedFolderCache = null; } public async Task ReplaceAsync(Dictionary folders) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_FoldersFormat, userId), folders); + await _stateService.SetEncryptedFoldersAsync(folders); _decryptedFolderCache = null; } public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_FoldersFormat, userId)); + await _stateService.SetEncryptedFoldersAsync(null, new StorageOptions { UserId = userId }); _decryptedFolderCache = null; } public async Task DeleteAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var folderKey = string.Format(Keys_FoldersFormat, userId); - var folders = await _storageService.GetAsync>(folderKey); + var folders = await _stateService.GetEncryptedFoldersAsync(); if (folders == null || !folders.ContainsKey(id)) { return; } folders.Remove(id); - await _storageService.SaveAsync(folderKey, folders); + await _stateService.SetEncryptedFoldersAsync(folders); _decryptedFolderCache = null; // Items in a deleted folder are re-assigned to "No Folder" - var ciphers = await _storageService.GetAsync>( - string.Format(Keys_CiphersFormat, userId)); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers != null) { var updates = new List(); diff --git a/src/Core/Services/KeyConnectorService.cs b/src/Core/Services/KeyConnectorService.cs index 733d79e78..ed8ae5acc 100644 --- a/src/Core/Services/KeyConnectorService.cs +++ b/src/Core/Services/KeyConnectorService.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Bit.Core.Abstractions; -using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; @@ -9,24 +8,20 @@ namespace Bit.Core.Services { public class KeyConnectorService : IKeyConnectorService { - private const string Keys_UsesKeyConnector = "usesKeyConnector"; - - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly ICryptoService _cryptoService; - private readonly IStorageService _storageService; private readonly ITokenService _tokenService; private readonly IApiService _apiService; + private readonly IOrganizationService _organizationService; - private bool? _usesKeyConnector; - - public KeyConnectorService(IUserService userService, ICryptoService cryptoService, - IStorageService storageService, ITokenService tokenService, IApiService apiService) + public KeyConnectorService(IStateService stateService, ICryptoService cryptoService, + ITokenService tokenService, IApiService apiService, OrganizationService organizationService) { - _userService = userService; + _stateService = stateService; _cryptoService = cryptoService; - _storageService = storageService; _tokenService = tokenService; _apiService = apiService; + _organizationService = organizationService; } public async Task GetAndSetKey(string url) @@ -46,23 +41,17 @@ namespace Bit.Core.Services public async Task SetUsesKeyConnector(bool usesKeyConnector) { - _usesKeyConnector = usesKeyConnector; - await _storageService.SaveAsync(Keys_UsesKeyConnector, usesKeyConnector); + await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector); } public async Task GetUsesKeyConnector() { - if (!_usesKeyConnector.HasValue) - { - _usesKeyConnector = await _storageService.GetAsync(Keys_UsesKeyConnector); - } - - return _usesKeyConnector.Value; + return await _stateService.GetUsesKeyConnectorAsync(); } public async Task GetManagingOrganization() { - var orgs = await _userService.GetAllOrganizationAsync(); + var orgs = await _organizationService.GetAllAsync(); return orgs.Find(o => o.UsesKeyConnector && !o.IsAdmin); @@ -88,7 +77,7 @@ namespace Bit.Core.Services public async Task UserNeedsMigration() { - var loggedInUsingSso = _tokenService.GetIsExternal(); + var loggedInUsingSso = await _tokenService.GetIsExternal(); var requiredByOrganization = await GetManagingOrganization() != null; var userIsNotUsingKeyConnector = !await GetUsesKeyConnector(); diff --git a/src/Core/Services/OrganizationService.cs b/src/Core/Services/OrganizationService.cs new file mode 100644 index 000000000..4c4e08fe2 --- /dev/null +++ b/src/Core/Services/OrganizationService.cs @@ -0,0 +1,55 @@ +using Bit.Core.Abstractions; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bit.Core.Services +{ + public class OrganizationService : IOrganizationService + { + private readonly IStateService _stateService; + + public OrganizationService(IStateService stateService) + { + _stateService = stateService; + } + + public async Task GetAsync(string id) + { + var organizations = await _stateService.GetOrganizationsAsync(); + if (organizations == null || !organizations.ContainsKey(id)) + { + return null; + } + return new Organization(organizations[id]); + } + + public async Task GetByIdentifierAsync(string identifier) + { + var organizations = await GetAllAsync(); + if (organizations == null || organizations.Count == 0) + { + return null; + } + return organizations.FirstOrDefault(o => o.Identifier == identifier); + } + + public async Task> GetAllAsync(string userId = null) + { + var organizations = await _stateService.GetOrganizationsAsync(new StorageOptions { UserId = userId }); + return organizations?.Select(o => new Organization(o.Value)).ToList() ?? new List(); + } + + public async Task ReplaceAsync(Dictionary organizations) + { + await _stateService.SetOrganizationsAsync(organizations); + } + + public async Task ClearAllAsync(string userId) + { + await _stateService.SetOrganizationsAsync(null, new StorageOptions { UserId = userId }); + } + } +} diff --git a/src/Core/Services/PasswordGenerationService.cs b/src/Core/Services/PasswordGenerationService.cs index 746d9b345..ca2530f36 100644 --- a/src/Core/Services/PasswordGenerationService.cs +++ b/src/Core/Services/PasswordGenerationService.cs @@ -14,8 +14,6 @@ namespace Bit.Core.Services { public class PasswordGenerationService : IPasswordGenerationService { - private const string Keys_Options = "passwordGenerationOptions"; - private const string Keys_History = "generatedPasswordHistory"; private const int MaxPasswordsInHistory = 100; private const string LowercaseCharSet = "abcdefghijkmnopqrstuvwxyz"; private const string UppercaseCharSet = "ABCDEFGHJKLMNPQRSTUVWXYZ"; @@ -23,7 +21,7 @@ namespace Bit.Core.Services private const string SpecialCharSet = "!@#$%^&*"; private readonly ICryptoService _cryptoService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly ICryptoFunctionService _cryptoFunctionService; private readonly IPolicyService _policyService; private PasswordGenerationOptions _defaultOptions = new PasswordGenerationOptions(true); @@ -32,12 +30,12 @@ namespace Bit.Core.Services public PasswordGenerationService( ICryptoService cryptoService, - IStorageService storageService, + IStateService stateService, ICryptoFunctionService cryptoFunctionService, IPolicyService policyService) { _cryptoService = cryptoService; - _storageService = storageService; + _stateService = stateService; _cryptoFunctionService = cryptoFunctionService; _policyService = policyService; } @@ -160,6 +158,12 @@ namespace Bit.Core.Services return password.ToString(); } + public void ClearCache() + { + _optionsCache = null; + _history = null; + } + public async Task GeneratePassphraseAsync(PasswordGenerationOptions options) { options.Merge(_defaultOptions); @@ -204,7 +208,7 @@ namespace Bit.Core.Services { if (_optionsCache == null) { - var options = await _storageService.GetAsync(Keys_Options); + var options = await _stateService.GetPasswordGenerationOptionsAsync(); if (options == null) { _optionsCache = _defaultOptions; @@ -432,7 +436,7 @@ namespace Bit.Core.Services public async Task SaveOptionsAsync(PasswordGenerationOptions options) { - await _storageService.SaveAsync(Keys_Options, options); + await _stateService.SetPasswordGenerationOptionsAsync(options); _optionsCache = options; } @@ -445,7 +449,7 @@ namespace Bit.Core.Services } if (_history == null) { - var encrypted = await _storageService.GetAsync>(Keys_History); + var encrypted = await _stateService.GetEncryptedPasswordGenerationHistory(); _history = await DecryptHistoryAsync(encrypted); } return _history ?? new List(); @@ -473,13 +477,15 @@ namespace Bit.Core.Services } var newHistory = await EncryptHistoryAsync(currentHistory); token.ThrowIfCancellationRequested(); - await _storageService.SaveAsync(Keys_History, newHistory); + var userId = await _stateService.GetActiveUserIdAsync(); + await _stateService.SetEncryptedPasswordGenerationHistoryAsync(newHistory); } - public async Task ClearAsync() + public async Task ClearAsync(string userId = null) { _history = new List(); - await _storageService.RemoveAsync(Keys_History); + await _stateService.SetEncryptedPasswordGenerationHistoryAsync(null, + new StorageOptions { UserId = userId }); } public Result PasswordStrength(string password, List userInputs = null) diff --git a/src/Core/Services/PolicyService.cs b/src/Core/Services/PolicyService.cs index 95529ab26..8cacc4d7d 100644 --- a/src/Core/Services/PolicyService.cs +++ b/src/Core/Services/PolicyService.cs @@ -12,19 +12,17 @@ namespace Bit.Core.Services { public class PolicyService : IPolicyService { - private const string Keys_PoliciesPrefix = "policies_{0}"; - - private readonly IStorageService _storageService; - private readonly IUserService _userService; + private readonly IStateService _stateService; + private readonly IOrganizationService _organizationService; private IEnumerable _policyCache; public PolicyService( - IStorageService storageService, - IUserService userService) + IStateService stateService, + IOrganizationService organizationService) { - _storageService = storageService; - _userService = userService; + _stateService = stateService; + _organizationService = organizationService; } public void ClearCache() @@ -36,9 +34,7 @@ namespace Bit.Core.Services { if (_policyCache == null) { - var userId = await _userService.GetUserIdAsync(); - var policies = await _storageService.GetAsync>( - string.Format(Keys_PoliciesPrefix, userId)); + var policies = await _stateService.GetEncryptedPoliciesAsync(); if (policies == null) { return null; @@ -58,14 +54,13 @@ namespace Bit.Core.Services public async Task Replace(Dictionary policies) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_PoliciesPrefix, userId), policies); + await _stateService.SetEncryptedPoliciesAsync(policies); _policyCache = null; } public async Task Clear(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_PoliciesPrefix, userId)); + await _stateService.SetEncryptedPoliciesAsync(null, new StorageOptions { UserId = userId }); _policyCache = null; } @@ -205,7 +200,7 @@ namespace Bit.Core.Services { return false; } - var organizations = await _userService.GetAllOrganizationAsync(); + var organizations = await _organizationService.GetAllAsync(); IEnumerable filteredPolicies; diff --git a/src/Core/Services/SendService.cs b/src/Core/Services/SendService.cs index 7eeacba55..abd55a00d 100644 --- a/src/Core/Services/SendService.cs +++ b/src/Core/Services/SendService.cs @@ -20,9 +20,8 @@ namespace Bit.Core.Services { private List _decryptedSendsCache; private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly IApiService _apiService; - private readonly IStorageService _storageService; private readonly II18nService _i18nService; private readonly ICryptoFunctionService _cryptoFunctionService; private Task> _getAllDecryptedTask; @@ -30,27 +29,23 @@ namespace Bit.Core.Services public SendService( ICryptoService cryptoService, - IUserService userService, + IStateService stateService, IApiService apiService, IFileUploadService fileUploadService, - IStorageService storageService, II18nService i18nService, ICryptoFunctionService cryptoFunctionService) { _cryptoService = cryptoService; - _userService = userService; + _stateService = stateService; _apiService = apiService; _fileUploadService = fileUploadService; - _storageService = storageService; _i18nService = i18nService; _cryptoFunctionService = cryptoFunctionService; } - public static string GetSendKey(string userId) => string.Format("sends_{0}", userId); - public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(GetSendKey(userId)); + await _stateService.SetEncryptedSendsAsync(null, new StorageOptions { UserId = userId }); ClearCache(); } @@ -58,8 +53,7 @@ namespace Bit.Core.Services public async Task DeleteAsync(params string[] ids) { - var userId = await _userService.GetUserIdAsync(); - var sends = await _storageService.GetAsync>(GetSendKey(userId)); + var sends = await _stateService.GetEncryptedSendsAsync(); if (sends == null) { @@ -71,7 +65,7 @@ namespace Bit.Core.Services sends.Remove(id); } - await _storageService.SaveAsync(GetSendKey(userId), sends); + await _stateService.SetEncryptedSendsAsync(sends); ClearCache(); } @@ -138,8 +132,7 @@ namespace Bit.Core.Services public async Task> GetAllAsync() { - var userId = await _userService.GetUserIdAsync(); - var sends = await _storageService.GetAsync>(GetSendKey(userId)); + var sends = await _stateService.GetEncryptedSendsAsync(); return sends?.Select(kvp => new Send(kvp.Value)).ToList() ?? new List(); } @@ -179,8 +172,7 @@ namespace Bit.Core.Services public async Task GetAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var sends = await _storageService.GetAsync>(GetSendKey(userId)); + var sends = await _stateService.GetEncryptedSendsAsync(); if (sends == null || !sends.ContainsKey(id)) { @@ -192,8 +184,7 @@ namespace Bit.Core.Services public async Task ReplaceAsync(Dictionary sends) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(GetSendKey(userId), sends); + await _stateService.SetEncryptedSendsAsync(sends); _decryptedSendsCache = null; } @@ -237,7 +228,7 @@ namespace Bit.Core.Services response = await _apiService.PutSendAsync(send.Id, request); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); await UpsertAsync(new SendData(response, userId)); return response.Id; } @@ -255,8 +246,7 @@ namespace Bit.Core.Services public async Task UpsertAsync(params SendData[] sends) { - var userId = await _userService.GetUserIdAsync(); - var knownSends = await _storageService.GetAsync>(GetSendKey(userId)) ?? + var knownSends = await _stateService.GetEncryptedSendsAsync() ?? new Dictionary(); foreach (var send in sends) @@ -264,14 +254,14 @@ namespace Bit.Core.Services knownSends[send.Id] = send; } - await _storageService.SaveAsync(GetSendKey(userId), knownSends); + await _stateService.SetEncryptedSendsAsync(knownSends); _decryptedSendsCache = null; } public async Task RemovePasswordWithServerAsync(string id) { var response = await _apiService.PutSendRemovePasswordAsync(id); - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); await UpsertAsync(new SendData(response, userId)); } diff --git a/src/Core/Services/SettingsService.cs b/src/Core/Services/SettingsService.cs index c0e776009..2a2664ab8 100644 --- a/src/Core/Services/SettingsService.cs +++ b/src/Core/Services/SettingsService.cs @@ -1,28 +1,23 @@ using Bit.Core.Abstractions; -using Bit.Core.Utilities; using Newtonsoft.Json.Linq; -using System; using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Models.Domain; namespace Bit.Core.Services { public class SettingsService : ISettingsService { - private const string Keys_SettingsFormat = "settings_{0}"; private const string Keys_EquivalentDomains = "equivalentDomains"; - private readonly IUserService _userService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private Dictionary _settingsCache; public SettingsService( - IUserService userService, - IStorageService storageService) + IStateService stateService) { - _userService = userService; - _storageService = storageService; + _stateService = stateService; } public void ClearCache() @@ -49,7 +44,7 @@ namespace Bit.Core.Services public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_SettingsFormat, userId)); + await _stateService.SetSettingsAsync(null, new StorageOptions { UserId = userId }); ClearCache(); } @@ -59,16 +54,13 @@ namespace Bit.Core.Services { if (_settingsCache == null) { - var userId = await _userService.GetUserIdAsync(); - _settingsCache = await _storageService.GetAsync>( - string.Format(Keys_SettingsFormat, userId)); + _settingsCache = await _stateService.GetSettingsAsync(); } return _settingsCache; } private async Task SetSettingsKeyAsync(string key, T value) { - var userId = await _userService.GetUserIdAsync(); var settings = await GetSettingsAsync(); if (settings == null) { @@ -82,7 +74,7 @@ namespace Bit.Core.Services { settings.Add(key, value); } - await _storageService.SaveAsync(string.Format(Keys_SettingsFormat, userId), settings); + await _stateService.SetSettingsAsync(settings); _settingsCache = settings; } } diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index a40bd6fb7..c41f887ee 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -1,44 +1,1527 @@ -using Bit.Core.Abstractions; +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 Dictionary _state = new Dictionary(); + private readonly IStorageService _storageService; + private readonly IStorageService _secureStorageService; - public Task GetAsync(string key) + private State _state; + + public bool BiometricLocked { get; set; } + + public ExtendedObservableCollection Accounts { get; set; } + + public StateService(IStorageService storageService, IStorageService secureStorageService) { - return Task.FromResult(_state.ContainsKey(key) ? (T)_state[key] : (T)(object)null); + _storageService = storageService; + _secureStorageService = secureStorageService; } - public Task SaveAsync(string key, T obj) + public async Task GetActiveUserIdAsync(StorageOptions options = null) { - if (_state.ContainsKey(key)) + await CheckStateAsync(); + + var activeUserId = _state?.ActiveUserId; + if (activeUserId == null) { - _state[key] = obj; + var state = await GetStateFromStorageAsync(); + activeUserId = state?.ActiveUserId; + } + return activeUserId; + } + + public async Task SetActiveUserAsync(string userId) + { + await ValidateUserAsync(userId); + await CheckStateAsync(false); + var state = await GetStateFromStorageAsync(); + state.ActiveUserId = userId; + await SaveStateToStorageAsync(state); + _state.ActiveUserId = userId; + await RefreshAccountList(); + + // 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(StorageOptions options = null) + { + return await GetAccessTokenAsync(options) != null && await GetActiveUserIdAsync(options) != null; + } + + public async Task HasMultipleAccountsAsync() + { + await CheckStateAsync(false); + return _state.Accounts?.Count > 1; + } + + public async Task AddAccountAsync(Account account) + { + await ScaffoldNewAccountAsync(account); + await SetActiveUserAsync(account.Profile.UserId); + } + + public async Task CleanAsync(string userId) + { + if (_state?.Accounts != null && (userId == null || userId == await GetActiveUserIdAsync())) + { + // Find the next user to make active + foreach (var account in _state.Accounts) + { + var uid = account.Value?.Profile?.UserId; + if (uid == null) + { + continue; + } + if (await IsAuthenticatedAsync(new StorageOptions { UserId = uid })) + { + await SetActiveUserAsync(uid); + break; + } + await SetActiveUserAsync(null); + } + } + + await RemoveAccountAsync(userId); + } + + 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(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Settings?.EnvironmentUrls; + } + + public async Task SetEnvironmentUrlsAsync(EnvironmentUrlData value, StorageOptions options = null) + { + var reconciledOptions = + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Settings.EnvironmentUrls = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetBiometricUnlockAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.BiometricUnlockKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetBiometricUnlockAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.BiometricUnlockKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task CanAccessPremiumAsync(StorageOptions options = null) + { + if (!await IsAuthenticatedAsync(options)) + { + return false; + } + + var account = await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync())); + if (account.Profile.HasPremiumPersonally.GetValueOrDefault()) + { + return true; + } + + var userId = account.Profile?.UserId; + if (userId == null) + { + userId = await GetActiveUserIdAsync(); + } + var organizationService = ServiceContainer.Resolve("organizationService"); + var organizations = await organizationService.GetAllAsync(userId); + return organizations?.Any(o => o.UsersGetPremium && o.Enabled) ?? false; + } + + public async Task GetProtectedPinAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultSecureStorageOptionsAsync()); + var key = Constants.ProtectedPinKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetProtectedPinAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultSecureStorageOptionsAsync()); + var key = Constants.ProtectedPinKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPinProtectedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PinProtectedKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPinProtectedAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PinProtectedKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPinProtectedCachedAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Settings?.PinProtected; + } + + public async Task SetPinProtectedCachedAsync(EncString value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Settings.PinProtected = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetKdfTypeAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.KdfType; + } + + public async Task SetKdfTypeAsync(KdfType? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Profile.KdfType = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetKdfIterationsAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.KdfIterations; + } + + public async Task SetKdfIterationsAsync(int? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Profile.KdfIterations = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetKeyEncryptedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultSecureStorageOptionsAsync()); + var key = Constants.KeyKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetKeyEncryptedAsync(string value, StorageOptions options) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultSecureStorageOptionsAsync()); + var key = Constants.KeyKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetKeyDecryptedAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultInMemoryOptionsAsync()) + ))?.Keys?.Key; + } + + public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultInMemoryOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Keys.Key = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetKeyHashAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.KeyHashKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetKeyHashAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.KeyHashKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetKeyHashCachedAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Keys?.KeyHash; + } + + public async Task SetKeyHashCachedAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Keys.KeyHash = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetEncKeyEncryptedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EncKeyKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetEncKeyEncryptedAsync(string value, StorageOptions options) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EncKeyKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetEncKeyDecryptedAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultInMemoryOptionsAsync()) + ))?.Keys?.EncKey; + } + + public async Task SetEncKeyDecryptedAsync(SymmetricCryptoKey value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultInMemoryOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Keys.EncKey = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task> GetOrgKeysEncryptedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EncOrgKeysKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetOrgKeysEncryptedAsync(Dictionary value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EncOrgKeysKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetOrgKeysDecryptedAsync( + StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultInMemoryOptionsAsync()) + ))?.Keys?.OrganizationKeys; + } + + public async Task SetOrgKeysDecryptedAsync(Dictionary value, + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultInMemoryOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Keys.OrganizationKeys = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetPublicKeyAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Keys?.PublicKey; + } + + public async Task SetPublicKeyAsync(byte[] value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Keys.PublicKey = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetPrivateKeyEncryptedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EncPrivateKeyKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPrivateKeyEncryptedAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EncPrivateKeyKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPrivateKeyDecryptedAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultInMemoryOptionsAsync()) + ))?.Keys?.PrivateKey; + } + + public async Task SetPrivateKeyDecryptedAsync(byte[] value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultInMemoryOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Keys.PrivateKey = value; + await SaveAccountAsync(account); + } + + public async Task> GetAutofillBlacklistedUrisAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillBlacklistedUrisKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetAutofillBlacklistedUrisAsync(List value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillBlacklistedUrisKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAutofillTileAddedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillTileAdded; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetAutofillTileAddedAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillTileAdded; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetEmailAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.Email; + } + + public async Task GetLastActiveTimeAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LastActiveTimeKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetLastActiveTimeAsync(long? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LastActiveTimeKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetVaultTimeoutAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.VaultTimeoutKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? 15; + } + + public async Task SetVaultTimeoutAsync(int? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.VaultTimeoutKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetVaultTimeoutActionAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.VaultTimeoutActionKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? "lock"; + } + + public async Task SetVaultTimeoutActionAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.VaultTimeoutActionKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetLastFileCacheClearAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LastFileCacheClearKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetLastFileCacheClearAsync(DateTime? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LastFileCacheClearKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPreviousPageInfoAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PreviousPageKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPreviousPageInfoAsync(PreviousPageInfo value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PreviousPageKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetInvalidUnlockAttemptsAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.InvalidUnlockAttemptsKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetInvalidUnlockAttemptsAsync(int? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.InvalidUnlockAttemptsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetLastBuildAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LastBuildKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetLastBuildAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LastBuildKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetDisableFaviconAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.DisableFaviconKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetDisableFaviconAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.DisableFaviconKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetDisableAutoTotpCopyAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetDisableAutoTotpCopyAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetInlineAutofillEnabledAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.InlineAutofillEnabledKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetInlineAutofillEnabledAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.InlineAutofillEnabledKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAutofillDisableSavePromptAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillDisableSavePromptKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetAutofillDisableSavePromptAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillDisableSavePromptKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task>> GetLocalDataAsync( + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LocalDataKey(reconciledOptions.UserId); + return await GetValueAsync>>(key, reconciledOptions); + } + + public async Task SetLocalDataAsync(Dictionary> value, + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LocalDataKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedCiphersAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.CiphersKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedCiphersAsync(Dictionary value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.CiphersKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetDefaultUriMatchAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.DefaultUriMatchKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetDefaultUriMatchAsync(int? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.DefaultUriMatchKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetNeverDomainsAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.NeverDomainsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetNeverDomainsAsync(HashSet value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.NeverDomainsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetClearClipboardAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.ClearClipboardKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetClearClipboardAsync(int? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.ClearClipboardKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedCollectionsAsync( + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.CollectionsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedCollectionsAsync(Dictionary value, + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.CollectionsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPasswordRepromptAutofillAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PasswordRepromptAutofillKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetPasswordRepromptAutofillAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PasswordRepromptAutofillKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPasswordVerifiedAutofillAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PasswordVerifiedAutofillKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetPasswordVerifiedAutofillAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PasswordVerifiedAutofillKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetLastSyncAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LastSyncKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetLastSyncAsync(DateTime? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.LastSyncKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetSecurityStampAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.SecurityStampKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetSecurityStampAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.SecurityStampKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetEmailVerifiedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EmailVerifiedKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetEmailVerifiedAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EmailVerifiedKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetForcePasswordReset(StorageOptions options) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.ForcePasswordResetKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetForcePasswordResetAsync(bool? value, StorageOptions options) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.ForcePasswordResetKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetSyncOnRefreshAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.SyncOnRefreshKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetSyncOnRefreshAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.SyncOnRefreshKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetRememberedEmailAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.RememberedEmailKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetRememberedEmailAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.RememberedEmailKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetRememberEmailAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.RememberEmailKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetRememberEmailAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.RememberEmailKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetRememberedOrgIdentifierAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.RememberedOrgIdentifierKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetRememberedOrgIdentifierAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.RememberedOrgIdentifierKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetRememberOrgIdentifierAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.RememberOrgIdentifierKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetRememberOrgIdentifierAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.RememberOrgIdentifierKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetThemeAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.ThemeKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetThemeAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.ThemeKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAddSitePromptShownAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AddSitePromptShownKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetAddSitePromptShownAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AddSitePromptShownKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetMigratedFromV1Async(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.MigratedFromV1; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetMigratedFromV1Async(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.MigratedFromV1; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetMigratedFromV1AutofillPromptShownAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.MigratedFromV1AutofillPromptShown; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetMigratedFromV1AutofillPromptShownAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.MigratedFromV1AutofillPromptShown; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetTriedV1ResyncAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.TriedV1Resync; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetTriedV1ResyncAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.TriedV1Resync; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPushInitialPromptShownAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PushInitialPromptShownKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPushInitialPromptShownAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PushInitialPromptShownKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPushLastRegistrationDateAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PushLastRegistrationDateKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPushLastRegistrationDateAsync(DateTime? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PushLastRegistrationDateKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPushCurrentTokenAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PushCurrentTokenKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPushCurrentTokenAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PushCurrentTokenKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEventCollectionAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EventCollectionKey; + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEventCollectionAsync(List value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.EventCollectionKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedFoldersAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.FoldersKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedFoldersAsync(Dictionary value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.FoldersKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedPoliciesAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PoliciesKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedPoliciesAsync(Dictionary value, StorageOptions options) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PoliciesKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPushRegisteredTokenAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PushRegisteredTokenKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPushRegisteredTokenAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PushRegisteredTokenKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAppExtensionStartedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AppExtensionStartedKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetAppExtensionStartedAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AppExtensionStartedKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAppExtensionActivatedAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AppExtensionActivatedKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetAppExtensionActivatedAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AppExtensionActivatedKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAppIdAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AppIdKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetAppIdAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.AppIdKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetUsesKeyConnectorAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.UsesKeyConnectorKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetUsesKeyConnectorAsync(bool? value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.UsesKeyConnectorKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetOrganizationsAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.OrganizationsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetOrganizationsAsync(Dictionary value, + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.OrganizationsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPasswordGenerationOptionsAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PassGenOptionsKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPasswordGenerationOptionsAsync(PasswordGenerationOptions value, + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PassGenOptionsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedPasswordGenerationHistory( + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PassGenHistoryKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedPasswordGenerationHistoryAsync(List value, + StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.PassGenHistoryKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedSendsAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.SendsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedSendsAsync(Dictionary value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.SendsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetSettingsAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.SettingsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetSettingsAsync(Dictionary value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.SettingsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAccessTokenAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Tokens?.AccessToken; + } + + public async Task SetAccessTokenAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Tokens.AccessToken = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetRefreshTokenAsync(StorageOptions options = null) + { + return (await GetAccountAsync( + ReconcileOptions(options, await GetDefaultStorageOptionsAsync()) + ))?.Tokens?.RefreshToken; + } + + public async Task SetRefreshTokenAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Tokens.RefreshToken = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetTwoFactorTokenAsync(StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, await GetDefaultStorageOptionsAsync()); + var key = Constants.TwoFactorTokenKey(reconciledOptions.Email); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetTwoFactorTokenAsync(string value, StorageOptions options = null) + { + var reconciledOptions = ReconcileOptions(options, 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(false); + + // 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 (await SkipTokenStorageAsync()) + { + state.Accounts[account.Profile.UserId].Tokens.AccessToken = null; + state.Accounts[account.Profile.UserId].Tokens.RefreshToken = null; + } + + await SaveStateToStorageAsync(state); + } + + await RefreshAccountList(); + } + + private async Task SkipTokenStorageAsync() + { + var timeout = await GetVaultTimeoutAsync(); + var action = await GetVaultTimeoutActionAsync(); + return timeout.HasValue && action == "logOut"; + } + + private async Task RemoveAccountAsync(string userId) + { + if (userId == null) + { + throw new Exception("userId cannot be null"); + } + + await CheckStateAsync(false); + + // Memory + if (_state?.Accounts?.ContainsKey(userId) ?? false) + { + _state?.Accounts?.Remove(userId); + } + + // Storage + var state = await GetStateFromStorageAsync(); + if (state?.Accounts?.ContainsKey(userId) ?? false) + { + state.Accounts.Remove(userId); + await SaveStateToStorageAsync(state); + } + + // Secure Storage + var options = new StorageOptions + { + UserId = userId, + UseSecureStorage = true, + }; + await SetProtectedPinAsync(null, options); + await SetKeyEncryptedAsync(null, options); + + await RefreshAccountList(); + } + + private async Task ScaffoldNewAccountAsync(Account account) + { + await CheckStateAsync(false); + + 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 { - _state.Add(key, obj); + if (_state.Accounts == null) + { + _state.Accounts = new Dictionary(); + } + _state.Accounts[account.Profile.UserId] = account; } - return Task.FromResult(0); } - public Task RemoveAsync(string key) + private StorageOptions ReconcileOptions(StorageOptions requestedOptions, + StorageOptions defaultOptions) { - if (_state.ContainsKey(key)) + if (requestedOptions == null) { - _state.Remove(key); + return defaultOptions; } - return Task.FromResult(0); + requestedOptions.StorageLocation = requestedOptions.StorageLocation ?? defaultOptions.StorageLocation; + requestedOptions.UseSecureStorage = requestedOptions.UseSecureStorage ?? defaultOptions.UseSecureStorage; + requestedOptions.UserId = requestedOptions.UserId ?? defaultOptions.UserId; + requestedOptions.Email = requestedOptions.Email ?? defaultOptions.Email; + return requestedOptions; } - public Task PurgeAsync() + private async Task GetDefaultStorageOptionsAsync() { - _state.Clear(); - return Task.FromResult(0); + 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); + Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented), + ">>> GetStateFromStorageAsync()"); + return state; + } + + private async Task SaveStateToStorageAsync(State state) + { + await _storageService.SaveAsync(Constants.StateKey, state); + Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented), + ">>> SaveStateToStorageAsync()"); + } + + private async Task PruneInMemoryAccountsAsync() + { + // TODO not sure if needed due to pref storage of settings + // We preserve settings for logged out accounts, but we don't want to consider them when thinking about + // active account state + var accounts = new Dictionary(); + foreach (var account in _state.Accounts) + { + if (await IsAuthenticatedAsync(new StorageOptions { UserId = account.Value.Profile.UserId })) + { + accounts.Add(account.Key, account.Value); + } + } + _state.Accounts = accounts; + } + + private async Task CheckStateAsync(bool includeAccountRefresh = true) + { + // TODO perform migration if necessary + + if (_state == null) + { + _state = await GetStateFromStorageAsync() ?? new State(); + } + if (includeAccountRefresh) + { + await RefreshAccountList(); + } + } + + private async Task RefreshAccountList() + { + Accounts = new ExtendedObservableCollection(); + var accountList = _state?.Accounts?.Values.ToList(); + if (accountList == null) + { + return; + } + foreach (var account in accountList) + { + var accountView = new AccountView(account); + if (accountView.UserId == _state.ActiveUserId) + { + accountView.AuthStatus = AuthenticationStatus.Active; + } + else + { + var vaultTimeService = ServiceContainer.Resolve("vaultTimeoutService"); + if (await vaultTimeService.IsLockedAsync(accountView.UserId)) + // { + // var action = await GetVaultTimeoutActionAsync(new StorageOptions { UserId = accountView.UserId }); + // if (action == "logOut") + // { + // accountView.AuthStatus = AuthenticationStatus.LoggedOut; + // } + // else + // { + // accountView.AuthStatus = AuthenticationStatus.Locked; + // } + // } + // else + // { + accountView.AuthStatus = AuthenticationStatus.Unlocked; + // } + } + Accounts.Add(accountView); + } + } + + 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) + { + var text = options?.UseSecureStorage ?? false ? "SECURE / " : ""; + text += "Key: " + key + " / "; + if (value != null) + { + text += "Value: " + value; + } + Debug.WriteLine(text, ">>> " + tag); } } } diff --git a/src/Core/Services/SyncService.cs b/src/Core/Services/SyncService.cs index 7d7795a4e..474d5f780 100644 --- a/src/Core/Services/SyncService.cs +++ b/src/Core/Services/SyncService.cs @@ -12,16 +12,14 @@ namespace Bit.Core.Services { public class SyncService : ISyncService { - private const string Keys_LastSyncFormat = "lastSync_{0}"; - - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly IApiService _apiService; private readonly ISettingsService _settingsService; private readonly IFolderService _folderService; private readonly ICipherService _cipherService; private readonly ICryptoService _cryptoService; private readonly ICollectionService _collectionService; - private readonly IStorageService _storageService; + private readonly IOrganizationService _organizationService; private readonly IMessagingService _messagingService; private readonly IPolicyService _policyService; private readonly ISendService _sendService; @@ -29,28 +27,28 @@ namespace Bit.Core.Services private readonly Func _logoutCallbackAsync; public SyncService( - IUserService userService, + IStateService stateService, IApiService apiService, ISettingsService settingsService, IFolderService folderService, ICipherService cipherService, ICryptoService cryptoService, ICollectionService collectionService, - IStorageService storageService, + IOrganizationService organizationService, IMessagingService messagingService, IPolicyService policyService, ISendService sendService, IKeyConnectorService keyConnectorService, Func logoutCallbackAsync) { - _userService = userService; + _stateService = stateService; _apiService = apiService; _settingsService = settingsService; _folderService = folderService; _cipherService = cipherService; _cryptoService = cryptoService; _collectionService = collectionService; - _storageService = storageService; + _organizationService = organizationService; _messagingService = messagingService; _policyService = policyService; _sendService = sendService; @@ -62,28 +60,26 @@ namespace Bit.Core.Services public async Task GetLastSyncAsync() { - var userId = await _userService.GetUserIdAsync(); - if (userId == null) + if (await _stateService.GetActiveUserIdAsync() == null) { return null; } - return await _storageService.GetAsync(string.Format(Keys_LastSyncFormat, userId)); + return await _stateService.GetLastSyncAsync(); } public async Task SetLastSyncAsync(DateTime date) { - var userId = await _userService.GetUserIdAsync(); - if (userId == null) + if (await _stateService.GetActiveUserIdAsync() == null) { return; } - await _storageService.SaveAsync(string.Format(Keys_LastSyncFormat, userId), date); + await _stateService.SetLastSyncAsync(date); } public async Task FullSyncAsync(bool forceSync, bool allowThrowOnError = false) { SyncStarted(); - var isAuthenticated = await _userService.IsAuthenticatedAsync(); + var isAuthenticated = await _stateService.IsAuthenticatedAsync(); if (!isAuthenticated) { return SyncCompleted(false); @@ -101,7 +97,7 @@ namespace Bit.Core.Services await SetLastSyncAsync(now); return SyncCompleted(false); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); try { var response = await _apiService.GetSyncAsync(); @@ -131,7 +127,7 @@ namespace Bit.Core.Services public async Task SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit) { SyncStarted(); - if (await _userService.IsAuthenticatedAsync()) + if (await _stateService.IsAuthenticatedAsync()) { try { @@ -142,7 +138,7 @@ namespace Bit.Core.Services var remoteFolder = await _apiService.GetFolderAsync(notification.Id); if (remoteFolder != null) { - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); await _folderService.UpsertAsync(new FolderData(remoteFolder, userId)); _messagingService.Send("syncedUpsertedFolder", new Dictionary { @@ -160,7 +156,7 @@ namespace Bit.Core.Services public async Task SyncDeleteFolderAsync(SyncFolderNotification notification) { SyncStarted(); - if (await _userService.IsAuthenticatedAsync()) + if (await _stateService.IsAuthenticatedAsync()) { await _folderService.DeleteAsync(notification.Id); _messagingService.Send("syncedDeletedFolder", new Dictionary @@ -175,7 +171,7 @@ namespace Bit.Core.Services public async Task SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit) { SyncStarted(); - if (await _userService.IsAuthenticatedAsync()) + if (await _stateService.IsAuthenticatedAsync()) { try { @@ -230,7 +226,7 @@ namespace Bit.Core.Services var remoteCipher = await _apiService.GetCipherAsync(notification.Id); if (remoteCipher != null) { - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); await _cipherService.UpsertAsync(new CipherData(remoteCipher, userId)); _messagingService.Send("syncedUpsertedCipher", new Dictionary { @@ -259,7 +255,7 @@ namespace Bit.Core.Services public async Task SyncDeleteCipherAsync(SyncCipherNotification notification) { SyncStarted(); - if (await _userService.IsAuthenticatedAsync()) + if (await _stateService.IsAuthenticatedAsync()) { await _cipherService.DeleteAsync(notification.Id); _messagingService.Send("syncedDeletedCipher", new Dictionary @@ -315,7 +311,7 @@ namespace Bit.Core.Services private async Task SyncProfileAsync(ProfileResponse response) { - var stamp = await _userService.GetSecurityStampAsync(); + var stamp = await _stateService.GetSecurityStampAsync(); if (stamp != null && stamp != response.SecurityStamp) { if (_logoutCallbackAsync != null) @@ -327,11 +323,11 @@ namespace Bit.Core.Services await _cryptoService.SetEncKeyAsync(response.Key); await _cryptoService.SetEncPrivateKeyAsync(response.PrivateKey); await _cryptoService.SetOrgKeysAsync(response.Organizations); - await _userService.SetSecurityStampAsync(response.SecurityStamp); + await _stateService.SetSecurityStampAsync(response.SecurityStamp); var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o)); - await _userService.ReplaceOrganizationsAsync(organizations); - await _userService.SetEmailVerifiedAsync(response.EmailVerified); - await _userService.SetForcePasswordReset(response.ForcePasswordReset); + await _organizationService.ReplaceAsync(organizations); + await _stateService.SetEmailVerifiedAsync(response.EmailVerified); + await _stateService.SetForcePasswordResetAsync(response.ForcePasswordReset); await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector); } diff --git a/src/Core/Services/TokenService.cs b/src/Core/Services/TokenService.cs index 09f1c44cc..dd4431163 100644 --- a/src/Core/Services/TokenService.cs +++ b/src/Core/Services/TokenService.cs @@ -5,24 +5,21 @@ using System; using System.Text; using System.Threading.Tasks; using System.Linq; +using Bit.Core.Models.Domain; namespace Bit.Core.Services { public class TokenService : ITokenService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private string _token; private JObject _decodedToken; private string _refreshToken; - private const string Keys_AccessToken = "accessToken"; - private const string Keys_RefreshToken = "refreshToken"; - private const string Keys_TwoFactorTokenFormat = "twoFactorToken_{0}"; - - public TokenService(IStorageService storageService) + public TokenService(IStateService stateService) { - _storageService = storageService; + _stateService = stateService; } public async Task SetTokensAsync(string accessToken, string refreshToken) @@ -43,7 +40,7 @@ namespace Bit.Core.Services return; } - await _storageService.SaveAsync(Keys_AccessToken, token); + // await _stateService.SetAccessTokenAsync(token); } public async Task GetTokenAsync() @@ -52,7 +49,7 @@ namespace Bit.Core.Services { return _token; } - _token = await _storageService.GetAsync(Keys_AccessToken); + _token = await _stateService.GetAccessTokenAsync(); return _token; } @@ -66,7 +63,7 @@ namespace Bit.Core.Services return; } - await _storageService.SaveAsync(Keys_RefreshToken, refreshToken); + // await _stateService.SetRefreshTokenAsync(refreshToken); } public async Task GetRefreshTokenAsync() @@ -75,7 +72,7 @@ namespace Bit.Core.Services { return _refreshToken; } - _refreshToken = await _storageService.GetAsync(Keys_RefreshToken); + _refreshToken = await _stateService.GetRefreshTokenAsync(); return _refreshToken; } @@ -97,27 +94,32 @@ namespace Bit.Core.Services public async Task SetTwoFactorTokenAsync(string token, string email) { - await _storageService.SaveAsync(string.Format(Keys_TwoFactorTokenFormat, email), token); + await _stateService.SetTwoFactorTokenAsync(token, new StorageOptions { Email = email }); } public async Task GetTwoFactorTokenAsync(string email) { - return await _storageService.GetAsync(string.Format(Keys_TwoFactorTokenFormat, email)); + return await _stateService.GetTwoFactorTokenAsync(new StorageOptions { Email = email }); } public async Task ClearTwoFactorTokenAsync(string email) { - await _storageService.RemoveAsync(string.Format(Keys_TwoFactorTokenFormat, email)); + await _stateService.SetTwoFactorTokenAsync(null, new StorageOptions { Email = email }); } - public async Task ClearTokenAsync() + public async Task ClearTokenAsync(string userId = null) + { + ClearCache(); + await Task.WhenAll( + _stateService.SetAccessTokenAsync(null, new StorageOptions { UserId = userId }), + _stateService.SetRefreshTokenAsync(null, new StorageOptions { UserId = userId })); + } + + public void ClearCache() { _token = null; _decodedToken = null; _refreshToken = null; - await Task.WhenAll( - _storageService.RemoveAsync(Keys_AccessToken), - _storageService.RemoveAsync(Keys_RefreshToken)); } public JObject DecodeToken() @@ -231,8 +233,12 @@ namespace Bit.Core.Services return decoded["iss"].Value(); } - public bool GetIsExternal() + public async Task GetIsExternal() { + if (_token == null) + { + await GetTokenAsync(); + } var decoded = DecodeToken(); if (decoded?["amr"] == null) { @@ -243,8 +249,8 @@ namespace Bit.Core.Services private async Task SkipTokenStorage() { - var timeout = await _storageService.GetAsync(Constants.VaultTimeoutKey); - var action = await _storageService.GetAsync(Constants.VaultTimeoutActionKey); + var timeout = await _stateService.GetVaultTimeoutAsync(); + var action = await _stateService.GetVaultTimeoutActionAsync(); return timeout.HasValue && action == "logOut"; } } diff --git a/src/Core/Services/TotpService.cs b/src/Core/Services/TotpService.cs index 0cf4dce15..07c64285a 100644 --- a/src/Core/Services/TotpService.cs +++ b/src/Core/Services/TotpService.cs @@ -10,14 +10,14 @@ namespace Bit.Core.Services { private const string SteamChars = "23456789BCDFGHJKMNPQRTVWXY"; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly ICryptoFunctionService _cryptoFunctionService; public TotpService( - IStorageService storageService, + IStateService stateService, ICryptoFunctionService cryptoFunctionService) { - _storageService = storageService; + _stateService = stateService; _cryptoFunctionService = cryptoFunctionService; } @@ -135,7 +135,7 @@ namespace Bit.Core.Services public async Task IsAutoCopyEnabledAsync() { - var disabled = await _storageService.GetAsync(Constants.DisableAutoTotpCopyKey); + var disabled = await _stateService.GetDisableAutoTotpCopyAsync(); return !disabled.GetValueOrDefault(); } } diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index 013611374..4c9674a89 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -10,9 +10,8 @@ namespace Bit.Core.Services public class VaultTimeoutService : IVaultTimeoutService { private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly IPlatformUtilsService _platformUtilsService; - private readonly IStorageService _storageService; private readonly IFolderService _folderService; private readonly ICipherService _cipherService; private readonly ICollectionService _collectionService; @@ -22,13 +21,12 @@ namespace Bit.Core.Services private readonly IPolicyService _policyService; private readonly IKeyConnectorService _keyConnectorService; private readonly Action _lockedCallback; - private readonly Func _loggedOutCallback; + private readonly Func, Task> _loggedOutCallback; public VaultTimeoutService( ICryptoService cryptoService, - IUserService userService, + IStateService stateService, IPlatformUtilsService platformUtilsService, - IStorageService storageService, IFolderService folderService, ICipherService cipherService, ICollectionService collectionService, @@ -38,12 +36,11 @@ namespace Bit.Core.Services IPolicyService policyService, IKeyConnectorService keyConnectorService, Action lockedCallback, - Func loggedOutCallback) + Func, Task> loggedOutCallback) { _cryptoService = cryptoService; - _userService = userService; + _stateService = stateService; _platformUtilsService = platformUtilsService; - _storageService = storageService; _folderService = folderService; _cipherService = cipherService; _collectionService = collectionService; @@ -56,16 +53,13 @@ namespace Bit.Core.Services _loggedOutCallback = loggedOutCallback; } - public EncString PinProtectedKey { get; set; } = null; - public bool BiometricLocked { get; set; } = true; - - public async Task IsLockedAsync() + public async Task IsLockedAsync(string userId = null) { var hasKey = await _cryptoService.HasKeyAsync(); if (hasKey) { var biometricSet = await IsBiometricLockSetAsync(); - if (biometricSet && BiometricLocked) + if (biometricSet && _stateService.BiometricLocked) { return true; } @@ -79,45 +73,58 @@ namespace Bit.Core.Services { return; } - var authed = await _userService.IsAuthenticatedAsync(); - if (!authed) + + foreach (var account in _stateService.Accounts) { - return; - } - if (await IsLockedAsync()) - { - return; - } - var vaultTimeoutMinutes = await GetVaultTimeout(); - if (vaultTimeoutMinutes < 0 || vaultTimeoutMinutes == null) - { - return; - } - var lastActiveTime = await _storageService.GetAsync(Constants.LastActiveTimeKey); - if (lastActiveTime == null) - { - return; - } - var diffMs = _platformUtilsService.GetActiveTime() - lastActiveTime; - var vaultTimeoutMs = vaultTimeoutMinutes * 60000; - if (diffMs >= vaultTimeoutMs) - { - // Pivot based on saved action - var action = await _storageService.GetAsync(Constants.VaultTimeoutActionKey); - if (action == "logOut") + if (account.UserId != null && await ShouldLockAsync(account.UserId)) { - await LogOutAsync(); - } - else - { - await LockAsync(true); + await ExecuteTimeoutActionAsync(account.UserId); } } } - public async Task LockAsync(bool allowSoftLock = false, bool userInitiated = false) + private async Task ShouldLockAsync(string userId) { - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(new StorageOptions { UserId = userId }); + if (!authed) + { + return false; + } + if (await IsLockedAsync(userId)) + { + return false; + } + var vaultTimeoutMinutes = await GetVaultTimeout(userId); + if (vaultTimeoutMinutes < 0 || vaultTimeoutMinutes == null) + { + return false; + } + var lastActiveTime = await _stateService.GetLastActiveTimeAsync(new StorageOptions { UserId = userId }); + if (lastActiveTime == null) + { + return false; + } + var diffMs = _platformUtilsService.GetActiveTime() - lastActiveTime; + var vaultTimeoutMs = vaultTimeoutMinutes * 60000; + return diffMs >= vaultTimeoutMs; + } + + private async Task ExecuteTimeoutActionAsync(string userId) + { + var action = await _stateService.GetVaultTimeoutActionAsync(new StorageOptions { UserId = userId }); + if (action == "logOut") + { + await LogOutAsync(userId); + } + else + { + await LockAsync(true, false, userId); + } + } + + public async Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null) + { + var authed = await _stateService.IsAuthenticatedAsync(new StorageOptions { UserId = userId }); if (!authed) { return; @@ -125,7 +132,7 @@ namespace Bit.Core.Services if (await _keyConnectorService.GetUsesKeyConnector()) { var pinSet = await IsPinLockSetAsync(); - var pinLock = (pinSet.Item1 && PinProtectedKey != null) || pinSet.Item2; + var pinLock = (pinSet.Item1 && _stateService.GetPinProtectedAsync() != null) || pinSet.Item2; if (!pinLock && !await IsBiometricLockSetAsync()) { @@ -136,19 +143,26 @@ namespace Bit.Core.Services if (allowSoftLock) { - BiometricLocked = await IsBiometricLockSetAsync(); - if (BiometricLocked) + var biometricLocked = await IsBiometricLockSetAsync(); + _stateService.BiometricLocked = biometricLocked; + if (biometricLocked) { _messagingService.Send("locked", userInitiated); _lockedCallback?.Invoke(userInitiated); return; } } + + if (userId == null || userId == await _stateService.GetActiveUserIdAsync()) + { + _searchService.ClearIndex(); + } + await Task.WhenAll( - _cryptoService.ClearKeyAsync(), - _cryptoService.ClearOrgKeysAsync(true), - _cryptoService.ClearKeyPairAsync(true), - _cryptoService.ClearEncKeyAsync(true)); + _cryptoService.ClearKeyAsync(userId), + _cryptoService.ClearOrgKeysAsync(true, userId), + _cryptoService.ClearKeyPairAsync(true, userId), + _cryptoService.ClearEncKeyAsync(true, userId)); _folderService.ClearCache(); await _cipherService.ClearCacheAsync(); @@ -158,43 +172,43 @@ namespace Bit.Core.Services _lockedCallback?.Invoke(userInitiated); } - public async Task LogOutAsync() + public async Task LogOutAsync(string userId = null) { if(_loggedOutCallback != null) { - await _loggedOutCallback.Invoke(false); + await _loggedOutCallback.Invoke(new Tuple(false, userId)); } } public async Task SetVaultTimeoutOptionsAsync(int? timeout, string action) { - await _storageService.SaveAsync(Constants.VaultTimeoutKey, timeout); - await _storageService.SaveAsync(Constants.VaultTimeoutActionKey, action); + await _stateService.SetVaultTimeoutAsync(timeout); + await _stateService.SetVaultTimeoutActionAsync(action); await _cryptoService.ToggleKeyAsync(); await _tokenService.ToggleTokensAsync(); } public async Task> IsPinLockSetAsync() { - var protectedPin = await _storageService.GetAsync(Constants.ProtectedPin); - var pinProtectedKey = await _storageService.GetAsync(Constants.PinProtectedKey); + var protectedPin = await _stateService.GetProtectedPinAsync(); + var pinProtectedKey = await _stateService.GetPinProtectedAsync(); return new Tuple(protectedPin != null, pinProtectedKey != null); } public async Task IsBiometricLockSetAsync() { - var biometricLock = await _storageService.GetAsync(Constants.BiometricUnlockKey); + var biometricLock = await _stateService.GetBiometricUnlockAsync(); return biometricLock.GetValueOrDefault(); } - public async Task ClearAsync() + public async Task ClearAsync(string userId = null) { - PinProtectedKey = null; - await _storageService.RemoveAsync(Constants.ProtectedPin); + await _stateService.SetPinProtectedAsync(null, new StorageOptions { UserId = userId }); + await _stateService.SetProtectedPinAsync(null, new StorageOptions { UserId = userId }); } - public async Task GetVaultTimeout() { - var vaultTimeout = await _storageService.GetAsync(Constants.VaultTimeoutKey); + public async Task GetVaultTimeout(string userId = null) { + var vaultTimeout = await _stateService.GetVaultTimeoutAsync(); if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout)) { var policy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout)).First(); @@ -213,7 +227,7 @@ namespace Bit.Core.Services // We really shouldn't need to set the value here, but multiple services relies on this value being correct. if (vaultTimeout != timeout) { - await _storageService.SaveAsync(Constants.VaultTimeoutKey, timeout); + await _stateService.SetVaultTimeoutAsync(timeout); } return timeout; diff --git a/src/Core/Utilities/ServiceContainer.cs b/src/Core/Utilities/ServiceContainer.cs index dcafe40c5..16a10f681 100644 --- a/src/Core/Utilities/ServiceContainer.cs +++ b/src/Core/Utilities/ServiceContainer.cs @@ -22,64 +22,65 @@ namespace Bit.Core.Utilities var platformUtilsService = Resolve("platformUtilsService"); var storageService = Resolve("storageService"); - var secureStorageService = Resolve("secureStorageService"); + var stateService = Resolve("stateService"); var i18nService = Resolve("i18nService"); var messagingService = Resolve("messagingService"); var cryptoFunctionService = Resolve("cryptoFunctionService"); var cryptoService = Resolve("cryptoService"); SearchService searchService = null; - var stateService = new StateService(); - var tokenService = new TokenService(storageService); + var tokenService = new TokenService(stateService); var apiService = new ApiService(tokenService, platformUtilsService, (bool expired) => { messagingService.Send("logout", expired); return Task.FromResult(0); }, customUserAgent); - var appIdService = new AppIdService(storageService); - var userService = new UserService(storageService, tokenService); - var settingsService = new SettingsService(userService, storageService); + var appIdService = new AppIdService(stateService); + var organizationService = new OrganizationService(stateService); + var settingsService = new SettingsService(stateService); var fileUploadService = new FileUploadService(apiService); - var cipherService = new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService, - storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys); - var folderService = new FolderService(cryptoService, userService, apiService, storageService, - i18nService, cipherService); - var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService); - var sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService, - i18nService, cryptoFunctionService); + var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService, + fileUploadService, storageService, i18nService, () => searchService, clearCipherCacheKey, + allClearCipherCacheKeys); + var folderService = new FolderService(cryptoService, stateService, apiService, i18nService, cipherService); + var collectionService = new CollectionService(cryptoService, stateService, i18nService); + var sendService = new SendService(cryptoService, stateService, apiService, fileUploadService, i18nService, + cryptoFunctionService); searchService = new SearchService(cipherService, sendService); - var policyService = new PolicyService(storageService, userService); - var keyConnectorService = new KeyConnectorService(userService, cryptoService, storageService, tokenService, apiService); - var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService, - storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService, - policyService, keyConnectorService, null, (expired) => + var policyService = new PolicyService(stateService, organizationService); + var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService, + organizationService); + var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService, + folderService, cipherService, collectionService, searchService, messagingService, tokenService, + policyService, keyConnectorService, null, (extras) => { - messagingService.Send("logout", expired); + messagingService.Send("logout", extras); return Task.FromResult(0); }); - var syncService = new SyncService(userService, apiService, settingsService, folderService, - cipherService, cryptoService, collectionService, storageService, messagingService, policyService, sendService, + var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService, + cryptoService, collectionService, organizationService, messagingService, policyService, sendService, keyConnectorService, (bool expired) => { messagingService.Send("logout", expired); return Task.FromResult(0); }); - var passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, + var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService); - var totpService = new TotpService(storageService, cryptoFunctionService); - var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, userService, tokenService, appIdService, - i18nService, platformUtilsService, messagingService, vaultTimeoutService, keyConnectorService); + var totpService = new TotpService(stateService, cryptoFunctionService); + var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService, + tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService, + keyConnectorService); var exportService = new ExportService(folderService, cipherService, cryptoService); var auditService = new AuditService(cryptoFunctionService, apiService); - var environmentService = new EnvironmentService(apiService, storageService); - var eventService = new EventService(storageService, apiService, userService, cipherService); - var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService, cryptoService); + var environmentService = new EnvironmentService(apiService, stateService); + var eventService = new EventService(apiService, stateService, organizationService, cipherService); + var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService, + cryptoService); - Register("stateService", stateService); Register("tokenService", tokenService); Register("apiService", apiService); Register("appIdService", appIdService); - Register("userService", userService); + Register("organizationService", organizationService); Register("settingsService", settingsService); Register("cipherService", cipherService); Register("folderService", folderService); diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index 2a9fed346..e0e529bdb 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -79,9 +79,9 @@ namespace Bit.iOS.Autofill public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity) { InitAppIfNeeded(); - var storageService = ServiceContainer.Resolve("storageService"); - await storageService.SaveAsync(Bit.Core.Constants.PasswordRepromptAutofillKey, false); - await storageService.SaveAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey, false); + var stateService = ServiceContainer.Resolve("stateService"); + await stateService.SetPasswordRepromptAutofillAsync(false); + await stateService.SetPasswordVerifiedAutofillAsync(false); if (!await IsAuthed() || await IsLocked()) { var err = new NSError(new NSString("ASExtensionErrorDomain"), @@ -230,7 +230,7 @@ namespace Bit.iOS.Autofill return; } - var storageService = ServiceContainer.Resolve("storageService"); + var stateService = ServiceContainer.Resolve("stateService"); var decCipher = await cipher.DecryptAsync(); if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None) { @@ -238,13 +238,13 @@ namespace Bit.iOS.Autofill // already verified the password. if (!userInteraction) { - await storageService.SaveAsync(Bit.Core.Constants.PasswordRepromptAutofillKey, true); + await stateService.SetPasswordRepromptAutofillAsync(true); var err = new NSError(new NSString("ASExtensionErrorDomain"), Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null); ExtensionContext?.CancelRequest(err); return; } - else if (!await storageService.GetAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey)) + else if (!await stateService.GetPasswordVerifiedAutofillAsync()) { // Add a timeout to resolve keyboard not always showing up. await Task.Delay(250); @@ -259,11 +259,10 @@ namespace Bit.iOS.Autofill } } string totpCode = null; - var disableTotpCopy = await storageService.GetAsync(Bit.Core.Constants.DisableAutoTotpCopyKey); + var disableTotpCopy = await stateService.GetDisableAutoTotpCopyAsync(); if (!disableTotpCopy.GetValueOrDefault(false)) { - var userService = ServiceContainer.Resolve("userService"); - var canAccessPremiumAsync = await userService.CanAccessPremiumAsync(); + var canAccessPremiumAsync = await stateService.CanAccessPremiumAsync(); if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) && (canAccessPremiumAsync || cipher.OrganizationUseTotp)) { @@ -277,8 +276,8 @@ namespace Bit.iOS.Autofill private async void CheckLock(Action notLockedAction) { - var storageService = ServiceContainer.Resolve("storageService"); - if (await IsLocked() || await storageService.GetAsync(Bit.Core.Constants.PasswordRepromptAutofillKey)) + var stateService = ServiceContainer.Resolve("stateService"); + if (await IsLocked() || await stateService.GetPasswordRepromptAutofillAsync()) { PerformSegue("lockPasswordSegue", this); } @@ -296,8 +295,8 @@ namespace Bit.iOS.Autofill private Task IsAuthed() { - var userService = ServiceContainer.Resolve("userService"); - return userService.IsAuthenticatedAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + return stateService.IsAuthenticatedAsync(); } private void LogoutIfAuthed() @@ -306,7 +305,8 @@ namespace Bit.iOS.Autofill { if (await IsAuthed()) { - await AppHelpers.LogOutAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync()); var deviceActionService = ServiceContainer.Resolve("deviceActionService"); if (deviceActionService.SystemMajorVersion() >= 12) { @@ -337,7 +337,7 @@ namespace Bit.iOS.Autofill } iOSCoreHelpers.Bootstrap(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); iOSCoreHelpers.AppearanceAdjustments(); _nfcDelegate = new Core.NFCReaderDelegate((success, message) => messagingService.Send("gotYubiKeyOTP", message)); @@ -356,7 +356,7 @@ namespace Bit.iOS.Autofill { var homePage = new HomePage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(homePage); if (homePage.BindingContext is HomeViewModel vm) { @@ -379,7 +379,7 @@ namespace Bit.iOS.Autofill { var environmentPage = new EnvironmentPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(environmentPage); if (environmentPage.BindingContext is EnvironmentPageViewModel vm) { @@ -397,7 +397,7 @@ namespace Bit.iOS.Autofill { var registerPage = new RegisterPage(null); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(registerPage); if (registerPage.BindingContext is RegisterPageViewModel vm) { @@ -415,7 +415,7 @@ namespace Bit.iOS.Autofill { var loginPage = new LoginPage(email); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginPageViewModel vm) { @@ -437,7 +437,7 @@ namespace Bit.iOS.Autofill { var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { @@ -460,7 +460,7 @@ namespace Bit.iOS.Autofill { var twoFactorPage = new TwoFactorPage(authingWithSso); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(twoFactorPage); if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm) { @@ -487,7 +487,7 @@ namespace Bit.iOS.Autofill { var setPasswordPage = new SetPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { @@ -506,7 +506,7 @@ namespace Bit.iOS.Autofill { var updateTempPasswordPage = new UpdateTempPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) { diff --git a/src/iOS.Autofill/Utilities/AutofillHelpers.cs b/src/iOS.Autofill/Utilities/AutofillHelpers.cs index fa1895968..2f274b913 100644 --- a/src/iOS.Autofill/Utilities/AutofillHelpers.cs +++ b/src/iOS.Autofill/Utilities/AutofillHelpers.cs @@ -41,12 +41,11 @@ namespace Bit.iOS.Autofill.Utilities if (!string.IsNullOrWhiteSpace(item.Username) && !string.IsNullOrWhiteSpace(item.Password)) { string totp = null; - var storageService = ServiceContainer.Resolve("storageService"); - var disableTotpCopy = await storageService.GetAsync(Bit.Core.Constants.DisableAutoTotpCopyKey); + var stateService = ServiceContainer.Resolve("stateService"); + var disableTotpCopy = await stateService.GetDisableAutoTotpCopyAsync(); if (!disableTotpCopy.GetValueOrDefault(false)) { - var userService = ServiceContainer.Resolve("userService"); - var canAccessPremiumAsync = await userService.CanAccessPremiumAsync(); + var canAccessPremiumAsync = await stateService.CanAccessPremiumAsync(); if (!string.IsNullOrWhiteSpace(item.Totp) && (canAccessPremiumAsync || item.CipherView.OrganizationUseTotp)) { @@ -118,4 +117,4 @@ namespace Bit.iOS.Autofill.Utilities } } } -} \ No newline at end of file +} diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index f35d9b64b..e1efa52f4 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -22,8 +22,7 @@ namespace Bit.iOS.Core.Controllers private IVaultTimeoutService _vaultTimeoutService; private ICryptoService _cryptoService; private IDeviceActionService _deviceActionService; - private IUserService _userService; - private IStorageService _storageService; + private IStateService _stateService; private IStorageService _secureStorageService; private IPlatformUtilsService _platformUtilsService; private IBiometricService _biometricService; @@ -82,8 +81,7 @@ namespace Bit.iOS.Core.Controllers _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _cryptoService = ServiceContainer.Resolve("cryptoService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); - _userService = ServiceContainer.Resolve("userService"); - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); _secureStorageService = ServiceContainer.Resolve("secureStorageService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _biometricService = ServiceContainer.Resolve("biometricService"); @@ -91,7 +89,7 @@ namespace Bit.iOS.Core.Controllers // We re-use the lock screen for autofill extension to verify master password // when trying to access protected items. - if (autofillExtension && await _storageService.GetAsync(Bit.Core.Constants.PasswordRepromptAutofillKey)) + if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) { _passwordReprompt = true; _pinSet = Tuple.Create(false, false); @@ -101,7 +99,7 @@ namespace Bit.iOS.Core.Controllers else { _pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult(); - _pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2; + _pinLock = (_pinSet.Item1 && _stateService.GetPinProtectedAsync() != null) || _pinSet.Item2; _biometricLock = _vaultTimeoutService.IsBiometricLockSetAsync().GetAwaiter().GetResult() && _cryptoService.HasKeyAsync().GetAwaiter().GetResult(); _biometricIntegrityValid = _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey).GetAwaiter() @@ -211,9 +209,9 @@ namespace Bit.iOS.Core.Controllers return; } - var email = await _userService.GetEmailAsync(); - var kdf = await _userService.GetKdfAsync(); - var kdfIterations = await _userService.GetKdfIterationsAsync(); + var email = await _stateService.GetEmailAsync(); + var kdf = await _stateService.GetKdfTypeAsync(); + var kdfIterations = await _stateService.GetKdfIterationsAsync(); var inputtedValue = MasterPasswordCell.TextField.Text; if (_pinLock) @@ -225,9 +223,9 @@ namespace Bit.iOS.Core.Controllers { var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), - _vaultTimeoutService.PinProtectedKey); + await _stateService.GetPinProtectedCachedAsync()); var encKey = await _cryptoService.GetEncKeyAsync(key); - var protectedPin = await _storageService.GetAsync(Bit.Core.Constants.ProtectedPin); + var protectedPin = await _stateService.GetProtectedPinAsync(); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); failed = decPin != inputtedValue; if (!failed) @@ -280,12 +278,12 @@ namespace Bit.iOS.Core.Controllers { if (_pinSet.Item1) { - var protectedPin = await _storageService.GetAsync(Bit.Core.Constants.ProtectedPin); + var protectedPin = await _stateService.GetProtectedPinAsync(); var encKey = await _cryptoService.GetEncKeyAsync(key2); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email, kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); - _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey); + await _stateService.SetPinProtectedCachedAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey)); } await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key2, true); @@ -312,7 +310,7 @@ namespace Bit.iOS.Core.Controllers var success = await _platformUtilsService.AuthenticateBiometricAsync(null, _pinLock ? AppResources.PIN : AppResources.MasterPassword, () => MasterPasswordCell.TextField.BecomeFirstResponder()); - _vaultTimeoutService.BiometricLocked = !success; + _stateService.BiometricLocked = !success; if (success) { DoContinue(); @@ -323,7 +321,7 @@ namespace Bit.iOS.Core.Controllers { var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { @@ -351,10 +349,10 @@ namespace Bit.iOS.Core.Controllers { if (masterPassword) { - await _storageService.SaveAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey, true); + await _stateService.SetPasswordVerifiedAutofillAsync(true); } await EnableBiometricsIfNeeded(); - _vaultTimeoutService.BiometricLocked = false; + _stateService.BiometricLocked = false; MasterPasswordCell.TextField.ResignFirstResponder(); Success(); } @@ -383,7 +381,7 @@ namespace Bit.iOS.Core.Controllers private async Task LogOutAsync() { - await AppHelpers.LogOutAsync(); + await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync()); var authService = ServiceContainer.Resolve("authService"); authService.LogOut(() => { diff --git a/src/iOS.Core/Renderers/CustomNavigationRenderer.cs b/src/iOS.Core/Renderers/CustomNavigationRenderer.cs new file mode 100644 index 000000000..0e578b951 --- /dev/null +++ b/src/iOS.Core/Renderers/CustomNavigationRenderer.cs @@ -0,0 +1,54 @@ +using System.Linq; +using Bit.App.Controls; +using Bit.iOS.Core.Renderers; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomNavigationRenderer))] +namespace Bit.iOS.Core.Renderers +{ + public class CustomNavigationRenderer : NavigationRenderer + { + public override void PushViewController(UIViewController viewController, bool animated) + { + base.PushViewController(viewController, animated); + + var currentPage = (Element as NavigationPage)?.CurrentPage; + if (currentPage == null) + { + return; + } + var toolbarItems = currentPage.ToolbarItems; + if (!toolbarItems.Any()) + { + return; + } + var uiBarButtonItems = TopViewController.NavigationItem.RightBarButtonItems; + if (uiBarButtonItems == null) + { + return; + } + + foreach (var toolbarItem in toolbarItems) + { + if (!(toolbarItem is ExtendedToolbarItem extendedToolbarItem) || !extendedToolbarItem.UseOriginalImage) + { + continue; + } + var index = currentPage.ToolbarItems.IndexOf(extendedToolbarItem) + 1; + if (index < 0 || index >= uiBarButtonItems.Length) + { + continue; + } + var uiBarButtonItem = uiBarButtonItems[index]; + if (uiBarButtonItem.Image == null) + { + continue; + } + var originalImage = uiBarButtonItem.Image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal); + uiBarButtonItem.Image = originalImage; + } + } + } +} diff --git a/src/iOS.Core/Services/ClipboardService.cs b/src/iOS.Core/Services/ClipboardService.cs index 6014da332..67f7743ac 100644 --- a/src/iOS.Core/Services/ClipboardService.cs +++ b/src/iOS.Core/Services/ClipboardService.cs @@ -9,11 +9,11 @@ namespace Bit.iOS.Core.Services { public class ClipboardService : IClipboardService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; - public ClipboardService(IStorageService storageService) + public ClipboardService(IStateService stateService) { - _storageService = storageService; + _stateService = stateService; } public async Task CopyTextAsync(string text, int expiresInMs = -1) @@ -21,7 +21,7 @@ namespace Bit.iOS.Core.Services int clearSeconds = -1; if (expiresInMs < 0) { - clearSeconds = await _storageService.GetAsync(Bit.Core.Constants.ClearClipboardKey) ?? -1; + clearSeconds = await _stateService.GetClearClipboardAsync() ?? -1; } else { diff --git a/src/iOS.Core/Services/DeviceActionService.cs b/src/iOS.Core/Services/DeviceActionService.cs index 2f7991951..2c89589f1 100644 --- a/src/iOS.Core/Services/DeviceActionService.cs +++ b/src/iOS.Core/Services/DeviceActionService.cs @@ -22,17 +22,17 @@ namespace Bit.iOS.Core.Services { public class DeviceActionService : IDeviceActionService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly IMessagingService _messagingService; private Toast _toast; private UIAlertController _progressAlert; private string _userAgent; public DeviceActionService( - IStorageService storageService, + IStateService stateService, IMessagingService messagingService) { - _storageService = storageService; + _stateService = stateService; _messagingService = messagingService; } @@ -152,7 +152,7 @@ namespace Bit.iOS.Core.Services NSFileManager.DefaultManager.Remove(item, out NSError itemError); } } - await _storageService.SaveAsync(Bit.Core.Constants.LastFileCacheClearKey, DateTime.UtcNow); + await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow); } public Task SelectFileAsync() diff --git a/src/iOS.Core/Utilities/ASHelpers.cs b/src/iOS.Core/Utilities/ASHelpers.cs index 5e81438fb..28fa3ef33 100644 --- a/src/iOS.Core/Utilities/ASHelpers.cs +++ b/src/iOS.Core/Utilities/ASHelpers.cs @@ -15,7 +15,8 @@ namespace Bit.iOS.Core.Utilities if (await AutofillEnabled()) { var storageService = ServiceContainer.Resolve("storageService"); - var timeoutAction = await storageService.GetAsync(Bit.Core.Constants.VaultTimeoutActionKey); + var stateService = ServiceContainer.Resolve("stateService"); + var timeoutAction = await stateService.GetVaultTimeoutActionAsync(); if (timeoutAction == "logOut") { return; @@ -47,8 +48,8 @@ namespace Bit.iOS.Core.Utilities public static async Task IdentitiesCanIncremental() { - var storageService = ServiceContainer.Resolve("storageService"); - var timeoutAction = await storageService.GetAsync(Bit.Core.Constants.VaultTimeoutActionKey); + var stateService = ServiceContainer.Resolve("stateService"); + var timeoutAction = await stateService.GetVaultTimeoutActionAsync(); if (timeoutAction == "logOut") { return false; diff --git a/src/iOS.Core/Utilities/AppCenterHelper.cs b/src/iOS.Core/Utilities/AppCenterHelper.cs index 50b64d154..ff19b62a5 100644 --- a/src/iOS.Core/Utilities/AppCenterHelper.cs +++ b/src/iOS.Core/Utilities/AppCenterHelper.cs @@ -11,22 +11,22 @@ namespace Bit.iOS.Core.Utilities private const string AppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3"; private readonly IAppIdService _appIdService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private string _userId; private string _appId; public AppCenterHelper( IAppIdService appIdService, - IUserService userService) + IStateService stateService) { _appIdService = appIdService; - _userService = userService; + _stateService = stateService; } public async Task InitAsync() { - _userId = await _userService.GetUserIdAsync(); + _userId = await _stateService.GetActiveUserIdAsync(); _appId = await _appIdService.GetAppIdAsync(); AppCenter.Start(AppSecret, typeof(Crashes)); diff --git a/src/iOS.Core/Utilities/iOSCoreHelpers.cs b/src/iOS.Core/Utilities/iOSCoreHelpers.cs index a8238c021..726a70f62 100644 --- a/src/iOS.Core/Utilities/iOSCoreHelpers.cs +++ b/src/iOS.Core/Utilities/iOSCoreHelpers.cs @@ -28,7 +28,7 @@ namespace Bit.iOS.Core.Utilities { var appCenterHelper = new AppCenterHelper( ServiceContainer.Resolve("appIdService"), - ServiceContainer.Resolve("userService")); + ServiceContainer.Resolve("stateService")); var appCenterTask = appCenterHelper.InitAsync(); } @@ -51,13 +51,14 @@ namespace Bit.iOS.Core.Utilities () => ServiceContainer.Resolve("appIdService").GetAppIdAsync()); var cryptoPrimitiveService = new CryptoPrimitiveService(); var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); - var deviceActionService = new DeviceActionService(mobileStorageService, messagingService); - var clipboardService = new ClipboardService(mobileStorageService); + var stateService = new StateService(mobileStorageService, secureStorageService); + var deviceActionService = new DeviceActionService(stateService, messagingService); + var clipboardService = new ClipboardService(stateService); var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService, broadcasterService); var biometricService = new BiometricService(mobileStorageService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); - var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService); + var cryptoService = new CryptoService(stateService, cryptoFunctionService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); ServiceContainer.Register("broadcasterService", broadcasterService); @@ -67,6 +68,7 @@ namespace Bit.iOS.Core.Utilities ServiceContainer.Register("cryptoPrimitiveService", cryptoPrimitiveService); ServiceContainer.Register("storageService", mobileStorageService); ServiceContainer.Register("secureStorageService", secureStorageService); + ServiceContainer.Register("stateService", stateService); ServiceContainer.Register("deviceActionService", deviceActionService); ServiceContainer.Register("clipboardService", clipboardService); ServiceContainer.Register("platformUtilsService", platformUtilsService); @@ -88,7 +90,7 @@ namespace Bit.iOS.Core.Utilities public static void AppearanceAdjustments() { - ThemeHelpers.SetAppearance(ThemeManager.GetTheme(false), ThemeManager.OsDarkModeEnabled()); + ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled()); UIApplication.SharedApplication.StatusBarHidden = false; UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent; } @@ -143,10 +145,6 @@ namespace Bit.iOS.Core.Utilities private static async Task BootstrapAsync(Func postBootstrapFunc = null) { - var disableFavicon = await ServiceContainer.Resolve("storageService").GetAsync( - Bit.Core.Constants.DisableFaviconKey); - await ServiceContainer.Resolve("stateService").SaveAsync( - Bit.Core.Constants.DisableFaviconKey, disableFavicon); await ServiceContainer.Resolve("environmentService").SetUrlsFromStorageAsync(); if (postBootstrapFunc != null) { diff --git a/src/iOS.Core/Views/ExtensionTableSource.cs b/src/iOS.Core/Views/ExtensionTableSource.cs index 9aa5e1b4b..4bd856b1e 100644 --- a/src/iOS.Core/Views/ExtensionTableSource.cs +++ b/src/iOS.Core/Views/ExtensionTableSource.cs @@ -23,7 +23,7 @@ namespace Bit.iOS.Core.Views private IEnumerable _allItems = new List(); protected ICipherService _cipherService; protected ITotpService _totpService; - protected IUserService _userService; + protected IStateService _stateService; protected ISearchService _searchService; private AppExtensionContext _context; private UIViewController _controller; @@ -32,7 +32,7 @@ namespace Bit.iOS.Core.Views { _cipherService = ServiceContainer.Resolve("cipherService"); _totpService = ServiceContainer.Resolve("totpService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _searchService = ServiceContainer.Resolve("searchService"); _context = context; _controller = controller; @@ -135,7 +135,7 @@ namespace Bit.iOS.Core.Views public async Task GetTotpAsync(CipherViewModel item) { string totp = null; - var accessPremium = await _userService.CanAccessPremiumAsync(); + var accessPremium = await _stateService.CanAccessPremiumAsync(); if (accessPremium || (item?.CipherView.OrganizationUseTotp ?? false)) { if (item != null && !string.IsNullOrWhiteSpace(item.Totp)) diff --git a/src/iOS.Core/iOS.Core.csproj b/src/iOS.Core/iOS.Core.csproj index a2353acf8..7a7ea52ec 100644 --- a/src/iOS.Core/iOS.Core.csproj +++ b/src/iOS.Core/iOS.Core.csproj @@ -156,6 +156,7 @@ + diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs index 44acc0084..a7b6cf484 100644 --- a/src/iOS.Extension/LoadingViewController.cs +++ b/src/iOS.Extension/LoadingViewController.cs @@ -415,7 +415,7 @@ namespace Bit.iOS.Extension } iOSCoreHelpers.Bootstrap(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); iOSCoreHelpers.AppearanceAdjustments(); _nfcDelegate = new NFCReaderDelegate((success, message) => messagingService.Send("gotYubiKeyOTP", message)); @@ -430,8 +430,8 @@ namespace Bit.iOS.Extension private Task IsAuthed() { - var userService = ServiceContainer.Resolve("userService"); - return userService.IsAuthenticatedAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + return stateService.IsAuthenticatedAsync(); } private void LogoutIfAuthed() @@ -440,7 +440,8 @@ namespace Bit.iOS.Extension { if (await IsAuthed()) { - await AppHelpers.LogOutAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync()); var deviceActionService = ServiceContainer.Resolve("deviceActionService"); if (deviceActionService.SystemMajorVersion() >= 12) { @@ -454,7 +455,7 @@ namespace Bit.iOS.Extension { var homePage = new HomePage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(homePage); if (homePage.BindingContext is HomeViewModel vm) { @@ -477,7 +478,7 @@ namespace Bit.iOS.Extension { var environmentPage = new EnvironmentPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(environmentPage); if (environmentPage.BindingContext is EnvironmentPageViewModel vm) { @@ -495,7 +496,7 @@ namespace Bit.iOS.Extension { var registerPage = new RegisterPage(null); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(registerPage); if (registerPage.BindingContext is RegisterPageViewModel vm) { @@ -513,7 +514,7 @@ namespace Bit.iOS.Extension { var loginPage = new LoginPage(email); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginPageViewModel vm) { @@ -535,7 +536,7 @@ namespace Bit.iOS.Extension { var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { @@ -558,7 +559,7 @@ namespace Bit.iOS.Extension { var twoFactorPage = new TwoFactorPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(twoFactorPage); if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm) { @@ -585,7 +586,7 @@ namespace Bit.iOS.Extension { var setPasswordPage = new SetPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { @@ -604,7 +605,7 @@ namespace Bit.iOS.Extension { var updateTempPasswordPage = new UpdateTempPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) { diff --git a/src/iOS.Extension/LoginListViewController.cs b/src/iOS.Extension/LoginListViewController.cs index 2dd2f7455..b42e2a7b3 100644 --- a/src/iOS.Extension/LoginListViewController.cs +++ b/src/iOS.Extension/LoginListViewController.cs @@ -128,8 +128,9 @@ namespace Bit.iOS.Extension { string totp = null; var storageService = ServiceContainer.Resolve("storageService"); + var userId = _stateService.GetActiveUserIdAsync().GetAwaiter().GetResult(); var disableTotpCopy = storageService.GetAsync( - Bit.Core.Constants.DisableAutoTotpCopyKey).GetAwaiter().GetResult(); + Bit.Core.Constants.DisableAutoTotpCopyKey(userId)).GetAwaiter().GetResult(); if (!disableTotpCopy.GetValueOrDefault(false)) { totp = GetTotpAsync(item).GetAwaiter().GetResult(); diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index a6fe27541..990e31168 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -35,7 +35,7 @@ namespace Bit.iOS private IMessagingService _messagingService; private IBroadcasterService _broadcasterService; private IStorageService _storageService; - private IVaultTimeoutService _vaultTimeoutService; + private IStateService _stateService; private IEventService _eventService; public override bool FinishedLaunching(UIApplication app, NSDictionary options) @@ -47,7 +47,7 @@ namespace Bit.iOS _messagingService = ServiceContainer.Resolve("messagingService"); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _storageService = ServiceContainer.Resolve("storageService"); - _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); + _stateService = ServiceContainer.Resolve("stateService"); _eventService = ServiceContainer.Resolve("eventService"); LoadApplication(new App.App(null)); @@ -88,11 +88,6 @@ namespace Bit.iOS { Device.BeginInvokeOnMainThread(() => ShowAppExtension((ExtensionPageViewModel)message.Data)); } - else if (message.Command == "showStatusBar") - { - Device.BeginInvokeOnMainThread(() => - UIApplication.SharedApplication.SetStatusBarHidden(!(bool)message.Data, false)); - } else if (message.Command == "syncCompleted") { if (message.Data is Dictionary data && data.ContainsKey("successfully")) @@ -160,7 +155,7 @@ namespace Bit.iOS } else if (message.Command == "vaultTimeoutActionChanged") { - var timeoutAction = await _storageService.GetAsync(Constants.VaultTimeoutActionKey); + var timeoutAction = await _stateService.GetVaultTimeoutActionAsync(); if (timeoutAction == "logOut") { await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); @@ -195,13 +190,12 @@ namespace Bit.iOS UIApplication.SharedApplication.KeyWindow.AddSubview(view); UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view); UIApplication.SharedApplication.KeyWindow.EndEditing(true); - UIApplication.SharedApplication.SetStatusBarHidden(true, false); base.OnResignActivation(uiApplication); } public override void DidEnterBackground(UIApplication uiApplication) { - _storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime()); + _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); _messagingService.Send("slept"); base.DidEnterBackground(uiApplication); } @@ -214,7 +208,6 @@ namespace Bit.iOS if (view != null) { view.RemoveFromSuperview(); - UIApplication.SharedApplication.SetStatusBarHidden(false, false); } } diff --git a/src/iOS/Resources/cog_environment.png b/src/iOS/Resources/cog_environment.png new file mode 100644 index 0000000000000000000000000000000000000000..5a44b48f41758a31e8109a379839e670e55f60bc GIT binary patch literal 1711 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}$5JCa(|mmy zw18|51~x_^24;{FAY@>aVqgWcfjSwb;p{j@4X7F>pc+F4Aju#K#A)rB3@lLfH9#7~ z)BzDNIv}$mH77T*B)>=@peSF#NY7Z$1Srokfdy=a2#~#L0VBfx1x#@BgcdL(*g$m* zMg~SkRtAPvh9(Mz=2k`qRz?;K3%)j{0;M<$JR*x381$4un6YB1eHjA-^QO#@h>{3j zAFJg2T)o7U{G?R9irfN_Neng>RzPNMYDuC(MQ%=Bu~mhw5?F;5kPQ;nS5g2gDap1~ zitr6kaLzAERWQ>t&@)i7<5EyiuqjGOvkG!?gK7uzY?U%fN(!v>^~=l4^~#O)@{7{- z4J|D#^$m>ljf`}GDs+o0^GXscbn}XpA%?)raY-#sF3Kz@$;{7F0GXSZlwVq6tE2?7 z2o50bOs&X7*pycc^%l^B`XCv7Lp=k1Y}$aHg}V<$S4Cn0PE945X*lcx$w0J$0|Vkl z8;}dEz!4tll9`*DR}A!zouP#d*l8FN2+tvDjKHN4NdieD&|6l{MX8|V6_THyV}~uZ z+32II!e&6KOJYf?9VmtA8W`#tnuNeon681Tm4N}0ArO^lX7~b=OiE%&A~sXZtPD(% zO+itMWDY1$ts;U_i}Q0zK}o^R*Z>Ul4MCKRK88HHUWC@ryv!0iBd8WMVRT)Q2zjhF zBFm%e^)Jdy1tw{*YYm~+A&WuP+vtN*HBu^vWFfF9Fb~;r0fQHoAMLmVrz))g79U4E zT^vI!1gB0puk8{ja{Ruuu~a5U;=(oVirL;vm;7NA;Y>}`e5$`;v*OY}@?m0PULJ;O zZl|OsFG)MLUSj_H>hI@v=Vyt1UVHvd@j2V~cgoN2&9_wC2v_B}Ha9{GmquC#L zvu&z#x+^V`3;4rCd-z{lEKbU^>9=<|cx#tXF5UdNf0X`Rvuz0{LNYHa?RPwQAxH@L#jba9M=eHq)aH#bLYV zm=`QsyL17!*ulNZR=QtZZVPS_-5m7J=!;{8u0hR=R-+w3$ybFRnnhaf(&84_%C646 zYw209_~w1*Z=KCKSoZ#c_`l^sua?Y^zHVG)CK6h@S^a^ST%zc-1sq|!(qEnA(s<5T zqHytANMFM*6Ruwkp*z_14r;Yu6%SNG6KobR~1bFR_8$={Dx dq$X{BD9<4l&+=lMLLsQk^>p=fS?83{1OO+A8}a}E literal 0 HcmV?d00001 diff --git a/src/iOS/Resources/cog_environment@2x.png b/src/iOS/Resources/cog_environment@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1f294dab40464f2bafa648c85813ae072f8232 GIT binary patch literal 2277 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`97~PxO!M_+ z&;qhK7}ywv7??pyfRKSvih&i(W?%?ol!mk87&V}3n1E^w8Gs~%C=jQ$XELxr)z<)N z5K{+4!03R?iqxFk#FG3Xg@B@b1tUFUJrkfj%LEp%86rUTrUi@$`xh|5%~M&xj9>%R zF&G&b8Ce+^S{a%s7@Aud8CV%vFc?mhF9S+(7I;J!Gcf2WgD_*oQhT5v^QO#@h>{3j zAFJg2T)o7U{G?R9irfN_Neng>RzPNMYDuC(MQ%=Bu~mhw5?F;5kPQ;nS5g2gDap1~ zitr6kaLzAERWQ>t&@)i7<5EyiuqjGOvkG!?gK7uzY?U%fN(!v>^~=l4^~#O)@{7{- z4J|D#^$m>ljf`}GDs+o0^GXscbn}XpA%?)raY-#sF3Kz@$;{7F0GXSZlwVq6tE2?7 z2o50bOs&X7*pycc^%l^B`XCv7Lp=k1Y}$aHg}V<$S4Cn0PE945X*lcx$w0J$0|Vkl z8;}dEz!4tll9`*DR}A!zow1n>*l8FN2+tvDjKHN4NdieD&|6l{MX8|V6_THyV}~uZ z+32II!e&6KOJYf?9VmtA8W`#tnuNeon681Tm4N}0ArO^lX7~b=OiE%&A~sV@fe9Ab z6cn{c=70j#Dk3PgI6tQploafY4ZuL(5JcJNW5}cHMQ9Dp%Pg@of@(n%M%NXIkjH8x zvOKz8|Dw!PV3G#A)(~nPvKUmojXo$&Hqw`-cGXVFE%(SyeR(f#)vgXFVEkpb~5E)=7y7;A{#HJOb~tM62L2yGPyfGwd1{0 ziDlegVg1{;^S9OdtrL2_^9uhVfv?6kP5JBkinK(ebMv>h^l5&$94e4(_SNdd!Z`mu z@7L+2b~gQin7x7NQGa8RW=XT}y~Q2p6_%z|ikdVgbru__rz#m8*dX#`&CJ5o z8K;VG@NK=o$CV@eGAfb(P22Sa66;>H$2ELdlEQHPfKOYRQ(f?D|1Ck`DYw%NgM-o& z-5&TXyl_R1S+Z}s#>2yDPWNQ5bQj$)IZ+Us{C8uY<2K`mLO0|jR~ZWT7Rqt0;q+{D zRJCdJIjzjLg2VTir$*buunSCns<$jR{eHAy*Us4)Y+n3(O&w3(;B(78-s#Bac0c^p z^o5eU=GMt}%ubYU$<3VmMOEeKPYLOT^K^n7^h6F7?ch)R#P!tegX&s4clFr@JG(c@ zd{X>q6{r;Lv7YNxg58Ub>+3h4otKs-zJ=|dmB^7Ro%u z_&oi=&0Nm7eACppZMK0tv9n8rC(W-9-*NFzs@1l__a(x!+NN(S3z@jKMyXrK>$&9X zOQDB9b#Gab%3UcC%C`0G^~AkmC2wOGJ}+R(>5jc!r@m$Bjc-?Pm^9_y{_naxdc}on z@0-eg=&MZQwOXRJfcel9)>By@&QAFyGdJhZ6S-4U16U?2R{UIWVa-*?8@&RhfBu$E z+Qc=@VnrN()%MoTr1$Ky3p^iL_kNX_;2-hw_bGGcw(WLId1S)+h)A8@_sqlV`$D%3wUrs)F8$?sbyVGBi_W@cv-Vg6jkfLtwWZoe zq*t`=y}*`uys4yI=CDtX?}w zz(0P~LZMj`&R(wETe2XY|6X*X-uvx~c;rO>%j+KTu;`E3HW^e?d%F6$taD0e0su^x BGxPue literal 0 HcmV?d00001 diff --git a/src/iOS/Resources/cog_environment@3x.png b/src/iOS/Resources/cog_environment@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5046423e7983b2aeb1dd6f16aaa93ff84d1cf339 GIT binary patch literal 2922 zcmZ`*3p~^78~?epTynp4vc_SN-7K=S+;179xmGf4EW2oir8+UUM3h`QQijxt#Bu5G zQYY)CR6z?ya&vX` z0ar`$ke3DLET?S);35&@<6;k->0CAezFegQxKRlNz!bFQ0a*z(KuQb&KY)ZDATw_R zfS-i^S36h&zJQSgF=zms0ZnjF(`kV80)7{`ip^Ex>B~x_aVc~LnZsg3iEI`WgRnqg zLBI3?>3Ize(5~Z4e2dSQ{HkZkm-=RdH~@pfU~nij4vmGPt#B9=4zmWZBWz0tT?)~z zfiVDpFcc37ZpHdI0FbJqkpegY1P@#oi-91AvqC8dE+bm31lVwKpvj!hBW{lxykuN%ql$bC!Et*4PF`;5z zawsd7vknFm2m1Pa&XdBWeT&45SttuE5GnQ`(FhdsM>Me5ulfCp#U)4ol*FM?e-caV z`AnNXjL$jRh%>;=f8l|SGzNtk1J;9IjkTFi?LUrh#VsNR{EWEBv4{wkg>zt2#O+05 zvFP|8b(;dT~Y@R$Y4ciZ2y0>Alx z7;&-xd#)FoMgd!TK3nucxW#)5cpId+s~2_o=PNY73*I3-1U%j^_Yn_y^h<;q0Lbof zbF?Ex%G@p5MAvlEX}4yS+B-QQodp0xT?+PEYk$TcDryGMrKj*ZDG7wE_x!Sq49C4` z(&fmjCL@`8zZwUtm6xIS^Irc}ZWlC>yxBd>8(&0XC_ z8~-Sd+E&}!7}lz_^CaO#!-VcpnMPFNpzfjIbDb1F6Y{xZ*=CP7&Pc!HdrBNWp473y zL#Y5aux}%QA<+4yVYi}QYcg&ulU!#VoL)+s#4bxCDtmLas99#RGEX1y4K`Cu_O+

(=q^<~u%Y+0y_9R|V;FyUdi>wFb427ou@VQo4`^YhN$E!pZzkM6v)tp5ZER&E?61`J5Y0K+h&E$6+N4Uh=f)7F zCfp9i0q<+3b+2p6Rkv6Zcv|_xyild_d+>Arv#xg6ZL4|+Jvd-wN-QGG1SK_G_muAE z9WuKwo3`ZWYV*It;LDJt$~SM&a~qx~ooIVk-n5)^I&Pv}pxqSYGLmg9>ENd8qzzx| z#o4PrV{qF==fjQ}!KecawNka1*={9_B{n|@*GLaejh|e-U0*Acrt9BE}3dXS(KgZ?o&s zV@Wk+i|2`x=7F;!`v8cQKt^boklwm{=r5x+Cq34ai#zLWC-&#SZ@rgcrcbFmCqo)# za6P^T1{2QxAC)92zm~e6q}rOBInR?{g{L*IxTR{{8EAsF zUuSeR_i?7qq+X=d`CDf%R`A~&*LLC<+q-its+yeQuIl%6z9!>eu#)GZHP?lG5PIEL z+S7JSxT`Q2wpHp@;;R?7g?q=-bt$tM{b98v?-G6uJ#9^M@iUQX&x*>>=bWv#1#dmFWTxJl)a7EXF zT&np*pp#`sZepQDZFEV8S}~pE8xqJng_26iP^ zrVID4D$C?ZCNz$Co$OL}FMlE}^2>gIe)E`n^=6U3M!wJ6=C}jP@#BVRK9y0iFX0LK zIBEClJiiwb6~68@qb?-u&ZWi|t5%u~ur(_vC6{dA38Lfb=r!!XiweWHx;Ju%9zMR|}{ z6KzZLQAyEnD&SP}gW$}hB?6Z`_IWwE9$ba&)%vom*|myr{lmwo*Sd_;3zH?grj}AQ z5~P?wUxxi^SkP#KXw&g~V=4R~p~K`31;_Klb@{3d_XDrA*xm820ir?_G`7f}HYA-O zt5W-h%VUSIvd(gMhh%bWX+x#s0e0PtM+z#!sHYRBUcyU*q5^bQ$4xk%4v z$VOJBS_ypd4&D<-yd&>4=~=`+;zo>djIN#d`>N9A+ue5@YODhN`!k1E9$}+i;y1Nc z58|fIDrdiK?b46wx>bHhd(A8U%QHTw+OU@%fABA|>gnDP*?(o$Z+h8m{B%AmSm|u9 hhkw*sjzhm_y+`bgOLAjI2=U)VHz!ZWGxj08{{x~RajyUX literal 0 HcmV?d00001 diff --git a/src/iOS/Resources/cog.png b/src/iOS/Resources/cog_settings.png similarity index 100% rename from src/iOS/Resources/cog.png rename to src/iOS/Resources/cog_settings.png diff --git a/src/iOS/Resources/cog@2x.png b/src/iOS/Resources/cog_settings@2x.png similarity index 100% rename from src/iOS/Resources/cog@2x.png rename to src/iOS/Resources/cog_settings@2x.png diff --git a/src/iOS/Resources/cog@3x.png b/src/iOS/Resources/cog_settings@3x.png similarity index 100% rename from src/iOS/Resources/cog@3x.png rename to src/iOS/Resources/cog_settings@3x.png diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj index 9749acf64..d7a920e43 100644 --- a/src/iOS/iOS.csproj +++ b/src/iOS/iOS.csproj @@ -207,13 +207,22 @@ - + - + - + + + + + + + + + + diff --git a/test/Core.Test/Services/SendServiceTests.cs b/test/Core.Test/Services/SendServiceTests.cs index fa2e3f94b..05680d2c4 100644 --- a/test/Core.Test/Services/SendServiceTests.cs +++ b/test/Core.Test/Services/SendServiceTests.cs @@ -28,7 +28,7 @@ namespace Bit.Core.Test.Services { public class SendServiceTests { - private string GetSendKey(string userId) => SendService.GetSendKey(userId); + private string GetSendKey(string userId) => Constants.SendsKey(userId); [Theory] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })] @@ -36,7 +36,7 @@ namespace Bit.Core.Test.Services public async Task ReplaceAsync_Success(SutProvider sutProvider, string userId, IEnumerable sendDatas) { var actualSendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); await sutProvider.Sut.ReplaceAsync(actualSendDataDict); @@ -53,7 +53,7 @@ namespace Bit.Core.Test.Services public async Task DeleteAsync_Success(int numberToDelete, SutProvider sutProvider, string userId, IEnumerable sendDatas) { var actualSendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency() .GetAsync>(GetSendKey(userId)).Returns(actualSendDataDict); @@ -84,7 +84,7 @@ namespace Bit.Core.Test.Services var initialSendDatas = sendDatas.ToDictionary(d => d.Id, d => d); var idToDelete = initialSendDatas.First().Key; var expectedSends = initialSendDatas.Skip(1).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency() .GetAsync>(Arg.Any()).Returns(initialSendDatas); @@ -102,7 +102,7 @@ namespace Bit.Core.Test.Services public async Task GetAsync_Success(SutProvider sutProvider, string userId, IEnumerable sendDatas) { var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(sendDataDict); foreach (var dataKvp in sendDataDict) @@ -117,7 +117,7 @@ namespace Bit.Core.Test.Services public async Task GetAsync_NonExistringId_ReturnsNull(SutProvider sutProvider, string userId, IEnumerable sendDatas) { var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(sendDataDict); var actual = await sutProvider.Sut.GetAsync(Guid.NewGuid().ToString()); @@ -131,7 +131,7 @@ namespace Bit.Core.Test.Services public async Task GetAllAsync_Success(SutProvider sutProvider, string userId, IEnumerable sendDatas) { var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(sendDataDict); var allExpected = sendDataDict.Select(kvp => new Send(kvp.Value)); @@ -154,7 +154,7 @@ namespace Bit.Core.Test.Services sutProvider.GetDependency().HasKeyAsync().Returns(true); ServiceContainer.Register("cryptoService", sutProvider.GetDependency()); sutProvider.GetDependency().StringComparer.Returns(StringComparer.CurrentCulture); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(sendDataDict); var actual = await sutProvider.Sut.GetAllDecryptedAsync(); @@ -175,7 +175,7 @@ namespace Bit.Core.Test.Services public async Task SaveWithServerAsync_NewTextSend_Success(SutProvider sutProvider, string userId, SendResponse response, Send send) { send.Id = null; - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().PostSendAsync(Arg.Any()).Returns(response); var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content")); @@ -208,7 +208,7 @@ namespace Bit.Core.Test.Services { send.Id = null; response.FileUploadType = FileUploadType.Azure; - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().PostFileTypeSendAsync(Arg.Any()).Returns(response); var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content")); @@ -231,7 +231,7 @@ namespace Bit.Core.Test.Services public async Task SaveWithServerAsync_NewFileSend_LegacyFallback_Success(SutProvider sutProvider, string userId, Send send, SendResponse response) { send.Id = null; - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); var error = new ErrorResponse(null, System.Net.HttpStatusCode.NotFound); sutProvider.GetDependency().PostFileTypeSendAsync(Arg.Any()).Throws(new ApiException(error)); sutProvider.GetDependency().PostSendFileAsync(Arg.Any()).Returns(response); @@ -248,7 +248,7 @@ namespace Bit.Core.Test.Services [InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })] public async Task SaveWithServerAsync_PutSend_Success(SutProvider sutProvider, string userId, SendResponse response, Send send) { - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().PutSendAsync(send.Id, Arg.Any()).Returns(response); await sutProvider.Sut.SaveWithServerAsync(send, null); @@ -281,7 +281,7 @@ namespace Bit.Core.Test.Services public async Task UpsertAsync_Update_Success(SutProvider sutProvider, string userId, IEnumerable initialSends) { var initialSendDict = initialSends.ToDictionary(s => s.Id, s => s); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(initialSendDict); var updatedSends = CoreHelpers.Clone(initialSendDict); @@ -311,7 +311,7 @@ namespace Bit.Core.Test.Services public async Task UpsertAsync_NewSends_Success(SutProvider sutProvider, string userId, IEnumerable initialSends, IEnumerable newSends) { var initialSendDict = initialSends.ToDictionary(s => s.Id, s => s); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(initialSendDict); var expectedDict = CoreHelpers.Clone(initialSendDict).Concat(newSends.Select(s => new KeyValuePair(s.Id, s)));