1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-11-21 11:25:56 +01:00

Account Switching (#1807)

* Account Switching (#1720)

* Account switching

* WIP

* wip

* wip

* updates to send test logic

* fixed Send tests

* fixes for theme handling on account switching and re-adding existing account

* switch fixes

* fixes

* fixes

* cleanup

* vault timeout fixes

* account list status enhancements

* logout fixes and token handling improvements

* merge latest (#1727)

* remove duplicate dependency

* fix for initial login token storage paradox (#1730)

* Fix avatar color update toolbar item issue on iOS for account switching (#1735)

* Updated account switching menu UI (#1733)

* updated account switching menu UI

* additional changes

* add key suffix to constant

* GetFirstLetters method tweaks

* Fix crash on account switching when logging out when having more than user at a time (#1740)

* single account migration to multi-account on app update (#1741)

* Account Switching Tap to dismiss (#1743)

* Added tap to dismiss on the Account switching overlay and improved a bit the code

* Fix account switching overlay background transparent on the proper place

* Fixed transparent background and the shadow on the account switching overlay

* Fix iOS top space on Account switching list overlay after modal (#1746)

* Fix top space added to Account switching list overlay after closing modal

* Fix top space added to Account switching list overlay after closing modal on lock, login and home views just in case we add modals in the future there as well

* Usability: dismiss account list on certain events (#1748)

* dismiss account list on certain events

* use new FireAndForget method for back button logic

* Create and use Account Switching overlay control (#1753)

* Added Account switching overlay control and its own ViewModel and refactored accordingly

* Fix account switching Accounts list binding update

* Implemented dismiss account switching overlay when changing tabs and when selecting the same tab. Also updated the deprecated listener on CustomTabbedRenderer on Android (#1755)

* Overriden Equals on AvatarImageSource so it doesn't get set multiple times when it's the same image thus producing blinking on tab chaged (#1756)

* Usability improvements for logout on vault timeout (#1781)

* accountswitching fixes (#1784)

* Fix for invalid PIN lock state when switching accounts (#1792)

* fix for pin lock flow

* named tuple values and updated async

* clear send service cache on account switch (#1796)

* Global theme and account removal (#1793)

* Global theme and account removal

* remove redundant call to hide account list overlay

* cleanup and additional tweaks

* add try/catch to remove account dialog flow

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
This commit is contained in:
Matt Portune 2022-02-23 12:40:17 -05:00 committed by GitHub
parent ded3f07fa6
commit 2e8824ce05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
144 changed files with 5064 additions and 1761 deletions

View File

@ -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<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("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<List<string>>(Constants.AutofillBlacklistedUrisKey);
var uris = await _stateService.GetAutofillBlacklistedUrisAsync();
if (uris != null)
{
_blacklistedUris = new HashSet<string>(uris);
}
var isAutoFillTileAdded = await _storageService.GetAsync<bool?>(Constants.AutofillTileAdded);
var isAutoFillTileAdded = await _stateService.GetAutofillTileAddedAsync();
AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault();
}
}

View File

@ -171,7 +171,8 @@
<AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable\card.xml" />
<AndroidResource Include="Resources\drawable\cog.xml" />
<AndroidResource Include="Resources\drawable\cog_environment.xml" />
<AndroidResource Include="Resources\drawable\cog_settings.xml" />
<AndroidResource Include="Resources\drawable\icon.xml" />
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
<AndroidResource Include="Resources\drawable\ic_warning.xml" />

View File

@ -26,9 +26,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)
@ -44,18 +43,18 @@ namespace Bit.Droid.Autofill
var parser = new Parser(structure, ApplicationContext);
parser.Parse();
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
var shouldAutofill = await parser.ShouldAutofillAsync(_stateService);
if (!shouldAutofill)
{
return;
}
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
var inlineAutofillEnabled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
if (_vaultTimeoutService == null)
{
@ -76,7 +75,7 @@ namespace Bit.Droid.Autofill
// build response
var response = AutofillHelpers.CreateFillResponse(parser, items, locked, inlineAutofillEnabled, request);
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync();
if (!disableSavePrompt.GetValueOrDefault())
{
AutofillHelpers.AddSaveInfo(parser, request, response, parser.FieldCollection);
@ -101,12 +100,12 @@ namespace Bit.Droid.Autofill
return;
}
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync();
if (disableSavePrompt.GetValueOrDefault())
{
return;

View File

@ -80,13 +80,13 @@ namespace Bit.Droid.Autofill
}
}
public async Task<bool> ShouldAutofillAsync(IStorageService storageService)
public async Task<bool> ShouldAutofillAsync(IStateService stateService)
{
var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
FieldCollection != null && FieldCollection.Fillable;
if (fillable)
{
var blacklistedUris = await storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
var blacklistedUris = await stateService.GetAutofillBlacklistedUrisAsync();
if (blacklistedUris != null && blacklistedUris.Count > 0)
{
fillable = !new HashSet<string>(blacklistedUris).Contains(Uri);

View File

@ -32,7 +32,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;
@ -53,7 +53,7 @@ namespace Bit.Droid
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
@ -70,7 +70,7 @@ namespace Bit.Droid
}
#if !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
var appCenterTask = appCenterHelper.InitAsync();
#endif
@ -375,7 +375,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()

View File

@ -113,13 +113,16 @@ 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 stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var deviceActionService = new DeviceActionService(stateService, messagingService,
broadcasterService, () => ServiceContainer.Resolve<IEventService>("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<IBroadcasterService>("broadcasterService", broadcasterService);
@ -129,7 +132,9 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(mobileStorageService));
ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
@ -148,7 +153,7 @@ namespace Bit.Droid
ServiceContainer.Register<IPushNotificationListenerService>(
"pushNotificationListenerService", notificationListenerService);
var androidPushNotificationService = new AndroidPushNotificationService(
mobileStorageService, notificationListenerService);
stateService, notificationListenerService);
ServiceContainer.Register<IPushNotificationService>(
"pushNotificationService", androidPushNotificationService);
#endif
@ -164,10 +169,6 @@ namespace Bit.Droid
private async Task BootstrapAsync()
{
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService")
.GetAsync<bool?>(Constants.DisableFaviconKey);
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
Constants.DisableFaviconKey, disableFavicon);
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
}
}

View File

@ -16,10 +16,10 @@ namespace Bit.Droid.Push
{
public async override void OnNewToken(string token)
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token);
await stateService.SetPushRegisteredTokenAsync(token);
await pushNotificationService.RegisterAsync();
}

View File

@ -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<IStorageService>("storageService");
await AppHelpers.PerformUpdateTasksAsync(ServiceContainer.Resolve<ISyncService>("syncService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"), storageService);
await AppHelpers.PerformUpdateTasksAsync(
ServiceContainer.Resolve<ISyncService>("syncService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"),
ServiceContainer.Resolve<IStateService>("stateService"));
}
}
}

View File

@ -1,7 +1,9 @@
using Android.Content;
using Android.Views;
using Bit.App.Pages;
using Bit.Droid.Renderers;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Navigation;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
@ -9,7 +11,7 @@ using Xamarin.Forms.Platform.Android.AppCompat;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomTabbedRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener
public class CustomTabbedRenderer : TabbedPageRenderer, NavigationBarView.IOnItemReselectedListener
{
private TabbedPage _page;
@ -21,7 +23,7 @@ namespace Bit.Droid.Renderers
if (e.NewElement != null)
{
_page = e.NewElement;
GetBottomNavigationView()?.SetOnNavigationItemReselectedListener(this);
GetBottomNavigationView()?.SetOnItemReselectedListener(this);
}
else
{
@ -53,6 +55,10 @@ namespace Bit.Droid.Renderers
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
if (_page is TabsPage tabsPage)
{
tabsPage.OnPageReselected();
}
Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync());
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="640dp"
android:height="512dp"
android:viewportWidth="640"
android:viewportHeight="512">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M532.989 289.887l-3.872-2.528c-3.197-1.866-5.744-4.667-7.299-8.026-1.558-3.358-2.048-7.113-1.405-10.759v-24.64c-0.682-3.57-0.202-7.266 1.37-10.542s4.154-5.964 7.366-7.666l4.768-2.4c12.013-7.054 20.768-18.547 24.384-32 3.421-13.333 1.661-27.466-4.928-39.552l-25.056-43.872c-7.011-11.727-18.259-20.313-31.418-23.987-13.161-3.674-27.229-2.155-39.303 4.243l-4.384 2.208c-3.286 1.769-6.983 2.63-10.711 2.496-3.731-0.135-7.357-1.261-10.505-3.263-7.082-4.796-14.579-8.951-22.4-12.416-3.28-1.636-6.038-4.157-7.962-7.278s-2.935-6.719-2.918-10.386v-6.528c0.099-6.965-1.197-13.879-3.808-20.335s-6.49-12.326-11.401-17.264c-4.915-4.937-10.765-8.842-17.209-11.486s-13.351-3.972-20.317-3.907h-51.2c-6.952-0.043-13.842 1.301-20.267 3.954s-12.257 6.561-17.154 11.496c-4.896 4.935-8.758 10.797-11.361 17.243s-3.892 13.347-3.794 20.298v5.472c0.032 3.614-0.938 7.165-2.802 10.261s-4.55 5.614-7.758 7.275c-5.691 2.572-11.197 5.533-16.48 8.864l-6.080 3.584c-3.102 2.221-6.788 3.481-10.6 3.623s-7.582-0.839-10.84-2.823l-3.968-1.952c-5.856-3.516-12.377-5.778-19.153-6.642s-13.656-0.314-20.208 1.618c-13.446 3.716-24.885 12.58-31.84 24.672l-24.96 43.68c-3.566 6.048-5.867 12.757-6.763 19.721s-0.37 14.037 1.547 20.791c1.743 6.495 4.779 12.571 8.925 17.866s9.317 9.699 15.203 12.95l2.88 2.848 1.312 0.928c3.197 1.867 5.744 4.667 7.3 8.026s2.046 7.113 1.403 10.758v24.704c0.326 3.533-0.314 7.087-1.853 10.283s-3.918 5.913-6.883 7.861l-4.768 2.4c-11.724 7.217-20.258 18.63-23.866 31.917s-2.020 27.447 4.442 39.603l25.088 43.872c6.806 11.955 18.058 20.739 31.308 24.445 13.25 3.702 27.425 2.026 39.445-4.669l4.352-2.176c3.287-1.792 6.994-2.669 10.736-2.547 3.742 0.125 7.382 1.248 10.544 3.251 7.082 4.797 14.578 8.954 22.4 12.416 3.281 1.635 6.038 4.157 7.962 7.28 1.923 3.12 2.934 6.717 2.918 10.384v5.472c-0.102 6.954 1.185 13.859 3.788 20.31s6.468 12.317 11.368 17.251c4.901 4.938 10.738 8.845 17.169 11.495s13.327 3.987 20.282 3.936h51.2c6.957 0.051 13.856-1.286 20.288-3.936s12.272-6.557 17.175-11.491c4.902-4.938 8.771-10.8 11.379-17.251 2.605-6.451 3.897-13.357 3.798-20.313v-5.472c-0.032-3.613 0.938-7.165 2.803-10.259 1.863-3.098 4.547-5.616 7.757-7.277 5.683-2.567 11.181-5.526 16.448-8.864l1.376-0.8 4.704-2.784c3.111-2.211 6.803-3.466 10.618-3.606 3.815-0.144 7.587 0.832 10.854 2.807l3.968 1.952c5.993 3.568 12.653 5.878 19.565 6.791 6.915 0.912 13.945 0.409 20.659-1.478 6.599-1.805 12.755-4.95 18.080-9.248 5.325-4.295 9.706-9.645 12.864-15.712l24.96-43.68c3.504-5.907 5.757-12.474 6.615-19.289 0.861-6.816 0.307-13.735-1.622-20.327-3.584-13.397-12.298-24.846-24.256-31.873zM319.997 346.752c-17.949 0-35.495-5.322-50.419-15.296-14.924-9.971-26.556-24.144-33.424-40.727s-8.666-34.83-5.165-52.434c3.502-17.604 12.145-33.775 24.837-46.466s28.862-21.335 46.466-24.837c17.604-3.502 35.852-1.704 52.434 5.164s30.755 18.501 40.73 33.425c9.971 14.924 15.293 32.47 15.293 50.419 0 24.069-9.562 47.153-26.579 64.17-17.021 17.021-40.103 26.582-64.173 26.582z" />
</vector>

View File

@ -6,4 +6,4 @@
<path
android:fillColor="#FF000000"
android:pathData="M532.989 289.887l-3.872-2.528c-3.197-1.866-5.744-4.667-7.299-8.026-1.558-3.358-2.048-7.113-1.405-10.759v-24.64c-0.682-3.57-0.202-7.266 1.37-10.542s4.154-5.964 7.366-7.666l4.768-2.4c12.013-7.054 20.768-18.547 24.384-32 3.421-13.333 1.661-27.466-4.928-39.552l-25.056-43.872c-7.011-11.727-18.259-20.313-31.418-23.987-13.161-3.674-27.229-2.155-39.303 4.243l-4.384 2.208c-3.286 1.769-6.983 2.63-10.711 2.496-3.731-0.135-7.357-1.261-10.505-3.263-7.082-4.796-14.579-8.951-22.4-12.416-3.28-1.636-6.038-4.157-7.962-7.278s-2.935-6.719-2.918-10.386v-6.528c0.099-6.965-1.197-13.879-3.808-20.335s-6.49-12.326-11.401-17.264c-4.915-4.937-10.765-8.842-17.209-11.486s-13.351-3.972-20.317-3.907h-51.2c-6.952-0.043-13.842 1.301-20.267 3.954s-12.257 6.561-17.154 11.496c-4.896 4.935-8.758 10.797-11.361 17.243s-3.892 13.347-3.794 20.298v5.472c0.032 3.614-0.938 7.165-2.802 10.261s-4.55 5.614-7.758 7.275c-5.691 2.572-11.197 5.533-16.48 8.864l-6.080 3.584c-3.102 2.221-6.788 3.481-10.6 3.623s-7.582-0.839-10.84-2.823l-3.968-1.952c-5.856-3.516-12.377-5.778-19.153-6.642s-13.656-0.314-20.208 1.618c-13.446 3.716-24.885 12.58-31.84 24.672l-24.96 43.68c-3.566 6.048-5.867 12.757-6.763 19.721s-0.37 14.037 1.547 20.791c1.743 6.495 4.779 12.571 8.925 17.866s9.317 9.699 15.203 12.95l2.88 2.848 1.312 0.928c3.197 1.867 5.744 4.667 7.3 8.026s2.046 7.113 1.403 10.758v24.704c0.326 3.533-0.314 7.087-1.853 10.283s-3.918 5.913-6.883 7.861l-4.768 2.4c-11.724 7.217-20.258 18.63-23.866 31.917s-2.020 27.447 4.442 39.603l25.088 43.872c6.806 11.955 18.058 20.739 31.308 24.445 13.25 3.702 27.425 2.026 39.445-4.669l4.352-2.176c3.287-1.792 6.994-2.669 10.736-2.547 3.742 0.125 7.382 1.248 10.544 3.251 7.082 4.797 14.578 8.954 22.4 12.416 3.281 1.635 6.038 4.157 7.962 7.28 1.923 3.12 2.934 6.717 2.918 10.384v5.472c-0.102 6.954 1.185 13.859 3.788 20.31s6.468 12.317 11.368 17.251c4.901 4.938 10.738 8.845 17.169 11.495s13.327 3.987 20.282 3.936h51.2c6.957 0.051 13.856-1.286 20.288-3.936s12.272-6.557 17.175-11.491c4.902-4.938 8.771-10.8 11.379-17.251 2.605-6.451 3.897-13.357 3.798-20.313v-5.472c-0.032-3.613 0.938-7.165 2.803-10.259 1.863-3.098 4.547-5.616 7.757-7.277 5.683-2.567 11.181-5.526 16.448-8.864l1.376-0.8 4.704-2.784c3.111-2.211 6.803-3.466 10.618-3.606 3.815-0.144 7.587 0.832 10.854 2.807l3.968 1.952c5.993 3.568 12.653 5.878 19.565 6.791 6.915 0.912 13.945 0.409 20.659-1.478 6.599-1.805 12.755-4.95 18.080-9.248 5.325-4.295 9.706-9.645 12.864-15.712l24.96-43.68c3.504-5.907 5.757-12.474 6.615-19.289 0.861-6.816 0.307-13.735-1.622-20.327-3.584-13.397-12.298-24.846-24.256-31.873zM319.997 346.752c-17.949 0-35.495-5.322-50.419-15.296-14.924-9.971-26.556-24.144-33.424-40.727s-8.666-34.83-5.165-52.434c3.502-17.604 12.145-33.775 24.837-46.466s28.862-21.335 46.466-24.837c17.604-3.502 35.852-1.704 52.434 5.164s30.755 18.501 40.73 33.425c9.971 14.924 15.293 32.47 15.293 50.419 0 24.069-9.562 47.153-26.579 64.17-17.021 17.021-40.103 26.582-64.173 26.582z" />
</vector>
</vector>

View File

@ -3,7 +3,6 @@ using System;
using System.Threading.Tasks;
using AndroidX.Core.App;
using Bit.App.Abstractions;
using Bit.Core;
using Bit.Core.Abstractions;
using Xamarin.Forms;
@ -11,14 +10,14 @@ 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;
}
@ -26,12 +25,12 @@ namespace Bit.Droid.Services
public async Task<string> GetTokenAsync()
{
return await _storageService.GetAsync<string>(Constants.PushCurrentTokenKey);
return await _stateService.GetPushCurrentTokenAsync();
}
public async Task RegisterAsync()
{
var registeredToken = await _storageService.GetAsync<string>(Constants.PushRegisteredTokenKey);
var registeredToken = await _stateService.GetPushRegisteredTokenAsync();
var currentToken = await GetTokenAsync();
if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken)
{
@ -39,7 +38,7 @@ namespace Bit.Droid.Services
}
else
{
await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow);
await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow);
}
}

View File

@ -12,12 +12,12 @@ namespace Bit.Droid.Services
{
public class ClipboardService : IClipboardService
{
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly Lazy<PendingIntent> _clearClipboardPendingIntent;
public ClipboardService(IStorageService storageService)
public ClipboardService(IStateService stateService)
{
_storageService = storageService;
_stateService = stateService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
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<int?>(Constants.ClearClipboardKey);
var clearSeconds = await _stateService.GetClearClipboardAsync();
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;

View File

@ -35,7 +35,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<IEventService> _eventServiceFunc;
@ -47,12 +47,12 @@ namespace Bit.Droid.Services
private string _userAgent;
public DeviceActionService(
IStorageService storageService,
IStateService stateService,
IMessagingService messagingService,
IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc)
{
_storageService = storageService;
_stateService = stateService;
_messagingService = messagingService;
_broadcasterService = broadcasterService;
_eventServiceFunc = eventServiceFunc;
@ -333,7 +333,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) { }
}
@ -916,9 +916,8 @@ namespace Bit.Droid.Services
{
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
{
var userService = ServiceContainer.Resolve<IUserService>("userService");
var autoCopyDisabled = await _storageService.GetAsync<bool?>(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<ITotpService>("totpService");

View File

@ -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<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
_storageService.SaveAsync(Constants.AutofillTileAdded, isAdded);
_stateService.SetAutofillTileAddedAsync(isAdded);
}
private void ScanAndFill()

View File

@ -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));

View File

@ -15,12 +15,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.2" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2291" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.1" />
</ItemGroup>
<ItemGroup>
@ -121,17 +122,20 @@
<DependentUpon>SendGroupingsPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Remove="Pages\Accounts\AccountsPopupPage.xaml.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Behaviors\" />
<Folder Include="Controls\AccountSwitchingOverlay\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
</ItemGroup>
<ItemGroup>
@ -416,5 +420,6 @@
<ItemGroup>
<None Remove="Behaviors\" />
<None Remove="Xamarin.CommunityToolkit" />
<None Remove="Controls\AccountSwitchingOverlay\" />
</ItemGroup>
</Project>

View File

@ -4,12 +4,12 @@ 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;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@ -18,7 +18,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;
@ -26,7 +25,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;
@ -40,7 +38,6 @@ namespace Bit.App
Current = this;
return;
}
_userService = ServiceContainer.Resolve<IUserService>("userService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
@ -48,7 +45,6 @@ namespace Bit.App
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@ -85,8 +81,12 @@ namespace Bit.App
}
else if (message.Command == "logout")
{
var extras = message.Data as Tuple<string, bool, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2;
var expired = extras?.Item3;
Device.BeginInvokeOnMainThread(async () =>
await LogOutAsync((message.Data as bool?).GetValueOrDefault()));
await LogOutAsync(userId, userInitiated, expired));
}
else if (message.Command == "loggedOut")
{
@ -107,6 +107,18 @@ namespace Bit.App
await SleptAsync();
}
}
else if (message.Command == "addAccount")
{
await AddAccount();
}
else if (message.Command == "accountAdded")
{
await UpdateThemeAsync();
}
else if (message.Command == "switchedAccount")
{
await SwitchedAccountAsync();
}
else if (message.Command == "migrated")
{
await Task.Delay(1000);
@ -168,7 +180,7 @@ namespace Bit.App
if (string.IsNullOrWhiteSpace(Options.Uri))
{
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_storageService);
_stateService);
if (!updated)
{
SyncIfNeeded();
@ -192,7 +204,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();
@ -233,7 +245,7 @@ namespace Bit.App
{
await Device.InvokeOnMainThreadAsync(() =>
{
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
ThemeManager.SetTheme(Current.Resources);
_messagingService.Send("updatedTheme");
});
}
@ -246,27 +258,70 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar();
}
private async Task LogOutAsync(bool expired)
private async Task LogOutAsync(string userId, bool? userInitiated, bool? expired)
{
await AppHelpers.LogOutAsync();
await AppHelpers.LogOutAsync(userId, userInitiated.GetValueOrDefault(true));
await SetMainPageAsync();
_authService.LogOut(() =>
{
Current.MainPage = new HomePage();
if (expired)
if (expired.GetValueOrDefault())
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
private async Task AddAccount()
{
Device.BeginInvokeOnMainThread(async () =>
{
Options.HideAccountSwitcher = false;
Current.MainPage = new NavigationPage(new HomePage(Options));
});
}
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync();
Device.BeginInvokeOnMainThread(async () =>
{
if (shouldTimeout)
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await SetMainPageAsync();
}
await Task.Delay(50);
await UpdateThemeAsync();
});
}
private async Task SetMainPageAsync()
{
var authed = await _userService.IsAuthenticatedAsync();
var authed = await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLockedAsync())
var isLocked = await _vaultTimeoutService.IsLockedAsync();
var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync();
if (isLocked || shouldTimeout)
{
Current.MainPage = new NavigationPage(new LockPage(Options));
var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (vaultTimeoutAction == VaultTimeoutAction.Logout)
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{
@ -287,13 +342,25 @@ namespace Bit.App
}
else
{
Current.MainPage = new HomePage(Options);
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else
{
Current.MainPage = new NavigationPage(new HomePage(Options));
}
}
}
private async Task ClearCacheIfNeededAsync()
{
var lastClear = await _storageService.GetAsync<DateTime?>(Constants.LastFileCacheClearKey);
var lastClear = await _stateService.GetLastFileCacheClearAsync();
if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
{
var task = Task.Run(() => _deviceActionService.ClearCacheAsync());
@ -336,12 +403,12 @@ namespace Bit.App
{
InitializeComponent();
SetCulture();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
ThemeManager.SetTheme(Current.Resources);
Current.RequestedThemeChanged += (s, a) =>
{
UpdateThemeAsync();
};
Current.MainPage = new HomePage();
Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
}
@ -365,10 +432,9 @@ namespace Bit.App
private async Task LockedAsync(bool autoPromptBiometric)
{
await _stateService.PurgeAsync();
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
@ -398,7 +464,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));
}

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:view="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
x:Name="_mainOverlay"
x:DataType="controls:AccountSwitchingOverlayViewModel"
x:Class="Bit.App.Controls.AccountSwitchingOverlayView"
BackgroundColor="#22000000"
Padding="0"
IsVisible="False">
<StackLayout
x:Name="_accountListContainer"
VerticalOptions="Fill"
HorizontalOptions="FillAndExpand"
BackgroundColor="Transparent">
<Frame
Padding="0"
HorizontalOptions="Fill"
VerticalOptions="Start"
xct:ShadowEffect.Color="Black"
xct:ShadowEffect.Radius="10"
xct:ShadowEffect.OffsetY="3">
<ListView
x:Name="_accountListView"
ItemsSource="{Binding BindingContext.AccountViews, Source={x:Reference _mainOverlay}}"
ItemSelected="AccountRow_Selected"
BackgroundColor="{DynamicResource BackgroundColor}"
VerticalOptions="Start"
RowHeight="{OnPlatform 70, iOS=70, Android=74}"
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
<ListView.ItemTemplate>
<DataTemplate x:DataType="view:AccountView">
<controls:AccountViewCell
Account="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Effects>
<effects:ScrollViewContentInsetAdjustmentBehaviorEffect />
</ListView.Effects>
</ListView>
</Frame>
<BoxView
BackgroundColor="Transparent"
HorizontalOptions="Fill"
VerticalOptions="FillAndExpand">
<BoxView.GestureRecognizers>
<TapGestureRecognizer Tapped="FreeSpaceOverlay_Tapped" />
</BoxView.GestureRecognizers>
</BoxView>
</StackLayout>
</ContentView>

View File

@ -0,0 +1,142 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class AccountSwitchingOverlayView : ContentView
{
public static readonly BindableProperty MainFabProperty = BindableProperty.Create(
nameof(MainFab),
typeof(View),
typeof(AccountSwitchingOverlayView),
defaultBindingMode: BindingMode.OneWay);
public View MainFab
{
get => (View)GetValue(MainFabProperty);
set => SetValue(MainFabProperty, value);
}
public AccountSwitchingOverlayView()
{
InitializeComponent();
ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync,
#if !FDROID
onException: ex => Crashes.TrackError(ex),
#endif
allowsMultipleExecutions: false);
}
public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel;
public ICommand ToggleVisibililtyCommand { get; }
public async Task ToggleVisibilityAsync()
{
if (IsVisible)
{
await HideAsync();
}
else
{
await ShowAsync();
}
}
public async Task ShowAsync()
{
await ViewModel?.RefreshAccountViewsAsync();
await Device.InvokeOnMainThreadAsync(async () =>
{
// start listView in default (off-screen) position
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0);
// set overlay opacity to zero before making visible and start fade-in
Opacity = 0;
IsVisible = true;
this.FadeTo(1, 100);
if (Device.RuntimePlatform == Device.Android && MainFab != null)
{
// start fab fade-out
MainFab.FadeTo(0, 200);
}
// slide account list into view
await _accountListContainer.TranslateTo(0, 0, 200, Easing.SinOut);
});
}
public async Task HideAsync()
{
if (!IsVisible)
{
// already hidden, don't animate again
return;
}
// Not all animations are awaited. This is intentional to allow multiple simultaneous animations.
await Device.InvokeOnMainThreadAsync(async () =>
{
// start overlay fade-out
this.FadeTo(0, 200);
if (Device.RuntimePlatform == Device.Android && MainFab != null)
{
// start fab fade-in
MainFab.FadeTo(1, 200);
}
// slide account list out of view
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 200, Easing.SinIn);
// remove overlay
IsVisible = false;
});
}
private async void FreeSpaceOverlay_Tapped(object sender, EventArgs e)
{
try
{
await HideAsync();
}
catch (Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
}
}
async void AccountRow_Selected(object sender, SelectedItemChangedEventArgs e)
{
try
{
if (!(e.SelectedItem is AccountViewCellViewModel item))
{
return;
}
((ListView)sender).SelectedItem = null;
await Task.Delay(100);
await HideAsync();
ViewModel?.SelectAccountCommand?.Execute(item);
}
catch (Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
}
}
}
}

View File

@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Microsoft.AppCenter.Crashes;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class AccountSwitchingOverlayViewModel : ExtendedViewModel
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
public AccountSwitchingOverlayViewModel(IStateService stateService,
IMessagingService messagingService)
{
_stateService = stateService;
_messagingService = messagingService;
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
#if !FDROID
onException: ex => Crashes.TrackError(ex),
#endif
allowsMultipleExecutions: false);
}
// this needs to be a new list every time for the binding to get updated,
// XF doesn't currentlyl provide a direct way to update on same instance
// https://github.com/xamarin/Xamarin.Forms/issues/1950
public List<AccountView> AccountViews => _stateService?.AccountViews is null ? null : new List<AccountView>(_stateService.AccountViews);
public bool AllowActiveAccountSelection { get; set; }
public bool AllowAddAccountRow { get; set; }
public ICommand SelectAccountCommand { get; }
private async Task SelectAccountAsync(AccountViewCellViewModel item)
{
if (item.AccountView.IsAccount)
{
if (!item.AccountView.IsActive)
{
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send("switchedAccount");
}
else if (AllowActiveAccountSelection)
{
_messagingService.Send("switchedAccount");
}
}
else
{
_messagingService.Send("addAccount");
}
}
public async Task RefreshAccountViewsAsync()
{
await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow);
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(AccountViews)));
}
}
}

