[Auto Logout] Final review of feature (#932)

* Initial commit of LockService name refactor (#831)

* [Auto-Logout] Update Service layer logic (#835)

* Initial commit of service logic update

* Added default value for action

* Updated ToggleTokensAsync conditional

* Removed unused variables, updated action conditional

* Initial commit: lockOption/lock refactor app layer (#840)

* [Auto-Logout] Settings Refactor - Application Layer Part 2 (#844)

* Initial commit of app layer part 2

* Updated biometrics position

* Reverted resource name refactor

* LockOptions refactor revert

* Updated method casing :: Removed VaultTimeout prefix for timeouts

* Fixed dupe string resource (#854)

* Updated dependency to use VaultTimeoutService (#896)

* [Auto Logout] Xamarin Forms in AutoFill flow (iOS) (#902)

* fix typo in PINRequireMasterPasswordRestart (#900)

* initial commit for xf usage in autofill

* Fixed databinding for hint button

* Updated Two Factor page launch - removed unused imports

* First pass at broadcast/messenger implentation for autofill

* setting theme in extension using theme manager

* extension app resources

* App resources from main app

* fix ref to twoFactorPage

* apply resources to page

* load empty app for sytling in extension

* move ios renderers to ios core

* static ref to resources and GetResourceColor helper

* fix method ref

* move application.current.resources refs to helper

* switch login page alerts to device action dialogs

* run on main thread

* showDialog with device action service

* abstract action sheet to device action service

* add support for yubikey

* add yubikey iimages to extension

* support close button action

* add support to action extension

* remove empty lines

Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com>
Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>

* [Auto Logout] Update lock option to be default value (#929)

* Initial commit - make lock action default

* Removed extra whitespace

Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com>
Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
This commit is contained in:
Vincent Salucci 2020-05-29 11:26:36 -05:00 committed by GitHub
parent 39e10ff01c
commit 4c3df2e1e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 744 additions and 379 deletions

View File

@ -21,7 +21,7 @@ namespace Bit.Droid.Autofill
public class AutofillService : Android.Service.Autofill.AutofillService public class AutofillService : Android.Service.Autofill.AutofillService
{ {
private ICipherService _cipherService; private ICipherService _cipherService;
private ILockService _lockService; private IVaultTimeoutService _vaultTimeoutService;
private IStorageService _storageService; private IStorageService _storageService;
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
@ -47,13 +47,13 @@ namespace Bit.Droid.Autofill
return; return;
} }
if (_lockService == null) if (_vaultTimeoutService == null)
{ {
_lockService = ServiceContainer.Resolve<ILockService>("lockService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
} }
List<FilledItem> items = null; List<FilledItem> items = null;
var locked = await _lockService.IsLockedAsync(); var locked = await _vaultTimeoutService.IsLockedAsync();
if (!locked) if (!locked)
{ {
if (_cipherService == null) if (_cipherService == null)

View File

@ -1,4 +1,5 @@
using Android.Graphics.Drawables; using Android.Graphics.Drawables;
using Bit.App.Utilities;
using Bit.Droid.Effects; using Bit.Droid.Effects;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.Android; using Xamarin.Forms.Platform.Android;
@ -13,7 +14,7 @@ namespace Bit.Droid.Effects
if (Control is Android.Widget.Button button) if (Control is Android.Widget.Button button)
{ {
var gd = new GradientDrawable(); var gd = new GradientDrawable();
gd.SetColor(((Color)Application.Current.Resources["FabColor"]).ToAndroid()); gd.SetColor(ThemeManager.GetResourceColor("FabColor").ToAndroid());
gd.SetCornerRadius(100); gd.SetCornerRadius(100);
button.SetBackground(gd); button.SetBackground(gd);

View File

@ -37,7 +37,7 @@ namespace Bit.Droid
private IAppIdService _appIdService; private IAppIdService _appIdService;
private IStorageService _storageService; private IStorageService _storageService;
private IEventService _eventService; private IEventService _eventService;
private PendingIntent _lockAlarmPendingIntent; private PendingIntent _vaultTimeoutAlarmPendingIntent;
private PendingIntent _clearClipboardPendingIntent; private PendingIntent _clearClipboardPendingIntent;
private PendingIntent _eventUploadPendingIntent; private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions; private AppOptions _appOptions;
@ -51,7 +51,7 @@ namespace Bit.Droid
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent, _eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
PendingIntentFlags.UpdateCurrent); PendingIntentFlags.UpdateCurrent);
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver)); var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
_lockAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent, _vaultTimeoutAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
PendingIntentFlags.UpdateCurrent); PendingIntentFlags.UpdateCurrent);
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver)); var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent, _clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
@ -90,18 +90,18 @@ namespace Bit.Droid
_broadcasterService.Subscribe(_activityKey, (message) => _broadcasterService.Subscribe(_activityKey, (message) =>
{ {
if (message.Command == "scheduleLockTimer") if (message.Command == "scheduleVaultTimeoutTimer")
{ {
var alarmManager = GetSystemService(AlarmService) as AlarmManager; var alarmManager = GetSystemService(AlarmService) as AlarmManager;
var lockOptionMinutes = (int)message.Data; var vaultTimeoutMinutes = (int)message.Data;
var lockOptionMs = lockOptionMinutes * 60000; var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + lockOptionMs + 10; var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + vaultTimeoutMs + 10;
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _lockAlarmPendingIntent); alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _vaultTimeoutAlarmPendingIntent);
} }
else if (message.Command == "cancelLockTimer") else if (message.Command == "cancelVaultTimeoutTimer")
{ {
var alarmManager = GetSystemService(AlarmService) as AlarmManager; var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Cancel(_lockAlarmPendingIntent); alarmManager.Cancel(_vaultTimeoutAlarmPendingIntent);
} }
else if (message.Command == "startEventTimer") else if (message.Command == "startEventTimer")
{ {

View File

@ -9,8 +9,8 @@ namespace Bit.Droid.Receivers
{ {
public async override void OnReceive(Context context, Intent intent) public async override void OnReceive(Context context, Intent intent)
{ {
var lockService = ServiceContainer.Resolve<ILockService>("lockService"); var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
await lockService.CheckLockAsync(); await vaultTimeoutService.CheckVaultTimeoutAsync();
} }
} }
} }

View File

@ -7,6 +7,7 @@ using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Android.Widget; using Android.Widget;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers; using Bit.Droid.Renderers;
using FFImageLoading; using FFImageLoading;
using FFImageLoading.Views; using FFImageLoading.Views;
@ -42,19 +43,15 @@ namespace Bit.Droid.Renderers
} }
if (_textColor == default(Android.Graphics.Color)) if (_textColor == default(Android.Graphics.Color))
{ {
_textColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["TextColor"]) _textColor = ThemeManager.GetResourceColor("TextColor").ToAndroid();
.ToAndroid();
} }
if (_mutedColor == default(Android.Graphics.Color)) if (_mutedColor == default(Android.Graphics.Color))
{ {
_mutedColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["MutedColor"]) _mutedColor = ThemeManager.GetResourceColor("MutedColor").ToAndroid();
.ToAndroid();
} }
if (_disabledIconColor == default(Android.Graphics.Color)) if (_disabledIconColor == default(Android.Graphics.Color))
{ {
_disabledIconColor = _disabledIconColor = ThemeManager.GetResourceColor("DisabledIconColor").ToAndroid();
((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["DisabledIconColor"])
.ToAndroid();
} }
var cipherCell = item as CipherViewCell; var cipherCell = item as CipherViewCell;

View File

@ -569,6 +569,13 @@ namespace Bit.Droid.Services
return result.Task; return result.Task;
} }
public async Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction,
params string[] buttons)
{
return await Xamarin.Forms.Application.Current.MainPage.DisplayActionSheet(
title, cancel, destruction, buttons);
}
public void Autofill(CipherView cipher) public void Autofill(CipherView cipher)
{ {
var activity = (MainActivity)CrossCurrentActivity.Current.Activity; var activity = (MainActivity)CrossCurrentActivity.Current.Activity;

View File

@ -32,6 +32,7 @@ namespace Bit.App.Abstractions
int SystemMajorVersion(); int SystemMajorVersion();
string SystemModel(); string SystemModel();
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons); Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
void Autofill(CipherView cipher); void Autofill(CipherView cipher);
void CloseAutofill(); void CloseAutofill();
void Background(); void Background();

View File

@ -22,7 +22,7 @@ namespace Bit.App
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly ILockService _lockService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly ITokenService _tokenService; private readonly ITokenService _tokenService;
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
@ -37,18 +37,22 @@ namespace Bit.App
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly IStorageService _secureStorageService; private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly AppOptions _appOptions;
private static bool _isResumed; private static bool _isResumed;
public App(AppOptions appOptions) public App(AppOptions appOptions)
{ {
_appOptions = appOptions ?? new AppOptions(); Options = appOptions ?? new AppOptions();
if (Options.EmptyApp)
{
Current = this;
return;
}
_userService = ServiceContainer.Resolve<IUserService>("userService"); _userService = ServiceContainer.Resolve<IUserService>("userService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_tokenService = ServiceContainer.Resolve<ITokenService>("tokenService"); _tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService"); _cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
@ -95,7 +99,7 @@ namespace Bit.App
} }
else if (message.Command == "lockVault") else if (message.Command == "lockVault")
{ {
await _lockService.LockAsync(true); await _vaultTimeoutService.LockAsync(true);
} }
else if (message.Command == "logout") else if (message.Command == "logout")
{ {
@ -139,12 +143,12 @@ namespace Bit.App
} }
if (message.Command == "popAllAndGoToTabMyVault") if (message.Command == "popAllAndGoToTabMyVault")
{ {
_appOptions.MyVaultTile = false; Options.MyVaultTile = false;
tabsPage.ResetToVaultPage(); tabsPage.ResetToVaultPage();
} }
else else
{ {
_appOptions.GeneratorTile = false; Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage(); tabsPage.ResetToGeneratorPage();
} }
} }
@ -153,13 +157,15 @@ namespace Bit.App
}); });
} }
public AppOptions Options { get; private set; }
protected async override void OnStart() protected async override void OnStart()
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnStart"); System.Diagnostics.Debug.WriteLine("XF App: OnStart");
await ClearCacheIfNeededAsync(); await ClearCacheIfNeededAsync();
await TryClearCiphersCacheAsync(); await TryClearCiphersCacheAsync();
Prime(); Prime();
if (string.IsNullOrWhiteSpace(_appOptions.Uri)) if (string.IsNullOrWhiteSpace(Options.Uri))
{ {
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService, var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_storageService); _storageService);
@ -177,7 +183,7 @@ namespace Bit.App
_isResumed = false; _isResumed = false;
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
var isLocked = await _lockService.IsLockedAsync(); var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked) if (!isLocked)
{ {
await _storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow); await _storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow);
@ -199,13 +205,13 @@ namespace Bit.App
private async Task SleptAsync() private async Task SleptAsync()
{ {
await HandleLockingAsync(); await HandleVaultTimeoutAsync();
_messagingService.Send("stopEventTimer"); _messagingService.Send("stopEventTimer");
} }
private async void ResumedAsync() private async void ResumedAsync()
{ {
_messagingService.Send("cancelLockTimer"); _messagingService.Send("cancelVaultTimeoutTimer");
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
await ClearCacheIfNeededAsync(); await ClearCacheIfNeededAsync();
await TryClearCiphersCacheAsync(); await TryClearCiphersCacheAsync();
@ -238,9 +244,9 @@ namespace Bit.App
_folderService.ClearAsync(userId), _folderService.ClearAsync(userId),
_collectionService.ClearAsync(userId), _collectionService.ClearAsync(userId),
_passwordGenerationService.ClearAsync(), _passwordGenerationService.ClearAsync(),
_lockService.ClearAsync(), _vaultTimeoutService.ClearAsync(),
_stateService.PurgeAsync()); _stateService.PurgeAsync());
_lockService.FingerprintLocked = true; _vaultTimeoutService.FingerprintLocked = true;
_searchService.ClearIndex(); _searchService.ClearIndex();
_authService.LogOut(() => _authService.LogOut(() =>
{ {
@ -257,32 +263,32 @@ namespace Bit.App
var authed = await _userService.IsAuthenticatedAsync(); var authed = await _userService.IsAuthenticatedAsync();
if (authed) if (authed)
{ {
if (await _lockService.IsLockedAsync()) if (await _vaultTimeoutService.IsLockedAsync())
{ {
Current.MainPage = new NavigationPage(new LockPage(_appOptions)); Current.MainPage = new NavigationPage(new LockPage(Options));
} }
else if (_appOptions.FromAutofillFramework && _appOptions.SaveType.HasValue) else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{ {
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: _appOptions)); Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
} }
else if (_appOptions.Uri != null) else if (Options.Uri != null)
{ {
Current.MainPage = new NavigationPage(new AutofillCiphersPage(_appOptions)); Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
} }
else else
{ {
Current.MainPage = new TabsPage(_appOptions); Current.MainPage = new TabsPage(Options);
} }
} }
else else
{ {
Current.MainPage = new HomePage(_appOptions); Current.MainPage = new HomePage(Options);
} }
} }
private async Task HandleLockingAsync() private async Task HandleVaultTimeoutAsync()
{ {
if (await _lockService.IsLockedAsync()) if (await _vaultTimeoutService.IsLockedAsync())
{ {
return; return;
} }
@ -291,19 +297,28 @@ namespace Bit.App
{ {
return; return;
} }
var lockOption = _platformUtilsService.LockTimeout(); // Will only ever be null - look to remove this in the future
if (lockOption == null) var vaultTimeout = _platformUtilsService.LockTimeout();
if (vaultTimeout == null)
{ {
lockOption = await _storageService.GetAsync<int?>(Constants.LockOptionKey); vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
} }
lockOption = lockOption.GetValueOrDefault(-1); vaultTimeout = vaultTimeout.GetValueOrDefault(-1);
if (lockOption > 0) if (vaultTimeout > 0)
{ {
_messagingService.Send("scheduleLockTimer", lockOption.Value); _messagingService.Send("scheduleVaultTimeoutTimer", vaultTimeout.Value);
} }
else if (lockOption == 0) else if (vaultTimeout == 0)
{ {
await _lockService.LockAsync(true); var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);
if (action == "logOut")
{
await _vaultTimeoutService.LogOutAsync();
}
else
{
await _vaultTimeoutService.LockAsync(true);
}
} }
} }
@ -318,14 +333,14 @@ namespace Bit.App
private void SetTabsPageFromAutofill(bool isLocked) private void SetTabsPageFromAutofill(bool isLocked)
{ {
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(_appOptions.Uri) && if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
!_appOptions.FromAutofillFramework) !Options.FromAutofillFramework)
{ {
Task.Run(() => Task.Run(() =>
{ {
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
{ {
_appOptions.Uri = null; Options.Uri = null;
if (isLocked) if (isLocked)
{ {
Current.MainPage = new NavigationPage(new LockPage()); Current.MainPage = new NavigationPage(new LockPage());
@ -352,7 +367,7 @@ namespace Bit.App
{ {
InitializeComponent(); InitializeComponent();
SetCulture(); SetCulture();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android); ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
Current.MainPage = new HomePage(); Current.MainPage = new HomePage();
var mainPageTask = SetMainPageAsync(); var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init(); ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
@ -394,8 +409,8 @@ namespace Bit.App
await _stateService.PurgeAsync(); await _stateService.PurgeAsync();
if (autoPromptFingerprint && Device.RuntimePlatform == Device.iOS) if (autoPromptFingerprint && Device.RuntimePlatform == Device.iOS)
{ {
var lockOptions = await _storageService.GetAsync<int?>(Constants.LockOptionKey); var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
if (lockOptions == 0) if (vaultTimeout == 0)
{ {
autoPromptFingerprint = false; autoPromptFingerprint = false;
} }
@ -430,7 +445,7 @@ namespace Bit.App
} }
} }
await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock); await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock);
var lockPage = new LockPage(_appOptions, autoPromptFingerprint); var lockPage = new LockPage(Options, autoPromptFingerprint);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage)); Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
} }
} }

View File

@ -18,5 +18,6 @@ namespace Bit.App.Models
public string SaveCardExpMonth { get; set; } public string SaveCardExpMonth { get; set; }
public string SaveCardExpYear { get; set; } public string SaveCardExpYear { get; set; }
public string SaveCardCode { get; set; } public string SaveCardCode { get; set; }
public bool EmptyApp { get; set; }
} }
} }

