lock page

This commit is contained in:
Kyle Spearrin 2019-05-15 17:37:59 -04:00
parent f7bb091366
commit 27b6631cc1
8 changed files with 418 additions and 3 deletions

View file

@ -29,6 +29,9 @@
<Compile Update="Pages\Accounts\HintPage.xaml.cs"> <Compile Update="Pages\Accounts\HintPage.xaml.cs">
<DependentUpon>HintPage.xaml</DependentUpon> <DependentUpon>HintPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Accounts\LockPage.xaml.cs">
<DependentUpon>LockPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Accounts\RegisterPage.xaml.cs"> <Compile Update="Pages\Accounts\RegisterPage.xaml.cs">
<DependentUpon>RegisterPage.xaml</DependentUpon> <DependentUpon>RegisterPage.xaml</DependentUpon>
</Compile> </Compile>

View file

@ -20,6 +20,18 @@ namespace Bit.App
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly ILockService _lockService;
private readonly ISyncService _syncService;
private readonly ITokenService _tokenService;
private readonly ICryptoService _cryptoService;
private readonly ICipherService _cipherService;
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly ISettingsService _settingsService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISearchService _searchService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService;
public App() public App()
{ {
@ -27,6 +39,19 @@ namespace Bit.App
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService; _i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
InitializeComponent(); InitializeComponent();
@ -58,8 +83,23 @@ namespace Bit.App
else if(message.Command == "locked") else if(message.Command == "locked")
{ {
await _stateService.PurgeAsync(); await _stateService.PurgeAsync();
MainPage = new NavigationPage(new LockPage());
}
else if(message.Command == "lockVault")
{
await _lockService.LockAsync(true);
}
else if(message.Command == "logout")
{
await LogOutAsync(false);
}
else if(message.Command == "loggedOut")
{
// TODO
}
else if(message.Command == "unlocked" || message.Command == "loggedIn")
{
// TODO // TODO
// MainPage = new LockPage();
} }
}); });
} }
@ -87,13 +127,47 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar(); new System.Globalization.UmAlQuraCalendar();
} }
private async Task LogOutAsync(bool expired)
{
var userId = await _userService.GetUserIdAsync();
await Task.WhenAll(
_syncService.SetLastSyncAsync(DateTime.MinValue),
_tokenService.ClearTokenAsync(),
_cryptoService.ClearKeysAsync(),
_userService.ClearAsync(),
_settingsService.ClearAsync(userId),
_cipherService.ClearAsync(userId),
_folderService.ClearAsync(userId),
_collectionService.ClearAsync(userId),
_passwordGenerationService.ClearAsync(),
_lockService.ClearAsync());
_lockService.PinLocked = false;
_searchService.ClearIndex();
_authService.LogOut(() =>
{
if(expired)
{
// TODO: Toast?
}
MainPage = new HomePage();
});
}
private async Task SetMainPageAsync() private async Task SetMainPageAsync()
{ {
var authed = await _userService.IsAuthenticatedAsync(); var authed = await _userService.IsAuthenticatedAsync();
if(authed) if(authed)
{
var locked = await _lockService.IsLockedAsync();
if(locked)
{
Current.MainPage = new NavigationPage(new LockPage());
}
else
{ {
Current.MainPage = new TabsPage(); Current.MainPage = new TabsPage();
} }
}
else else
{ {
Current.MainPage = new HomePage(); Current.MainPage = new HomePage();

View file

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.LockPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LockPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:LockPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Unlock}" Clicked="Unlock_Clicked" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Grid StyleClass="box-row" IsVisible="{Binding PinLock}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n PIN}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_pin"
Text="{Binding Pin}"
StyleClass="box-value"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
</Grid>
<Grid StyleClass="box-row" IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogOut}" Clicked="LogOut_Clicked"></Button>
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View file

@ -0,0 +1,52 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class LockPage : BaseContentPage
{
private LockPageViewModel _vm;
public LockPage()
{
InitializeComponent();
_vm = BindingContext as LockPageViewModel;
_vm.Page = this;
MasterPasswordEntry = _masterPassword;
PinEntry = _pin;
}
public Entry MasterPasswordEntry { get; set; }
public Entry PinEntry { get; set; }
protected override async void OnAppearing()
{
base.OnAppearing();
await _vm.InitAsync();
if(_vm.PinLock)
{
RequestFocus(PinEntry);
}
else
{
RequestFocus(MasterPasswordEntry);
}
}
private async void Unlock_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
await _vm.SubmitAsync();
}
}
private async void LogOut_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
await _vm.LogOutAsync();
}
}
}
}