View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="Bit.App.Controls.AccountViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="controls:AccountViewCellViewModel">
<Grid RowSpacing="0"
ColumnSpacing="0">
<Grid.Resources>
<u:InverseBoolConverter x:Key="inverseBool" />
</Grid.Resources>
<Grid
IsVisible="{Binding IsAccount}"
VerticalOptions="CenterAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Source="{Binding AvatarImageSource}"
HorizontalOptions="Center"
Margin="10,0"
VerticalOptions="Center" />
<Grid
Grid.Column="1"
RowSpacing="1"
VerticalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Text="{Binding AccountView.Email}"
IsVisible="{Binding IsActive}"
StyleClass="list-title"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="0"
Text="{Binding AccountView.Email}"
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
StyleClass="list-title"
TextColor="{DynamicResource MutedColor}"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="1"
IsVisible="{Binding ShowHostname}"
Text="{Binding AccountView.Hostname}"
StyleClass="list-sub"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="2"
Text="{u:I18n AccountUnlocked}"
IsVisible="{Binding IsUnlocked}"
StyleClass="list-sub"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="2"
Text="{u:I18n AccountLocked}"
IsVisible="{Binding IsLocked}"
StyleClass="list-sub"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="2"
Text="{u:I18n AccountLoggedOut}"
IsVisible="{Binding IsLoggedOut}"
StyleClass="list-sub"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
</Grid>
<controls:IconLabel
Grid.Column="2"
Text="{Binding AuthStatusIconNotActive}"
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
Margin="12,0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform" />
<controls:IconLabel
Grid.Column="2"
Text="{Binding AuthStatusIconActive}"
IsVisible="{Binding IsActive}"
Margin="12,0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
TextColor="{DynamicResource TextColor}"/>
</Grid>
<Grid
IsVisible="{Binding IsAccount, Converter={StaticResource inverseBool}}"
VerticalOptions="CenterAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
VerticalOptions="Center"
HorizontalOptions="Center"
Margin="14,0"
WidthRequest="{OnPlatform 24, iOS=24, Android=26}"
HeightRequest="{OnPlatform 24, iOS=24, Android=26}"
Source="plus.png"
xct:IconTintColorEffect.TintColor="{DynamicResource TextColor}"
AutomationProperties.IsInAccessibleTree="False" />
<Label
Text="{u:I18n AddAccount}"
StyleClass="list-title"
LineBreakMode="TailTruncation"
VerticalOptions="Center"
Grid.Column="1" />
</Grid>
</Grid>
</ViewCell>