View File

@ -16,7 +16,7 @@ namespace Bit.App.Pages
{ {
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly ILockService _lockService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly IUserService _userService; private readonly IUserService _userService;
@ -39,7 +39,7 @@ namespace Bit.App.Pages
{ {
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService"); _cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_userService = ServiceContainer.Resolve<IUserService>("userService"); _userService = ServiceContainer.Resolve<IUserService>("userService");
@ -102,9 +102,9 @@ namespace Bit.App.Pages
public async Task InitAsync(bool autoPromptFingerprint) public async Task InitAsync(bool autoPromptFingerprint)
{ {
_pinSet = await _lockService.IsPinLockSetAsync(); _pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_pinSet.Item1 && _lockService.PinProtectedKey != null) || _pinSet.Item2; PinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
FingerprintLock = await _lockService.IsFingerprintLockSetAsync(); FingerprintLock = await _vaultTimeoutService.IsFingerprintLockSetAsync();
_email = await _userService.GetEmailAsync(); _email = await _userService.GetEmailAsync();
var webVault = _environmentService.GetWebVaultUrl(); var webVault = _environmentService.GetWebVaultUrl();
if (string.IsNullOrWhiteSpace(webVault)) if (string.IsNullOrWhiteSpace(webVault))
@ -180,7 +180,7 @@ namespace Bit.App.Pages
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
_lockService.PinProtectedKey); _vaultTimeoutService.PinProtectedKey);
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin); var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
@ -240,7 +240,7 @@ namespace Bit.App.Pages
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
_lockService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey); _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
} }
MasterPassword = string.Empty; MasterPassword = string.Empty;
await SetKeyAndContinueAsync(key); await SetKeyAndContinueAsync(key);
@ -290,7 +290,7 @@ namespace Bit.App.Pages
page.MasterPasswordEntry.Focus(); page.MasterPasswordEntry.Focus();
} }
}); });
_lockService.FingerprintLocked = !success; _vaultTimeoutService.FingerprintLocked = !success;
if (success) if (success)
{ {
await DoContinueAsync(); await DoContinueAsync();
@ -309,7 +309,7 @@ namespace Bit.App.Pages
private async Task DoContinueAsync() private async Task DoContinueAsync()
{ {
_lockService.FingerprintLocked = false; _vaultTimeoutService.FingerprintLocked = false;
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey); var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault()); await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
_messagingService.Send("unlocked"); _messagingService.Send("unlocked");

View File

@ -73,8 +73,8 @@
AutomationProperties.Name="{u:I18n ToggleVisibility}" /> AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid> </Grid>
</StackLayout> </StackLayout>
<StackLayout Padding="10, 0"> <StackLayout Padding="10, 0" IsVisible="{Binding HideHintButton, Converter={StaticResource inverseBool}}">
<Button Text="{u:I18n GetPasswordHint}" Clicked="Hint_Clicked"></Button> <Button Text="{u:I18n GetPasswordHint}" Clicked="Hint_Clicked" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>

View File

@ -26,6 +26,11 @@ namespace Bit.App.Pages
_vm.Page = this; _vm.Page = this;
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync()); _vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LoggedInAction = () => Device.BeginInvokeOnMainThread(async () => await LoggedInAsync()); _vm.LoggedInAction = () => Device.BeginInvokeOnMainThread(async () => await LoggedInAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
_vm.Email = email; _vm.Email = email;
MasterPasswordEntry = _masterPassword; MasterPasswordEntry = _masterPassword;
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
@ -73,8 +78,7 @@ namespace Bit.App.Pages
{ {
if (DoOnce()) if (DoOnce())
{ {
_messagingService.Send("showStatusBar", false); _vm.CloseAction();
await Navigation.PopModalAsync();
} }
} }

View File

@ -25,6 +25,7 @@ namespace Bit.App.Pages
private bool _showPassword; private bool _showPassword;
private string _email; private string _email;
private string _masterPassword; private string _masterPassword;
private bool _hideHintButton;
public LoginPageViewModel() public LoginPageViewModel()
{ {
@ -68,6 +69,13 @@ namespace Bit.App.Pages
public bool RememberEmail { get; set; } public bool RememberEmail { get; set; }
public Action StartTwoFactorAction { get; set; } public Action StartTwoFactorAction { get; set; }
public Action LoggedInAction { get; set; } public Action LoggedInAction { get; set; }
public Action CloseAction { get; set; }
public bool HideHintButton
{
get => _hideHintButton;
set => SetProperty(ref _hideHintButton, value);
}
public async Task InitAsync() public async Task InitAsync()
{ {
@ -89,20 +97,23 @@ namespace Bit.App.Pages
} }
if (string.IsNullOrWhiteSpace(Email)) if (string.IsNullOrWhiteSpace(Email))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress), string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress),
AppResources.AnErrorHasOccurred,
AppResources.Ok); AppResources.Ok);
return; return;
} }
if (!Email.Contains("@")) if (!Email.Contains("@"))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok); await _platformUtilsService.ShowDialogAsync(AppResources.InvalidEmail, AppResources.AnErrorHasOccurred,
AppResources.Ok);
return; return;
} }
if (string.IsNullOrWhiteSpace(MasterPassword)) if (string.IsNullOrWhiteSpace(MasterPassword))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.AnErrorHasOccurred,
AppResources.Ok); AppResources.Ok);
return; return;
} }

View File

@ -30,6 +30,7 @@ namespace Bit.App.Pages
_vm = BindingContext as TwoFactorPageViewModel; _vm = BindingContext as TwoFactorPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.TwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthAsync()); _vm.TwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthAsync());
_vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView; DuoWebView = _duoWebView;
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
@ -144,7 +145,7 @@ namespace Bit.App.Pages
{ {
if (DoOnce()) if (DoOnce())
{ {
await Navigation.PopModalAsync(); _vm.CloseAction();
} }
} }

View File

