[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:
parent
39e10ff01c
commit
4c3df2e1e1
@ -21,7 +21,7 @@ namespace Bit.Droid.Autofill
|
||||
public class AutofillService : Android.Service.Autofill.AutofillService
|
||||
{
|
||||
private ICipherService _cipherService;
|
||||
private ILockService _lockService;
|
||||
private IVaultTimeoutService _vaultTimeoutService;
|
||||
private IStorageService _storageService;
|
||||
|
||||
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
|
||||
@ -47,13 +47,13 @@ namespace Bit.Droid.Autofill
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lockService == null)
|
||||
if (_vaultTimeoutService == null)
|
||||
{
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
}
|
||||
|
||||
List<FilledItem> items = null;
|
||||
var locked = await _lockService.IsLockedAsync();
|
||||
var locked = await _vaultTimeoutService.IsLockedAsync();
|
||||
if (!locked)
|
||||
{
|
||||
if (_cipherService == null)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Android.Graphics.Drawables;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
@ -13,7 +14,7 @@ namespace Bit.Droid.Effects
|
||||
if (Control is Android.Widget.Button button)
|
||||
{
|
||||
var gd = new GradientDrawable();
|
||||
gd.SetColor(((Color)Application.Current.Resources["FabColor"]).ToAndroid());
|
||||
gd.SetColor(ThemeManager.GetResourceColor("FabColor").ToAndroid());
|
||||
gd.SetCornerRadius(100);
|
||||
|
||||
button.SetBackground(gd);
|
||||
|
@ -37,7 +37,7 @@ namespace Bit.Droid
|
||||
private IAppIdService _appIdService;
|
||||
private IStorageService _storageService;
|
||||
private IEventService _eventService;
|
||||
private PendingIntent _lockAlarmPendingIntent;
|
||||
private PendingIntent _vaultTimeoutAlarmPendingIntent;
|
||||
private PendingIntent _clearClipboardPendingIntent;
|
||||
private PendingIntent _eventUploadPendingIntent;
|
||||
private AppOptions _appOptions;
|
||||
@ -51,7 +51,7 @@ namespace Bit.Droid
|
||||
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
|
||||
PendingIntentFlags.UpdateCurrent);
|
||||
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
|
||||
_lockAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
|
||||
_vaultTimeoutAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
|
||||
PendingIntentFlags.UpdateCurrent);
|
||||
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
|
||||
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
|
||||
@ -90,18 +90,18 @@ namespace Bit.Droid
|
||||
|
||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
||||
{
|
||||
if (message.Command == "scheduleLockTimer")
|
||||
if (message.Command == "scheduleVaultTimeoutTimer")
|
||||
{
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
var lockOptionMinutes = (int)message.Data;
|
||||
var lockOptionMs = lockOptionMinutes * 60000;
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + lockOptionMs + 10;
|
||||
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _lockAlarmPendingIntent);
|
||||
var vaultTimeoutMinutes = (int)message.Data;
|
||||
var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + vaultTimeoutMs + 10;
|
||||
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _vaultTimeoutAlarmPendingIntent);
|
||||
}
|
||||
else if (message.Command == "cancelLockTimer")
|
||||
else if (message.Command == "cancelVaultTimeoutTimer")
|
||||
{
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
alarmManager.Cancel(_lockAlarmPendingIntent);
|
||||
alarmManager.Cancel(_vaultTimeoutAlarmPendingIntent);
|
||||
}
|
||||
else if (message.Command == "startEventTimer")
|
||||
{
|
||||
|
@ -9,8 +9,8 @@ namespace Bit.Droid.Receivers
|
||||
{
|
||||
public async override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
await lockService.CheckLockAsync();
|
||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
await vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using Android.Views;
|
||||
using Android.Views.InputMethods;
|
||||
using Android.Widget;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Droid.Renderers;
|
||||
using FFImageLoading;
|
||||
using FFImageLoading.Views;
|
||||
@ -42,19 +43,15 @@ namespace Bit.Droid.Renderers
|
||||
}
|
||||
if (_textColor == default(Android.Graphics.Color))
|
||||
{
|
||||
_textColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["TextColor"])
|
||||
.ToAndroid();
|
||||
_textColor = ThemeManager.GetResourceColor("TextColor").ToAndroid();
|
||||
}
|
||||
if (_mutedColor == default(Android.Graphics.Color))
|
||||
{
|
||||
_mutedColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["MutedColor"])
|
||||
.ToAndroid();
|
||||
_mutedColor = ThemeManager.GetResourceColor("MutedColor").ToAndroid();
|
||||
}
|
||||
if (_disabledIconColor == default(Android.Graphics.Color))
|
||||
{
|
||||
_disabledIconColor =
|
||||
((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["DisabledIconColor"])
|
||||
.ToAndroid();
|
||||
_disabledIconColor = ThemeManager.GetResourceColor("DisabledIconColor").ToAndroid();
|
||||
}
|
||||
|
||||
var cipherCell = item as CipherViewCell;
|
||||
|
@ -200,13 +200,13 @@ namespace Bit.Droid.Services
|
||||
catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
|
||||
|
||||
if (contentUri != null)
|
||||
{
|
||||
var uri = Android.Net.Uri.Parse(contentUri);
|
||||
@ -219,7 +219,7 @@ namespace Bit.Droid.Services
|
||||
javaStream.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Prompt for location to save file
|
||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||
if (extension == null)
|
||||
@ -238,7 +238,7 @@ namespace Bit.Droid.Services
|
||||
intent.SetType(mimeType);
|
||||
intent.AddCategory(Intent.CategoryOpenable);
|
||||
intent.PutExtra(Intent.ExtraTitle, fileName);
|
||||
|
||||
|
||||
activity.StartActivityForResult(intent, Constants.SaveFileRequestCode);
|
||||
return true;
|
||||
}
|
||||
@ -569,6 +569,13 @@ namespace Bit.Droid.Services
|
||||
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)
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
|
@ -32,6 +32,7 @@ namespace Bit.App.Abstractions
|
||||
int SystemMajorVersion();
|
||||
string SystemModel();
|
||||
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 CloseAutofill();
|
||||
void Background();
|
||||
|
@ -22,7 +22,7 @@ namespace Bit.App
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
@ -37,18 +37,22 @@ namespace Bit.App
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly IStorageService _secureStorageService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly AppOptions _appOptions;
|
||||
|
||||
private static bool _isResumed;
|
||||
|
||||
public App(AppOptions appOptions)
|
||||
{
|
||||
_appOptions = appOptions ?? new AppOptions();
|
||||
Options = appOptions ?? new AppOptions();
|
||||
if (Options.EmptyApp)
|
||||
{
|
||||
Current = this;
|
||||
return;
|
||||
}
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||
@ -95,7 +99,7 @@ namespace Bit.App
|
||||
}
|
||||
else if (message.Command == "lockVault")
|
||||
{
|
||||
await _lockService.LockAsync(true);
|
||||
await _vaultTimeoutService.LockAsync(true);
|
||||
}
|
||||
else if (message.Command == "logout")
|
||||
{
|
||||
@ -139,12 +143,12 @@ namespace Bit.App
|
||||
}
|
||||
if (message.Command == "popAllAndGoToTabMyVault")
|
||||
{
|
||||
_appOptions.MyVaultTile = false;
|
||||
Options.MyVaultTile = false;
|
||||
tabsPage.ResetToVaultPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
_appOptions.GeneratorTile = false;
|
||||
Options.GeneratorTile = false;
|
||||
tabsPage.ResetToGeneratorPage();
|
||||
}
|
||||
}
|
||||
@ -152,14 +156,16 @@ namespace Bit.App
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public AppOptions Options { get; private set; }
|
||||
|
||||
protected async override void OnStart()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
|
||||
await ClearCacheIfNeededAsync();
|
||||
await TryClearCiphersCacheAsync();
|
||||
Prime();
|
||||
if (string.IsNullOrWhiteSpace(_appOptions.Uri))
|
||||
if (string.IsNullOrWhiteSpace(Options.Uri))
|
||||
{
|
||||
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
|
||||
_storageService);
|
||||
@ -177,7 +183,7 @@ namespace Bit.App
|
||||
_isResumed = false;
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
var isLocked = await _lockService.IsLockedAsync();
|
||||
var isLocked = await _vaultTimeoutService.IsLockedAsync();
|
||||
if (!isLocked)
|
||||
{
|
||||
await _storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow);
|
||||
@ -199,13 +205,13 @@ namespace Bit.App
|
||||
|
||||
private async Task SleptAsync()
|
||||
{
|
||||
await HandleLockingAsync();
|
||||
await HandleVaultTimeoutAsync();
|
||||
_messagingService.Send("stopEventTimer");
|
||||
}
|
||||
|
||||
private async void ResumedAsync()
|
||||
{
|
||||
_messagingService.Send("cancelLockTimer");
|
||||
_messagingService.Send("cancelVaultTimeoutTimer");
|
||||
_messagingService.Send("startEventTimer");
|
||||
await ClearCacheIfNeededAsync();
|
||||
await TryClearCiphersCacheAsync();
|
||||
@ -238,9 +244,9 @@ namespace Bit.App
|
||||
_folderService.ClearAsync(userId),
|
||||
_collectionService.ClearAsync(userId),
|
||||
_passwordGenerationService.ClearAsync(),
|
||||
_lockService.ClearAsync(),
|
||||
_vaultTimeoutService.ClearAsync(),
|
||||
_stateService.PurgeAsync());
|
||||
_lockService.FingerprintLocked = true;
|
||||
_vaultTimeoutService.FingerprintLocked = true;
|
||||
_searchService.ClearIndex();
|
||||
_authService.LogOut(() =>
|
||||
{
|
||||
@ -257,32 +263,32 @@ namespace Bit.App
|
||||
var authed = await _userService.IsAuthenticatedAsync();
|
||||
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
|
||||
{
|
||||
Current.MainPage = new TabsPage(_appOptions);
|
||||
Current.MainPage = new TabsPage(Options);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
@ -291,19 +297,28 @@ namespace Bit.App
|
||||
{
|
||||
return;
|
||||
}
|
||||
var lockOption = _platformUtilsService.LockTimeout();
|
||||
if (lockOption == null)
|
||||
// Will only ever be null - look to remove this in the future
|
||||
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);
|
||||
if (lockOption > 0)
|
||||
vaultTimeout = vaultTimeout.GetValueOrDefault(-1);
|
||||
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)
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(_appOptions.Uri) &&
|
||||
!_appOptions.FromAutofillFramework)
|
||||
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
|
||||
!Options.FromAutofillFramework)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
_appOptions.Uri = null;
|
||||
Options.Uri = null;
|
||||
if (isLocked)
|
||||
{
|
||||
Current.MainPage = new NavigationPage(new LockPage());
|
||||
@ -352,7 +367,7 @@ namespace Bit.App
|
||||
{
|
||||
InitializeComponent();
|
||||
SetCulture();
|
||||
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android);
|
||||
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
|
||||
Current.MainPage = new HomePage();
|
||||
var mainPageTask = SetMainPageAsync();
|
||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||
@ -394,8 +409,8 @@ namespace Bit.App
|
||||
await _stateService.PurgeAsync();
|
||||
if (autoPromptFingerprint && Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
var lockOptions = await _storageService.GetAsync<int?>(Constants.LockOptionKey);
|
||||
if (lockOptions == 0)
|
||||
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
|
||||
if (vaultTimeout == 0)
|
||||
{
|
||||
autoPromptFingerprint = false;
|
||||
}
|
||||
@ -430,7 +445,7 @@ namespace Bit.App
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -18,5 +18,6 @@ namespace Bit.App.Models
|
||||
public string SaveCardExpMonth { get; set; }
|
||||
public string SaveCardExpYear { get; set; }
|
||||
public string SaveCardCode { get; set; }
|
||||
public bool EmptyApp { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly IUserService _userService;
|
||||
@ -39,7 +39,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
@ -102,9 +102,9 @@ namespace Bit.App.Pages
|
||||
|
||||
public async Task InitAsync(bool autoPromptFingerprint)
|
||||
{
|
||||
_pinSet = await _lockService.IsPinLockSetAsync();
|
||||
PinLock = (_pinSet.Item1 && _lockService.PinProtectedKey != null) || _pinSet.Item2;
|
||||
FingerprintLock = await _lockService.IsFingerprintLockSetAsync();
|
||||
_pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
PinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
|
||||
FingerprintLock = await _vaultTimeoutService.IsFingerprintLockSetAsync();
|
||||
_email = await _userService.GetEmailAsync();
|
||||
var webVault = _environmentService.GetWebVaultUrl();
|
||||
if (string.IsNullOrWhiteSpace(webVault))
|
||||
@ -180,7 +180,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
|
||||
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
|
||||
_lockService.PinProtectedKey);
|
||||
_vaultTimeoutService.PinProtectedKey);
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
|
||||
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 pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
|
||||
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;
|
||||
await SetKeyAndContinueAsync(key);
|
||||
@ -290,7 +290,7 @@ namespace Bit.App.Pages
|
||||
page.MasterPasswordEntry.Focus();
|
||||
}
|
||||
});
|
||||
_lockService.FingerprintLocked = !success;
|
||||
_vaultTimeoutService.FingerprintLocked = !success;
|
||||
if (success)
|
||||
{
|
||||
await DoContinueAsync();
|
||||
@ -309,7 +309,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task DoContinueAsync()
|
||||
{
|
||||
_lockService.FingerprintLocked = false;
|
||||
_vaultTimeoutService.FingerprintLocked = false;
|
||||
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
|
||||
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
|
||||
_messagingService.Send("unlocked");
|
||||
|
@ -73,8 +73,8 @@
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
<StackLayout Padding="10, 0">
|
||||
<Button Text="{u:I18n GetPasswordHint}" Clicked="Hint_Clicked"></Button>
|
||||
<StackLayout Padding="10, 0" IsVisible="{Binding HideHintButton, Converter={StaticResource inverseBool}}">
|
||||
<Button Text="{u:I18n GetPasswordHint}" Clicked="Hint_Clicked" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
@ -26,6 +26,11 @@ namespace Bit.App.Pages
|
||||
_vm.Page = this;
|
||||
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||
_vm.LoggedInAction = () => Device.BeginInvokeOnMainThread(async () => await LoggedInAsync());
|
||||
_vm.CloseAction = async () =>
|
||||
{
|
||||
_messagingService.Send("showStatusBar", false);
|
||||
await Navigation.PopModalAsync();
|
||||
};
|
||||
_vm.Email = email;
|
||||
MasterPasswordEntry = _masterPassword;
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
@ -73,8 +78,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
_messagingService.Send("showStatusBar", false);
|
||||
await Navigation.PopModalAsync();
|
||||
_vm.CloseAction();
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +87,7 @@ namespace Bit.App.Pages
|
||||
var page = new TwoFactorPage();
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
|
||||
private async Task LoggedInAsync()
|
||||
{
|
||||
if (_appOptions != null)
|
||||
|
@ -25,6 +25,7 @@ namespace Bit.App.Pages
|
||||
private bool _showPassword;
|
||||
private string _email;
|
||||
private string _masterPassword;
|
||||
private bool _hideHintButton;
|
||||
|
||||
public LoginPageViewModel()
|
||||
{
|
||||
@ -68,7 +69,14 @@ namespace Bit.App.Pages
|
||||
public bool RememberEmail { get; set; }
|
||||
public Action StartTwoFactorAction { 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()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Email))
|
||||
@ -89,20 +97,23 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Email))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
await _platformUtilsService.ShowDialogAsync(
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress),
|
||||
AppResources.AnErrorHasOccurred,
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
if (!Email.Contains("@"))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok);
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidEmail, AppResources.AnErrorHasOccurred,
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(MasterPassword))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
await _platformUtilsService.ShowDialogAsync(
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
||||
AppResources.AnErrorHasOccurred,
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ namespace Bit.App.Pages
|
||||
_vm = BindingContext as TwoFactorPageViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.TwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthAsync());
|
||||
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
||||
DuoWebView = _duoWebView;
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
@ -144,7 +145,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
_vm.CloseAction();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
public Command SubmitCommand { get; }
|
||||
public Action TwoFactorAction { get; set; }
|
||||
public Action CloseAction { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
@ -228,8 +229,8 @@ namespace Bit.App.Pages
|
||||
var supportedProviders = _authService.GetSupportedTwoFactorProviders();
|
||||
var options = supportedProviders.Select(p => p.Name).ToList();
|
||||
options.Add(AppResources.RecoveryCodeTitle);
|
||||
var method = await Page.DisplayActionSheet(AppResources.TwoStepLoginOptions, AppResources.Cancel,
|
||||
null, options.ToArray());
|
||||
var method = await _deviceActionService.DisplayActionSheetAsync(AppResources.TwoStepLoginOptions,
|
||||
AppResources.Cancel, null, options.ToArray());
|
||||
if (method == AppResources.RecoveryCodeTitle)
|
||||
{
|
||||
_platformUtilsService.LaunchUri("https://help.bitwarden.com/article/lost-two-step-device/");
|
||||
|
@ -129,9 +129,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using Xamarin.Forms;
|
||||
|
||||
@ -11,7 +12,7 @@ namespace Bit.App.Pages
|
||||
public string SubLabel { get; set; }
|
||||
public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled;
|
||||
public Color SubLabelColor => SubLabelTextEnabled ?
|
||||
(Color)Application.Current.Resources["SuccessColor"] :
|
||||
(Color)Application.Current.Resources["MutedColor"];
|
||||
ThemeManager.GetResourceColor("SuccessColor") :
|
||||
ThemeManager.GetResourceColor("MutedColor");
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace Bit.App.Pages
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IEnvironmentService _environmentService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly ISyncService _syncService;
|
||||
|
||||
@ -27,20 +27,27 @@ namespace Bit.App.Pages
|
||||
private bool _pin;
|
||||
private bool _fingerprint;
|
||||
private string _lastSyncDate;
|
||||
private string _lockOptionValue;
|
||||
private List<KeyValuePair<string, int?>> _lockOptions =
|
||||
private string _vaultTimeoutDisplayValue;
|
||||
private string _vaultTimeoutActionDisplayValue;
|
||||
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
||||
new List<KeyValuePair<string, int?>>
|
||||
{
|
||||
new KeyValuePair<string, int?>(AppResources.LockOptionImmediately, 0),
|
||||
new KeyValuePair<string, int?>(AppResources.LockOption1Minute, 1),
|
||||
new KeyValuePair<string, int?>(AppResources.LockOption5Minutes, 5),
|
||||
new KeyValuePair<string, int?>(AppResources.LockOption15Minutes, 15),
|
||||
new KeyValuePair<string, int?>(AppResources.LockOption30Minutes, 30),
|
||||
new KeyValuePair<string, int?>(AppResources.LockOption1Hour, 60),
|
||||
new KeyValuePair<string, int?>(AppResources.LockOption4Hours, 240),
|
||||
new KeyValuePair<string, int?>(AppResources.LockOptionOnRestart, -1),
|
||||
new KeyValuePair<string, int?>(AppResources.Immediately, 0),
|
||||
new KeyValuePair<string, int?>(AppResources.OneMinute, 1),
|
||||
new KeyValuePair<string, int?>(AppResources.FiveMinutes, 5),
|
||||
new KeyValuePair<string, int?>(AppResources.FifteenMinutes, 15),
|
||||
new KeyValuePair<string, int?>(AppResources.ThirtyMinutes, 30),
|
||||
new KeyValuePair<string, int?>(AppResources.OneHour, 60),
|
||||
new KeyValuePair<string, int?>(AppResources.FourHours, 240),
|
||||
new KeyValuePair<string, int?>(AppResources.OnRestart, -1),
|
||||
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()
|
||||
{
|
||||
@ -50,7 +57,7 @@ namespace Bit.App.Pages
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
|
||||
@ -70,11 +77,13 @@ namespace Bit.App.Pages
|
||||
_lastSyncDate = string.Format("{0} {1}", lastSync.Value.ToShortDateString(),
|
||||
lastSync.Value.ToShortTimeString());
|
||||
}
|
||||
var option = await _storageService.GetAsync<int?>(Constants.LockOptionKey);
|
||||
_lockOptionValue = _lockOptions.FirstOrDefault(o => o.Value == option).Key;
|
||||
var pinSet = await _lockService.IsPinLockSetAsync();
|
||||
var timeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
|
||||
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == timeout).Key;
|
||||
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;
|
||||
_fingerprint = await _lockService.IsFingerprintLockSetAsync();
|
||||
_fingerprint = await _vaultTimeoutService.IsFingerprintLockSetAsync();
|
||||
BuildList();
|
||||
}
|
||||
|
||||
@ -179,21 +188,48 @@ namespace Bit.App.Pages
|
||||
|
||||
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 selection = await Page.DisplayActionSheet(AppResources.LockOptions, AppResources.Cancel, null, options);
|
||||
var options = _vaultTimeouts.Select(o => o.Key == _vaultTimeoutDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
|
||||
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeout, AppResources.Cancel, null, options);
|
||||
if (selection == null || selection == AppResources.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var cleanSelection = selection.Replace("✓ ", string.Empty);
|
||||
var selectionOption = _lockOptions.FirstOrDefault(o => o.Key == cleanSelection);
|
||||
_lockOptionValue = selectionOption.Key;
|
||||
await _lockService.SetLockOptionAsync(selectionOption.Value);
|
||||
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
|
||||
_vaultTimeoutDisplayValue = selectionOption.Key;
|
||||
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();
|
||||
}
|
||||
|
||||
@ -223,7 +259,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
var encPin = await _cryptoService.EncryptAsync(pin);
|
||||
await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString);
|
||||
_lockService.PinProtectedKey = pinProtectedKey;
|
||||
_vaultTimeoutService.PinProtectedKey = pinProtectedKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -238,7 +274,7 @@ namespace Bit.App.Pages
|
||||
if (!_pin)
|
||||
{
|
||||
await _cryptoService.ClearPinProtectedKeyAsync();
|
||||
await _lockService.ClearAsync();
|
||||
await _vaultTimeoutService.ClearAsync();
|
||||
}
|
||||
BuildList();
|
||||
}
|
||||
@ -267,7 +303,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await _storageService.RemoveAsync(Constants.FingerprintUnlockKey);
|
||||
}
|
||||
_lockService.FingerprintLocked = false;
|
||||
_vaultTimeoutService.FingerprintLocked = false;
|
||||
await _cryptoService.ToggleKeyAsync();
|
||||
BuildList();
|
||||
}
|
||||
@ -312,7 +348,8 @@ namespace Bit.App.Pages
|
||||
};
|
||||
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
|
||||
{
|
||||
Name = AppResources.UnlockWithPIN,
|
||||
@ -338,7 +375,7 @@ namespace Bit.App.Pages
|
||||
Name = string.Format(AppResources.UnlockWith, fingerprintName),
|
||||
SubLabel = _fingerprint ? AppResources.Enabled : AppResources.Disabled
|
||||
};
|
||||
securityItems.Insert(1, item);
|
||||
securityItems.Insert(2, item);
|
||||
}
|
||||
var accountItems = new List<SettingsPageListItem>
|
||||
{
|
||||
@ -370,5 +407,15 @@ namespace Bit.App.Pages
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace Bit.App.Pages
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly GroupingsPageViewModel _vm;
|
||||
@ -39,7 +39,7 @@ namespace Bit.App.Pages
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_vm = BindingContext as GroupingsPageViewModel;
|
||||
@ -247,7 +247,7 @@ namespace Bit.App.Pages
|
||||
|
||||
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)
|
||||
|
@ -39,7 +39,7 @@ namespace Bit.App.Pages
|
||||
private readonly ICollectionService _collectionService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
@ -52,7 +52,7 @@ namespace Bit.App.Pages
|
||||
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
@ -145,7 +145,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (await _lockService.IsLockedAsync())
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ namespace Bit.App.Pages
|
||||
fs.Spans.Add(new Span
|
||||
{
|
||||
Text = string.Format(" {0}", Cipher.PasswordHistory.Count.ToString()),
|
||||
TextColor = (Color)Application.Current.Resources["PrimaryColor"]
|
||||
TextColor = ThemeManager.GetResourceColor("PrimaryColor")
|
||||
});
|
||||
return fs;
|
||||
}
|
||||
@ -209,7 +209,7 @@ namespace Bit.App.Pages
|
||||
set
|
||||
{
|
||||
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;
|
||||
|
57
src/App/Resources/AppResources.Designer.cs
generated
57
src/App/Resources/AppResources.Designer.cs
generated
@ -1,7 +1,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 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
|
||||
// the code is regenerated.
|
||||
@ -755,39 +754,51 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string LockOption15Minutes {
|
||||
public static string FifteenMinutes {
|
||||
get {
|
||||
return ResourceManager.GetString("LockOption15Minutes", resourceCulture);
|
||||
return ResourceManager.GetString("FifteenMinutes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LockOption1Hour {
|
||||
public static string OneHour {
|
||||
get {
|
||||
return ResourceManager.GetString("LockOption1Hour", resourceCulture);
|
||||
return ResourceManager.GetString("OneHour", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LockOption1Minute {
|
||||
public static string OneMinute {
|
||||
get {
|
||||
return ResourceManager.GetString("LockOption1Minute", resourceCulture);
|
||||
return ResourceManager.GetString("OneMinute", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LockOption4Hours {
|
||||
public static string FourHours {
|
||||
get {
|
||||
return ResourceManager.GetString("LockOption4Hours", resourceCulture);
|
||||
return ResourceManager.GetString("FourHours", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LockOptionImmediately {
|
||||
public static string Immediately {
|
||||
get {
|
||||
return ResourceManager.GetString("LockOptionImmediately", resourceCulture);
|
||||
return ResourceManager.GetString("Immediately", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LockOptions {
|
||||
public static string VaultTimeout {
|
||||
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 {
|
||||
return ResourceManager.GetString("LockOption30Minutes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LockOption5Minutes {
|
||||
get {
|
||||
return ResourceManager.GetString("LockOption5Minutes", resourceCulture);
|
||||
return ResourceManager.GetString("ThirtyMinutes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2555,12 +2560,6 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string OneMinute {
|
||||
get {
|
||||
return ResourceManager.GetString("OneMinute", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string TenSeconds {
|
||||
get {
|
||||
return ResourceManager.GetString("TenSeconds", resourceCulture);
|
||||
@ -2687,9 +2686,9 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string LockOptionOnRestart {
|
||||
public static string OnRestart {
|
||||
get {
|
||||
return ResourceManager.GetString("LockOptionOnRestart", resourceCulture);
|
||||
return ResourceManager.GetString("OnRestart", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Are you sure you want to exit Bitwarden?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Black</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Sind Sie sicher, dass Sie Bitwarden verlassen möchten?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Schwarz</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>¿Estás seguro de que deseas salir de Bitwarden?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Negro</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Kas soovid tõesti Bitwardeni sulgeda?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Must</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>آیا مطمئنید که میخواهید از Bitwarden خارج شوید؟</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>سیاه</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Biztos vagy benne, hogy kilépsz?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Fekete</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Apakah anda yakin ingin keluar dari Bitwarden?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Hitam</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Sei sicuro di voler uscire da Bitwarden?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Nero</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>정말로 Bitwarden에서 나가시려는 건가요?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>검은 테마</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Er du sikker på at du vil lukke Bitwarden?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Svart</value>
|
||||
|
@ -518,23 +518,29 @@
|
||||
<data name="Lock" xml:space="preserve">
|
||||
<value>Lock</value>
|
||||
</data>
|
||||
<data name="LockOption15Minutes" xml:space="preserve">
|
||||
<data name="FifteenMinutes" xml:space="preserve">
|
||||
<value>15 minutes</value>
|
||||
</data>
|
||||
<data name="LockOption1Hour" xml:space="preserve">
|
||||
<data name="OneHour" xml:space="preserve">
|
||||
<value>1 hour</value>
|
||||
</data>
|
||||
<data name="LockOption1Minute" xml:space="preserve">
|
||||
<data name="OneMinute" xml:space="preserve">
|
||||
<value>1 minute</value>
|
||||
</data>
|
||||
<data name="LockOption4Hours" xml:space="preserve">
|
||||
<data name="FourHours" xml:space="preserve">
|
||||
<value>4 hours</value>
|
||||
</data>
|
||||
<data name="LockOptionImmediately" xml:space="preserve">
|
||||
<data name="Immediately" xml:space="preserve">
|
||||
<value>Immediately</value>
|
||||
</data>
|
||||
<data name="LockOptions" xml:space="preserve">
|
||||
<value>Lock Options</value>
|
||||
<data name="VaultTimeout" xml:space="preserve">
|
||||
<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 name="LoggingIn" xml:space="preserve">
|
||||
<value>Logging in...</value>
|
||||
@ -1433,12 +1439,9 @@
|
||||
<data name="Unlock" xml:space="preserve">
|
||||
<value>Unlock</value>
|
||||
</data>
|
||||
<data name="LockOption30Minutes" xml:space="preserve">
|
||||
<data name="ThirtyMinutes" xml:space="preserve">
|
||||
<value>30 minutes</value>
|
||||
</data>
|
||||
<data name="LockOption5Minutes" xml:space="preserve">
|
||||
<value>5 minutes</value>
|
||||
</data>
|
||||
<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>
|
||||
</data>
|
||||
@ -1463,9 +1466,6 @@
|
||||
<data name="FiveMinutes" xml:space="preserve">
|
||||
<value>5 minutes</value>
|
||||
</data>
|
||||
<data name="OneMinute" xml:space="preserve">
|
||||
<value>1 minute</value>
|
||||
</data>
|
||||
<data name="TenSeconds" xml:space="preserve">
|
||||
<value>10 seconds</value>
|
||||
</data>
|
||||
@ -1535,7 +1535,7 @@
|
||||
<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>
|
||||
</data>
|
||||
<data name="LockOptionOnRestart" xml:space="preserve">
|
||||
<data name="OnRestart" xml:space="preserve">
|
||||
<value>On App Restart</value>
|
||||
</data>
|
||||
<data name="AutofillServiceNotEnabled" xml:space="preserve">
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Sunteți sigur că doriți să ieșiți?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Neagră</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Naozaj chcete ukončiť Bitwarden?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Čierna</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Are you sure you want to exit Bitwarden?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Black</value>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<value>Are you sure you want to exit Bitwarden?</value>
|
||||
</data>
|
||||
<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 name="Black" xml:space="preserve">
|
||||
<value>Black</value>
|
||||
|
@ -13,7 +13,8 @@ namespace Bit.App.Services
|
||||
|
||||
private readonly HashSet<string> _preferenceStorageKeys = new HashSet<string>
|
||||
{
|
||||
Constants.LockOptionKey,
|
||||
Constants.VaultTimeoutKey,
|
||||
Constants.VaultTimeoutActionKey,
|
||||
Constants.ThemeKey,
|
||||
Constants.DefaultUriMatch,
|
||||
Constants.DisableAutoTotpCopyKey,
|
||||
|
@ -18,7 +18,7 @@ namespace Bit.App.Utilities
|
||||
{
|
||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
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 };
|
||||
if (!cipher.IsDeleted)
|
||||
{
|
||||
@ -67,7 +67,7 @@ namespace Bit.App.Utilities
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
@ -137,10 +137,16 @@ namespace Bit.App.Utilities
|
||||
if (lastBuild == null)
|
||||
{
|
||||
// Installed
|
||||
var currentLock = await storageService.GetAsync<int?>(Constants.LockOptionKey);
|
||||
if (currentLock == null)
|
||||
var currentTimeout = await storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
|
||||
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)
|
||||
|
@ -31,9 +31,9 @@ namespace Bit.App.Utilities
|
||||
|
||||
// First two digits of returned hex code contains the alpha,
|
||||
// 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 numberColor = $"<span style=\"color:#{((Color)Application.Current.Resources["PasswordNumberColor"]).ToHex().Substring(3)}\">";
|
||||
var specialColor = $"<span style=\"color:#{((Color)Application.Current.Resources["PasswordSpecialColor"]).ToHex().Substring(3)}\">";
|
||||
var normalColor = $"<span style=\"color:#{ThemeManager.GetResourceColor("TextColor").ToHex().Substring(3)}\">";
|
||||
var numberColor = $"<span style=\"color:#{ThemeManager.GetResourceColor("PasswordNumberColor").ToHex().Substring(3)}\">";
|
||||
var specialColor = $"<span style=\"color:#{ThemeManager.GetResourceColor("PasswordSpecialColor").ToHex().Substring(3)}\">";
|
||||
var result = string.Empty;
|
||||
|
||||
// iOS won't hide the zero-width space char without these div attrs, but Android won't respect
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.App.Abstractions;
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Styles;
|
||||
using Bit.Core;
|
||||
@ -10,35 +11,38 @@ namespace Bit.App.Utilities
|
||||
public static class ThemeManager
|
||||
{
|
||||
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
|
||||
Application.Current.Resources.Clear();
|
||||
Application.Current.Resources.MergedDictionaries.Clear();
|
||||
resources.Clear();
|
||||
resources.MergedDictionaries.Clear();
|
||||
|
||||
// Variables
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Variables());
|
||||
resources.MergedDictionaries.Add(new Variables());
|
||||
|
||||
// Themed variables
|
||||
if (name == "dark")
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Dark());
|
||||
resources.MergedDictionaries.Add(new Dark());
|
||||
UsingLightTheme = false;
|
||||
}
|
||||
else if (name == "black")
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Black());
|
||||
resources.MergedDictionaries.Add(new Black());
|
||||
UsingLightTheme = false;
|
||||
}
|
||||
else if (name == "nord")
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Nord());
|
||||
resources.MergedDictionaries.Add(new Nord());
|
||||
UsingLightTheme = false;
|
||||
}
|
||||
else if (name == "light")
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Light());
|
||||
resources.MergedDictionaries.Add(new Light());
|
||||
UsingLightTheme = true;
|
||||
}
|
||||
else
|
||||
@ -46,33 +50,33 @@ namespace Bit.App.Utilities
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService", true);
|
||||
if (deviceActionService?.UsingDarkTheme() ?? false)
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Dark());
|
||||
resources.MergedDictionaries.Add(new Dark());
|
||||
UsingLightTheme = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Light());
|
||||
resources.MergedDictionaries.Add(new Light());
|
||||
UsingLightTheme = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Base styles
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Base());
|
||||
resources.MergedDictionaries.Add(new Base());
|
||||
|
||||
// Platform styles
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Add(new Android());
|
||||
resources.MergedDictionaries.Add(new Android());
|
||||
}
|
||||
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)
|
||||
@ -81,5 +85,18 @@ namespace Bit.App.Utilities
|
||||
string.Format(PreferencesStorageService.KeyFormat, Constants.ThemeKey), 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ namespace Bit.Core.Abstractions
|
||||
bool GetPremium();
|
||||
Task<string> GetRefreshTokenAsync();
|
||||
Task<string> GetTokenAsync();
|
||||
Task ToggleTokensAsync();
|
||||
DateTime? GetTokenExpirationDate();
|
||||
Task<string> GetTwoFactorTokenAsync(string email);
|
||||
string GetUserId();
|
||||
@ -26,4 +27,4 @@ namespace Bit.Core.Abstractions
|
||||
bool TokenNeedsRefresh(int minutes = 5);
|
||||
int TokenSecondsRemaining();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,17 +4,18 @@ using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface ILockService
|
||||
public interface IVaultTimeoutService
|
||||
{
|
||||
CipherString PinProtectedKey { get; set; }
|
||||
bool FingerprintLocked { get; set; }
|
||||
|
||||
Task CheckLockAsync();
|
||||
Task CheckVaultTimeoutAsync();
|
||||
Task ClearAsync();
|
||||
Task<bool> IsLockedAsync();
|
||||
Task<Tuple<bool, bool>> IsPinLockSetAsync();
|
||||
Task<bool> IsFingerprintLockSetAsync();
|
||||
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false);
|
||||
Task SetLockOptionAsync(int? lockOption);
|
||||
Task LogOutAsync();
|
||||
Task SetVaultTimeoutOptionsAsync(int? timeout, string action);
|
||||
}
|
||||
}
|
@ -4,7 +4,8 @@
|
||||
{
|
||||
public const string AndroidAppProtocol = "androidapp://";
|
||||
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 FingerprintUnlockKey = "fingerprintUnlock";
|
||||
public static string ProtectedPin = "protectedPin";
|
||||
|
@ -19,7 +19,7 @@ namespace Bit.Core.Services
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly bool _setCryptoKeys;
|
||||
|
||||
private SymmetricCryptoKey _key;
|
||||
@ -35,7 +35,7 @@ namespace Bit.Core.Services
|
||||
II18nService i18nService,
|
||||
IPlatformUtilsService platformUtilsService,
|
||||
IMessagingService messagingService,
|
||||
ILockService lockService,
|
||||
IVaultTimeoutService vaultTimeoutService,
|
||||
bool setCryptoKeys = true)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
@ -46,7 +46,7 @@ namespace Bit.Core.Services
|
||||
_i18nService = i18nService;
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_messagingService = messagingService;
|
||||
_lockService = lockService;
|
||||
_vaultTimeoutService = vaultTimeoutService;
|
||||
_setCryptoKeys = setCryptoKeys;
|
||||
|
||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||
@ -315,7 +315,7 @@ namespace Bit.Core.Services
|
||||
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
|
||||
}
|
||||
|
||||
_lockService.FingerprintLocked = false;
|
||||
_vaultTimeoutService.FingerprintLocked = false;
|
||||
_messagingService.Send("loggedIn");
|
||||
return result;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ namespace Bit.Core.Services
|
||||
public async Task SetKeyAsync(SymmetricCryptoKey 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);
|
||||
if (option.HasValue && !fingerprint.GetValueOrDefault())
|
||||
{
|
||||
@ -353,7 +353,7 @@ namespace Bit.Core.Services
|
||||
public async Task ToggleKeyAsync()
|
||||
{
|
||||
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);
|
||||
if (!fingerprint.GetValueOrDefault() && (option != null || option == 0))
|
||||
{
|
||||
|
@ -35,6 +35,13 @@ namespace Bit.Core.Services
|
||||
{
|
||||
_token = token;
|
||||
_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);
|
||||
}
|
||||
|
||||
@ -51,6 +58,13 @@ namespace Bit.Core.Services
|
||||
public async Task SetRefreshTokenAsync(string 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);
|
||||
}
|
||||
|
||||
@ -64,6 +78,22 @@ namespace Bit.Core.Services
|
||||
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)
|
||||
{
|
||||
await _storageService.SaveAsync(string.Format(Keys_TwoFactorTokenFormat, email), token);
|
||||
@ -225,5 +255,12 @@ namespace Bit.Core.Services
|
||||
// Standard base64 decoder
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class LockService : ILockService
|
||||
public class VaultTimeoutService : IVaultTimeoutService
|
||||
{
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IUserService _userService;
|
||||
@ -16,9 +16,11 @@ namespace Bit.Core.Services
|
||||
private readonly ICollectionService _collectionService;
|
||||
private readonly ISearchService _searchService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly Action<bool> _lockedCallback;
|
||||
private readonly Func<bool, Task> _loggedOutCallback;
|
||||
|
||||
public LockService(
|
||||
public VaultTimeoutService(
|
||||
ICryptoService cryptoService,
|
||||
IUserService userService,
|
||||
IPlatformUtilsService platformUtilsService,
|
||||
@ -28,7 +30,9 @@ namespace Bit.Core.Services
|
||||
ICollectionService collectionService,
|
||||
ISearchService searchService,
|
||||
IMessagingService messagingService,
|
||||
Action<bool> lockedCallback)
|
||||
ITokenService tokenService,
|
||||
Action<bool> lockedCallback,
|
||||
Func<bool, Task> loggedOutCallback)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
_userService = userService;
|
||||
@ -39,7 +43,9 @@ namespace Bit.Core.Services
|
||||
_collectionService = collectionService;
|
||||
_searchService = searchService;
|
||||
_messagingService = messagingService;
|
||||
_tokenService = tokenService;
|
||||
_lockedCallback = lockedCallback;
|
||||
_loggedOutCallback = loggedOutCallback;
|
||||
}
|
||||
|
||||
public CipherString PinProtectedKey { get; set; } = null;
|
||||
@ -59,7 +65,7 @@ namespace Bit.Core.Services
|
||||
return !hasKey;
|
||||
}
|
||||
|
||||
public async Task CheckLockAsync()
|
||||
public async Task CheckVaultTimeoutAsync()
|
||||
{
|
||||
if (_platformUtilsService.IsViewOpen())
|
||||
{
|
||||
@ -74,12 +80,13 @@ namespace Bit.Core.Services
|
||||
{
|
||||
return;
|
||||
}
|
||||
var lockOption = _platformUtilsService.LockTimeout();
|
||||
if (lockOption == null)
|
||||
// This only returns 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;
|
||||
}
|
||||
@ -89,10 +96,18 @@ namespace Bit.Core.Services
|
||||
return;
|
||||
}
|
||||
var diff = DateTime.UtcNow - lastActive.Value;
|
||||
if (diff.TotalSeconds >= lockOption.Value)
|
||||
if (diff.TotalSeconds >= vaultTimeout.Value)
|
||||
{
|
||||
// need to lock now
|
||||
await LockAsync(true);
|
||||
// Pivot based on saved action
|
||||
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);
|
||||
if (action == "logOut")
|
||||
{
|
||||
await LogOutAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await LockAsync(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,11 +141,21 @@ namespace Bit.Core.Services
|
||||
_messagingService.Send("locked", 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 _tokenService.ToggleTokensAsync();
|
||||
}
|
||||
|
||||
public async Task<Tuple<bool, bool>> IsPinLockSetAsync()
|
@ -45,8 +45,13 @@ namespace Bit.Core.Utilities
|
||||
i18nService, cipherService);
|
||||
var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
|
||||
searchService = new SearchService(cipherService);
|
||||
var lockService = new LockService(cryptoService, userService, platformUtilsService, storageService,
|
||||
folderService, cipherService, collectionService, searchService, messagingService, null);
|
||||
var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService,
|
||||
storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
||||
null, (expired) =>
|
||||
{
|
||||
messagingService.Send("logout", expired);
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
var policyService = new PolicyService(storageService, userService);
|
||||
var syncService = new SyncService(userService, apiService, settingsService, folderService,
|
||||
cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
|
||||
@ -59,7 +64,7 @@ namespace Bit.Core.Utilities
|
||||
cryptoFunctionService, policyService);
|
||||
var totpService = new TotpService(storageService, cryptoFunctionService);
|
||||
var authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
|
||||
i18nService, platformUtilsService, messagingService, lockService);
|
||||
i18nService, platformUtilsService, messagingService, vaultTimeoutService);
|
||||
var exportService = new ExportService(folderService, cipherService);
|
||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||
var environmentService = new EnvironmentService(apiService, storageService);
|
||||
@ -79,7 +84,7 @@ namespace Bit.Core.Utilities
|
||||
Register<ISearchService>("searchService", searchService);
|
||||
Register<IPolicyService>("policyService", policyService);
|
||||
Register<ISyncService>("syncService", syncService);
|
||||
Register<ILockService>("lockService", lockService);
|
||||
Register<IVaultTimeoutService>("vaultTimeoutService", vaultTimeoutService);
|
||||
Register<IPasswordGenerationService>("passwordGenerationService", passwordGenerationService);
|
||||
Register<ITotpService>("totpService", totpService);
|
||||
Register<IAuthService>("authService", authService);
|
||||
|
@ -1,6 +1,5 @@
|
||||
using AuthenticationServices;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Autofill.Models;
|
||||
@ -8,7 +7,12 @@ using Bit.iOS.Core.Utilities;
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Pages;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Models;
|
||||
using CoreNFC;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
@ -16,6 +20,8 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
private Context _context;
|
||||
private bool _initedAppCenter;
|
||||
private NFCNdefReaderSession _nfcSession = null;
|
||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||
|
||||
public CredentialProviderViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
@ -48,11 +54,11 @@ namespace Bit.iOS.Autofill
|
||||
}
|
||||
_context.UrlString = uri;
|
||||
}
|
||||
if (!CheckAuthed())
|
||||
if (!IsAuthed())
|
||||
{
|
||||
return;
|
||||
LaunchLoginFlow();
|
||||
}
|
||||
if (IsLocked())
|
||||
else if (IsLocked())
|
||||
{
|
||||
PerformSegue("lockPasswordSegue", this);
|
||||
}
|
||||
@ -86,8 +92,9 @@ namespace Bit.iOS.Autofill
|
||||
public override void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
|
||||
{
|
||||
InitAppIfNeeded();
|
||||
if (!CheckAuthed())
|
||||
if (!IsAuthed())
|
||||
{
|
||||
LaunchLoginFlow();
|
||||
return;
|
||||
}
|
||||
_context.CredentialIdentity = credentialIdentity;
|
||||
@ -98,8 +105,9 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
InitAppIfNeeded();
|
||||
_context.Configuring = true;
|
||||
if (!CheckAuthed())
|
||||
if (!IsAuthed())
|
||||
{
|
||||
LaunchLoginFlow();
|
||||
return;
|
||||
}
|
||||
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()
|
||||
{
|
||||
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
return lockService.IsLockedAsync().GetAwaiter().GetResult();
|
||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
return vaultTimeoutService.IsLockedAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private bool IsAuthed()
|
||||
@ -263,12 +257,16 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
private void InitApp()
|
||||
{
|
||||
// Init Xamarin Forms
|
||||
Forms.Init();
|
||||
|
||||
if (ServiceContainer.RegisteredServices.Count > 0)
|
||||
{
|
||||
ServiceContainer.Reset();
|
||||
}
|
||||
iOSCoreHelpers.RegisterLocalServices();
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
|
||||
if (!_initedAppCenter)
|
||||
{
|
||||
@ -277,6 +275,9 @@ namespace Bit.iOS.Autofill
|
||||
}
|
||||
iOSCoreHelpers.Bootstrap();
|
||||
iOSCoreHelpers.AppearanceAdjustments(deviceActionService);
|
||||
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
||||
messagingService.Send("gotYubiKeyOTP", message));
|
||||
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
|
||||
}
|
||||
|
||||
private void InitAppIfNeeded()
|
||||
@ -286,5 +287,43 @@ namespace Bit.iOS.Autofill
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
src/iOS.Autofill/Resources/yubikey.png
Normal file
BIN
src/iOS.Autofill/Resources/yubikey.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
src/iOS.Autofill/Resources/yubikey@2x.png
Normal file
BIN
src/iOS.Autofill/Resources/yubikey@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
BIN
src/iOS.Autofill/Resources/yubikey@3x.png
Normal file
BIN
src/iOS.Autofill/Resources/yubikey@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 417 KiB |
@ -256,5 +256,14 @@
|
||||
<Version>3.2.1</Version>
|
||||
</PackageReference>
|
||||
</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" />
|
||||
</Project>
|
@ -15,7 +15,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
public abstract class LockPasswordViewController : ExtendedUITableViewController
|
||||
{
|
||||
private ILockService _lockService;
|
||||
private IVaultTimeoutService _vaultTimeoutService;
|
||||
private ICryptoService _cryptoService;
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private IUserService _userService;
|
||||
@ -42,7 +42,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
|
||||
public override void ViewDidLoad()
|
||||
{
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
@ -50,9 +50,9 @@ namespace Bit.iOS.Core.Controllers
|
||||
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
|
||||
_pinSet = _lockService.IsPinLockSetAsync().GetAwaiter().GetResult();
|
||||
_pinLock = (_pinSet.Item1 && _lockService.PinProtectedKey != null) || _pinSet.Item2;
|
||||
_fingerprintLock = _lockService.IsFingerprintLockSetAsync().GetAwaiter().GetResult();
|
||||
_pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult();
|
||||
_pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
|
||||
_fingerprintLock = _vaultTimeoutService.IsFingerprintLockSetAsync().GetAwaiter().GetResult();
|
||||
|
||||
BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword;
|
||||
BaseCancelButton.Title = AppResources.Cancel;
|
||||
@ -125,7 +125,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
|
||||
_lockService.PinProtectedKey);
|
||||
_vaultTimeoutService.PinProtectedKey);
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
|
||||
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 pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
|
||||
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);
|
||||
}
|
||||
@ -205,7 +205,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
|
||||
private void DoContinue()
|
||||
{
|
||||
_lockService.FingerprintLocked = false;
|
||||
_vaultTimeoutService.FingerprintLocked = false;
|
||||
MasterPasswordCell.TextField.ResignFirstResponder();
|
||||
Success();
|
||||
}
|
||||
@ -219,7 +219,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||
_lockService.FingerprintLocked = !success;
|
||||
_vaultTimeoutService.FingerprintLocked = !success;
|
||||
if (success)
|
||||
{
|
||||
DoContinue();
|
||||
|
@ -4,7 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Bit.iOS
|
||||
namespace Bit.iOS.Core
|
||||
{
|
||||
public class NFCReaderDelegate : NFCNdefReaderSessionDelegate
|
||||
{
|
@ -1,12 +1,12 @@
|
||||
using System.ComponentModel;
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Utilities;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomButtonRenderer : ButtonRenderer
|
||||
{
|
@ -1,13 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomContentPageRenderer : PageRenderer
|
||||
{
|
@ -1,11 +1,11 @@
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using System.ComponentModel;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomEditorRenderer : EditorRenderer
|
||||
{
|
||||
@ -40,7 +40,7 @@ namespace Bit.iOS.Renderers
|
||||
|
||||
private void UpdateKeyboardAppearance()
|
||||
{
|
||||
if (!Core.Utilities.ThemeHelpers.LightTheme)
|
||||
if (!Utilities.ThemeHelpers.LightTheme)
|
||||
{
|
||||
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
using System.ComponentModel;
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Utilities;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomEntryRenderer : EntryRenderer
|
||||
{
|
||||
@ -61,7 +61,7 @@ namespace Bit.iOS.Renderers
|
||||
|
||||
private void UpdateKeyboardAppearance()
|
||||
{
|
||||
if (!Core.Utilities.ThemeHelpers.LightTheme)
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
{
|
||||
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
using System.ComponentModel;
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Utilities;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomLabelRenderer : LabelRenderer
|
||||
{
|
@ -1,11 +1,11 @@
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Utilities;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Picker), typeof(CustomPickerRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomPickerRenderer : PickerRenderer
|
||||
{
|
||||
@ -23,7 +23,7 @@ namespace Bit.iOS.Renderers
|
||||
|
||||
private void UpdateKeyboardAppearance()
|
||||
{
|
||||
if (!Core.Utilities.ThemeHelpers.LightTheme)
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
{
|
||||
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomSearchBarRenderer : SearchBarRenderer
|
||||
{
|
||||
@ -19,7 +19,7 @@ namespace Bit.iOS.Renderers
|
||||
|
||||
private void UpdateKeyboardAppearance()
|
||||
{
|
||||
if (!Core.Utilities.ThemeHelpers.LightTheme)
|
||||
if (!Utilities.ThemeHelpers.LightTheme)
|
||||
{
|
||||
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomTabbedRenderer : TabbedRenderer
|
||||
{
|
@ -1,10 +1,11 @@
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ViewCell), typeof(CustomViewCellRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomViewCellRenderer : ViewCellRenderer
|
||||
{
|
||||
@ -12,7 +13,7 @@ namespace Bit.iOS.Renderers
|
||||
|
||||
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)
|
@ -3,11 +3,11 @@ using WebKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
using Bit.App.Controls;
|
||||
using Bit.iOS.Renderers;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using System.ComponentModel;
|
||||
|
||||
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
||||
namespace Bit.iOS.Renderers
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
|
||||
{
|
@ -347,6 +347,45 @@ namespace Bit.iOS.Core.Services
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
@ -15,8 +15,8 @@ namespace Bit.iOS.Core.Utilities
|
||||
if (await AutofillEnabled())
|
||||
{
|
||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
if (await lockService.IsLockedAsync())
|
||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
if (await vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
await storageService.SaveAsync(Constants.AutofillNeedsIdentityReplacementKey, true);
|
||||
return;
|
||||
|
@ -27,12 +27,18 @@ namespace Bit.iOS.Core.Utilities
|
||||
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 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));
|
||||
if (!string.IsNullOrWhiteSpace(cancel))
|
||||
{
|
||||
alert.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Default, cancelHandle));
|
||||
}
|
||||
return alert;
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,15 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Core.Services;
|
||||
using CoreNFC;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
@ -68,6 +71,8 @@ namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
|
||||
ServiceContainer.Resolve<IAuthService>("authService").Init();
|
||||
(ServiceContainer.
|
||||
Resolve<IPlatformUtilsService>("platformUtilsService") as MobilePlatformUtilsService).Init();
|
||||
// Note: This is not awaited
|
||||
var bootstrapTask = BootstrapAsync(postBootstrapFunc);
|
||||
}
|
||||
@ -79,6 +84,54 @@ namespace Bit.iOS.Core.Utilities
|
||||
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)
|
||||
{
|
||||
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService").GetAsync<bool?>(
|
||||
|
@ -1,8 +1,9 @@
|
||||
using UIKit;
|
||||
using Bit.App.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
namespace Bit.iOS.Utilities
|
||||
namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
public static class iOSHelpers
|
||||
{
|
||||
@ -44,7 +45,7 @@ namespace Bit.iOS.Utilities
|
||||
{
|
||||
var borderLine = new UIView
|
||||
{
|
||||
BackgroundColor = ((Color)Xamarin.Forms.Application.Current.Resources["BoxBorderColor"]).ToUIColor(),
|
||||
BackgroundColor = ThemeManager.GetResourceColor("BoxBorderColor").ToUIColor(),
|
||||
TranslatesAutoresizingMaskIntoConstraints = false
|
||||
};
|
||||
control.AddSubview(borderLine);
|
||||
@ -60,4 +61,4 @@ namespace Bit.iOS.Utilities
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Foundation;
|
||||
using Bit.App.Utilities;
|
||||
using Foundation;
|
||||
using System;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
@ -19,8 +20,8 @@ namespace Bit.iOS.Core.Views
|
||||
{
|
||||
TranslatesAutoresizingMaskIntoConstraints = false;
|
||||
var bgColor = UIColor.DarkGray;
|
||||
var nordTheme = Application.Current?.Resources != null &&
|
||||
((Color)Application.Current.Resources["BackgroundColor"]) == Color.FromHex("#3b4252");
|
||||
var nordTheme = ThemeManager.Resources() != null &&
|
||||
ThemeManager.GetResourceColor("BackgroundColor") == Color.FromHex("#3b4252");
|
||||
if (nordTheme)
|
||||
{
|
||||
bgColor = Color.FromHex("#4c566a").ToUIColor();
|
||||
|
@ -88,6 +88,17 @@
|
||||
<Compile Include="Models\FillScript.cs" />
|
||||
<Compile Include="Models\PageDetails.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="Utilities\ASHelpers.cs" />
|
||||
<Compile Include="Utilities\Dialogs.cs" />
|
||||
@ -97,6 +108,7 @@
|
||||
<Compile Include="Services\KeyChainStorageService.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
<Compile Include="Utilities\iOSCoreHelpers.cs" />
|
||||
<Compile Include="Utilities\iOSHelpers.cs" />
|
||||
<Compile Include="Utilities\ThemeHelpers.cs" />
|
||||
<Compile Include="Views\ExtensionSearchDelegate.cs" />
|
||||
<Compile Include="Views\ExtensionTableSource.cs" />
|
||||
|
@ -13,6 +13,11 @@ using Bit.iOS.Core.Models;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.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
|
||||
{
|
||||
@ -20,6 +25,8 @@ namespace Bit.iOS.Extension
|
||||
{
|
||||
private Context _context = new Context();
|
||||
private bool _initedAppCenter;
|
||||
private NFCNdefReaderSession _nfcSession = null;
|
||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||
|
||||
public LoadingViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
@ -59,21 +66,17 @@ namespace Bit.iOS.Extension
|
||||
public override void ViewDidAppear(bool 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)
|
||||
{
|
||||
PerformSegue("setupSegue", this);
|
||||
return;
|
||||
}
|
||||
if (IsLocked())
|
||||
if (!IsAuthed())
|
||||
{
|
||||
LaunchLoginFlow();
|
||||
return;
|
||||
}
|
||||
else if (IsLocked())
|
||||
{
|
||||
PerformSegue("lockPasswordSegue", this);
|
||||
}
|
||||
@ -382,12 +385,16 @@ namespace Bit.iOS.Extension
|
||||
|
||||
private void InitApp()
|
||||
{
|
||||
// Init Xamarin Forms
|
||||
Forms.Init();
|
||||
|
||||
if (ServiceContainer.RegisteredServices.Count > 0)
|
||||
{
|
||||
ServiceContainer.Reset();
|
||||
}
|
||||
iOSCoreHelpers.RegisterLocalServices();
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
|
||||
if (!_initedAppCenter)
|
||||
{
|
||||
@ -396,12 +403,15 @@ namespace Bit.iOS.Extension
|
||||
}
|
||||
iOSCoreHelpers.Bootstrap();
|
||||
iOSCoreHelpers.AppearanceAdjustments(deviceActionService);
|
||||
_nfcDelegate = new NFCReaderDelegate((success, message) =>
|
||||
messagingService.Send("gotYubiKeyOTP", message));
|
||||
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
|
||||
}
|
||||
|
||||
private bool IsLocked()
|
||||
{
|
||||
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
return lockService.IsLockedAsync().GetAwaiter().GetResult();
|
||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
return vaultTimeoutService.IsLockedAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private bool IsAuthed()
|
||||
@ -409,5 +419,43 @@ namespace Bit.iOS.Extension
|
||||
var userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
src/iOS.Extension/Resources/yubikey.png
Normal file
BIN
src/iOS.Extension/Resources/yubikey.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
src/iOS.Extension/Resources/yubikey@2x.png
Normal file
BIN
src/iOS.Extension/Resources/yubikey@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
BIN
src/iOS.Extension/Resources/yubikey@3x.png
Normal file
BIN
src/iOS.Extension/Resources/yubikey@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 417 KiB |
@ -225,6 +225,9 @@
|
||||
<BundleResource Include="Resources\logo_white.png" />
|
||||
<BundleResource Include="Resources\logo_white%402x.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>
|
||||
<PackageReference Include="Microsoft.AppCenter.Crashes">
|
||||
|
@ -26,10 +26,10 @@ namespace Bit.iOS
|
||||
{
|
||||
private NFCNdefReaderSession _nfcSession = null;
|
||||
private iOSPushNotificationHandler _pushHandler = null;
|
||||
private NFCReaderDelegate _nfcDelegate = null;
|
||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||
private NSTimer _clipboardTimer = null;
|
||||
private nint _clipboardBackgroundTaskId;
|
||||
private NSTimer _lockTimer = null;
|
||||
private NSTimer _vaultTimeoutTimer = null;
|
||||
private nint _lockBackgroundTaskId;
|
||||
private NSTimer _eventTimer = null;
|
||||
private nint _eventBackgroundTaskId;
|
||||
@ -38,7 +38,7 @@ namespace Bit.iOS
|
||||
private IMessagingService _messagingService;
|
||||
private IBroadcasterService _broadcasterService;
|
||||
private IStorageService _storageService;
|
||||
private ILockService _lockService;
|
||||
private IVaultTimeoutService _vaultTimeoutService;
|
||||
private IEventService _eventService;
|
||||
|
||||
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
|
||||
@ -50,7 +50,7 @@ namespace Bit.iOS
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
|
||||
LoadApplication(new App.App(null));
|
||||
@ -59,13 +59,13 @@ namespace Bit.iOS
|
||||
|
||||
_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")
|
||||
{
|
||||
@ -89,7 +89,7 @@ namespace Bit.iOS
|
||||
}
|
||||
else if (message.Command == "listenYubiKeyOTP")
|
||||
{
|
||||
ListenYubiKey((bool)message.Data);
|
||||
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
|
||||
}
|
||||
else if (message.Command == "unlocked")
|
||||
{
|
||||
@ -186,8 +186,7 @@ namespace Bit.iOS
|
||||
};
|
||||
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||
{
|
||||
BackgroundColor = ((Color)Xamarin.Forms.Application.Current.Resources["SplashBackgroundColor"])
|
||||
.ToUIColor()
|
||||
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToUIColor()
|
||||
};
|
||||
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
|
||||
var imageView = new UIImageView(logo)
|
||||
@ -284,7 +283,7 @@ namespace Bit.iOS
|
||||
iOSCoreHelpers.RegisterAppCenter();
|
||||
_pushHandler = new iOSPushNotificationHandler(
|
||||
ServiceContainer.Resolve<IPushNotificationListenerService>("pushNotificationListenerService"));
|
||||
_nfcDelegate = new NFCReaderDelegate((success, message) =>
|
||||
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
||||
_messagingService.Send("gotYubiKeyOTP", message));
|
||||
|
||||
iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync());
|
||||
@ -300,25 +299,7 @@ namespace Bit.iOS
|
||||
"pushNotificationService", iosPushNotificationService);
|
||||
}
|
||||
|
||||
private void ListenYubiKey(bool listen)
|
||||
{
|
||||
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)
|
||||
private void VaultTimeoutTimer(int vaultTimeoutMinutes)
|
||||
{
|
||||
if (_lockBackgroundTaskId > 0)
|
||||
{
|
||||
@ -330,21 +311,21 @@ namespace Bit.iOS
|
||||
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
|
||||
_lockBackgroundTaskId = 0;
|
||||
});
|
||||
var lockOptionMs = lockOptionMinutes * 60000;
|
||||
_lockTimer?.Invalidate();
|
||||
_lockTimer?.Dispose();
|
||||
_lockTimer = null;
|
||||
var lockMsSpan = TimeSpan.FromMilliseconds(lockOptionMs + 10);
|
||||
var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
|
||||
_vaultTimeoutTimer?.Invalidate();
|
||||
_vaultTimeoutTimer?.Dispose();
|
||||
_vaultTimeoutTimer = null;
|
||||
var vaultTimeoutMsSpan = TimeSpan.FromMilliseconds(vaultTimeoutMs + 10);
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
_lockTimer = NSTimer.CreateScheduledTimer(lockMsSpan, timer =>
|
||||
_vaultTimeoutTimer = NSTimer.CreateScheduledTimer(vaultTimeoutMsSpan, timer =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
_lockService.CheckLockAsync();
|
||||
_lockTimer?.Invalidate();
|
||||
_lockTimer?.Dispose();
|
||||
_lockTimer = null;
|
||||
_vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
_vaultTimeoutTimer?.Invalidate();
|
||||
_vaultTimeoutTimer?.Dispose();
|
||||
_vaultTimeoutTimer = null;
|
||||
if (_lockBackgroundTaskId > 0)
|
||||
{
|
||||
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
|
||||
@ -355,11 +336,11 @@ namespace Bit.iOS
|
||||
});
|
||||
}
|
||||
|
||||
private void CancelLockTimer()
|
||||
private void CancelVaultTimeoutTimer()
|
||||
{
|
||||
_lockTimer?.Invalidate();
|
||||
_lockTimer?.Dispose();
|
||||
_lockTimer = null;
|
||||
_vaultTimeoutTimer?.Invalidate();
|
||||
_vaultTimeoutTimer?.Dispose();
|
||||
_vaultTimeoutTimer = null;
|
||||
if (_lockBackgroundTaskId > 0)
|
||||
{
|
||||
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
|
||||
|
@ -134,20 +134,8 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Main.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\iOSPushNotificationService.cs" />
|
||||
<Compile Include="Utilities\iOSHelpers.cs" />
|
||||
<None Include="Entitlements.plist" />
|
||||
<None Include="Info.plist" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user