View File

@ -0,0 +1,35 @@
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);
}
}
}
}

View File

@ -0,0 +1,78 @@
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Controls
{
public class AccountViewCellViewModel : ExtendedViewModel
{
private AccountView _accountView;
private AvatarImageSource _avatar;
public AccountViewCellViewModel(AccountView accountView)
{
AccountView = accountView;
AvatarImageSource = new AvatarImageSource(AccountView.Name, AccountView.Email);
}
public AccountView AccountView
{
get => _accountView;
set => SetProperty(ref _accountView, value);
}
public AvatarImageSource AvatarImageSource
{
get => _avatar;
set => SetProperty(ref _avatar, value);
}
public bool IsAccount
{
get => AccountView.IsAccount;
}
public bool ShowHostname
{
get => !string.IsNullOrWhiteSpace(AccountView.Hostname) && AccountView.Hostname != "vault.bitwarden.com";
}
public bool IsActive
{
get => AccountView.IsActive;
}
public bool IsUnlocked
{
get => AccountView.AuthStatus == AuthenticationStatus.Unlocked;
}
public bool IsLocked
{
get => AccountView.AuthStatus == AuthenticationStatus.Locked;
}
public bool IsLoggedOut
{
get => AccountView.AuthStatus == AuthenticationStatus.LoggedOut;
}
public string AuthStatusIconActive
{
get => BitwardenIcons.CheckCircle;
}
public string AuthStatusIconNotActive
{
get
{
if (IsUnlocked)
{
return BitwardenIcons.Unlock;
}
return BitwardenIcons.Lock;
}
}
}
}

View File

@ -0,0 +1,149 @@
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 override bool Equals(object obj)
{
if (obj is null)
{
return false;
}
if (obj is AvatarImageSource avatar)
{
return avatar._data == _data;
}
return base.Equals(obj);
}
public override int GetHashCode() => _data?.GetHashCode() ?? -1;
public AvatarImageSource(string name = null, string email = null)
{
_data = name;
if (string.IsNullOrWhiteSpace(_data))
{
_data = email;
}
}
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
private Task<Stream> 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 > 1)
{
upperData = _data.ToUpper();
chars = GetFirstLetters(upperData, 2);
}
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 string GetFirstLetters(string data, int charCount)
{
var parts = data.Split();
if (parts.Length > 1 && charCount <= 2)
{
var text = "";
for (int i = 0; i < charCount; i++)
{
text += parts[i].Substring(0,1);
}
return text;
}
if (data.Length > 2)
{
return data.Substring(0, 2);
}
return null;
}
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;
var base16 = "00" + Convert.ToString(value, 16);
color += base16.Substring(base16.Length - 2);
}
return Color.FromHex(color);
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedToolbarItem : ToolbarItem
{
public bool UseOriginalImage { get; set; }
// HACK: For the issue of correctly updating the avatar toolbar item color on iOS
// we need to subscribe to the PropertyChanged event of the ToolbarItem on the CustomNavigationRenderer
// The problem is that there are a lot of private places where the navigation renderer disposes objects
// that we don't have access to, and that we should in order to properly prevent memory leaks
// So as a hack solution we have this OnAppearing/OnDisappearing actions and methods to be called on page lifecycle
// to subscribe/unsubscribe indirectly on the CustomNavigationRenderer
public Action OnAppearingAction { get; set; }
public Action OnDisappearingAction { get; set; }
public void OnAppearing()
{
OnAppearingAction?.Invoke();
}
public void OnDisappearing()
{
OnDisappearingAction?.Invoke();
}
}
}

View File

@ -0,0 +1,33 @@
using Xamarin.Forms;
namespace Bit.App.Effects
{
public enum ScrollContentInsetAdjustmentBehavior
{
Automatic,
ScrollableAxes,
Never,
Always
}
public class ScrollViewContentInsetAdjustmentBehaviorEffect : RoutingEffect
{
public static readonly BindableProperty ContentInsetAdjustmentBehaviorProperty =
BindableProperty.CreateAttached("ContentInsetAdjustmentBehavior", typeof(ScrollContentInsetAdjustmentBehavior), typeof(ScrollViewContentInsetAdjustmentBehaviorEffect), ScrollContentInsetAdjustmentBehavior.Automatic);
public static ScrollContentInsetAdjustmentBehavior GetContentInsetAdjustmentBehavior(BindableObject view)
{
return (ScrollContentInsetAdjustmentBehavior)view.GetValue(ContentInsetAdjustmentBehaviorProperty);
}
public static void SetContentInsetAdjustmentBehavior(BindableObject view, ScrollContentInsetAdjustmentBehavior value)
{
view.SetValue(ContentInsetAdjustmentBehaviorProperty, value);
}
public ScrollViewContentInsetAdjustmentBehaviorEffect()
: base($"Bitwarden.{nameof(ScrollViewContentInsetAdjustmentBehaviorEffect)}")
{
}
}
}

View File

@ -22,6 +22,7 @@ namespace Bit.App.Models
public bool IosExtension { get; set; }
public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
public bool CopyInsteadOfShareAfterSaving { get; set; }
public bool HideAccountSwitcher { get; set; }
public void SetAllFrom(AppOptions o)
{
@ -46,6 +47,7 @@ namespace Bit.App.Models
IosExtension = o.IosExtension;
CreateSend = o.CreateSend;
CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving;
HideAccountSwitcher = o.HideAccountSwitcher;
}
}
}

View File

@ -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<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
@ -172,7 +172,7 @@ namespace Bit.App.Pages
private async Task<List<string>> GetPasswordStrengthUserInput()
{
var email = await _userService.GetEmailAsync();
var email = await _stateService.GetEmailAsync();
List<string> userInput = null;
var atPosition = email.IndexOf('@');
if (atPosition > -1)

View File

@ -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<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("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();
};
}

View File

@ -6,49 +6,74 @@
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:HomeViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:HomeViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem
Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</ContentPage.ToolbarItems>
<StackLayout Spacing="0" Padding="10, 5">
<controls:IconButton Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
StyleClass="btn-muted, btn-icon, btn-icon-platform"
HorizontalOptions="Start"
Clicked="Environment_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}">
<controls:IconButton.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 10, 0, 0" />
<On Platform="Android" Value="0" />
</OnPlatform>
</controls:IconButton.Margin>
</controls:IconButton>
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
<Image
x:Name="_logo"
Source="logo.png"
VerticalOptions="Center" />
<Label Text="{u:I18n LoginOrCreateNewAccount}"
StyleClass="text-lg"
HorizontalTextAlignment="Center"></Label>
<StackLayout Spacing="5">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked" />
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked" />
<ContentPage.Resources>
<ResourceDictionary>
<StackLayout x:Name="_mainLayout" x:Key="mainLayout" Spacing="0" Padding="10, 5">
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
<Image
x:Name="_logo"
Source="logo.png"
VerticalOptions="Center" />
<Label Text="{u:I18n LoginOrCreateNewAccount}"
StyleClass="text-lg"
HorizontalTextAlignment="Center">
</Label>
<StackLayout Spacing="5">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked" />
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked" />
<Button Text="{u:I18n Cancel}"
IsVisible="{Binding ShowCancelButton}"
Margin="0,10,0,0"
Clicked="Cancel_Clicked" />
</StackLayout>
</StackLayout>
</StackLayout>
</StackLayout>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@ -1,9 +1,9 @@
using Bit.App.Models;
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages
@ -12,13 +12,10 @@ namespace Bit.App.Pages
{
private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", false);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent();
@ -29,6 +26,16 @@ namespace Bit.App.Pages
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
UpdateLogo();
if (_appOptions?.IosExtension ?? false)
{
_vm.ShowCancelButton = true;
}
if (_appOptions?.HideAccountSwitcher ?? false)
{
ToolbarItems.Remove(_accountAvatar);
}
}
public async Task DismissRegisterPageAndLogInAsync(string email)
@ -37,10 +44,16 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(new LoginPage(email, _appOptions)));
}
protected override void OnAppearing()
protected override async void OnAppearing()
{
base.OnAppearing();
_messagingService.Send("showStatusBar", false);
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
if (!_appOptions?.HideAccountSwitcher ?? false)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
{
if (message.Command == "updatedTheme")
@ -53,10 +66,21 @@ namespace Bit.App.Pages
});
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(HomePage));
_accountAvatar?.OnDisappearing();
}
private void UpdateLogo()
@ -64,7 +88,7 @@ namespace Bit.App.Pages
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
}
private void Close_Clicked(object sender, EventArgs e)
private void Cancel_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
@ -124,6 +148,7 @@ namespace Bit.App.Pages
private async Task StartEnvironmentAsync()
{
await _accountListOverlay.HideAsync();
var page = new EnvironmentPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@ -1,15 +1,38 @@
using System;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{
public class HomeViewModel : BaseViewModel
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private bool _showCancelButton;
public HomeViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
PageTitle = AppResources.Bitwarden;
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService)
{
AllowActiveAccountSelection = true
};
}
public bool ShowCancelButton
{
get => _showCancelButton;
set => SetProperty(ref _showCancelButton, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Action StartLoginAction { get; set; }
public Action StartRegisterAction { get; set; }
public Action StartSsoLoginAction { get; set; }

View File

@ -13,6 +13,19 @@
<pages:LockPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
@ -25,119 +38,135 @@
x:Name="_logOut"
Clicked="LogOut_Clicked"
Order="Secondary"/>
<ScrollView x:Name="_mainLayout" x:Key="mainLayout">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Grid
StyleClass="box-row"
IsVisible="{Binding PinLock}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n PIN}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_pin"
Text="{Binding Pin}"
StyleClass="box-value"
Keyboard="Numeric"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Grid
x:Name="_passwordGrid"
StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout
StyleClass="box-row"
Padding="0, 10, 0, 0">
<Label
Text="{Binding LockedVerifyText}"
StyleClass="box-footer-label" />
<Label
Text="{Binding LoggedInAsText}"
StyleClass="box-footer-label"
Margin="0, 10, 0, 0" />
</StackLayout>
</StackLayout>
<StackLayout Padding="10, 0">
<Label
Text="{u:I18n BiometricInvalidated}"
StyleClass="box-footer-label,text-danger,text-bold"
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
IsVisible="{Binding BiometricButtonVisible}">
</Button>
<Button
x:Name="_unlockButton"
Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
</ContentPage.ToolbarItems>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Grid
StyleClass="box-row"
IsVisible="{Binding PinLock}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n PIN}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_pin"
Text="{Binding Pin}"
StyleClass="box-value"
Keyboard="Numeric"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Grid
x:Name="_passwordGrid"
StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout
StyleClass="box-row"
Padding="0, 10, 0, 0">
<Label
Text="{Binding LockedVerifyText}"
StyleClass="box-footer-label" />
<Label
Text="{Binding LoggedInAsText}"
StyleClass="box-footer-label"
Margin="0, 10, 0, 0" />
</StackLayout>
</StackLayout>
<StackLayout Padding="10, 0">
<Label
Text="{u:I18n BiometricInvalidated}"
StyleClass="box-footer-label,text-danger,text-bold"
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
IsVisible="{Binding BiometricButtonVisible}"></Button>
<Button
x:Name="_unlockButton"
Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@ -1,10 +1,9 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@ -62,7 +61,14 @@ namespace Bit.App.Pages
{
return;
}
_appeared = true;
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
await _vm.InitAsync();
if (!_vm.BiometricLock)
{
@ -93,6 +99,23 @@ namespace Bit.App.Pages
}
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_accountAvatar?.OnDisappearing();
}
private void Unlock_Clicked(object sender, EventArgs e)
{
if (DoOnce())
@ -107,6 +130,7 @@ namespace Bit.App.Pages
private async void LogOut_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
if (DoOnce())
{
await _vm.LogOutAsync();
@ -123,6 +147,8 @@ namespace Bit.App.Pages
private async void More_Clicked(object sender, System.EventArgs e)
{
await _accountListOverlay.HideAsync();
if (!DoOnce())
{
return;

View File

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
@ -23,10 +24,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICryptoService _cryptoService;
private readonly IStorageService _storageService;
private readonly IUserService _userService;
private readonly IMessagingService _messagingService;
private readonly IStorageService _secureStorageService;
private readonly IEnvironmentService _environmentService;
private readonly IStateService _stateService;
private readonly IBiometricService _biometricService;
@ -42,7 +40,8 @@ namespace Bit.App.Pages
private string _biometricButtonText;
private string _loggedInAsText;
private string _lockedVerifyText;
private Tuple<bool, bool> _pinSet;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
public LockPageViewModel()
{
@ -51,10 +50,7 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
@ -63,6 +59,12 @@ namespace Bit.App.Pages
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
SubmitCommand = new Command(async () => await SubmitAsync());
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService)
{
AllowAddAccountRow = true,
AllowActiveAccountSelection = true
};
}
public bool ShowPassword
@ -122,6 +124,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _lockedVerifyText, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
@ -131,8 +135,9 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
_pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey;
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
// Users with key connector and without biometric or pin has no MP to unlock with
@ -142,7 +147,7 @@ namespace Bit.App.Pages
await _vaultTimeoutService.LogOutAsync();
return;
}
_email = await _userService.GetEmailAsync();
_email = await _stateService.GetEmailAsync();
if (string.IsNullOrWhiteSpace(_email))
{
await _vaultTimeoutService.LogOutAsync();
@ -215,21 +220,21 @@ namespace Bit.App.Pages
}
ShowPassword = false;
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
if (PinLock)
{
var failed = true;
try
{
if (_pinSet.Item1)
if (_isPinProtected)
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
_vaultTimeoutService.PinProtectedKey);
await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin;
if (!failed)
@ -296,14 +301,14 @@ namespace Bit.App.Pages
}
if (passwordValid)
{
if (_pinSet.Item1)
if (_isPinProtected)
{
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key);
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(key.Key, pinKey);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
}
MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
@ -369,7 +374,7 @@ namespace Bit.App.Pages
page.MasterPasswordEntry.Focus();
}
});
_vaultTimeoutService.BiometricLocked = !success;
_stateService.BiometricLocked = !success;
if (success)
{
await DoContinueAsync();
@ -388,9 +393,7 @@ namespace Bit.App.Pages
private async Task DoContinueAsync()
{
_vaultTimeoutService.BiometricLocked = false;
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
_stateService.BiometricLocked = false;
_messagingService.Send("unlocked");
UnlockedAction?.Invoke();
}

View File

@ -13,6 +13,19 @@
<pages:LoginPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
@ -24,69 +37,100 @@
x:Key="getPasswordHint"
x:Name="_getPasswordHint"
Clicked="Hint_Clicked"
Order="Secondary"/>
Order="Secondary" />
<ToolbarItem Text="{u:I18n RemoveAccount}"
x:Key="removeAccount"
x:Name="_removeAccount"
Clicked="RemoveAccount_Clicked"
Order="Secondary" />
<ScrollView x:Name="_mainLayout" x:Key="mainLayout">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n EmailAddress}"
StyleClass="box-label" />
<Entry
x:Name="_email"
Text="{Binding Email}"
Keyboard="Email"
StyleClass="box-value">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{DynamicResource MutedColor}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding LogInCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n Cancel}"
IsVisible="{Binding ShowCancelButton}"
Clicked="Cancel_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n EmailAddress}"
StyleClass="box-label" />
<Entry
x:Name="_email"
Text="{Binding Email}"
Keyboard="Email"
StyleClass="box-value" />
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding LogInCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@ -1,18 +1,15 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class LoginPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly LoginPageViewModel _vm;
private readonly AppOptions _appOptions;
@ -20,9 +17,6 @@ namespace Bit.App.Pages
public LoginPage(string email = null, AppOptions appOptions = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as LoginPageViewModel;
@ -33,15 +27,19 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await _accountListOverlay.HideAsync();
await Navigation.PopModalAsync();
};
if (!string.IsNullOrWhiteSpace(email))
{
_email.IsEnabled = false;
}
else
{
_vm.ShowCancelButton = true;
}
_vm.Email = email;
MasterPasswordEntry = _masterPassword;
if (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
}
_email.ReturnType = ReturnType.Next;
_email.ReturnCommand = new Command(() => _masterPassword.Focus());
@ -54,6 +52,21 @@ namespace Bit.App.Pages
{
ToolbarItems.Add(_getPasswordHint);
}
if (Device.RuntimePlatform == Device.Android && !_email.IsEnabled)
{
ToolbarItems.Add(_removeAccount);
}
if (_appOptions?.IosExtension ?? false)
{
_vm.ShowCancelButton = true;
}
if (_appOptions?.HideAccountSwitcher ?? false)
{
ToolbarItems.Remove(_accountAvatar);
}
}
public Entry MasterPasswordEntry { get; set; }
@ -61,6 +74,13 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
if (!_appOptions?.HideAccountSwitcher ?? false)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
await _vm.InitAsync();
if (!_inputFocused)
{
@ -69,11 +89,28 @@ namespace Bit.App.Pages
}
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_accountAvatar?.OnDisappearing();
}
private async void LogIn_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.LogInAsync();
await _vm.LogInAsync(true, _email.IsEnabled);
}
}
@ -85,7 +122,16 @@ namespace Bit.App.Pages
}
}
private void Close_Clicked(object sender, EventArgs e)
private async void RemoveAccount_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
if (DoOnce())
{
await _vm.RemoveAccountAsync();
}
}
private void Cancel_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
@ -95,18 +141,23 @@ namespace Bit.App.Pages
private async void More_Clicked(object sender, System.EventArgs e)
{
await _accountListOverlay.HideAsync();
if (!DoOnce())
{
return;
}
var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, AppResources.GetPasswordHint);
AppResources.Cancel, null, AppResources.GetPasswordHint, AppResources.RemoveAccount);
if (selection == AppResources.GetPasswordHint)
{
await Navigation.PushModalAsync(new NavigationPage(new HintPage()));
}
else if (selection == AppResources.RemoveAccount)
{
await _vm.RemoveAccountAsync();
}
}
private async Task StartTwoFactorAsync()