@ -90,6 +90,7 @@ namespace Bit.App.Pages
} }
public Command SubmitCommand { get; } public Command SubmitCommand { get; }
public Action TwoFactorAction { get; set; } public Action TwoFactorAction { get; set; }
public Action CloseAction { get; set; }
public void Init() public void Init()
{ {
@ -228,8 +229,8 @@ namespace Bit.App.Pages
var supportedProviders = _authService.GetSupportedTwoFactorProviders(); var supportedProviders = _authService.GetSupportedTwoFactorProviders();
var options = supportedProviders.Select(p => p.Name).ToList(); var options = supportedProviders.Select(p => p.Name).ToList();
options.Add(AppResources.RecoveryCodeTitle); options.Add(AppResources.RecoveryCodeTitle);
var method = await Page.DisplayActionSheet(AppResources.TwoStepLoginOptions, AppResources.Cancel, var method = await _deviceActionService.DisplayActionSheetAsync(AppResources.TwoStepLoginOptions,
null, options.ToArray()); AppResources.Cancel, null, options.ToArray());
if (method == AppResources.RecoveryCodeTitle) if (method == AppResources.RecoveryCodeTitle)
{ {
_platformUtilsService.LaunchUri("https://help.bitwarden.com/article/lost-two-step-device/"); _platformUtilsService.LaunchUri("https://help.bitwarden.com/article/lost-two-step-device/");

View File

@ -129,9 +129,13 @@ namespace Bit.App.Pages
{ {
await _vm.LockAsync(); await _vm.LockAsync();
} }
else if (item.Name == AppResources.LockOptions) else if (item.Name == AppResources.VaultTimeout)
{ {
await _vm.LockOptionsAsync(); await _vm.VaultTimeoutAsync();
}
else if (item.Name == AppResources.VaultTimeoutAction)
{
await _vm.VaultTimeoutActionAsync();
} }
else if (item.Name == AppResources.UnlockWithPIN) else if (item.Name == AppResources.UnlockWithPIN)
{ {

View File

@ -1,4 +1,5 @@
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities;
using System.Collections.Generic; using System.Collections.Generic;
using Xamarin.Forms; using Xamarin.Forms;
@ -11,7 +12,7 @@ namespace Bit.App.Pages
public string SubLabel { get; set; } public string SubLabel { get; set; }
public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled; public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled;
public Color SubLabelColor => SubLabelTextEnabled ? public Color SubLabelColor => SubLabelTextEnabled ?
(Color)Application.Current.Resources["SuccessColor"] : ThemeManager.GetResourceColor("SuccessColor") :
(Color)Application.Current.Resources["MutedColor"]; ThemeManager.GetResourceColor("MutedColor");
} }
} }

View File

@ -19,7 +19,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IEnvironmentService _environmentService; private readonly IEnvironmentService _environmentService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly ILockService _lockService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
@ -27,20 +27,27 @@ namespace Bit.App.Pages
private bool _pin; private bool _pin;
private bool _fingerprint; private bool _fingerprint;
private string _lastSyncDate; private string _lastSyncDate;
private string _lockOptionValue; private string _vaultTimeoutDisplayValue;
private List<KeyValuePair<string, int?>> _lockOptions = private string _vaultTimeoutActionDisplayValue;
private List<KeyValuePair<string, int?>> _vaultTimeouts =
new List<KeyValuePair<string, int?>> new List<KeyValuePair<string, int?>>
{ {
new KeyValuePair<string, int?>(AppResources.LockOptionImmediately, 0), new KeyValuePair<string, int?>(AppResources.Immediately, 0),
new KeyValuePair<string, int?>(AppResources.LockOption1Minute, 1), new KeyValuePair<string, int?>(AppResources.OneMinute, 1),
new KeyValuePair<string, int?>(AppResources.LockOption5Minutes, 5), new KeyValuePair<string, int?>(AppResources.FiveMinutes, 5),
new KeyValuePair<string, int?>(AppResources.LockOption15Minutes, 15), new KeyValuePair<string, int?>(AppResources.FifteenMinutes, 15),
new KeyValuePair<string, int?>(AppResources.LockOption30Minutes, 30), new KeyValuePair<string, int?>(AppResources.ThirtyMinutes, 30),
new KeyValuePair<string, int?>(AppResources.LockOption1Hour, 60), new KeyValuePair<string, int?>(AppResources.OneHour, 60),
new KeyValuePair<string, int?>(AppResources.LockOption4Hours, 240), new KeyValuePair<string, int?>(AppResources.FourHours, 240),
new KeyValuePair<string, int?>(AppResources.LockOptionOnRestart, -1), new KeyValuePair<string, int?>(AppResources.OnRestart, -1),
new KeyValuePair<string, int?>(AppResources.Never, null), new KeyValuePair<string, int?>(AppResources.Never, null),
}; };
private List<KeyValuePair<string, string>> _vaultTimeoutActions =
new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(AppResources.Lock, "lock"),
new KeyValuePair<string, string>(AppResources.LogOut, "logOut"),
};
public SettingsPageViewModel() public SettingsPageViewModel()
{ {
@ -50,7 +57,7 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService"); _environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
@ -70,11 +77,13 @@ namespace Bit.App.Pages
_lastSyncDate = string.Format("{0} {1}", lastSync.Value.ToShortDateString(), _lastSyncDate = string.Format("{0} {1}", lastSync.Value.ToShortDateString(),
lastSync.Value.ToShortTimeString()); lastSync.Value.ToShortTimeString());
} }
var option = await _storageService.GetAsync<int?>(Constants.LockOptionKey); var timeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
_lockOptionValue = _lockOptions.FirstOrDefault(o => o.Value == option).Key; _vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == timeout).Key;
var pinSet = await _lockService.IsPinLockSetAsync(); var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey) ?? "lock";
_vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2; _pin = pinSet.Item1 || pinSet.Item2;
_fingerprint = await _lockService.IsFingerprintLockSetAsync(); _fingerprint = await _vaultTimeoutService.IsFingerprintLockSetAsync();
BuildList(); BuildList();
} }
@ -179,21 +188,48 @@ namespace Bit.App.Pages
public async Task LockAsync() public async Task LockAsync()
{ {
await _lockService.LockAsync(true, true); await _vaultTimeoutService.LockAsync(true, true);
} }
public async Task LockOptionsAsync() public async Task VaultTimeoutAsync()
{ {
var options = _lockOptions.Select(o => o.Key == _lockOptionValue ? $"✓ {o.Key}" : o.Key).ToArray(); var options = _vaultTimeouts.Select(o => o.Key == _vaultTimeoutDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
var selection = await Page.DisplayActionSheet(AppResources.LockOptions, AppResources.Cancel, null, options); var selection = await Page.DisplayActionSheet(AppResources.VaultTimeout, AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel) if (selection == null || selection == AppResources.Cancel)
{ {
return; return;
} }
var cleanSelection = selection.Replace("✓ ", string.Empty); var cleanSelection = selection.Replace("✓ ", string.Empty);
var selectionOption = _lockOptions.FirstOrDefault(o => o.Key == cleanSelection); var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
_lockOptionValue = selectionOption.Key; _vaultTimeoutDisplayValue = selectionOption.Key;
await _lockService.SetLockOptionAsync(selectionOption.Value); await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(selectionOption.Value,
GetVaultTimeoutActionFromKey(_vaultTimeoutActionDisplayValue));
BuildList();
}
public async Task VaultTimeoutActionAsync()
{
var options = _vaultTimeoutActions.Select(o => o.Key == _vaultTimeoutActionDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction, AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel)
{
return;
}
var cleanSelection = selection.Replace("✓ ", string.Empty);
if (cleanSelection == AppResources.LogOut)
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.VaultTimeoutLogOutConfirmation,
AppResources.Warning, AppResources.Yes, AppResources.Cancel);
if (!confirmed)
{
// Reset to lock and continue process as if lock were selected
cleanSelection = AppResources.Lock;
}
}
var selectionOption = _vaultTimeoutActions.FirstOrDefault(o => o.Key == cleanSelection);
_vaultTimeoutActionDisplayValue = selectionOption.Key;
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(GetVaultTimeoutFromKey(_vaultTimeoutDisplayValue),
selectionOption.Value);
BuildList(); BuildList();
} }
@ -223,7 +259,7 @@ namespace Bit.App.Pages
{ {
var encPin = await _cryptoService.EncryptAsync(pin); var encPin = await _cryptoService.EncryptAsync(pin);
await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString); await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString);
_lockService.PinProtectedKey = pinProtectedKey; _vaultTimeoutService.PinProtectedKey = pinProtectedKey;
} }
else else
{ {
@ -238,7 +274,7 @@ namespace Bit.App.Pages
if (!_pin) if (!_pin)
{ {
await _cryptoService.ClearPinProtectedKeyAsync(); await _cryptoService.ClearPinProtectedKeyAsync();
await _lockService.ClearAsync(); await _vaultTimeoutService.ClearAsync();
} }
BuildList(); BuildList();
} }
@ -267,7 +303,7 @@ namespace Bit.App.Pages
{ {
await _storageService.RemoveAsync(Constants.FingerprintUnlockKey); await _storageService.RemoveAsync(Constants.FingerprintUnlockKey);
} }
_lockService.FingerprintLocked = false; _vaultTimeoutService.FingerprintLocked = false;
await _cryptoService.ToggleKeyAsync(); await _cryptoService.ToggleKeyAsync();
BuildList(); BuildList();
} }
@ -312,7 +348,8 @@ namespace Bit.App.Pages
}; };
var securityItems = new List<SettingsPageListItem> var securityItems = new List<SettingsPageListItem>
{ {
new SettingsPageListItem { Name = AppResources.LockOptions, SubLabel = _lockOptionValue }, new SettingsPageListItem { Name = AppResources.VaultTimeout, SubLabel = _vaultTimeoutDisplayValue },
new SettingsPageListItem { Name = AppResources.VaultTimeoutAction, SubLabel = _vaultTimeoutActionDisplayValue },
new SettingsPageListItem new SettingsPageListItem
{ {
Name = AppResources.UnlockWithPIN, Name = AppResources.UnlockWithPIN,
@ -338,7 +375,7 @@ namespace Bit.App.Pages
Name = string.Format(AppResources.UnlockWith, fingerprintName), Name = string.Format(AppResources.UnlockWith, fingerprintName),
SubLabel = _fingerprint ? AppResources.Enabled : AppResources.Disabled SubLabel = _fingerprint ? AppResources.Enabled : AppResources.Disabled
}; };
securityItems.Insert(1, item); securityItems.Insert(2, item);
} }
var accountItems = new List<SettingsPageListItem> var accountItems = new List<SettingsPageListItem>
{ {
@ -370,5 +407,15 @@ namespace Bit.App.Pages
new SettingsPageListGroup(otherItems, AppResources.Other, doUpper) new SettingsPageListGroup(otherItems, AppResources.Other, doUpper)
}); });
} }
private string GetVaultTimeoutActionFromKey(string key)
{
return _vaultTimeoutActions.FirstOrDefault(o => o.Key == key).Value;
}
private int? GetVaultTimeoutFromKey(string key)
{
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
}
} }
} }

View File

@ -19,7 +19,7 @@ namespace Bit.App.Pages
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IPushNotificationService _pushNotificationService; private readonly IPushNotificationService _pushNotificationService;
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly ILockService _lockService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly GroupingsPageViewModel _vm; private readonly GroupingsPageViewModel _vm;
@ -39,7 +39,7 @@ namespace Bit.App.Pages
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService"); _pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService"); _cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vm = BindingContext as GroupingsPageViewModel; _vm = BindingContext as GroupingsPageViewModel;
@ -247,7 +247,7 @@ namespace Bit.App.Pages
private async void Lock_Clicked(object sender, EventArgs e) private async void Lock_Clicked(object sender, EventArgs e)
{ {
await _lockService.LockAsync(true, true); await _vaultTimeoutService.LockAsync(true, true);
} }
private async void Exit_Clicked(object sender, EventArgs e) private async void Exit_Clicked(object sender, EventArgs e)

View File

@ -39,7 +39,7 @@ namespace Bit.App.Pages
private readonly ICollectionService _collectionService; private readonly ICollectionService _collectionService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly ILockService _lockService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
@ -52,7 +52,7 @@ namespace Bit.App.Pages
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService"); _collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService"); _userService = ServiceContainer.Resolve<IUserService>("userService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@ -145,7 +145,7 @@ namespace Bit.App.Pages
{ {
return; return;
} }
if (await _lockService.IsLockedAsync()) if (await _vaultTimeoutService.IsLockedAsync())
{ {
return; return;
} }

View File

@ -174,7 +174,7 @@ namespace Bit.App.Pages
fs.Spans.Add(new Span fs.Spans.Add(new Span
{ {
Text = string.Format(" {0}", Cipher.PasswordHistory.Count.ToString()), Text = string.Format(" {0}", Cipher.PasswordHistory.Count.ToString()),
TextColor = (Color)Application.Current.Resources["PrimaryColor"] TextColor = ThemeManager.GetResourceColor("PrimaryColor")
}); });
return fs; return fs;
} }
@ -209,7 +209,7 @@ namespace Bit.App.Pages
set set
{ {
SetProperty(ref _totpLow, value); SetProperty(ref _totpLow, value);
Page.Resources["textTotp"] = Application.Current.Resources[value ? "text-danger" : "text-default"]; Page.Resources["textTotp"] = ThemeManager.Resources()[value ? "text-danger" : "text-default"];
} }
} }
public bool IsDeleted => Cipher.IsDeleted; public bool IsDeleted => Cipher.IsDeleted;

View File