View file

@ -0,0 +1,180 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LockPageViewModel : BaseViewModel
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IDeviceActionService _deviceActionService;
private readonly ILockService _lockService;
private readonly ICryptoService _cryptoService;
private readonly IStorageService _storageService;
private readonly IUserService _userService;
private readonly IMessagingService _messagingService;
private string _email;
private bool _showPassword;
private bool _pinLock;
private int _invalidPinAttempts = 0;
private Tuple<bool, bool> _pinSet;
public LockPageViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
}
public bool ShowPassword
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon)
});
}
public bool PinLock
{
get => _pinLock;
set => SetProperty(ref _pinLock, value);
}
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? "" : "";
public string MasterPassword { get; set; }
public string Pin { get; set; }
public async Task InitAsync()
{
_pinSet = await _lockService.IsPinLockSetAsync();
var hasKey = await _cryptoService.HasKeyAsync();
PinLock = (_pinSet.Item1 && hasKey) || _pinSet.Item2;
_email = await _userService.GetEmailAsync();
PageTitle = PinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword;
}
public async Task SubmitAsync()
{
if(PinLock && string.IsNullOrWhiteSpace(Pin))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
AppResources.Ok);
return;
}
if(!PinLock && string.IsNullOrWhiteSpace(MasterPassword))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.Ok);
return;
}
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
if(PinLock)
{
var failed = true;
try
{
if(_pinSet.Item1)
{
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin));
failed = decPin != Pin;
_lockService.PinLocked = failed;
if(failed)
{
DoContinue();
}
}
else
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
failed = false;
await SetKeyAndContinueAsync(key);
}
}
catch
{
failed = true;
}
if(failed)
{
_invalidPinAttempts++;
if(_invalidPinAttempts >= 5)
{
_messagingService.Send("logout");
return;
}
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
AppResources.AnErrorHasOccurred);
}
}
else
{
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if(storedKeyHash != null && keyHash != null && storedKeyHash == keyHash)
{
await SetKeyAndContinueAsync(key);
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
AppResources.AnErrorHasOccurred);
}
}
}
public async Task LogOutAsync()
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LogoutConfirmation,
AppResources.LogOut, AppResources.Yes, AppResources.Cancel);
if(confirmed)
{
_messagingService.Send("logout");
}
}
public void TogglePassword()
{
ShowPassword = !ShowPassword;
var page = (Page as LockPage);
var entry = PinLock ? page.PinEntry : page.MasterPasswordEntry;
entry.Focus();
}
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
{
await _cryptoService.SetKeyAsync(key);
}
private void DoContinue()
{
_messagingService.Send("unlocked");
Application.Current.MainPage = new TabsPage();
}
}
}

View file

@ -2850,6 +2850,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to PIN.
/// </summary>
public static string PIN {
get {
return ResourceManager.GetString("PIN", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Possible Matching Items. /// Looks up a localized string similar to Possible Matching Items.
/// </summary> /// </summary>

View file

@ -1484,4 +1484,7 @@
<data name="LockNow" xml:space="preserve"> <data name="LockNow" xml:space="preserve">
<value>Lock Now</value> <value>Lock Now</value>
</data> </data>
<data name="PIN" xml:space="preserve">
<value>PIN</value>
</data>
</root> </root>

View file

@ -24,7 +24,7 @@ namespace Bit.Core.Services
public void ClearCache() public void ClearCache()
{ {
_settingsCache.Clear(); _settingsCache?.Clear();
_settingsCache = null; _settingsCache = null;
} }