View File

@ -1,31 +1,33 @@
using Bit.App.Abstractions;
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LoginPageViewModel : CaptchaProtectedViewModel
{
private const string Keys_RememberedEmail = "rememberedEmail";
private const string Keys_RememberEmail = "rememberEmail";
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private readonly IEnvironmentService _environmentService;
private readonly II18nService _i18nService;
private readonly IMessagingService _messagingService;
private bool _showPassword;
private bool _showCancelButton;
private string _email;
private string _masterPassword;
@ -34,15 +36,21 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword);
LogInCommand = new Command(async () => await LogInAsync());
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService)
{
AllowAddAccountRow = true,
AllowActiveAccountSelection = true
};
}
public bool ShowPassword
@ -55,6 +63,12 @@ namespace Bit.App.Pages
});
}
public bool ShowCancelButton
{
get => _showCancelButton;
set => SetProperty(ref _showCancelButton, value);
}
public string Email
{
get => _email;
@ -67,10 +81,11 @@ namespace Bit.App.Pages
set => SetProperty(ref _masterPassword, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public bool RememberEmail { get; set; }
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
@ -85,13 +100,11 @@ namespace Bit.App.Pages
{
if (string.IsNullOrWhiteSpace(Email))
{
Email = await _storageService.GetAsync<string>(Keys_RememberedEmail);
Email = await _stateService.GetRememberedEmailAsync();
}
var rememberEmail = await _storageService.GetAsync<bool?>(Keys_RememberEmail);
RememberEmail = rememberEmail.GetValueOrDefault(true);
}
public async Task LogInAsync(bool showLoading = true)
public async Task LogInAsync(bool showLoading = true, bool checkForExistingAccount = false)
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
@ -123,20 +136,30 @@ namespace Bit.App.Pages
ShowPassword = false;
try
{
if (checkForExistingAccount)
{
var userId = await _stateService.GetUserIdAsync(Email);
if (!string.IsNullOrWhiteSpace(userId))
{
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
AppResources.SwitchToAlreadyAddedAccountConfirmation,
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
if (switchToAccount)
{
await _stateService.SetActiveUserAsync(userId);
_messagingService.Send("switchedAccount");
}
return;
}
}
if (showLoading)
{
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
}
var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken);
if (RememberEmail)
{
await _storageService.SaveAsync(Keys_RememberedEmail, Email);
}
else
{
await _storageService.RemoveAsync(Keys_RememberedEmail);
}
await _stateService.SetRememberedEmailAsync(Email);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (response.CaptchaNeeded)
@ -163,8 +186,6 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
LogInSuccessAction?.Invoke();
}
@ -189,5 +210,24 @@ namespace Bit.App.Pages
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
}
public async Task RemoveAccountAsync()
{
try
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.RemoveAccountConfirmation,
AppResources.RemoveAccount, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
_messagingService.Send("logout");
}
}
catch (Exception e)
{
#if !FDROID
Crashes.TrackError(e);
#endif
}
}
}
}

View File

@ -10,8 +10,6 @@ namespace Bit.App.Pages
{
public partial class LoginSsoPage : BaseContentPage
{
private readonly IStorageService _storageService;
private readonly IMessagingService _messagingService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly LoginSsoPageViewModel _vm;
private readonly AppOptions _appOptions;
@ -20,10 +18,7 @@ namespace Bit.App.Pages
public LoginSsoPage(AppOptions appOptions = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as LoginSsoPageViewModel;
@ -36,7 +31,6 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
if (Device.RuntimePlatform == Device.Android)

View File

@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
@ -16,16 +15,12 @@ namespace Bit.App.Pages
{
public class LoginSsoPageViewModel : BaseViewModel
{
private const string Keys_RememberedOrgIdentifier = "rememberedOrgIdentifier";
private const string Keys_RememberOrgIdentifier = "rememberOrgIdentifier";
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IApiService _apiService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
@ -40,7 +35,6 @@ namespace Bit.App.Pages
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>("cryptoFunctionService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
@ -55,7 +49,6 @@ namespace Bit.App.Pages
}
public Command LogInCommand { get; }
public bool RememberOrgIdentifier { get; set; }
public Action StartTwoFactorAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; }
@ -66,10 +59,8 @@ namespace Bit.App.Pages
{
if (string.IsNullOrWhiteSpace(OrgIdentifier))
{
OrgIdentifier = await _storageService.GetAsync<string>(Keys_RememberedOrgIdentifier);
OrgIdentifier = await _stateService.GetRememberedOrgIdentifierAsync();
}
var rememberOrgIdentifier = await _storageService.GetAsync<bool?>(Keys_RememberOrgIdentifier);
RememberOrgIdentifier = rememberOrgIdentifier.GetValueOrDefault(true);
}
public async Task LogInAsync()
@ -170,14 +161,7 @@ namespace Bit.App.Pages
{
var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri, orgId);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (RememberOrgIdentifier)
{
await _storageService.SaveAsync(Keys_RememberedOrgIdentifier, OrgIdentifier);
}
else
{
await _storageService.RemoveAsync(Keys_RememberedOrgIdentifier);
}
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
await _deviceActionService.HideLoadingAsync();
if (response.TwoFactor)
{
@ -193,8 +177,6 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
SsoAuthSuccessAction?.Invoke();
}

View File

@ -1,6 +1,4 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
@ -8,22 +6,18 @@ namespace Bit.App.Pages
{
public partial class RegisterPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly RegisterPageViewModel _vm;
private bool _inputFocused;
public RegisterPage(HomePage homePage)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
InitializeComponent();
_vm = BindingContext as RegisterPageViewModel;
_vm.Page = this;
_vm.RegistrationSuccess = () => Device.BeginInvokeOnMainThread(async () => await RegistrationSuccessAsync(homePage));
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
MasterPasswordEntry = _masterPassword;

View File

@ -1,6 +1,4 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
@ -10,14 +8,11 @@ namespace Bit.App.Pages
{
public partial class SetPasswordPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly SetPasswordPageViewModel _vm;
private readonly AppOptions _appOptions;
public SetPasswordPage(AppOptions appOptions = null, string orgIdentifier = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as SetPasswordPageViewModel;
@ -26,7 +21,6 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await SetPasswordSuccessAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
_vm.OrgIdentifier = orgIdentifier;

View File

@ -7,7 +7,6 @@ using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -24,7 +23,7 @@ namespace Bit.App.Pages
private readonly IApiService _apiService;
private readonly ICryptoService _cryptoService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IPolicyService _policyService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly II18nService _i18nService;
@ -41,7 +40,7 @@ namespace Bit.App.Pages
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
@ -160,7 +159,7 @@ namespace Bit.App.Pages
var kdf = KdfType.PBKDF2_SHA256;
var kdfIterations = 100000;
var email = await _userService.GetEmailAsync();
var email = await _stateService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
@ -197,8 +196,8 @@ namespace Bit.App.Pages
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
// Set Password and relevant information
await _apiService.SetPasswordAsync(request);
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
await _userService.GetEmailAsync(), kdf, kdfIterations);
await _stateService.SetKdfTypeAsync(kdf);
await _stateService.SetKdfIterationsAsync(kdfIterations);
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
@ -217,7 +216,7 @@ namespace Bit.App.Pages
{
ResetPasswordKey = encryptedKey.EncryptedString
};
var userId = await _userService.GetUserIdAsync();
var userId = await _stateService.GetActiveUserIdAsync();
// Enroll user
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest);
}
@ -290,7 +289,7 @@ namespace Bit.App.Pages
private async Task<List<string>> GetPasswordStrengthUserInput()
{
var email = await _userService.GetEmailAsync();
var email = await _stateService.GetEmailAsync();
List<string> userInput = null;
var atPosition = email.IndexOf('@');
if (atPosition > -1)

View File

@ -14,8 +14,6 @@ namespace Bit.App.Pages
{
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly AppOptions _appOptions;
private TwoFactorPageViewModel _vm;
@ -30,10 +28,8 @@ namespace Bit.App.Pages
_authingWithSso = authingWithSso ?? false;
_appOptions = appOptions;
_orgIdentifier = orgIdentifier;
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_vm = BindingContext as TwoFactorPageViewModel;
_vm.Page = this;
_vm.StartSetPasswordAction = () =>

View File

@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -23,7 +22,6 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IStorageService _storageService;
private readonly IApiService _apiService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IEnvironmentService _environmentService;
@ -43,7 +41,6 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
@ -303,8 +300,6 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
TwoFactorAuthSuccessAction?.Invoke();
}
}

View File

@ -19,9 +19,6 @@ namespace Bit.App.Pages
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
// Service Use
_messagingService.Send("showStatusBar", true);
// Binding
InitializeComponent();
_pageName = string.Concat(nameof(UpdateTempPasswordPage), "_", DateTime.UtcNow.Ticks);

View File

@ -43,9 +43,9 @@ namespace Bit.App.Pages
}
// Retrieve details for key generation
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _stateService.GetEmailAsync();
// Create new key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);

View File

@ -1,10 +1,10 @@
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@ -13,7 +13,7 @@ namespace Bit.App.Pages
{
public class BaseContentPage : ContentPage
{
private IStorageService _storageService;
private IStateService _stateService;
private IDeviceActionService _deviceActionService;
protected int ShowModalAnimationDelay = 400;
@ -32,16 +32,16 @@ namespace Bit.App.Pages
public bool IsThemeDirty { get; set; }
protected override void OnAppearing()
protected async override void OnAppearing()
{
base.OnAppearing();
if (IsThemeDirty)
{
UpdateOnThemeChanged();
}
SaveActivity();
await SaveActivityAsync();
}
public bool DoOnce(Action action = null, int milliseconds = 1000)
@ -56,6 +56,12 @@ namespace Bit.App.Pages
return true;
}
public virtual Task UpdateOnThemeChanged()
{
IsThemeDirty = false;
return Task.CompletedTask;
}
protected void SetActivityIndicator(ContentView targetView = null)
{
var indicator = new ActivityIndicator
@ -114,11 +120,25 @@ namespace Bit.App.Pages
});
}
protected async Task<bool> ShowAccountSwitcherAsync()
{
return await _stateService.GetActiveUserIdAsync() != null;
}
protected async Task<AvatarImageSource> GetAvatarImageSourceAsync(bool useCurrentActiveAccount = true)
{
if (useCurrentActiveAccount)
{
return new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
}
return new AvatarImageSource();
}
private void SetServices()
{
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
if (_deviceActionService == null)
{
@ -126,16 +146,10 @@ namespace Bit.App.Pages
}
}
private void SaveActivity()
private async Task SaveActivityAsync()
{
SetServices();
_storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime());
}
public virtual Task UpdateOnThemeChanged()
{
IsThemeDirty = false;
return Task.CompletedTask;
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
}
}

View File

@ -1,4 +1,5 @@
using Bit.Core.Utilities;
using Bit.App.Controls;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@ -6,6 +7,7 @@ namespace Bit.App.Pages
public abstract class BaseViewModel : ExtendedViewModel
{
private string _pageTitle = string.Empty;
private AvatarImageSource _avatar;
public string PageTitle
{
@ -13,6 +15,12 @@ namespace Bit.App.Pages
set => SetProperty(ref _pageTitle, value);
}
public AvatarImageSource AvatarImageSource
{
get => _avatar ?? new AvatarImageSource();
set => SetProperty(ref _avatar, value);
}
public ContentPage Page { get; set; }
}
}

View File

@ -23,7 +23,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly ISendService _sendService;
private bool _sendEnabled;
private bool _canAccessPremium;
@ -56,7 +56,7 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_sendService = ServiceContainer.Resolve<ISendService>("sendService");
TogglePasswordCommand = new Command(TogglePassword);
@ -235,8 +235,8 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
_canAccessPremium = await _userService.CanAccessPremiumAsync();
_emailVerified = await _userService.GetEmailVerifiedAsync();
_canAccessPremium = await _stateService.CanAccessPremiumAsync();
_emailVerified = await _stateService.GetEmailVerifiedAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync();
SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail;

View File

@ -32,21 +32,19 @@ namespace Bit.App.Pages
private readonly ISendService _sendService;
private readonly ISyncService _syncService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStorageService _storageService;
public SendGroupingsPageViewModel()
{
_sendService = ServiceContainer.Resolve<ISendService>("sendService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
Loading = true;
PageTitle = AppResources.Send;
@ -116,7 +114,7 @@ namespace Bit.App.Pages
{
return;
}
var authed = await _userService.IsAuthenticatedAsync();
var authed = await _stateService.IsAuthenticatedAsync();
if (!authed)
{
return;
@ -125,7 +123,7 @@ namespace Bit.App.Pages
{
return;
}
if (await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey) && Refreshing && !SyncRefreshing)
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
{
SyncRefreshing = true;
await _syncService.FullSyncAsync(false);

View File

@ -2,7 +2,6 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Services;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
@ -11,7 +10,7 @@ namespace Bit.App.Pages
public class AutofillServicesPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly MobileI18nService _i18nService;
private bool _autofillServiceToggled;
@ -23,7 +22,7 @@ namespace Bit.App.Pages
public AutofillServicesPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
PageTitle = AppResources.AutofillServices;
}
@ -152,7 +151,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
InlineAutofillToggled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
InlineAutofillToggled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
_inited = true;
}
@ -202,7 +201,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.InlineAutofillEnabledKey, InlineAutofillToggled);
await _stateService.SetInlineAutofillEnabledAsync(InlineAutofillToggled);
}
}
}

View File

@ -7,12 +7,7 @@ namespace Bit.App.Pages
{
public class ExtensionPageViewModel : BaseViewModel
{
private const string StartedKey = "appExtensionStarted";
private const string ActivatedKey = "appExtensionActivated";
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private bool _started;
private bool _activated;
@ -20,8 +15,6 @@ namespace Bit.App.Pages
public ExtensionPageViewModel()
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
PageTitle = AppResources.AppExtension;
}
@ -52,10 +45,8 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
var started = await _storageService.GetAsync<bool?>(StartedKey);
var activated = await _storageService.GetAsync<bool?>(ActivatedKey);
Started = started.GetValueOrDefault();
Activated = activated.GetValueOrDefault();
Started = false;
Activated = false;
}
public void ShowExtension()

View File

@ -1,5 +1,4 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
@ -13,9 +12,6 @@ namespace Bit.App.Pages
{
public class OptionsPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStorageService _storageService;
private readonly ITotpService _totpService;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
@ -34,9 +30,6 @@ namespace Bit.App.Pages
public OptionsPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@ -166,19 +159,17 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
AutofillDisableSavePrompt = (await _storageService.GetAsync<bool?>(
Constants.AutofillDisableSavePromptKey)).GetValueOrDefault();
var blacklistedUrisList = await _storageService.GetAsync<List<string>>(
Constants.AutofillBlacklistedUrisKey);
AutofillDisableSavePrompt = (await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
var blacklistedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
AutofillBlacklistedUris = blacklistedUrisList != null ? string.Join(", ", blacklistedUrisList) : null;
DisableAutoTotpCopy = !(await _totpService.IsAutoCopyEnabledAsync());
DisableFavicon = (await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey)).GetValueOrDefault();
var theme = await _storageService.GetAsync<string>(Constants.ThemeKey);
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var theme = await _stateService.GetThemeAsync();
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
var defaultUriMatch = await _storageService.GetAsync<int?>(Constants.DefaultUriMatch);
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
var clearClipboard = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
var clearClipboard = await _stateService.GetClearClipboardAsync();
ClearClipboardSelectedIndex = ClearClipboardOptions.FindIndex(k => k.Key == clearClipboard);
_inited = true;
}
@ -187,7 +178,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.DisableAutoTotpCopyKey, DisableAutoTotpCopy);
await _stateService.SetDisableAutoTotpCopyAsync(DisableAutoTotpCopy);
}
}
@ -195,8 +186,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.DisableFaviconKey, DisableFavicon);
await _stateService.SaveAsync(Constants.DisableFaviconKey, DisableFavicon);
await _stateService.SetDisableFaviconAsync(DisableFavicon);
}
}
@ -204,8 +194,7 @@ namespace Bit.App.Pages
{
if (_inited && ClearClipboardSelectedIndex > -1)
{
await _storageService.SaveAsync(Constants.ClearClipboardKey,
ClearClipboardOptions[ClearClipboardSelectedIndex].Key);
await _stateService.SetClearClipboardAsync(ClearClipboardOptions[ClearClipboardSelectedIndex].Key);
}
}
@ -214,9 +203,10 @@ namespace Bit.App.Pages
if (_inited && ThemeSelectedIndex > -1)
{
var theme = ThemeOptions[ThemeSelectedIndex].Key;
await _storageService.SaveAsync(Constants.ThemeKey, theme);
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Application.Current.Resources);
await _stateService.SetThemeAsync(theme);
ThemeManager.SetTheme(Application.Current.Resources);
_messagingService.Send("updatedTheme");
_stateService.ApplyThemeGloballyAsync(theme).FireAndForget();
}
}
@ -224,8 +214,7 @@ namespace Bit.App.Pages
{
if (_inited && UriMatchSelectedIndex > -1)
{
await _storageService.SaveAsync(Constants.DefaultUriMatch,
(int?)UriMatchOptions[UriMatchSelectedIndex].Key);
await _stateService.SetDefaultUriMatchAsync((int?)UriMatchOptions[UriMatchSelectedIndex].Key);
}
}
@ -233,7 +222,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.AutofillDisableSavePromptKey, AutofillDisableSavePrompt);
await _stateService.SetAutofillDisableSavePromptAsync(AutofillDisableSavePrompt);
}
}
@ -243,7 +232,7 @@ namespace Bit.App.Pages
{
if (string.IsNullOrWhiteSpace(AutofillBlacklistedUris))
{
await _storageService.RemoveAsync(Constants.AutofillBlacklistedUrisKey);
await _stateService.SetAutofillBlacklistedUrisAsync(null);
AutofillBlacklistedUris = null;
return;
}
@ -265,7 +254,7 @@ namespace Bit.App.Pages
}
urisList.Add(cleanedUri);
}
await _storageService.SaveAsync(Constants.AutofillBlacklistedUrisKey, urisList);
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
AutofillBlacklistedUris = string.Join(", ", urisList);
}
catch { }

View File

@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
@ -18,12 +17,11 @@ namespace Bit.App.Pages
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICryptoService _cryptoService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService;
private readonly IEnvironmentService _environmentService;
private readonly IMessagingService _messagingService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IStorageService _storageService;
private readonly ISyncService _syncService;
private readonly IBiometricService _biometricService;
private readonly IPolicyService _policyService;
@ -55,11 +53,11 @@ namespace Bit.App.Pages
new KeyValuePair<string, int?>(AppResources.Never, null),
new KeyValuePair<string, int?>(AppResources.Custom, CustomVaultTimeoutValue),
};
private List<KeyValuePair<string, string>> _vaultTimeoutActions =
new List<KeyValuePair<string, string>>
private List<KeyValuePair<string, VaultTimeoutAction>> _vaultTimeoutActions =
new List<KeyValuePair<string, VaultTimeoutAction>>
{
new KeyValuePair<string, string>(AppResources.Lock, "lock"),
new KeyValuePair<string, string>(AppResources.LogOut, "logOut"),
new KeyValuePair<string, VaultTimeoutAction>(AppResources.Lock, VaultTimeoutAction.Lock),
new KeyValuePair<string, VaultTimeoutAction>(AppResources.LogOut, VaultTimeoutAction.Logout),
};
private Policy _vaultTimeoutPolicy;
@ -69,12 +67,11 @@ namespace Bit.App.Pages
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
@ -112,7 +109,7 @@ namespace Bit.App.Pages
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey) ?? "lock";
var action = await _stateService.GetVaultTimeoutActionAsync() ?? VaultTimeoutAction.Lock;
_vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2;
@ -136,10 +133,10 @@ namespace Bit.App.Pages
#if DEBUG
var pushNotificationsRegistered = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService").IsRegisteredForPush;
var pnServerRegDate = await _storageService.GetAsync<DateTime>(Constants.PushLastRegistrationDateKey);
var pnServerError = await _storageService.GetAsync<string>(Constants.PushInstallationRegistrationError);
var pnServerRegDate = await _stateService.GetPushLastRegistrationDateAsync();
var pnServerError = await _stateService.GetPushInstallationRegistrationErrorAsync();
var pnServerRegDateMessage = default(DateTime) == pnServerRegDate ? "-" : $"{pnServerRegDate.ToShortDateString()}-{pnServerRegDate.ToShortTimeString()} UTC";
var pnServerRegDateMessage = default(DateTime) == pnServerRegDate ? "-" : $"{pnServerRegDate.GetValueOrDefault().ToShortDateString()}-{pnServerRegDate.GetValueOrDefault().ToShortTimeString()} UTC";
var errorMessage = string.IsNullOrEmpty(pnServerError) ? string.Empty : $"Push Notifications Server Registration error: {pnServerError}";
var text = string.Format("© Bitwarden Inc. 2015-{0}\n\n{1}\nPush Notifications registered:{2}\nPush Notifications Server Last Date :{3}\n{4}", DateTime.Now.Year, debugText, pushNotificationsRegistered, pnServerRegDateMessage, errorMessage);
@ -165,7 +162,7 @@ namespace Bit.App.Pages
List<string> fingerprint;
try
{
fingerprint = await _cryptoService.GetFingerprintAsync(await _userService.GetUserIdAsync());
fingerprint = await _cryptoService.GetFingerprintAsync(await _stateService.GetActiveUserIdAsync());
}
catch (Exception e) when (e.Message == "No public key available.")
{
@ -343,9 +340,9 @@ namespace Bit.App.Pages
AppResources.Yes, AppResources.No);
}
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
@ -355,12 +352,12 @@ namespace Bit.App.Pages
if (masterPassOnRestart)
{
var encPin = await _cryptoService.EncryptAsync(pin);
await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString);
_vaultTimeoutService.PinProtectedKey = pinProtectedKey;
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
}
else
{
await _storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString);
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
}
}
else
@ -395,13 +392,13 @@ namespace Bit.App.Pages
if (_biometric)
{
await _biometricService.SetupBiometricAsync();
await _storageService.SaveAsync(Constants.BiometricUnlockKey, true);
await _stateService.SetBiometricUnlockAsync(true);
}
else
{
await _storageService.RemoveAsync(Constants.BiometricUnlockKey);
await _stateService.SetBiometricUnlockAsync(null);
}
_vaultTimeoutService.BiometricLocked = false;
_stateService.BiometricLocked = false;
await _cryptoService.ToggleKeyAsync();
BuildList();
}
@ -529,7 +526,7 @@ namespace Bit.App.Pages
return true;
}
private string GetVaultTimeoutActionFromKey(string key)
private VaultTimeoutAction GetVaultTimeoutActionFromKey(string key)
{
return _vaultTimeoutActions.FirstOrDefault(o => o.Key == key).Value;
}

View File

@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
@ -12,7 +11,7 @@ namespace Bit.App.Pages
{
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly ISyncService _syncService;
private readonly ILocalizeService _localizeService;
@ -24,7 +23,7 @@ namespace Bit.App.Pages
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
@ -52,7 +51,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
await SetLastSyncAsync();
EnableSyncOnRefresh = await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey);
EnableSyncOnRefresh = await _stateService.GetSyncOnRefreshAsync();
_inited = true;
}
@ -60,7 +59,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.SyncOnRefreshKey, _syncOnRefresh);
await _stateService.SetSyncOnRefreshAsync(_syncOnRefresh);
}
}

View File

@ -2,6 +2,7 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Xamarin.Forms;
@ -45,7 +46,7 @@ namespace Bit.App.Pages
var settingsPage = new NavigationPage(new SettingsPage(this))
{
Title = AppResources.Settings,
IconImageSource = "cog.png"
IconImageSource = "cog_settings.png"
};
Children.Add(settingsPage);
@ -102,8 +103,13 @@ namespace Bit.App.Pages
{
if (CurrentPage is NavigationPage navPage)
{
if (_groupingsPage?.RootPage is GroupingsPage groupingsPage)
{
await groupingsPage.HideAccountSwitchingOverlayAsync();
}
_messagingService.Send("updatedTheme");
if (navPage.RootPage is GroupingsPage groupingsPage)
if (navPage.RootPage is GroupingsPage)
{
// Load something?
}
@ -117,5 +123,13 @@ namespace Bit.App.Pages
}
}
}
public void OnPageReselected()
{
if (_groupingsPage?.RootPage is GroupingsPage groupingsPage)
{
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
}
}
}
}

View File

@ -2,7 +2,6 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
@ -18,7 +17,7 @@ namespace Bit.App.Pages
public partial class AddEditPage : BaseContentPage
{
private readonly AppOptions _appOptions;
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IKeyConnectorService _keyConnectorService;
@ -38,7 +37,7 @@ namespace Bit.App.Pages
bool cloneMode = false,
ViewPage viewPage = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
@ -333,10 +332,10 @@ namespace Bit.App.Pages
{
return;
}
var addLoginShown = await _storageService.GetAsync<bool?>(Constants.AddSitePromptShownKey);
var addLoginShown = await _stateService.GetAddSitePromptShownAsync();
if (_vm.Cipher.Type == CipherType.Login && !_fromAutofill && !addLoginShown.GetValueOrDefault())
{
await _storageService.SaveAsync(Constants.AddSitePromptShownKey, true);
await _stateService.SetAddSitePromptShownAsync(true);
if (Device.RuntimePlatform == Device.iOS)
{
if (_deviceActionService.SystemMajorVersion() < 12)

View File

@ -25,7 +25,8 @@ namespace Bit.App.Pages
private readonly ICipherService _cipherService;
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IOrganizationService _organizationService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService;
@ -73,7 +74,8 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@ -305,9 +307,9 @@ namespace Bit.App.Pages
public async Task<bool> LoadAsync(AppOptions appOptions = null)
{
var myEmail = await _userService.GetEmailAsync();
var myEmail = await _stateService.GetEmailAsync();
OwnershipOptions.Add(new KeyValuePair<string, string>(myEmail, null));
var orgs = await _userService.GetAllOrganizationAsync();
var orgs = await _organizationService.GetAllAsync();
foreach (var org in orgs.OrderBy(o => o.Name))
{
if (org.Enabled && org.Status == OrganizationUserStatusType.Confirmed)

View File

@ -17,8 +17,8 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly ICryptoService _cryptoService;
private readonly IUserService _userService;
private readonly IVaultTimeoutService _timeoutService;
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IPlatformUtilsService _platformUtilsService;
private CipherView _cipher;
private Cipher _cipherDomain;
@ -33,8 +33,8 @@ namespace Bit.App.Pages
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_timeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
Attachments = new ExtendedObservableCollection<AttachmentView>();
DeleteAttachmentCommand = new Command<AttachmentView>(DeleteAsync);
PageTitle = AppResources.Attachments;
@ -66,7 +66,7 @@ namespace Bit.App.Pages
Cipher = await _cipherDomain.DecryptAsync();
LoadAttachments();
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
var canAccessPremium = await _userService.CanAccessPremiumAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
if (!_canAccessAttachments)
{
@ -140,7 +140,7 @@ namespace Bit.App.Pages
// Prevent Android from locking if vault timeout set to "immediate"
if (Device.RuntimePlatform == Device.Android)
{
_timeoutService.DelayLockAndLogoutMs = 60000;
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
}
await _deviceActionService.SelectFileAsync();
}

View File

@ -8,10 +8,8 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms;
@ -88,8 +86,7 @@ namespace Bit.App.Pages
public async Task LoadAsync()
{
WebsiteIconsEnabled = !(await _stateService.GetAsync<bool?>(Constants.DisableFaviconKey))
.GetValueOrDefault();
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
ShowList = false;
var groupedItems = new List<GroupingsPageListGroup>();
var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null);

View File

@ -76,8 +76,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
WebsiteIconsEnabled = !(await _stateService.GetAsync<bool?>(Constants.DisableFaviconKey))
.GetValueOrDefault();
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
{
Search((Page as CiphersPage).SearchBar.Text, 200);

View File

@ -15,6 +15,15 @@
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Search}" />
@ -136,6 +145,8 @@
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<!-- Android FAB -->
<Button
x:Name="_fab"
Image="plus.png"
@ -149,6 +160,13 @@
<effects:FabShadowEffect />
</Button.Effects>
</Button>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
MainFab="{Binding Source={x:Reference _fab}, Path=.}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@ -1,14 +1,13 @@
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;
using System;
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
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<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("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,12 @@ namespace Bit.App.Pages
IsBusy = true;
}
_accountAvatar?.OnAppearing();
if (_vm.MainPage)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
_broadcasterService.Subscribe(_pageName, async (message) =>
{
if (message.Command == "syncStarted")
@ -100,7 +109,6 @@ namespace Bit.App.Pages
}
});
var migratedFromV1 = await _storageService.GetAsync<bool?>(Constants.MigratedFromV1);
await LoadOnAppearedAsync(_mainLayout, false, async () =>
{
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
@ -123,18 +131,6 @@ namespace Bit.App.Pages
await _vm.LoadAsync();
}
}
// Forced sync if for some reason we have no data after a v1 migration
if (_vm.MainPage && !_syncService.SyncInProgress && migratedFromV1.GetValueOrDefault() &&
!_vm.HasCiphers &&
Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
{
var triedV1ReSync = await _storageService.GetAsync<bool?>(Constants.TriedV1Resync);
if (!triedV1ReSync.GetValueOrDefault())
{
await _storageService.SaveAsync(Constants.TriedV1Resync, true);
await _syncService.FullSyncAsync(true);
}
}
await ShowPreviousPageAsync();
AdjustToolbar();
}, _mainContent);
@ -145,14 +141,14 @@ namespace Bit.App.Pages
}
// Push registration
var lastPushRegistration = await _storageService.GetAsync<DateTime?>(Constants.PushLastRegistrationDateKey);
var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync();
lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue);
if (Device.RuntimePlatform == Device.iOS)
{
var pushPromptShow = await _storageService.GetAsync<bool?>(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);
}
@ -168,30 +164,26 @@ namespace Bit.App.Pages
{
await _pushNotificationService.RegisterAsync();
}
if (!_deviceActionService.AutofillAccessibilityServiceRunning()
&& !_deviceActionService.AutofillServiceEnabled())
{
if (migratedFromV1.GetValueOrDefault())
{
var migratedFromV1AutofillPromptShown = await _storageService.GetAsync<bool?>(
Constants.MigratedFromV1AutofillPromptShown);
if (!migratedFromV1AutofillPromptShown.GetValueOrDefault())
{
await DisplayAlert(AppResources.Autofill,
AppResources.AutofillServiceNotEnabled, AppResources.Ok);
}
}
}
await _storageService.SaveAsync(Constants.MigratedFromV1AutofillPromptShown, true);
}
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
IsBusy = false;
_broadcasterService.Unsubscribe(_pageName);
_vm.DisableRefreshing();
_accountAvatar?.OnDisappearing();
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
@ -230,6 +222,7 @@ namespace Bit.App.Pages
private async void Search_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
if (DoOnce())
{
var page = new CiphersPage(_vm.Filter, _vm.FolderId != null, _vm.CollectionId != null,
@ -240,21 +233,31 @@ namespace Bit.App.Pages
private async void Sync_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
await _vm.SyncAsync();
}
private async void Lock_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
await _vaultTimeoutService.LockAsync(true, true);
}
private async void Exit_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
await _vm.ExitAsync();
}
private async void AddButton_Clicked(object sender, EventArgs e)
{
var skipAction = _accountListOverlay.IsVisible && Device.RuntimePlatform == Device.Android;
await _accountListOverlay.HideAsync();
if (skipAction)
{
// Account list in the process of closing via tapping on invisible FAB, skip this attempt
return;
}
if (!_vm.Deleted && DoOnce())
{
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId);
@ -268,6 +271,7 @@ namespace Bit.App.Pages
{
return;
}
await _accountListOverlay.HideAsync();
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId))
{
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.CipherId)));
@ -284,5 +288,10 @@ namespace Bit.App.Pages
_addItem.IsEnabled = !_vm.Deleted;
_addItem.IconImageSource = _vm.Deleted ? null : "plus.png";
}
public async Task HideAccountSwitchingOverlayAsync()
{
await _accountListOverlay.HideAsync();
}
}
}

View File

@ -1,16 +1,16 @@
using Bit.App.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages
@ -39,13 +39,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 +52,11 @@ namespace Bit.App.Pages
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
Loading = true;
@ -72,6 +68,11 @@ namespace Bit.App.Pages
await LoadAsync();
});
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService)
{
AllowAddAccountRow = true
};
}
public bool MainPage { get; set; }
@ -80,7 +81,6 @@ namespace Bit.App.Pages
public string CollectionId { get; set; }
public Func<CipherView, bool> 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 +139,9 @@ namespace Bit.App.Pages
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ExtendedObservableCollection<GroupingsPageListGroup> GroupedItems { get; set; }
public Command RefreshCommand { get; set; }
public Command<CipherView> CipherOptionsCommand { get; set; }
@ -150,7 +153,7 @@ namespace Bit.App.Pages
{
return;
}
var authed = await _userService.IsAuthenticatedAsync();
var authed = await _stateService.IsAuthenticatedAsync();
if (!authed)
{
return;
@ -159,7 +162,7 @@ namespace Bit.App.Pages
{
return;
}
if (await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey) && Refreshing && !SyncRefreshing)
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
{
SyncRefreshing = true;
await _syncService.FullSyncAsync(false);
@ -175,8 +178,7 @@ namespace Bit.App.Pages
var groupedItems = new List<GroupingsPageListGroup>();
var page = Page as GroupingsPage;
WebsiteIconsEnabled = !(await _stateService.GetAsync<bool?>(Constants.DisableFaviconKey))
.GetValueOrDefault();
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
try
{
await LoadDataAsync();

View File

@ -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<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
Collections = new ExtendedObservableCollection<CollectionViewModel>();
@ -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<string, string>(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;

View File

@ -10,10 +10,6 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core;
using Xamarin.Forms;
@ -23,7 +19,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;
@ -53,7 +49,7 @@ namespace Bit.App.Pages
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
@ -253,7 +249,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) &&

View File

@ -311,6 +311,30 @@ namespace Bit.App.Resources {
}
}
public static string RemoveAccount {
get {
return ResourceManager.GetString("RemoveAccount", resourceCulture);
}
}
public static string RemoveAccountConfirmation {
get {
return ResourceManager.GetString("RemoveAccountConfirmation", resourceCulture);
}
}
public static string AccountAlreadyAdded {
get {
return ResourceManager.GetString("AccountAlreadyAdded", resourceCulture);
}
}
public static string SwitchToAlreadyAddedAccountConfirmation {
get {
return ResourceManager.GetString("SwitchToAlreadyAddedAccountConfirmation", resourceCulture);
}
}
public static string MasterPassword {
get {
return ResourceManager.GetString("MasterPassword", resourceCulture);
@ -3719,6 +3743,36 @@ namespace Bit.App.Resources {
}
}
public static string AddAccount {
get {
return ResourceManager.GetString("AddAccount", resourceCulture);
}
}
public static string AccountUnlocked {
get {
return ResourceManager.GetString("AccountUnlocked", resourceCulture);
}
}
public static string AccountLocked {
get {
return ResourceManager.GetString("AccountLocked", resourceCulture);
}
}
public static string AccountLoggedOut {
get {
return ResourceManager.GetString("AccountLoggedOut", resourceCulture);
}
}
public static string AccountSwitchedAutomatically {
get {
return ResourceManager.GetString("AccountSwitchedAutomatically", resourceCulture);
}
}
public static string DeleteAccount {
get {
return ResourceManager.GetString("DeleteAccount", resourceCulture);

View File

@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve">
<value>Are you sure you want to log out?</value>
</data>
<data name="RemoveAccount" xml:space="preserve">
<value>Remove Account</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Are you sure you want to remove this account?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Account Already Added</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Would you like to switch to it now?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Master Password</value>
<comment>Label for a master password.</comment>
@ -2093,6 +2105,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect">
<value>One or more organization policies prevents your from exporting your personal vault.</value>
</data>
<data name="AddAccount" xml:space="preserve">
<value>Add Account</value>
</data>
<data name="AccountUnlocked" xml:space="preserve">
<value>Unlocked</value>
</data>
<data name="AccountLocked" xml:space="preserve">
<value>Locked</value>
</data>
<data name="AccountLoggedOut" xml:space="preserve">
<value>Logged Out</value>
</data>
<data name="AccountSwitchedAutomatically" xml:space="preserve">
<value>Switched to next available account</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Delete Account</value>
</data>

View File

@ -13,41 +13,26 @@ namespace Bit.App.Services
private readonly HashSet<string> _preferenceStorageKeys = new HashSet<string>
{
Constants.VaultTimeoutKey,
Constants.VaultTimeoutActionKey,
Constants.ThemeKey,
Constants.DefaultUriMatch,
Constants.DisableAutoTotpCopyKey,
Constants.DisableFaviconKey,
Constants.ClearClipboardKey,
Constants.AutofillDisableSavePromptKey,
Constants.LastActiveTimeKey,
Constants.StateVersionKey,
Constants.PreAuthEnvironmentUrlsKey,
Constants.AutofillTileAdded,
Constants.AddSitePromptShownKey,
Constants.PushInitialPromptShownKey,
Constants.LastFileCacheClearKey,
Constants.PushLastRegistrationDateKey,
Constants.PushRegisteredTokenKey,
Constants.PushCurrentTokenKey,
Constants.LastBuildKey,
Constants.MigratedFromV1,
Constants.MigratedFromV1AutofillPromptShown,
Constants.TriedV1Resync,
Constants.ClearCiphersCacheKey,
Constants.BiometricIntegrityKey,
Constants.iOSAutoFillClearCiphersCacheKey,
Constants.iOSAutoFillBiometricIntegrityKey,
Constants.iOSExtensionClearCiphersCacheKey,
Constants.iOSExtensionBiometricIntegrityKey,
Constants.EnvironmentUrlsKey,
Constants.InlineAutofillEnabledKey,
Constants.InvalidUnlockAttempts,
Constants.RememberedEmailKey,
Constants.RememberedOrgIdentifierKey,
};
private readonly HashSet<string> _migrateToPreferences = new HashSet<string>
{
Constants.EnvironmentUrlsKey,
};
private readonly HashSet<string> _haveMigratedToPreferences = new HashSet<string>();
public MobileStorageService(
IStorageService preferenceStorageService,
IStorageService liteDbStorageService)
@ -60,24 +45,9 @@ namespace Bit.App.Services
{
if (_preferenceStorageKeys.Contains(key))
{
var prefValue = await _preferencesStorageService.GetAsync<T>(key);
if (prefValue != null || !_migrateToPreferences.Contains(key) ||
_haveMigratedToPreferences.Contains(key))
{
return prefValue;
}
return await _preferencesStorageService.GetAsync<T>(key);
}
var liteDbValue = await _liteDbStorageService.GetAsync<T>(key);
if (_migrateToPreferences.Contains(key))
{
if (liteDbValue != null)
{
await _preferencesStorageService.SaveAsync(key, liteDbValue);
await _liteDbStorageService.RemoveAsync(key);
}
_haveMigratedToPreferences.Add(key);
}
return liteDbValue;
return await _liteDbStorageService.GetAsync<T>(key);
}
public Task SaveAsync<T>(string key, T obj)

View File

@ -21,9 +21,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;
@ -64,8 +63,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:
@ -135,7 +134,7 @@ namespace Bit.App.Services
{
Resolve();
Debug.WriteLine($"{TAG} - Device Registered - Token : {token}");
var isAuthenticated = await _userService.IsAuthenticatedAsync();
var isAuthenticated = await _stateService.IsAuthenticatedAsync();
if (!isAuthenticated)
{
Debug.WriteLine($"{TAG} - not auth");
@ -146,7 +145,7 @@ namespace Bit.App.Services
try
{
#if DEBUG
await _storageService.RemoveAsync(Constants.PushInstallationRegistrationError);
await _stateService.SetPushInstallationRegistrationErrorAsync(null);
#endif
await _apiService.PutDeviceTokenAsync(appId,
@ -154,10 +153,10 @@ namespace Bit.App.Services
Debug.WriteLine($"{TAG} 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);
}
}
#if DEBUG
@ -165,11 +164,11 @@ namespace Bit.App.Services
{
Debug.WriteLine($"{TAG} Failed to register device.");
await _storageService.SaveAsync(Constants.PushInstallationRegistrationError, apiEx.Error?.Message);
await _stateService.SetPushInstallationRegistrationErrorAsync(apiEx.Error?.Message);
}
catch (Exception e)
{
await _storageService.SaveAsync(Constants.PushInstallationRegistrationError, e.Message);
await _stateService.SetPushInstallationRegistrationErrorAsync(e.Message);
throw;
}
#else
@ -200,9 +199,8 @@ namespace Bit.App.Services
{
return;
}
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");

View File

@ -2,7 +2,6 @@
using Bit.App.Abstractions;
using Bit.App.Pages;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
@ -14,6 +13,7 @@ using System.Threading.Tasks;
using Bit.App.Models;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Forms;
@ -46,8 +46,8 @@ namespace Bit.App.Utilities
}
if (!string.IsNullOrWhiteSpace(cipher.Login.Totp))
{
var userService = ServiceContainer.Resolve<IUserService>("userService");
var canAccessPremium = await userService.CanAccessPremiumAsync();
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var canAccessPremium = await stateService.CanAccessPremiumAsync();
if (canAccessPremium || cipher.OrganizationUseTotp)
{
options.Add(AppResources.CopyTotp);
@ -330,33 +330,15 @@ namespace Bit.App.Utilities
}
public static async Task<bool> PerformUpdateTasksAsync(ISyncService syncService,
IDeviceActionService deviceActionService, IStorageService storageService)
IDeviceActionService deviceActionService, IStateService stateService)
{
var currentBuild = deviceActionService.GetBuildNumber();
var lastBuild = await storageService.GetAsync<string>(Constants.LastBuildKey);
if (lastBuild == null)
{
// Installed
var currentTimeout = await storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
if (currentTimeout == null)
{
await storageService.SaveAsync(Constants.VaultTimeoutKey, 15);
}
var currentAction = await storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);
if (currentAction == null)
{
await storageService.SaveAsync(Constants.VaultTimeoutActionKey, "lock");
}
}
else if (lastBuild != currentBuild)
var lastBuild = await stateService.GetLastBuildAsync();
if (lastBuild == null || 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 +400,34 @@ namespace Bit.App.Utilities
public static async Task<PreviousPageInfo> ClearPreviousPage()
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var previousPage = await storageService.GetAsync<PreviousPageInfo>(Constants.PreviousPageKey);
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var previousPage = await stateService.GetPreviousPageInfoAsync();
if (previousPage != null)
{
await storageService.RemoveAsync(Constants.PreviousPageKey);
await stateService.SetPreviousPageInfoAsync(null);
}
return previousPage;
}
public static async Task<int> IncrementInvalidUnlockAttemptsAsync()
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var invalidUnlockAttempts = await storageService.GetAsync<int>(Constants.InvalidUnlockAttempts);
var stateService = ServiceContainer.Resolve<IStateService>("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<IStorageService>("storageService");
await storageService.RemoveAsync(Constants.InvalidUnlockAttempts);
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
await stateService.SetInvalidUnlockAttemptsAsync(null);
}
public static async Task<bool> IsVaultTimeoutImmediateAsync()
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var vaultTimeoutMinutes = await storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var vaultTimeoutMinutes = await stateService.GetVaultTimeoutAsync();
if (vaultTimeoutMinutes.GetValueOrDefault(-1) == 0)
{
return true;
@ -466,10 +447,8 @@ namespace Bit.App.Utilities
return Convert.ToBase64String(Encoding.UTF8.GetBytes(multiByteEscaped));
}
public static async Task LogOutAsync()
public static async Task LogOutAsync(string userId, bool userInitiated = false)
{
var userService = ServiceContainer.Resolve<IUserService>("userService");
var syncService = ServiceContainer.Resolve<ISyncService>("syncService");
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
var settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
@ -481,23 +460,70 @@ namespace Bit.App.Utilities
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
var searchService = ServiceContainer.Resolve<ISearchService>("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(),
settingsService.ClearAsync(userId),
cipherService.ClearAsync(userId),
folderService.ClearAsync(userId),
collectionService.ClearAsync(userId),
passwordGenerationService.ClearAsync(),
vaultTimeoutService.ClearAsync(),
stateService.PurgeAsync(),
passwordGenerationService.ClearAsync(userId),
deviceActionService.ClearCacheAsync(),
tokenService.ClearTokenAsync(userId),
cryptoService.ClearKeysAsync(userId),
settingsService.ClearAsync(userId),
vaultTimeoutService.ClearAsync(userId),
policyService.ClearAsync(userId),
stateService.LogoutAccountAsync(userId, userInitiated));
stateService.BiometricLocked = true;
searchService.ClearIndex();
// check if we switched accounts automatically
if (userInitiated && await stateService.GetActiveUserIdAsync() != null)
{
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
messagingService.Send("switchedAccount");
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
platformUtilsService.ShowToast("info", null, AppResources.AccountSwitchedAutomatically);
}
}
public static async Task OnAccountSwitchAsync()
{
var environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
var settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
var folderService = ServiceContainer.Resolve<IFolderService>("folderService");
var collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
var sendService = ServiceContainer.Resolve<ISendService>("sendService");
var passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
var searchService = ServiceContainer.Resolve<ISearchService>("searchService");
await environmentService.SetUrlsFromStorageAsync();
await Task.WhenAll(
cipherService.ClearCacheAsync(),
deviceActionService.ClearCacheAsync());
vaultTimeoutService.BiometricLocked = true;
tokenService.ClearCache();
cryptoService.ClearCache();
settingsService.ClearCache();
folderService.ClearCache();
collectionService.ClearCache();
sendService.ClearCache();
passwordGenerationService.ClearCache();
policyService.ClearCache();
searchService.ClearIndex();
}
}

View File

@ -1,8 +1,8 @@
using System;
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;
using System.Linq;
using System.Threading.Tasks;
@ -110,16 +110,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<IStateService>("stateService");
return stateService.GetThemeAsync().GetAwaiter().GetResult();
}
public static bool OsDarkModeEnabled()

View File

@ -22,4 +22,4 @@ namespace Bit.Core.Abstractions
Task UpsertAsync(CollectionData collection);
Task UpsertAsync(List<CollectionData> collection);
}
}
}

View File

@ -9,13 +9,14 @@ 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);
void ClearCache();
Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key);
Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null);
Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null);
@ -24,7 +25,7 @@ namespace Bit.Core.Abstractions
Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
Task<SymmetricCryptoKey> GetKeyAsync();
Task<SymmetricCryptoKey> GetKeyAsync(string userId = null);
Task<string> GetKeyHashAsync();
Task<SymmetricCryptoKey> GetOrgKeyAsync(string orgId);
Task<Dictionary<string, SymmetricCryptoKey>> GetOrgKeysAsync();
@ -33,7 +34,7 @@ namespace Bit.Core.Abstractions
Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key);
Task<bool> HasEncKeyAsync();
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task<bool> HasKeyAsync();
Task<bool> HasKeyAsync(string userId = null);
Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key);
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations);
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations,