@ -1,7 +1,6 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.
@ -755,39 +754,51 @@ namespace Bit.App.Resources {
} }
} }
public static string LockOption15Minutes { public static string FifteenMinutes {
get { get {
return ResourceManager.GetString("LockOption15Minutes", resourceCulture); return ResourceManager.GetString("FifteenMinutes", resourceCulture);
} }
} }
public static string LockOption1Hour { public static string OneHour {
get { get {
return ResourceManager.GetString("LockOption1Hour", resourceCulture); return ResourceManager.GetString("OneHour", resourceCulture);
} }
} }
public static string LockOption1Minute { public static string OneMinute {
get { get {
return ResourceManager.GetString("LockOption1Minute", resourceCulture); return ResourceManager.GetString("OneMinute", resourceCulture);
} }
} }
public static string LockOption4Hours { public static string FourHours {
get { get {
return ResourceManager.GetString("LockOption4Hours", resourceCulture); return ResourceManager.GetString("FourHours", resourceCulture);
} }
} }
public static string LockOptionImmediately { public static string Immediately {
get { get {
return ResourceManager.GetString("LockOptionImmediately", resourceCulture); return ResourceManager.GetString("Immediately", resourceCulture);
} }
} }
public static string LockOptions { public static string VaultTimeout {
get { get {
return ResourceManager.GetString("LockOptions", resourceCulture); return ResourceManager.GetString("VaultTimeout", resourceCulture);
}
}
public static string VaultTimeoutAction {
get {
return ResourceManager.GetString("VaultTimeoutAction", resourceCulture);
}
}
public static string VaultTimeoutLogOutConfirmation {
get {
return ResourceManager.GetString("VaultTimeoutLogOutConfirmation", resourceCulture);
} }
} }
@ -2501,15 +2512,9 @@ namespace Bit.App.Resources {
} }
} }
public static string LockOption30Minutes { public static string ThirtyMinutes {
get { get {
return ResourceManager.GetString("LockOption30Minutes", resourceCulture); return ResourceManager.GetString("ThirtyMinutes", resourceCulture);
}
}
public static string LockOption5Minutes {
get {
return ResourceManager.GetString("LockOption5Minutes", resourceCulture);
} }
} }
@ -2555,12 +2560,6 @@ namespace Bit.App.Resources {
} }
} }
public static string OneMinute {
get {
return ResourceManager.GetString("OneMinute", resourceCulture);
}
}
public static string TenSeconds { public static string TenSeconds {
get { get {
return ResourceManager.GetString("TenSeconds", resourceCulture); return ResourceManager.GetString("TenSeconds", resourceCulture);
@ -2687,9 +2686,9 @@ namespace Bit.App.Resources {
} }
} }
public static string LockOptionOnRestart { public static string OnRestart {
get { get {
return ResourceManager.GetString("LockOptionOnRestart", resourceCulture); return ResourceManager.GetString("OnRestart", resourceCulture);
} }
} }

View File

@ -1517,7 +1517,7 @@
<value>Are you sure you want to exit Bitwarden?</value> <value>Are you sure you want to exit Bitwarden?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Black</value> <value>Black</value>

View File

@ -1517,7 +1517,7 @@
<value>Sind Sie sicher, dass Sie Bitwarden verlassen möchten?</value> <value>Sind Sie sicher, dass Sie Bitwarden verlassen möchten?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Schwarz</value> <value>Schwarz</value>

View File

@ -1517,7 +1517,7 @@
<value>¿Estás seguro de que deseas salir de Bitwarden?</value> <value>¿Estás seguro de que deseas salir de Bitwarden?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Negro</value> <value>Negro</value>

View File

@ -1517,7 +1517,7 @@
<value>Kas soovid tõesti Bitwardeni sulgeda?</value> <value>Kas soovid tõesti Bitwardeni sulgeda?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Must</value> <value>Must</value>

View File

@ -1517,7 +1517,7 @@
<value>آیا مطمئنید که می‌خواهید از Bitwarden خارج شوید؟</value> <value>آیا مطمئنید که می‌خواهید از Bitwarden خارج شوید؟</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>سیاه</value> <value>سیاه</value>

View File

@ -1517,7 +1517,7 @@
<value>Biztos vagy benne, hogy kilépsz?</value> <value>Biztos vagy benne, hogy kilépsz?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Fekete</value> <value>Fekete</value>

View File

@ -1517,7 +1517,7 @@
<value>Apakah anda yakin ingin keluar dari Bitwarden?</value> <value>Apakah anda yakin ingin keluar dari Bitwarden?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Hitam</value> <value>Hitam</value>

View File

@ -1517,7 +1517,7 @@
<value>Sei sicuro di voler uscire da Bitwarden?</value> <value>Sei sicuro di voler uscire da Bitwarden?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Nero</value> <value>Nero</value>

View File

@ -1517,7 +1517,7 @@
<value>정말로 Bitwarden에서 나가시려는 건가요?</value> <value>정말로 Bitwarden에서 나가시려는 건가요?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>검은 테마</value> <value>검은 테마</value>

View File

@ -1517,7 +1517,7 @@
<value>Er du sikker på at du vil lukke Bitwarden?</value> <value>Er du sikker på at du vil lukke Bitwarden?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Svart</value> <value>Svart</value>

View File

@ -518,23 +518,29 @@
<data name="Lock" xml:space="preserve"> <data name="Lock" xml:space="preserve">
<value>Lock</value> <value>Lock</value>
</data> </data>
<data name="LockOption15Minutes" xml:space="preserve"> <data name="FifteenMinutes" xml:space="preserve">
<value>15 minutes</value> <value>15 minutes</value>
</data> </data>
<data name="LockOption1Hour" xml:space="preserve"> <data name="OneHour" xml:space="preserve">
<value>1 hour</value> <value>1 hour</value>
</data> </data>
<data name="LockOption1Minute" xml:space="preserve"> <data name="OneMinute" xml:space="preserve">
<value>1 minute</value> <value>1 minute</value>
</data> </data>
<data name="LockOption4Hours" xml:space="preserve"> <data name="FourHours" xml:space="preserve">
<value>4 hours</value> <value>4 hours</value>
</data> </data>
<data name="LockOptionImmediately" xml:space="preserve"> <data name="Immediately" xml:space="preserve">
<value>Immediately</value> <value>Immediately</value>
</data> </data>
<data name="LockOptions" xml:space="preserve"> <data name="VaultTimeout" xml:space="preserve">
<value>Lock Options</value> <value>Vault Timeout</value>
</data>
<data name="VaultTimeoutAction" xml:space="preserve">
<value>Vault Timeout Action</value>
</data>
<data name="VaultTimeoutLogOutConfirmation" xml:space="preserve">
<value>Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?</value>
</data> </data>
<data name="LoggingIn" xml:space="preserve"> <data name="LoggingIn" xml:space="preserve">
<value>Logging in...</value> <value>Logging in...</value>
@ -1433,12 +1439,9 @@
<data name="Unlock" xml:space="preserve"> <data name="Unlock" xml:space="preserve">
<value>Unlock</value> <value>Unlock</value>
</data> </data>
<data name="LockOption30Minutes" xml:space="preserve"> <data name="ThirtyMinutes" xml:space="preserve">
<value>30 minutes</value> <value>30 minutes</value>
</data> </data>
<data name="LockOption5Minutes" xml:space="preserve">
<value>5 minutes</value>
</data>
<data name="SetPINDescription" xml:space="preserve"> <data name="SetPINDescription" xml:space="preserve">
<value>Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application.</value> <value>Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application.</value>
</data> </data>
@ -1463,9 +1466,6 @@
<data name="FiveMinutes" xml:space="preserve"> <data name="FiveMinutes" xml:space="preserve">
<value>5 minutes</value> <value>5 minutes</value>
</data> </data>
<data name="OneMinute" xml:space="preserve">
<value>1 minute</value>
</data>
<data name="TenSeconds" xml:space="preserve"> <data name="TenSeconds" xml:space="preserve">
<value>10 seconds</value> <value>10 seconds</value>
</data> </data>
@ -1535,7 +1535,7 @@
<data name="DisableSavePromptDescription" xml:space="preserve"> <data name="DisableSavePromptDescription" xml:space="preserve">
<value>The "Save Prompt" automatically prompts you to save new items to your vault whenever you enter them for the first time.</value> <value>The "Save Prompt" automatically prompts you to save new items to your vault whenever you enter them for the first time.</value>
</data> </data>
<data name="LockOptionOnRestart" xml:space="preserve"> <data name="OnRestart" xml:space="preserve">
<value>On App Restart</value> <value>On App Restart</value>
</data> </data>
<data name="AutofillServiceNotEnabled" xml:space="preserve"> <data name="AutofillServiceNotEnabled" xml:space="preserve">

View File

@ -1517,7 +1517,7 @@
<value>Sunteți sigur că doriți să ieșiți?</value> <value>Sunteți sigur că doriți să ieșiți?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Neagră</value> <value>Neagră</value>

View File

@ -1517,7 +1517,7 @@
<value>Naozaj chcete ukončiť Bitwarden?</value> <value>Naozaj chcete ukončiť Bitwarden?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Čierna</value> <value>Čierna</value>

View File

@ -1517,7 +1517,7 @@
<value>Are you sure you want to exit Bitwarden?</value> <value>Are you sure you want to exit Bitwarden?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Black</value> <value>Black</value>

View File

@ -1517,7 +1517,7 @@
<value>Are you sure you want to exit Bitwarden?</value> <value>Are you sure you want to exit Bitwarden?</value>
</data> </data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve"> <data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value> <value>Do you want to require unlocking with your master password when the application is restarted?</value>
</data> </data>
<data name="Black" xml:space="preserve"> <data name="Black" xml:space="preserve">
<value>Black</value> <value>Black</value>

View File

@ -13,7 +13,8 @@ namespace Bit.App.Services
private readonly HashSet<string> _preferenceStorageKeys = new HashSet<string> private readonly HashSet<string> _preferenceStorageKeys = new HashSet<string>
{ {
Constants.LockOptionKey, Constants.VaultTimeoutKey,
Constants.VaultTimeoutActionKey,
Constants.ThemeKey, Constants.ThemeKey,
Constants.DefaultUriMatch, Constants.DefaultUriMatch,
Constants.DisableAutoTotpCopyKey, Constants.DisableAutoTotpCopyKey,

View File

@ -18,7 +18,7 @@ namespace Bit.App.Utilities
{ {
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
var eventService = ServiceContainer.Resolve<IEventService>("eventService"); var eventService = ServiceContainer.Resolve<IEventService>("eventService");
var lockService = ServiceContainer.Resolve<ILockService>("lockService"); var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
var options = new List<string> { AppResources.View }; var options = new List<string> { AppResources.View };
if (!cipher.IsDeleted) if (!cipher.IsDeleted)
{ {
@ -67,7 +67,7 @@ namespace Bit.App.Utilities
} }
} }
var selection = await page.DisplayActionSheet(cipher.Name, AppResources.Cancel, null, options.ToArray()); var selection = await page.DisplayActionSheet(cipher.Name, AppResources.Cancel, null, options.ToArray());
if (await lockService.IsLockedAsync()) if (await vaultTimeoutService.IsLockedAsync())
{ {
platformUtilsService.ShowToast("info", null, AppResources.VaultIsLocked); platformUtilsService.ShowToast("info", null, AppResources.VaultIsLocked);
} }
@ -137,10 +137,16 @@ namespace Bit.App.Utilities
if (lastBuild == null) if (lastBuild == null)
{ {
// Installed // Installed
var currentLock = await storageService.GetAsync<int?>(Constants.LockOptionKey); var currentTimeout = await storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
if (currentLock == null) if (currentTimeout == null)
{ {
await storageService.SaveAsync(Constants.LockOptionKey, 15); 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) else if (lastBuild != currentBuild)

View File

@ -31,9 +31,9 @@ namespace Bit.App.Utilities
// First two digits of returned hex code contains the alpha, // First two digits of returned hex code contains the alpha,
// which is not supported in HTML color, so we need to cut those out. // which is not supported in HTML color, so we need to cut those out.
var normalColor = $"<span style=\"color:#{((Color)Application.Current.Resources["TextColor"]).ToHex().Substring(3)}\">"; var normalColor = $"<span style=\"color:#{ThemeManager.GetResourceColor("TextColor").ToHex().Substring(3)}\">";
var numberColor = $"<span style=\"color:#{((Color)Application.Current.Resources["PasswordNumberColor"]).ToHex().Substring(3)}\">"; var numberColor = $"<span style=\"color:#{ThemeManager.GetResourceColor("PasswordNumberColor").ToHex().Substring(3)}\">";
var specialColor = $"<span style=\"color:#{((Color)Application.Current.Resources["PasswordSpecialColor"]).ToHex().Substring(3)}\">"; var specialColor = $"<span style=\"color:#{ThemeManager.GetResourceColor("PasswordSpecialColor").ToHex().Substring(3)}\">";
var result = string.Empty; var result = string.Empty;
// iOS won't hide the zero-width space char without these div attrs, but Android won't respect // iOS won't hide the zero-width space char without these div attrs, but Android won't respect

View File

@ -1,4 +1,5 @@
using Bit.App.Abstractions; using System;
using Bit.App.Abstractions;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Styles; using Bit.App.Styles;
using Bit.Core; using Bit.Core;
@ -10,35 +11,38 @@ namespace Bit.App.Utilities
public static class ThemeManager public static class ThemeManager
{ {
public static bool UsingLightTheme = true; public static bool UsingLightTheme = true;
public static Func<ResourceDictionary> Resources = () => null;
public static void SetThemeStyle(string name) public static void SetThemeStyle(string name, ResourceDictionary resources)
{ {
Resources = () => resources;
// Reset styles // Reset styles
Application.Current.Resources.Clear(); resources.Clear();
Application.Current.Resources.MergedDictionaries.Clear(); resources.MergedDictionaries.Clear();
// Variables // Variables
Application.Current.Resources.MergedDictionaries.Add(new Variables()); resources.MergedDictionaries.Add(new Variables());
// Themed variables // Themed variables
if (name == "dark") if (name == "dark")
{ {
Application.Current.Resources.MergedDictionaries.Add(new Dark()); resources.MergedDictionaries.Add(new Dark());
UsingLightTheme = false; UsingLightTheme = false;
} }
else if (name == "black") else if (name == "black")
{ {
Application.Current.Resources.MergedDictionaries.Add(new Black()); resources.MergedDictionaries.Add(new Black());
UsingLightTheme = false; UsingLightTheme = false;
} }
else if (name == "nord") else if (name == "nord")
{ {
Application.Current.Resources.MergedDictionaries.Add(new Nord()); resources.MergedDictionaries.Add(new Nord());
UsingLightTheme = false; UsingLightTheme = false;
} }
else if (name == "light") else if (name == "light")
{ {
Application.Current.Resources.MergedDictionaries.Add(new Light()); resources.MergedDictionaries.Add(new Light());
UsingLightTheme = true; UsingLightTheme = true;
} }
else else
@ -46,33 +50,33 @@ namespace Bit.App.Utilities
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService", true); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService", true);
if (deviceActionService?.UsingDarkTheme() ?? false) if (deviceActionService?.UsingDarkTheme() ?? false)
{ {
Application.Current.Resources.MergedDictionaries.Add(new Dark()); resources.MergedDictionaries.Add(new Dark());
UsingLightTheme = false; UsingLightTheme = false;
} }
else else
{ {
Application.Current.Resources.MergedDictionaries.Add(new Light()); resources.MergedDictionaries.Add(new Light());
UsingLightTheme = true; UsingLightTheme = true;
} }
} }
// Base styles // Base styles
Application.Current.Resources.MergedDictionaries.Add(new Base()); resources.MergedDictionaries.Add(new Base());
// Platform styles // Platform styles
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
Application.Current.Resources.MergedDictionaries.Add(new Android()); resources.MergedDictionaries.Add(new Android());
} }
else if (Device.RuntimePlatform == Device.iOS) else if (Device.RuntimePlatform == Device.iOS)
{ {
Application.Current.Resources.MergedDictionaries.Add(new iOS()); resources.MergedDictionaries.Add(new iOS());
} }
} }
public static void SetTheme(bool android) public static void SetTheme(bool android, ResourceDictionary resources)
{ {
SetThemeStyle(GetTheme(android)); SetThemeStyle(GetTheme(android), resources);
} }
public static string GetTheme(bool android) public static string GetTheme(bool android)
@ -81,5 +85,18 @@ namespace Bit.App.Utilities
string.Format(PreferencesStorageService.KeyFormat, Constants.ThemeKey), default(string), string.Format(PreferencesStorageService.KeyFormat, Constants.ThemeKey), default(string),
!android ? "group.com.8bit.bitwarden" : default(string)); !android ? "group.com.8bit.bitwarden" : default(string));
} }
public static void ApplyResourcesToPage(ContentPage page)
{
foreach (var resourceDict in Resources().MergedDictionaries)
{
page.Resources.Add(resourceDict);
}
}
public static Color GetResourceColor(string color)
{
return (Color)Resources()[color];
}
} }
} }

View File

@ -16,6 +16,7 @@ namespace Bit.Core.Abstractions
bool GetPremium(); bool GetPremium();
Task<string> GetRefreshTokenAsync(); Task<string> GetRefreshTokenAsync();
Task<string> GetTokenAsync(); Task<string> GetTokenAsync();
Task ToggleTokensAsync();
DateTime? GetTokenExpirationDate(); DateTime? GetTokenExpirationDate();
Task<string> GetTwoFactorTokenAsync(string email); Task<string> GetTwoFactorTokenAsync(string email);
string GetUserId(); string GetUserId();

View File

@ -4,17 +4,18 @@ using Bit.Core.Models.Domain;
namespace Bit.Core.Abstractions namespace Bit.Core.Abstractions
{ {
public interface ILockService public interface IVaultTimeoutService
{ {
CipherString PinProtectedKey { get; set; } CipherString PinProtectedKey { get; set; }
bool FingerprintLocked { get; set; } bool FingerprintLocked { get; set; }
Task CheckLockAsync(); Task CheckVaultTimeoutAsync();
Task ClearAsync(); Task ClearAsync();
Task<bool> IsLockedAsync(); Task<bool> IsLockedAsync();
Task<Tuple<bool, bool>> IsPinLockSetAsync(); Task<Tuple<bool, bool>> IsPinLockSetAsync();
Task<bool> IsFingerprintLockSetAsync(); Task<bool> IsFingerprintLockSetAsync();
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false); Task LockAsync(bool allowSoftLock = false, bool userInitiated = false);
Task SetLockOptionAsync(int? lockOption); Task LogOutAsync();
Task SetVaultTimeoutOptionsAsync(int? timeout, string action);
} }
} }

View File

@ -4,7 +4,8 @@
{ {
public const string AndroidAppProtocol = "androidapp://"; public const string AndroidAppProtocol = "androidapp://";
public const string iOSAppProtocol = "iosapp://"; public const string iOSAppProtocol = "iosapp://";
public static string LockOptionKey = "lockOption"; public static string VaultTimeoutKey = "lockOption";
public static string VaultTimeoutActionKey = "vaultTimeoutAction";
public static string LastActiveKey = "lastActive"; public static string LastActiveKey = "lastActive";
public static string FingerprintUnlockKey = "fingerprintUnlock"; public static string FingerprintUnlockKey = "fingerprintUnlock";
public static string ProtectedPin = "protectedPin"; public static string ProtectedPin = "protectedPin";

View File

@ -19,7 +19,7 @@ namespace Bit.Core.Services
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly ILockService _lockService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly bool _setCryptoKeys; private readonly bool _setCryptoKeys;
private SymmetricCryptoKey _key; private SymmetricCryptoKey _key;
@ -35,7 +35,7 @@ namespace Bit.Core.Services
II18nService i18nService, II18nService i18nService,
IPlatformUtilsService platformUtilsService, IPlatformUtilsService platformUtilsService,
IMessagingService messagingService, IMessagingService messagingService,
ILockService lockService, IVaultTimeoutService vaultTimeoutService,
bool setCryptoKeys = true) bool setCryptoKeys = true)
{ {
_cryptoService = cryptoService; _cryptoService = cryptoService;
@ -46,7 +46,7 @@ namespace Bit.Core.Services
_i18nService = i18nService; _i18nService = i18nService;
_platformUtilsService = platformUtilsService; _platformUtilsService = platformUtilsService;
_messagingService = messagingService; _messagingService = messagingService;
_lockService = lockService; _vaultTimeoutService = vaultTimeoutService;
_setCryptoKeys = setCryptoKeys; _setCryptoKeys = setCryptoKeys;
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>(); TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
@ -315,7 +315,7 @@ namespace Bit.Core.Services
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey); await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
} }
_lockService.FingerprintLocked = false; _vaultTimeoutService.FingerprintLocked = false;
_messagingService.Send("loggedIn"); _messagingService.Send("loggedIn");
return result; return result;
} }

View File

@ -47,7 +47,7 @@ namespace Bit.Core.Services
public async Task SetKeyAsync(SymmetricCryptoKey key) public async Task SetKeyAsync(SymmetricCryptoKey key)
{ {
_key = key; _key = key;
var option = await _storageService.GetAsync<int?>(Constants.LockOptionKey); var option = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var fingerprint = await _storageService.GetAsync<bool?>(Constants.FingerprintUnlockKey); var fingerprint = await _storageService.GetAsync<bool?>(Constants.FingerprintUnlockKey);
if (option.HasValue && !fingerprint.GetValueOrDefault()) if (option.HasValue && !fingerprint.GetValueOrDefault())
{ {
@ -353,7 +353,7 @@ namespace Bit.Core.Services
public async Task ToggleKeyAsync() public async Task ToggleKeyAsync()
{ {
var key = await GetKeyAsync(); var key = await GetKeyAsync();
var option = await _storageService.GetAsync<int?>(Constants.LockOptionKey); var option = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var fingerprint = await _storageService.GetAsync<bool?>(Constants.FingerprintUnlockKey); var fingerprint = await _storageService.GetAsync<bool?>(Constants.FingerprintUnlockKey);
if (!fingerprint.GetValueOrDefault() && (option != null || option == 0)) if (!fingerprint.GetValueOrDefault() && (option != null || option == 0))
{ {

View File

@ -35,6 +35,13 @@ namespace Bit.Core.Services
{ {
_token = token; _token = token;
_decodedToken = null; _decodedToken = null;
if (await SkipTokenStorage())
{
// If we have a vault timeout and the action is log out, don't store token
return;
}
await _storageService.SaveAsync(Keys_AccessToken, token); await _storageService.SaveAsync(Keys_AccessToken, token);
} }
@ -51,6 +58,13 @@ namespace Bit.Core.Services
public async Task SetRefreshTokenAsync(string refreshToken) public async Task SetRefreshTokenAsync(string refreshToken)
{ {
_refreshToken = refreshToken; _refreshToken = refreshToken;
if (await SkipTokenStorage())
{
// If we have a vault timeout and the action is log out, don't store token
return;
}
await _storageService.SaveAsync(Keys_RefreshToken, refreshToken); await _storageService.SaveAsync(Keys_RefreshToken, refreshToken);
} }
@ -64,6 +78,22 @@ namespace Bit.Core.Services
return _refreshToken; return _refreshToken;
} }
public async Task ToggleTokensAsync()
{
var token = await GetTokenAsync();
var refreshToken = await GetRefreshTokenAsync();
if (await SkipTokenStorage())
{
await ClearTokenAsync();
_token = token;
_refreshToken = refreshToken;
return;
}
await SetTokenAsync(token);
await SetRefreshTokenAsync(refreshToken);
}
public async Task SetTwoFactorTokenAsync(string token, string email) public async Task SetTwoFactorTokenAsync(string token, string email)
{ {
await _storageService.SaveAsync(string.Format(Keys_TwoFactorTokenFormat, email), token); await _storageService.SaveAsync(string.Format(Keys_TwoFactorTokenFormat, email), token);
@ -225,5 +255,12 @@ namespace Bit.Core.Services
// Standard base64 decoder // Standard base64 decoder
return Convert.FromBase64String(output); return Convert.FromBase64String(output);
} }
private async Task<bool> SkipTokenStorage()
{
var timeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);
return timeout.HasValue && action == "logOut";
}
} }
} }

View File

@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
public class LockService : ILockService public class VaultTimeoutService : IVaultTimeoutService
{ {
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
private readonly IUserService _userService; private readonly IUserService _userService;
@ -16,9 +16,11 @@ namespace Bit.Core.Services
private readonly ICollectionService _collectionService; private readonly ICollectionService _collectionService;
private readonly ISearchService _searchService; private readonly ISearchService _searchService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly ITokenService _tokenService;
private readonly Action<bool> _lockedCallback; private readonly Action<bool> _lockedCallback;
private readonly Func<bool, Task> _loggedOutCallback;
public LockService( public VaultTimeoutService(
ICryptoService cryptoService, ICryptoService cryptoService,
IUserService userService, IUserService userService,
IPlatformUtilsService platformUtilsService, IPlatformUtilsService platformUtilsService,
@ -28,7 +30,9 @@ namespace Bit.Core.Services
ICollectionService collectionService, ICollectionService collectionService,
ISearchService searchService, ISearchService searchService,
IMessagingService messagingService, IMessagingService messagingService,
Action<bool> lockedCallback) ITokenService tokenService,
Action<bool> lockedCallback,
Func<bool, Task> loggedOutCallback)
{ {
_cryptoService = cryptoService; _cryptoService = cryptoService;
_userService = userService; _userService = userService;
@ -39,7 +43,9 @@ namespace Bit.Core.Services
_collectionService = collectionService; _collectionService = collectionService;
_searchService = searchService; _searchService = searchService;
_messagingService = messagingService; _messagingService = messagingService;
_tokenService = tokenService;
_lockedCallback = lockedCallback; _lockedCallback = lockedCallback;
_loggedOutCallback = loggedOutCallback;
} }
public CipherString PinProtectedKey { get; set; } = null; public CipherString PinProtectedKey { get; set; } = null;
@ -59,7 +65,7 @@ namespace Bit.Core.Services
return !hasKey; return !hasKey;
} }
public async Task CheckLockAsync() public async Task CheckVaultTimeoutAsync()
{ {
if (_platformUtilsService.IsViewOpen()) if (_platformUtilsService.IsViewOpen())
{ {
@ -74,12 +80,13 @@ namespace Bit.Core.Services
{ {
return; return;
} }
var lockOption = _platformUtilsService.LockTimeout(); // This only returns null
if (lockOption == null) var vaultTimeout = _platformUtilsService.LockTimeout();
if (vaultTimeout == null)
{ {
lockOption = await _storageService.GetAsync<int?>(Constants.LockOptionKey); vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
} }
if (lockOption.GetValueOrDefault(-1) < 0) if (vaultTimeout.GetValueOrDefault(-1) < 0)
{ {
return; return;
} }
@ -89,10 +96,18 @@ namespace Bit.Core.Services
return; return;
} }
var diff = DateTime.UtcNow - lastActive.Value; var diff = DateTime.UtcNow - lastActive.Value;
if (diff.TotalSeconds >= lockOption.Value) if (diff.TotalSeconds >= vaultTimeout.Value)
{ {
// need to lock now // Pivot based on saved action
await LockAsync(true); var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);
if (action == "logOut")
{
await LogOutAsync();
}
else
{
await LockAsync(true);
}
} }
} }
@ -127,10 +142,20 @@ namespace Bit.Core.Services
_lockedCallback?.Invoke(userInitiated); _lockedCallback?.Invoke(userInitiated);
} }
public async Task SetLockOptionAsync(int? lockOption) public async Task LogOutAsync()
{ {
await _storageService.SaveAsync(Constants.LockOptionKey, lockOption); if(_loggedOutCallback != null)
{
await _loggedOutCallback.Invoke(false);
}
}
public async Task SetVaultTimeoutOptionsAsync(int? timeout, string action)
{
await _storageService.SaveAsync(Constants.VaultTimeoutKey, timeout);
await _storageService.SaveAsync(Constants.VaultTimeoutActionKey, action);
await _cryptoService.ToggleKeyAsync(); await _cryptoService.ToggleKeyAsync();
await _tokenService.ToggleTokensAsync();
} }
public async Task<Tuple<bool, bool>> IsPinLockSetAsync() public async Task<Tuple<bool, bool>> IsPinLockSetAsync()