View File

@ -9,4 +9,4 @@ namespace Bit.Core.Abstractions
Task CollectAsync(EventType eventType, string cipherId = null, bool uploadImmediately = false);
Task UploadEventsAsync();
}
}
}

View File

@ -23,4 +23,4 @@ namespace Bit.Core.Abstractions
Task UpsertAsync(FolderData folder);
Task UpsertAsync(List<FolderData> folder);
}
}
}

View File

@ -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<Organization> GetAsync(string id);
Task<Organization> GetByIdentifierAsync(string identifier);
Task<List<Organization>> GetAllAsync(string userId = null);
Task ReplaceAsync(Dictionary<string, OrganizationData> organizations);
Task ClearAllAsync(string userId);
}
}

View File

@ -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<string> GeneratePassphraseAsync(PasswordGenerationOptions options);
Task<string> GeneratePasswordAsync(PasswordGenerationOptions options);
Task<List<GeneratedPasswordHistory>> GetHistoryAsync();

View File

@ -10,15 +10,15 @@ namespace Bit.Core.Abstractions
public interface IPolicyService
{
void ClearCache();
Task<IEnumerable<Policy>> GetAll(PolicyType? type);
Task Replace(Dictionary<string, PolicyData> policies);
Task Clear(string userId);
Task<MasterPasswordPolicyOptions> GetMasterPasswordPolicyOptions(IEnumerable<Policy> policies = null);
Task<IEnumerable<Policy>> GetAll(PolicyType? type, string userId = null);
Task Replace(Dictionary<string, PolicyData> policies, string userId = null);
Task ClearAsync(string userId);
Task<MasterPasswordPolicyOptions> GetMasterPasswordPolicyOptions(IEnumerable<Policy> policies = null, string userId = null);
Task<bool> EvaluateMasterPassword(int passwordStrength, string newPassword,
MasterPasswordPolicyOptions enforcedPolicyOptions);
Tuple<ResetPasswordPolicyOptions, bool> GetResetPasswordPolicyOptions(IEnumerable<Policy> policies,
string orgId);
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null);
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
int? GetPolicyInt(Policy policy, string key);
}
}

View File

@ -10,4 +10,4 @@ namespace Bit.Core.Abstractions
Task<List<List<string>>> GetEquivalentDomainsAsync();
Task SetEquivalentDomainsAsync(List<List<string>> equivalentDomains);
}
}
}

View File

@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{
public interface IStateMigrationService
{
Task<bool> NeedsMigration();
Task Migrate();
}
}

View File

@ -1,12 +1,147 @@
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;
namespace Bit.Core.Abstractions
{
public interface IStateService
{
Task<T> GetAsync<T>(string key);
Task RemoveAsync(string key);
Task SaveAsync<T>(string key, T obj);
Task PurgeAsync();
bool BiometricLocked { get; set; }
List<AccountView> AccountViews { get; }
Task<string> GetActiveUserIdAsync();
Task SetActiveUserAsync(string userId);
Task<bool> IsAuthenticatedAsync(string userId = null);
Task<string> GetUserIdAsync(string email);
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
Task AddAccountAsync(Account account);
Task LogoutAccountAsync(string userId, bool userInitiated);
Task<EnvironmentUrlData> GetPreAuthEnvironmentUrlsAsync();
Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value);
Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null);
Task<bool?> GetBiometricUnlockAsync(string userId = null);
Task SetBiometricUnlockAsync(bool? value, string userId = null);
Task<bool> CanAccessPremiumAsync(string userId = null);
Task<string> GetProtectedPinAsync(string userId = null);
Task SetProtectedPinAsync(string value, string userId = null);
Task<string> GetPinProtectedAsync(string userId = null);
Task SetPinProtectedAsync(string value, string userId = null);
Task<EncString> GetPinProtectedKeyAsync(string userId = null);
Task SetPinProtectedKeyAsync(EncString value, string userId = null);
Task<KdfType?> GetKdfTypeAsync(string userId = null);
Task SetKdfTypeAsync(KdfType? value, string userId = null);
Task<int?> GetKdfIterationsAsync(string userId = null);
Task SetKdfIterationsAsync(int? value, string userId = null);
Task<string> GetKeyEncryptedAsync(string userId = null);
Task SetKeyEncryptedAsync(string value, string userId = null);
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);
Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null);
Task<string> GetKeyHashAsync(string userId = null);
Task SetKeyHashAsync(string value, string userId = null);
Task<string> GetEncKeyEncryptedAsync(string userId = null);
Task SetEncKeyEncryptedAsync(string value, string userId = null);
Task<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null);
Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null);
Task<string> GetPrivateKeyEncryptedAsync(string userId = null);
Task SetPrivateKeyEncryptedAsync(string value, string userId = null);
Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null);
Task SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
Task<bool?> GetAutofillTileAddedAsync();
Task SetAutofillTileAddedAsync(bool? value);
Task<string> GetEmailAsync(string userId = null);
Task<string> GetNameAsync(string userId = null);
Task<string> GetOrgIdentifierAsync(string userId = null);
Task<long?> GetLastActiveTimeAsync(string userId = null);
Task SetLastActiveTimeAsync(long? value, string userId = null);
Task<int?> GetVaultTimeoutAsync(string userId = null);
Task SetVaultTimeoutAsync(int? value, string userId = null);
Task<VaultTimeoutAction?> GetVaultTimeoutActionAsync(string userId = null);
Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null);
Task<DateTime?> GetLastFileCacheClearAsync(string userId = null);
Task SetLastFileCacheClearAsync(DateTime? value, string userId = null);
Task<PreviousPageInfo> GetPreviousPageInfoAsync(string userId = null);
Task SetPreviousPageInfoAsync(PreviousPageInfo value, string userId = null);
Task<int> GetInvalidUnlockAttemptsAsync(string userId = null);
Task SetInvalidUnlockAttemptsAsync(int? value, string userId = null);
Task<string> GetLastBuildAsync();
Task SetLastBuildAsync(string value);
Task<bool?> GetDisableFaviconAsync(string userId = null);
Task SetDisableFaviconAsync(bool? value, string userId = null);
Task<bool?> GetDisableAutoTotpCopyAsync(string userId = null);
Task SetDisableAutoTotpCopyAsync(bool? value, string userId = null);
Task<bool?> GetInlineAutofillEnabledAsync(string userId = null);
Task SetInlineAutofillEnabledAsync(bool? value, string userId = null);
Task<bool?> GetAutofillDisableSavePromptAsync(string userId = null);
Task SetAutofillDisableSavePromptAsync(bool? value, string userId = null);
Task<Dictionary<string, Dictionary<string, object>>> GetLocalDataAsync(string userId = null);
Task SetLocalDataAsync(Dictionary<string, Dictionary<string, object>> value, string userId = null);
Task<Dictionary<string, CipherData>> GetEncryptedCiphersAsync(string userId = null);
Task SetEncryptedCiphersAsync(Dictionary<string, CipherData> value, string userId = null);
Task<int?> GetDefaultUriMatchAsync(string userId = null);
Task SetDefaultUriMatchAsync(int? value, string userId = null);
Task<HashSet<string>> GetNeverDomainsAsync(string userId = null);
Task SetNeverDomainsAsync(HashSet<string> value, string userId = null);
Task<int?> GetClearClipboardAsync(string userId = null);
Task SetClearClipboardAsync(int? value, string userId = null);
Task<Dictionary<string, CollectionData>> GetEncryptedCollectionsAsync(string userId = null);
Task SetEncryptedCollectionsAsync(Dictionary<string, CollectionData> value, string userId = null);
Task<bool> GetPasswordRepromptAutofillAsync(string userId = null);
Task SetPasswordRepromptAutofillAsync(bool? value, string userId = null);
Task<bool> GetPasswordVerifiedAutofillAsync(string userId = null);
Task SetPasswordVerifiedAutofillAsync(bool? value, string userId = null);
Task<DateTime?> GetLastSyncAsync(string userId = null);
Task SetLastSyncAsync(DateTime? value, string userId = null);
Task<string> GetSecurityStampAsync(string userId = null);
Task SetSecurityStampAsync(string value, string userId = null);
Task<bool> GetEmailVerifiedAsync(string userId = null);
Task SetEmailVerifiedAsync(bool? value, string userId = null);
Task<bool> GetSyncOnRefreshAsync(string userId = null);
Task SetSyncOnRefreshAsync(bool? value, string userId = null);
Task<string> GetRememberedEmailAsync();
Task SetRememberedEmailAsync(string value);
Task<string> GetRememberedOrgIdentifierAsync();
Task SetRememberedOrgIdentifierAsync(string value);
Task<string> GetThemeAsync(string userId = null);
Task SetThemeAsync(string value, string userId = null);
Task ApplyThemeGloballyAsync(string value);
Task<bool?> GetAddSitePromptShownAsync(string userId = null);
Task SetAddSitePromptShownAsync(bool? value, string userId = null);
Task<bool?> GetPushInitialPromptShownAsync();
Task SetPushInitialPromptShownAsync(bool? value);
Task<DateTime?> GetPushLastRegistrationDateAsync();
Task SetPushLastRegistrationDateAsync(DateTime? value);
Task<string> GetPushInstallationRegistrationErrorAsync();
Task SetPushInstallationRegistrationErrorAsync(string value);
Task<string> GetPushCurrentTokenAsync();
Task SetPushCurrentTokenAsync(string value);
Task<List<EventData>> GetEventCollectionAsync();
Task SetEventCollectionAsync(List<EventData> value);
Task<Dictionary<string, FolderData>> GetEncryptedFoldersAsync(string userId = null);
Task SetEncryptedFoldersAsync(Dictionary<string, FolderData> value, string userId = null);
Task<Dictionary<string, PolicyData>> GetEncryptedPoliciesAsync(string userId = null);
Task SetEncryptedPoliciesAsync(Dictionary<string, PolicyData> value, string userId = null);
Task<string> GetPushRegisteredTokenAsync();
Task SetPushRegisteredTokenAsync(string value);
Task<bool> GetUsesKeyConnectorAsync(string userId = null);
Task SetUsesKeyConnectorAsync(bool? value, string userId = null);
Task<Dictionary<string, OrganizationData>> GetOrganizationsAsync(string userId = null);
Task SetOrganizationsAsync(Dictionary<string, OrganizationData> organizations, string userId = null);
Task<PasswordGenerationOptions> GetPasswordGenerationOptionsAsync(string userId = null);
Task SetPasswordGenerationOptionsAsync(PasswordGenerationOptions value, string userId = null);
Task<List<GeneratedPasswordHistory>> GetEncryptedPasswordGenerationHistory(string userId = null);
Task SetEncryptedPasswordGenerationHistoryAsync(List<GeneratedPasswordHistory> value, string userId = null);
Task<Dictionary<string, SendData>> GetEncryptedSendsAsync(string userId = null);
Task SetEncryptedSendsAsync(Dictionary<string, SendData> value, string userId = null);
Task<Dictionary<string, object>> GetSettingsAsync(string userId = null);
Task SetSettingsAsync(Dictionary<string, object> value, string userId = null);
Task<string> GetAccessTokenAsync(string userId = null);
Task SetAccessTokenAsync(string value, bool skipTokenStorage, string userId = null);
Task<string> GetRefreshTokenAsync(string userId = null);
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
Task<string> GetTwoFactorTokenAsync(string email = null);
Task SetTwoFactorTokenAsync(string value, string email = null);
}
}
}