View File

@ -45,8 +45,13 @@ namespace Bit.Core.Utilities
i18nService, cipherService); i18nService, cipherService);
var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService); var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
searchService = new SearchService(cipherService); searchService = new SearchService(cipherService);
var lockService = new LockService(cryptoService, userService, platformUtilsService, storageService, var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService,
folderService, cipherService, collectionService, searchService, messagingService, null); storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService,
null, (expired) =>
{
messagingService.Send("logout", expired);
return Task.FromResult(0);
});
var policyService = new PolicyService(storageService, userService); var policyService = new PolicyService(storageService, userService);
var syncService = new SyncService(userService, apiService, settingsService, folderService, var syncService = new SyncService(userService, apiService, settingsService, folderService,
cipherService, cryptoService, collectionService, storageService, messagingService, policyService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
@ -59,7 +64,7 @@ namespace Bit.Core.Utilities
cryptoFunctionService, policyService); cryptoFunctionService, policyService);
var totpService = new TotpService(storageService, cryptoFunctionService); var totpService = new TotpService(storageService, cryptoFunctionService);
var authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService, var authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
i18nService, platformUtilsService, messagingService, lockService); i18nService, platformUtilsService, messagingService, vaultTimeoutService);
var exportService = new ExportService(folderService, cipherService); var exportService = new ExportService(folderService, cipherService);
var auditService = new AuditService(cryptoFunctionService, apiService); var auditService = new AuditService(cryptoFunctionService, apiService);
var environmentService = new EnvironmentService(apiService, storageService); var environmentService = new EnvironmentService(apiService, storageService);
@ -79,7 +84,7 @@ namespace Bit.Core.Utilities
Register<ISearchService>("searchService", searchService); Register<ISearchService>("searchService", searchService);
Register<IPolicyService>("policyService", policyService); Register<IPolicyService>("policyService", policyService);
Register<ISyncService>("syncService", syncService); Register<ISyncService>("syncService", syncService);
Register<ILockService>("lockService", lockService); Register<IVaultTimeoutService>("vaultTimeoutService", vaultTimeoutService);
Register<IPasswordGenerationService>("passwordGenerationService", passwordGenerationService); Register<IPasswordGenerationService>("passwordGenerationService", passwordGenerationService);
Register<ITotpService>("totpService", totpService); Register<ITotpService>("totpService", totpService);
Register<IAuthService>("authService", authService); Register<IAuthService>("authService", authService);

View File

@ -1,6 +1,5 @@
using AuthenticationServices; using AuthenticationServices;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.iOS.Autofill.Models; using Bit.iOS.Autofill.Models;
@ -8,7 +7,12 @@ using Bit.iOS.Core.Utilities;
using Foundation; using Foundation;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Pages;
using UIKit; using UIKit;
using Xamarin.Forms;
using Bit.App.Utilities;
using Bit.App.Models;
using CoreNFC;
namespace Bit.iOS.Autofill namespace Bit.iOS.Autofill
{ {
@ -16,6 +20,8 @@ namespace Bit.iOS.Autofill
{ {
private Context _context; private Context _context;
private bool _initedAppCenter; private bool _initedAppCenter;
private NFCNdefReaderSession _nfcSession = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
public CredentialProviderViewController(IntPtr handle) public CredentialProviderViewController(IntPtr handle)
: base(handle) : base(handle)
@ -48,11 +54,11 @@ namespace Bit.iOS.Autofill
} }
_context.UrlString = uri; _context.UrlString = uri;
} }
if (!CheckAuthed()) if (!IsAuthed())
{ {
return; LaunchLoginFlow();
} }
if (IsLocked()) else if (IsLocked())
{ {
PerformSegue("lockPasswordSegue", this); PerformSegue("lockPasswordSegue", this);
} }
@ -86,8 +92,9 @@ namespace Bit.iOS.Autofill
public override void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity) public override void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
{ {
InitAppIfNeeded(); InitAppIfNeeded();
if (!CheckAuthed()) if (!IsAuthed())
{ {
LaunchLoginFlow();
return; return;
} }
_context.CredentialIdentity = credentialIdentity; _context.CredentialIdentity = credentialIdentity;
@ -98,8 +105,9 @@ namespace Bit.iOS.Autofill
{ {
InitAppIfNeeded(); InitAppIfNeeded();
_context.Configuring = true; _context.Configuring = true;
if (!CheckAuthed()) if (!IsAuthed())
{ {
LaunchLoginFlow();
return; return;
} }
CheckLock(() => PerformSegue("setupSegue", this)); CheckLock(() => PerformSegue("setupSegue", this));
@ -235,24 +243,10 @@ namespace Bit.iOS.Autofill
} }
} }
private bool CheckAuthed()
{
if (!IsAuthed())
{
var alert = Dialogs.CreateAlert(null, AppResources.MustLogInMainAppAutofill, AppResources.Ok, (a) =>
{
CompleteRequest();
});
PresentViewController(alert, true, null);
return false;
}
return true;
}
private bool IsLocked() private bool IsLocked()
{ {
var lockService = ServiceContainer.Resolve<ILockService>("lockService"); var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
return lockService.IsLockedAsync().GetAwaiter().GetResult(); return vaultTimeoutService.IsLockedAsync().GetAwaiter().GetResult();
} }
private bool IsAuthed() private bool IsAuthed()
@ -263,12 +257,16 @@ namespace Bit.iOS.Autofill
private void InitApp() private void InitApp()
{ {
// Init Xamarin Forms
Forms.Init();
if (ServiceContainer.RegisteredServices.Count > 0) if (ServiceContainer.RegisteredServices.Count > 0)
{ {
ServiceContainer.Reset(); ServiceContainer.Reset();
} }
iOSCoreHelpers.RegisterLocalServices(); iOSCoreHelpers.RegisterLocalServices();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent); ServiceContainer.Init(deviceActionService.DeviceUserAgent);
if (!_initedAppCenter) if (!_initedAppCenter)
{ {
@ -277,6 +275,9 @@ namespace Bit.iOS.Autofill
} }
iOSCoreHelpers.Bootstrap(); iOSCoreHelpers.Bootstrap();
iOSCoreHelpers.AppearanceAdjustments(deviceActionService); iOSCoreHelpers.AppearanceAdjustments(deviceActionService);
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
} }
private void InitAppIfNeeded() private void InitAppIfNeeded()
@ -286,5 +287,43 @@ namespace Bit.iOS.Autofill
InitApp(); InitApp();
} }
} }
private void LaunchLoginFlow()
{
var loginPage = new LoginPage();
var app = new App.App(new AppOptions { EmptyApp = true });
ThemeManager.SetTheme(false, app.Resources);
ThemeManager.ApplyResourcesToPage(loginPage);
if (loginPage.BindingContext is LoginPageViewModel vm)
{
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow());
vm.LoggedInAction = () => DismissLockAndContinue();
vm.CloseAction = () => CompleteRequest();
vm.HideHintButton = true;
}
var navigationPage = new NavigationPage(loginPage);
var loginController = navigationPage.CreateViewController();
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginController, true, null);
}
private void LaunchTwoFactorFlow()
{
var twoFactorPage = new TwoFactorPage();
var app = new App.App(new AppOptions { EmptyApp = true });
ThemeManager.SetTheme(false, app.Resources);
ThemeManager.ApplyResourcesToPage(twoFactorPage);
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
{
vm.TwoFactorAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
}
var navigationPage = new NavigationPage(twoFactorPage);
var twoFactorController = navigationPage.CreateViewController();
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(twoFactorController, true, null);
}
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

View File

@ -256,5 +256,14 @@
<Version>3.2.1</Version> <Version>3.2.1</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\yubikey.png" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\yubikey%402x.png" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\yubikey%403x.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.AppExtension.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.AppExtension.CSharp.targets" />
</Project> </Project>

View File