View File

@ -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<bool> GetIsExternal();
Task<string> GetRefreshTokenAsync();
Task<string> GetTokenAsync();
Task ToggleTokensAsync();
@ -22,7 +23,7 @@ namespace Bit.Core.Abstractions
Task<string> GetTwoFactorTokenAsync(string email);
string GetUserId();
Task SetRefreshTokenAsync(string refreshToken);
Task SetTokenAsync(string token);
Task SetAccessTokenAsync(string token, bool forDecodeOnly = false);
Task SetTokensAsync(string accessToken, string refreshToken);
Task SetTwoFactorTokenAsync(string token, string email);
bool TokenNeedsRefresh(int minutes = 5);

View File

@ -1,31 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
namespace Bit.Core.Abstractions
{
public interface IUserService
{
Task<bool> CanAccessPremiumAsync();
Task ClearAsync();
Task ClearOrganizationsAsync(string userId);
Task<List<Organization>> GetAllOrganizationAsync();
Task<string> GetEmailAsync();
Task<KdfType?> GetKdfAsync();
Task<int?> GetKdfIterationsAsync();
Task<Organization> GetOrganizationAsync(string id);
Task<Organization> GetOrganizationByIdentifierAsync(string identifier);
Task<string> GetSecurityStampAsync();
Task<bool> GetEmailVerifiedAsync();
Task<bool> GetForcePasswordReset();
Task<string> GetUserIdAsync();
Task<bool> IsAuthenticatedAsync();
Task ReplaceOrganizationsAsync(Dictionary<string, OrganizationData> organizations);
Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations);
Task SetSecurityStampAsync(string stamp);
Task SetEmailVerifiedAsync(bool emailVerified);
Task SetForcePasswordReset(bool forcePasswordReset);
}
}

View File

@ -1,23 +1,24 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
using Bit.Core.Enums;
namespace Bit.Core.Abstractions
{
public interface IVaultTimeoutService
{
EncString PinProtectedKey { get; set; }
bool BiometricLocked { get; set; }
long? DelayLockAndLogoutMs { get; set; }
Task CheckVaultTimeoutAsync();
Task ClearAsync();
Task<bool> IsLockedAsync();
Task<Tuple<bool, bool>> IsPinLockSetAsync();
Task<bool> IsBiometricLockSetAsync();
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false);
Task LogOutAsync();
Task SetVaultTimeoutOptionsAsync(int? timeout, string action);
Task<int?> GetVaultTimeout();
Task<bool> ShouldTimeoutAsync(string userId = null);
Task ExecuteTimeoutActionAsync(string userId = null);
Task ClearAsync(string userId = null);
Task<bool> IsLockedAsync(string userId = null);
Task<bool> IsLoggedOutByTimeoutAsync(string userId = null);
Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null);
Task<bool> IsBiometricLockSetAsync(string userId = null);
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null);
Task LogOutAsync(bool userInitiated = true, string userId = null);
Task SetVaultTimeoutOptionsAsync(int? timeout, VaultTimeoutAction? action);
Task<int?> GetVaultTimeout(string userId = null);
}
}

View File

@ -2,32 +2,20 @@
{
public static class Constants
{
public const int MaxAccounts = 5;
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 StateVersionKey = "stateVersion";
public static string StateKey = "state";
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 PushInstallationRegistrationError = "pushInstallationRegistrationError";
public static string ThemeKey = "theme";
public static string ClearClipboardKey = "clearClipboard";
public static string PushInstallationRegistrationErrorKey = "pushInstallationRegistrationError";
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";
@ -35,15 +23,9 @@
public static string iOSAutoFillBiometricIntegrityKey = "iOSAutoFillBiometricIntegrityState";
public static string iOSExtensionClearCiphersCacheKey = "iOSExtensionClearCiphersCache";
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
public static string MigratedFromV1 = "migratedFromV1";
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 RememberedEmailKey = "rememberedEmail";
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
public const int SelectFileRequestCode = 42;
public const int SelectFilePermissionRequestCode = 43;
public const int SaveFileRequestCode = 44;
@ -59,5 +41,42 @@
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 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 LastSyncKey(string userId) => $"lastSync_{userId}";
public static string BiometricUnlockKey(string userId) => $"biometricUnlock_{userId}";
}
}

View File

@ -0,0 +1,9 @@
namespace Bit.Core.Enums
{
public enum AuthenticationStatus : byte
{
LoggedOut = 0,
Locked = 1,
Unlocked = 2,
}
}

View File

@ -0,0 +1,9 @@
namespace Bit.Core.Enums
{
public enum StorageLocation
{
Both = 0,
Disk = 1,
Memory = 2
}
}

View File

@ -0,0 +1,8 @@
namespace Bit.Core.Enums
{
public enum VaultTimeoutAction
{
Lock = 0,
Logout = 1,
}
}

View File

@ -1,4 +1,4 @@
namespace Bit.App.Models
namespace Bit.Core.Models.Data
{
public class PreviousPageInfo
{

View File

@ -0,0 +1,110 @@
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;
Name = copy.Name;
Stamp = copy.Stamp;
OrgIdentifier = copy.OrgIdentifier;
KdfType = copy.KdfType;
KdfIterations = copy.KdfIterations;
EmailVerified = copy.EmailVerified;
HasPremiumPersonally = copy.HasPremiumPersonally;
}
public string UserId;
public string Email;
public string Name;
public string Stamp;
public string OrgIdentifier;
public KdfType? KdfType;
public int? KdfIterations;
public bool? EmailVerified;
public bool? HasPremiumPersonally;
}
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;
VaultTimeout = copy.VaultTimeout;
VaultTimeoutAction = copy.VaultTimeoutAction;
}
public EnvironmentUrlData EnvironmentUrls;
public int? VaultTimeout;
public VaultTimeoutAction? VaultTimeoutAction;
}
public class AccountKeys
{
public SymmetricCryptoKey Key;
public EncString PinProtectedKey;
}
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Bit.Core.Models.Domain
{
public class State : Domain
{
public Dictionary<string, Account> Accounts { get; set; }
public string ActiveUserId { get; set; }
}
}

View File

@ -0,0 +1,13 @@
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; }
public bool? SkipTokenStorage { get; set; }
}
}

View File

@ -0,0 +1,41 @@
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
namespace Bit.Core.Models.View
{
public class AccountView : View
{
public AccountView() { }
public AccountView(Account a = null, bool isActive = false)
{
if (a == null)
{
// null will render as "Add Account" row
return;
}
IsAccount = true;
IsActive = isActive;
UserId = a.Profile?.UserId;
Email = a.Profile?.Email;
Name = a.Profile?.Name;
if (!string.IsNullOrWhiteSpace(a.Settings?.EnvironmentUrls?.WebVault))
{
Hostname = CoreHelpers.GetHostname(a.Settings?.EnvironmentUrls?.WebVault);
}
else if (!string.IsNullOrWhiteSpace(a.Settings?.EnvironmentUrls?.Base))
{
Hostname = CoreHelpers.GetHostname(a.Settings?.EnvironmentUrls?.Base);
}
}
public bool IsAccount { get; set; }
public AuthenticationStatus? AuthStatus { get; set; }
public bool IsActive { get; set; }
public string UserId { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public string Hostname { get; set; }
}
}

View File

@ -25,12 +25,12 @@ namespace Bit.Core.Services
private readonly HttpClient _httpClient = new HttpClient();
private readonly ITokenService _tokenService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly Func<bool, Task> _logoutCallbackAsync;
private readonly Func<Tuple<string, bool, bool>, Task> _logoutCallbackAsync;
public ApiService(
ITokenService tokenService,
IPlatformUtilsService platformUtilsService,
Func<bool, Task> logoutCallbackAsync,
Func<Tuple<string, bool, bool>, Task> logoutCallbackAsync,
string customUserAgent = null)
{
_tokenService = tokenService;
@ -709,7 +709,7 @@ namespace Bit.Core.Services
response.StatusCode == HttpStatusCode.Forbidden
))
{
await _logoutCallbackAsync(true);
await _logoutCallbackAsync(new Tuple<string, bool, bool>(null, false, true));
return null;
}
try

View File

@ -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;
@ -354,9 +352,26 @@ 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 _tokenService.SetAccessTokenAsync(tokenResponse.AccessToken, true);
await _stateService.AddAccountAsync(
new Account(
new Account.AccountProfile()
{
UserId = _tokenService.GetUserId(),
Email = _tokenService.GetEmail(),
Name = _tokenService.GetName(),
KdfType = tokenResponse.Kdf,
KdfIterations = tokenResponse.KdfIterations,
HasPremiumPersonally = _tokenService.GetPremium(),
},
new Account.AccountTokens()
{
AccessToken = tokenResponse.AccessToken,
RefreshToken = tokenResponse.RefreshToken,
}
)
);
_messagingService.Send("accountAdded");
if (_setCryptoKeys)
{
if (key != null)
@ -430,7 +445,7 @@ namespace Bit.Core.Services
}
_vaultTimeoutService.BiometricLocked = false;
_stateService.BiometricLocked = false;
_messagingService.Send("loggedIn");
return result;
}

View File

@ -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<CipherView> _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<Cipher> GetAsync(string id)
{
var userId = await _userService.GetUserIdAsync();
var localData = await _storageService.GetAsync<Dictionary<string, Dictionary<string, object>>>(
Keys_LocalData);
var ciphers = await _storageService.GetAsync<Dictionary<string, CipherData>>(
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<List<Cipher>> GetAllAsync()
{
var userId = await _userService.GetUserIdAsync();
var localData = await _storageService.GetAsync<Dictionary<string, Dictionary<string, object>>>(
Keys_LocalData);
var ciphers = await _storageService.GetAsync<Dictionary<string, CipherData>>(
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<Cipher>();
@ -347,7 +337,7 @@ namespace Bit.Core.Services
var others = new List<CipherView>();
var ciphers = await ciphersTask;
var defaultMatch = (UriMatchType?)(await _storageService.GetAsync<int?>(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<Dictionary<string, Dictionary<string, object>>>(
Keys_LocalData);
var ciphersLocalData = await _stateService.GetLocalDataAsync();
if (ciphersLocalData == null)
{
ciphersLocalData = new Dictionary<string, Dictionary<string, object>>();
@ -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<HashSet<string>>(Keys_NeverDomains);
var domains = await _stateService.GetNeverDomainsAsync();
if (domains == null)
{
domains = new HashSet<string>();
}
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<Dictionary<string, CipherData>>(storageKey);
var ciphers = await _stateService.GetEncryptedCiphersAsync();
if (ciphers == null)
{
ciphers = new Dictionary<string, CipherData>();
@ -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<CipherData> cipher)
{
var userId = await _userService.GetUserIdAsync();
var storageKey = string.Format(Keys_CiphersFormat, userId);
var ciphers = await _storageService.GetAsync<Dictionary<string, CipherData>>(storageKey);
var ciphers = await _stateService.GetEncryptedCiphersAsync();
if (ciphers == null)
{
ciphers = new Dictionary<string, CipherData>();
@ -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<string, CipherData> 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, 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<Dictionary<string, CipherData>>(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<string> ids)
{
var userId = await _userService.GetUserIdAsync();
var cipherKey = string.Format(Keys_CiphersFormat, userId);
var ciphers = await _storageService.GetAsync<Dictionary<string, CipherData>>(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<Dictionary<string, CipherData>>(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<Dictionary<string, CipherData>>(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<Dictionary<string, CipherData>>(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();
}

View File

@ -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<CollectionView> _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<Collection> GetAsync(string id)
{
var userId = await _userService.GetUserIdAsync();
var collections = await _storageService.GetAsync<Dictionary<string, CollectionData>>(
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<List<Collection>> GetAllAsync()
{
var userId = await _userService.GetUserIdAsync();
var collections = await _storageService.GetAsync<Dictionary<string, CollectionData>>(
string.Format(Keys_CollectionsFormat, userId));
var collections = await _stateService.GetEncryptedCollectionsAsync();
var response = collections?.Select(c => new Collection(c.Value));
return response?.ToList() ?? new List<Collection>();
}
@ -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<Dictionary<string, CollectionData>>(storageKey);
var collections = await _stateService.GetEncryptedCollectionsAsync();
if (collections == null)
{
collections = new Dictionary<string, CollectionData>();
@ -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<CollectionData> collection)
{
var userId = await _userService.GetUserIdAsync();
var storageKey = string.Format(Keys_CollectionsFormat, userId);
var collections = await _storageService.GetAsync<Dictionary<string, CollectionData>>(storageKey);
var collections = await _stateService.GetEncryptedCollectionsAsync();
if (collections == null)
{
collections = new Dictionary<string, CollectionData>();
@ -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<string, CollectionData> 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, 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<Dictionary<string, CollectionData>>(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;
}

Some files were not shown because too many files have changed in this diff Show More