@ -15,7 +15,7 @@ namespace Bit.iOS.Core.Controllers
{ {
public abstract class LockPasswordViewController : ExtendedUITableViewController public abstract class LockPasswordViewController : ExtendedUITableViewController
{ {
private ILockService _lockService; private IVaultTimeoutService _vaultTimeoutService;
private ICryptoService _cryptoService; private ICryptoService _cryptoService;
private IDeviceActionService _deviceActionService; private IDeviceActionService _deviceActionService;
private IUserService _userService; private IUserService _userService;
@ -42,7 +42,7 @@ namespace Bit.iOS.Core.Controllers
public override void ViewDidLoad() public override void ViewDidLoad()
{ {
_lockService = ServiceContainer.Resolve<ILockService>("lockService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService"); _cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_userService = ServiceContainer.Resolve<IUserService>("userService"); _userService = ServiceContainer.Resolve<IUserService>("userService");
@ -50,9 +50,9 @@ namespace Bit.iOS.Core.Controllers
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService"); _secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_pinSet = _lockService.IsPinLockSetAsync().GetAwaiter().GetResult(); _pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult();
_pinLock = (_pinSet.Item1 && _lockService.PinProtectedKey != null) || _pinSet.Item2; _pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
_fingerprintLock = _lockService.IsFingerprintLockSetAsync().GetAwaiter().GetResult(); _fingerprintLock = _vaultTimeoutService.IsFingerprintLockSetAsync().GetAwaiter().GetResult();
BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword; BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword;
BaseCancelButton.Title = AppResources.Cancel; BaseCancelButton.Title = AppResources.Cancel;
@ -125,7 +125,7 @@ namespace Bit.iOS.Core.Controllers
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
_lockService.PinProtectedKey); _vaultTimeoutService.PinProtectedKey);
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin); var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
@ -182,7 +182,7 @@ namespace Bit.iOS.Core.Controllers
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email, var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
_lockService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey); _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey);
} }
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(key2);
} }
@ -205,7 +205,7 @@ namespace Bit.iOS.Core.Controllers
private void DoContinue() private void DoContinue()
{ {
_lockService.FingerprintLocked = false; _vaultTimeoutService.FingerprintLocked = false;
MasterPasswordCell.TextField.ResignFirstResponder(); MasterPasswordCell.TextField.ResignFirstResponder();
Success(); Success();
} }
@ -219,7 +219,7 @@ namespace Bit.iOS.Core.Controllers
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword, _pinLock ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
_lockService.FingerprintLocked = !success; _vaultTimeoutService.FingerprintLocked = !success;
if (success) if (success)
{ {
DoContinue(); DoContinue();

View File

@ -4,7 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Bit.iOS namespace Bit.iOS.Core
{ {
public class NFCReaderDelegate : NFCNdefReaderSessionDelegate public class NFCReaderDelegate : NFCNdefReaderSessionDelegate
{ {

View File

@ -1,12 +1,12 @@
using System.ComponentModel; using System.ComponentModel;
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using Bit.iOS.Utilities; using Bit.iOS.Core.Utilities;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))] [assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomButtonRenderer : ButtonRenderer public class CustomButtonRenderer : ButtonRenderer
{ {

View File

@ -1,13 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))] [assembly: ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomContentPageRenderer : PageRenderer public class CustomContentPageRenderer : PageRenderer
{ {

View File

@ -1,11 +1,11 @@
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using System.ComponentModel; using System.ComponentModel;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))] [assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomEditorRenderer : EditorRenderer public class CustomEditorRenderer : EditorRenderer
{ {
@ -40,7 +40,7 @@ namespace Bit.iOS.Renderers
private void UpdateKeyboardAppearance() private void UpdateKeyboardAppearance()
{ {
if (!Core.Utilities.ThemeHelpers.LightTheme) if (!Utilities.ThemeHelpers.LightTheme)
{ {
Control.KeyboardAppearance = UIKeyboardAppearance.Dark; Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
} }

View File

@ -1,12 +1,12 @@
using System.ComponentModel; using System.ComponentModel;
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using Bit.iOS.Utilities; using Bit.iOS.Core.Utilities;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))] [assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomEntryRenderer : EntryRenderer public class CustomEntryRenderer : EntryRenderer
{ {
@ -61,7 +61,7 @@ namespace Bit.iOS.Renderers
private void UpdateKeyboardAppearance() private void UpdateKeyboardAppearance()
{ {
if (!Core.Utilities.ThemeHelpers.LightTheme) if (!ThemeHelpers.LightTheme)
{ {
Control.KeyboardAppearance = UIKeyboardAppearance.Dark; Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
} }

View File

@ -1,12 +1,12 @@
using System.ComponentModel; using System.ComponentModel;
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using Bit.iOS.Utilities; using Bit.iOS.Core.Utilities;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))] [assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomLabelRenderer : LabelRenderer public class CustomLabelRenderer : LabelRenderer
{ {

View File

@ -1,11 +1,11 @@
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using Bit.iOS.Utilities; using Bit.iOS.Core.Utilities;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Picker), typeof(CustomPickerRenderer))] [assembly: ExportRenderer(typeof(Picker), typeof(CustomPickerRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomPickerRenderer : PickerRenderer public class CustomPickerRenderer : PickerRenderer
{ {
@ -23,7 +23,7 @@ namespace Bit.iOS.Renderers
private void UpdateKeyboardAppearance() private void UpdateKeyboardAppearance()
{ {
if (!Core.Utilities.ThemeHelpers.LightTheme) if (!ThemeHelpers.LightTheme)
{ {
Control.KeyboardAppearance = UIKeyboardAppearance.Dark; Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
} }

View File

@ -1,10 +1,10 @@
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))] [assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomSearchBarRenderer : SearchBarRenderer public class CustomSearchBarRenderer : SearchBarRenderer
{ {
@ -19,7 +19,7 @@ namespace Bit.iOS.Renderers
private void UpdateKeyboardAppearance() private void UpdateKeyboardAppearance()
{ {
if (!Core.Utilities.ThemeHelpers.LightTheme) if (!Utilities.ThemeHelpers.LightTheme)
{ {
Control.KeyboardAppearance = UIKeyboardAppearance.Dark; Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
} }

View File

@ -1,9 +1,9 @@
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))] [assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomTabbedRenderer : TabbedRenderer public class CustomTabbedRenderer : TabbedRenderer
{ {

View File

@ -1,10 +1,11 @@
using Bit.iOS.Renderers; using Bit.App.Utilities;
using Bit.iOS.Core.Renderers;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ViewCell), typeof(CustomViewCellRenderer))] [assembly: ExportRenderer(typeof(ViewCell), typeof(CustomViewCellRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class CustomViewCellRenderer : ViewCellRenderer public class CustomViewCellRenderer : ViewCellRenderer
{ {
@ -12,7 +13,7 @@ namespace Bit.iOS.Renderers
public CustomViewCellRenderer() public CustomViewCellRenderer()
{ {
_noSelectionStyle = (Color)Xamarin.Forms.Application.Current.Resources["BackgroundColor"] != Color.White; _noSelectionStyle = ThemeManager.GetResourceColor("BackgroundColor") != Color.White;
} }
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv) public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)

View File

@ -3,11 +3,11 @@ using WebKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.iOS.Renderers; using Bit.iOS.Core.Renderers;
using System.ComponentModel; using System.ComponentModel;
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))] [assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace Bit.iOS.Renderers namespace Bit.iOS.Core.Renderers
{ {
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
{ {

View File

@ -347,6 +347,45 @@ namespace Bit.iOS.Core.Services
return result.Task; return result.Task;
} }
public Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction,
params string[] buttons)
{
if (Application.Current is App.App app && app.Options != null && !app.Options.EmptyApp)
{
return app.MainPage.DisplayActionSheet(title, cancel, destruction, buttons);
}
var result = new TaskCompletionSource<string>();
var vc = GetPresentedViewController();
var sheet = UIAlertController.Create(title, null, UIAlertControllerStyle.ActionSheet);
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
{
var x = vc.View.Bounds.Width / 2;
var y = vc.View.Bounds.Bottom;
var rect = new CGRect(x, y, 0, 0);
sheet.PopoverPresentationController.SourceView = vc.View;
sheet.PopoverPresentationController.SourceRect = rect;
sheet.PopoverPresentationController.PermittedArrowDirections = UIPopoverArrowDirection.Unknown;
}
foreach (var button in buttons)
{
sheet.AddAction(UIAlertAction.Create(button, UIAlertActionStyle.Default,
x => result.TrySetResult(button)));
}
if (!string.IsNullOrWhiteSpace(destruction))
{
sheet.AddAction(UIAlertAction.Create(destruction, UIAlertActionStyle.Destructive,
x => result.TrySetResult(destruction)));
}
if (!string.IsNullOrWhiteSpace(cancel))
{
sheet.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Cancel,
x => result.TrySetResult(cancel)));
}
vc.PresentViewController(sheet, true, null);
return result.Task;
}
public void Autofill(CipherView cipher) public void Autofill(CipherView cipher)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -15,8 +15,8 @@ namespace Bit.iOS.Core.Utilities
if (await AutofillEnabled()) if (await AutofillEnabled())
{ {
var storageService = ServiceContainer.Resolve<IStorageService>("storageService"); var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var lockService = ServiceContainer.Resolve<ILockService>("lockService"); var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
if (await lockService.IsLockedAsync()) if (await vaultTimeoutService.IsLockedAsync())
{ {
await storageService.SaveAsync(Constants.AutofillNeedsIdentityReplacementKey, true); await storageService.SaveAsync(Constants.AutofillNeedsIdentityReplacementKey, true);
return; return;

View File

@ -27,12 +27,18 @@ namespace Bit.iOS.Core.Utilities
return alert; return alert;
} }
public static UIAlertController CreateAlert(string title, string message, string accept, Action<UIAlertAction> acceptHandle = null) public static UIAlertController CreateAlert(string title, string message, string accept,
Action<UIAlertAction> acceptHandle = null, string cancel = null, Action<UIAlertAction> cancelHandle = null)
{ {
var alert = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert); var alert = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert);
var oldFrame = alert.View.Frame; var oldFrame = alert.View.Frame;
alert.View.Frame = new RectangleF((float)oldFrame.X, (float)oldFrame.Y, (float)oldFrame.Width, (float)oldFrame.Height - 20); alert.View.Frame = new RectangleF((float)oldFrame.X, (float)oldFrame.Y, (float)oldFrame.Width,
(float)oldFrame.Height - 20);
alert.AddAction(UIAlertAction.Create(accept, UIAlertActionStyle.Default, acceptHandle)); alert.AddAction(UIAlertAction.Create(accept, UIAlertActionStyle.Default, acceptHandle));
if (!string.IsNullOrWhiteSpace(cancel))
{
alert.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Default, cancelHandle));
}
return alert; return alert;
} }

View File

@ -2,12 +2,15 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.iOS.Core.Services; using Bit.iOS.Core.Services;
using CoreNFC;
using Foundation; using Foundation;
using UIKit; using UIKit;
@ -68,6 +71,8 @@ namespace Bit.iOS.Core.Utilities
{ {
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init(); (ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
ServiceContainer.Resolve<IAuthService>("authService").Init(); ServiceContainer.Resolve<IAuthService>("authService").Init();
(ServiceContainer.
Resolve<IPlatformUtilsService>("platformUtilsService") as MobilePlatformUtilsService).Init();
// Note: This is not awaited // Note: This is not awaited
var bootstrapTask = BootstrapAsync(postBootstrapFunc); var bootstrapTask = BootstrapAsync(postBootstrapFunc);
} }
@ -79,6 +84,54 @@ namespace Bit.iOS.Core.Utilities
UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent; UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent;
} }
public static void SubscribeBroadcastReceiver(UIViewController controller, NFCNdefReaderSession nfcSession,
NFCReaderDelegate nfcDelegate)
{
var broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
broadcasterService.Subscribe(nameof(controller), (message) =>
{
if (message.Command == "showDialog")
{
var details = message.Data as DialogDetails;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{
var result = await deviceActionService.DisplayAlertAsync(details.Title, details.Text,
details.CancelText, details.ConfirmText);
var confirmed = result == details.ConfirmText;
messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
else if (message.Command == "listenYubiKeyOTP")
{
ListenYubiKey((bool)message.Data, deviceActionService, nfcSession, nfcDelegate);
}
});
}
public static void ListenYubiKey(bool listen, IDeviceActionService deviceActionService,
NFCNdefReaderSession nfcSession, NFCReaderDelegate nfcDelegate)
{
if (deviceActionService.SupportsNfc())
{
nfcSession?.InvalidateSession();
nfcSession?.Dispose();
nfcSession = null;
if (listen)
{
nfcSession = new NFCNdefReaderSession(nfcDelegate, null, true)
{
AlertMessage = AppResources.HoldYubikeyNearTop
};
nfcSession.BeginSession();
}
}
}
private static async Task BootstrapAsync(Func<Task> postBootstrapFunc = null) private static async Task BootstrapAsync(Func<Task> postBootstrapFunc = null)
{ {
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService").GetAsync<bool?>( var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService").GetAsync<bool?>(

View File

@ -1,8 +1,9 @@
using UIKit; using Bit.App.Utilities;
using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
namespace Bit.iOS.Utilities namespace Bit.iOS.Core.Utilities
{ {
public static class iOSHelpers public static class iOSHelpers
{ {
@ -44,7 +45,7 @@ namespace Bit.iOS.Utilities
{ {
var borderLine = new UIView var borderLine = new UIView
{ {
BackgroundColor = ((Color)Xamarin.Forms.Application.Current.Resources["BoxBorderColor"]).ToUIColor(), BackgroundColor = ThemeManager.GetResourceColor("BoxBorderColor").ToUIColor(),
TranslatesAutoresizingMaskIntoConstraints = false TranslatesAutoresizingMaskIntoConstraints = false
}; };
control.AddSubview(borderLine); control.AddSubview(borderLine);

View File

@ -1,4 +1,5 @@
using Foundation; using Bit.App.Utilities;
using Foundation;
using System; using System;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
@ -19,8 +20,8 @@ namespace Bit.iOS.Core.Views
{ {
TranslatesAutoresizingMaskIntoConstraints = false; TranslatesAutoresizingMaskIntoConstraints = false;
var bgColor = UIColor.DarkGray; var bgColor = UIColor.DarkGray;
var nordTheme = Application.Current?.Resources != null && var nordTheme = ThemeManager.Resources() != null &&
((Color)Application.Current.Resources["BackgroundColor"]) == Color.FromHex("#3b4252"); ThemeManager.GetResourceColor("BackgroundColor") == Color.FromHex("#3b4252");
if (nordTheme) if (nordTheme)
{ {
bgColor = Color.FromHex("#4c566a").ToUIColor(); bgColor = Color.FromHex("#4c566a").ToUIColor();

View File

@ -88,6 +88,17 @@
<Compile Include="Models\FillScript.cs" /> <Compile Include="Models\FillScript.cs" />
<Compile Include="Models\PageDetails.cs" /> <Compile Include="Models\PageDetails.cs" />
<Compile Include="Models\PasswordGenerationOptions.cs" /> <Compile Include="Models\PasswordGenerationOptions.cs" />
<Compile Include="NFCReaderDelegate.cs" />
<Compile Include="Renderers\CustomButtonRenderer.cs" />
<Compile Include="Renderers\CustomContentPageRenderer.cs" />
<Compile Include="Renderers\CustomEditorRenderer.cs" />
<Compile Include="Renderers\CustomEntryRenderer.cs" />
<Compile Include="Renderers\CustomLabelRenderer.cs" />
<Compile Include="Renderers\CustomPickerRenderer.cs" />
<Compile Include="Renderers\CustomSearchBarRenderer.cs" />
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
<Compile Include="Renderers\CustomViewCellRenderer.cs" />
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
<Compile Include="Services\DeviceActionService.cs" /> <Compile Include="Services\DeviceActionService.cs" />
<Compile Include="Utilities\ASHelpers.cs" /> <Compile Include="Utilities\ASHelpers.cs" />
<Compile Include="Utilities\Dialogs.cs" /> <Compile Include="Utilities\Dialogs.cs" />
@ -97,6 +108,7 @@
<Compile Include="Services\KeyChainStorageService.cs" /> <Compile Include="Services\KeyChainStorageService.cs" />
<Compile Include="Services\LocalizeService.cs" /> <Compile Include="Services\LocalizeService.cs" />
<Compile Include="Utilities\iOSCoreHelpers.cs" /> <Compile Include="Utilities\iOSCoreHelpers.cs" />
<Compile Include="Utilities\iOSHelpers.cs" />
<Compile Include="Utilities\ThemeHelpers.cs" /> <Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="Views\ExtensionSearchDelegate.cs" /> <Compile Include="Views\ExtensionSearchDelegate.cs" />
<Compile Include="Views\ExtensionTableSource.cs" /> <Compile Include="Views\ExtensionTableSource.cs" />

View File

@ -13,6 +13,11 @@ using Bit.iOS.Core.Models;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using CoreNFC;
using Xamarin.Forms;
using Bit.App.Pages;
using Bit.App.Models;
using Bit.App.Utilities;
namespace Bit.iOS.Extension namespace Bit.iOS.Extension
{ {
@ -20,6 +25,8 @@ namespace Bit.iOS.Extension
{ {
private Context _context = new Context(); private Context _context = new Context();
private bool _initedAppCenter; private bool _initedAppCenter;
private NFCNdefReaderSession _nfcSession = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
public LoadingViewController(IntPtr handle) public LoadingViewController(IntPtr handle)
: base(handle) : base(handle)
@ -59,21 +66,17 @@ namespace Bit.iOS.Extension
public override void ViewDidAppear(bool animated) public override void ViewDidAppear(bool animated)
{ {
base.ViewDidAppear(animated); base.ViewDidAppear(animated);
if (!IsAuthed())
{
var alert = Dialogs.CreateAlert(null, AppResources.MustLogInMainApp, AppResources.Ok, (a) =>
{
CompleteRequest(null, null);
});
PresentViewController(alert, true, null);
return;
}
if (_context.ProviderType == Constants.UTTypeAppExtensionSetup) if (_context.ProviderType == Constants.UTTypeAppExtensionSetup)
{ {
PerformSegue("setupSegue", this); PerformSegue("setupSegue", this);
return; return;
} }
if (IsLocked()) if (!IsAuthed())
{
LaunchLoginFlow();
return;
}
else if (IsLocked())
{ {
PerformSegue("lockPasswordSegue", this); PerformSegue("lockPasswordSegue", this);
} }
@ -382,12 +385,16 @@ namespace Bit.iOS.Extension
private void InitApp() private void InitApp()
{ {
// Init Xamarin Forms
Forms.Init();
if (ServiceContainer.RegisteredServices.Count > 0) if (ServiceContainer.RegisteredServices.Count > 0)
{ {
ServiceContainer.Reset(); ServiceContainer.Reset();
} }
iOSCoreHelpers.RegisterLocalServices(); iOSCoreHelpers.RegisterLocalServices();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent); ServiceContainer.Init(deviceActionService.DeviceUserAgent);
if (!_initedAppCenter) if (!_initedAppCenter)
{ {
@ -396,12 +403,15 @@ namespace Bit.iOS.Extension
} }
iOSCoreHelpers.Bootstrap(); iOSCoreHelpers.Bootstrap();
iOSCoreHelpers.AppearanceAdjustments(deviceActionService); iOSCoreHelpers.AppearanceAdjustments(deviceActionService);
_nfcDelegate = new NFCReaderDelegate((success, message) =>
messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
} }
private bool IsLocked() private bool IsLocked()
{ {
var lockService = ServiceContainer.Resolve<ILockService>("lockService"); var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
return lockService.IsLockedAsync().GetAwaiter().GetResult(); return vaultTimeoutService.IsLockedAsync().GetAwaiter().GetResult();
} }
private bool IsAuthed() private bool IsAuthed()
@ -409,5 +419,43 @@ namespace Bit.iOS.Extension
var userService = ServiceContainer.Resolve<IUserService>("userService"); var userService = ServiceContainer.Resolve<IUserService>("userService");
return userService.IsAuthenticatedAsync().GetAwaiter().GetResult(); return userService.IsAuthenticatedAsync().GetAwaiter().GetResult();
} }
private void LaunchLoginFlow()
{
var loginPage = new LoginPage();
var app = new App.App(new AppOptions { EmptyApp = true });
ThemeManager.SetTheme(false, app.Resources);
ThemeManager.ApplyResourcesToPage(loginPage);
if (loginPage.BindingContext is LoginPageViewModel vm)
{
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow());
vm.LoggedInAction = () => DismissLockAndContinue();
vm.CloseAction = () => CompleteRequest(null, null);
vm.HideHintButton = true;
}
var navigationPage = new NavigationPage(loginPage);
var loginController = navigationPage.CreateViewController();
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginController, true, null);
}
private void LaunchTwoFactorFlow()
{
var twoFactorPage = new TwoFactorPage();
var app = new App.App(new AppOptions { EmptyApp = true });
ThemeManager.SetTheme(false, app.Resources);
ThemeManager.ApplyResourcesToPage(twoFactorPage);
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
{
vm.TwoFactorAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
}
var navigationPage = new NavigationPage(twoFactorPage);
var twoFactorController = navigationPage.CreateViewController();
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(twoFactorController, true, null);
}
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

View File

@ -225,6 +225,9 @@
<BundleResource Include="Resources\logo_white.png" /> <BundleResource Include="Resources\logo_white.png" />
<BundleResource Include="Resources\logo_white%402x.png" /> <BundleResource Include="Resources\logo_white%402x.png" />
<BundleResource Include="Resources\logo_white%403x.png" /> <BundleResource Include="Resources\logo_white%403x.png" />
<BundleResource Include="Resources\yubikey.png" />
<BundleResource Include="Resources\yubikey%403x.png" />
<BundleResource Include="Resources\yubikey%402x.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes"> <PackageReference Include="Microsoft.AppCenter.Crashes">

View File

@ -26,10 +26,10 @@ namespace Bit.iOS
{ {
private NFCNdefReaderSession _nfcSession = null; private NFCNdefReaderSession _nfcSession = null;
private iOSPushNotificationHandler _pushHandler = null; private iOSPushNotificationHandler _pushHandler = null;
private NFCReaderDelegate _nfcDelegate = null; private Core.NFCReaderDelegate _nfcDelegate = null;
private NSTimer _clipboardTimer = null; private NSTimer _clipboardTimer = null;
private nint _clipboardBackgroundTaskId; private nint _clipboardBackgroundTaskId;
private NSTimer _lockTimer = null; private NSTimer _vaultTimeoutTimer = null;
private nint _lockBackgroundTaskId; private nint _lockBackgroundTaskId;
private NSTimer _eventTimer = null; private NSTimer _eventTimer = null;
private nint _eventBackgroundTaskId; private nint _eventBackgroundTaskId;
@ -38,7 +38,7 @@ namespace Bit.iOS
private IMessagingService _messagingService; private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService; private IBroadcasterService _broadcasterService;
private IStorageService _storageService; private IStorageService _storageService;
private ILockService _lockService; private IVaultTimeoutService _vaultTimeoutService;
private IEventService _eventService; private IEventService _eventService;
public override bool FinishedLaunching(UIApplication app, NSDictionary options) public override bool FinishedLaunching(UIApplication app, NSDictionary options)
@ -50,7 +50,7 @@ namespace Bit.iOS
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService"); _eventService = ServiceContainer.Resolve<IEventService>("eventService");
LoadApplication(new App.App(null)); LoadApplication(new App.App(null));
@ -59,13 +59,13 @@ namespace Bit.iOS
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) => _broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
{ {
if (message.Command == "scheduleLockTimer") if (message.Command == "scheduleVaultTimeoutTimer")
{ {
LockTimer((int)message.Data); VaultTimeoutTimer((int)message.Data);
} }
else if (message.Command == "cancelLockTimer") else if (message.Command == "cancelVaultTimeoutTimer")
{ {
CancelLockTimer(); CancelVaultTimeoutTimer();
} }
else if (message.Command == "startEventTimer") else if (message.Command == "startEventTimer")
{ {
@ -89,7 +89,7 @@ namespace Bit.iOS
} }
else if (message.Command == "listenYubiKeyOTP") else if (message.Command == "listenYubiKeyOTP")
{ {
ListenYubiKey((bool)message.Data); iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
} }
else if (message.Command == "unlocked") else if (message.Command == "unlocked")
{ {
@ -186,8 +186,7 @@ namespace Bit.iOS
}; };
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame) var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
{ {
BackgroundColor = ((Color)Xamarin.Forms.Application.Current.Resources["SplashBackgroundColor"]) BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToUIColor()
.ToUIColor()
}; };
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png"); var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
var imageView = new UIImageView(logo) var imageView = new UIImageView(logo)
@ -284,7 +283,7 @@ namespace Bit.iOS
iOSCoreHelpers.RegisterAppCenter(); iOSCoreHelpers.RegisterAppCenter();
_pushHandler = new iOSPushNotificationHandler( _pushHandler = new iOSPushNotificationHandler(
ServiceContainer.Resolve<IPushNotificationListenerService>("pushNotificationListenerService")); ServiceContainer.Resolve<IPushNotificationListenerService>("pushNotificationListenerService"));
_nfcDelegate = new NFCReaderDelegate((success, message) => _nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
_messagingService.Send("gotYubiKeyOTP", message)); _messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync()); iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync());
@ -300,25 +299,7 @@ namespace Bit.iOS
"pushNotificationService", iosPushNotificationService); "pushNotificationService", iosPushNotificationService);
} }
private void ListenYubiKey(bool listen) private void VaultTimeoutTimer(int vaultTimeoutMinutes)
{
if (_deviceActionService.SupportsNfc())
{
_nfcSession?.InvalidateSession();
_nfcSession?.Dispose();
_nfcSession = null;
if (listen)
{
_nfcSession = new NFCNdefReaderSession(_nfcDelegate, null, true)
{
AlertMessage = AppResources.HoldYubikeyNearTop
};
_nfcSession.BeginSession();
}
}
}
private void LockTimer(int lockOptionMinutes)
{ {
if (_lockBackgroundTaskId > 0) if (_lockBackgroundTaskId > 0)
{ {
@ -330,21 +311,21 @@ namespace Bit.iOS
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId); UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
_lockBackgroundTaskId = 0; _lockBackgroundTaskId = 0;
}); });
var lockOptionMs = lockOptionMinutes * 60000; var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
_lockTimer?.Invalidate(); _vaultTimeoutTimer?.Invalidate();
_lockTimer?.Dispose(); _vaultTimeoutTimer?.Dispose();
_lockTimer = null; _vaultTimeoutTimer = null;
var lockMsSpan = TimeSpan.FromMilliseconds(lockOptionMs + 10); var vaultTimeoutMsSpan = TimeSpan.FromMilliseconds(vaultTimeoutMs + 10);
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
{ {
_lockTimer = NSTimer.CreateScheduledTimer(lockMsSpan, timer => _vaultTimeoutTimer = NSTimer.CreateScheduledTimer(vaultTimeoutMsSpan, timer =>
{ {
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
{ {
_lockService.CheckLockAsync(); _vaultTimeoutService.CheckVaultTimeoutAsync();
_lockTimer?.Invalidate(); _vaultTimeoutTimer?.Invalidate();
_lockTimer?.Dispose(); _vaultTimeoutTimer?.Dispose();
_lockTimer = null; _vaultTimeoutTimer = null;
if (_lockBackgroundTaskId > 0) if (_lockBackgroundTaskId > 0)
{ {
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId); UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
@ -355,11 +336,11 @@ namespace Bit.iOS
}); });
} }
private void CancelLockTimer() private void CancelVaultTimeoutTimer()
{ {
_lockTimer?.Invalidate(); _vaultTimeoutTimer?.Invalidate();
_lockTimer?.Dispose(); _vaultTimeoutTimer?.Dispose();
_lockTimer = null; _vaultTimeoutTimer = null;
if (_lockBackgroundTaskId > 0) if (_lockBackgroundTaskId > 0)
{ {
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId); UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);

View File

@ -134,20 +134,8 @@
<ItemGroup> <ItemGroup>
<Compile Include="Main.cs" /> <Compile Include="Main.cs" />
<Compile Include="AppDelegate.cs" /> <Compile Include="AppDelegate.cs" />
<Compile Include="NFCReaderDelegate.cs" />
<Compile Include="Renderers\CustomButtonRenderer.cs" />
<Compile Include="Renderers\CustomSearchBarRenderer.cs" />
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
<Compile Include="Renderers\CustomPickerRenderer.cs" />
<Compile Include="Renderers\CustomEntryRenderer.cs" />
<Compile Include="Renderers\CustomEditorRenderer.cs" />
<Compile Include="Renderers\CustomViewCellRenderer.cs" />
<Compile Include="Renderers\CustomLabelRenderer.cs" />
<Compile Include="Renderers\CustomContentPageRenderer.cs" />
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
<Compile Include="Services\iOSPushNotificationHandler.cs" /> <Compile Include="Services\iOSPushNotificationHandler.cs" />
<Compile Include="Services\iOSPushNotificationService.cs" /> <Compile Include="Services\iOSPushNotificationService.cs" />
<Compile Include="Utilities\iOSHelpers.cs" />
<None Include="Entitlements.plist" /> <None Include="Entitlements.plist" />
<None Include="Info.plist" /> <None Include="Info.plist" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />