mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 02:18:27 +03:00
Added SSO flows and functionality (#1047)
* SSO login flow for pre-existing user and no 2FA * 2FA progress * 2FA support * Added SSO flows and functionality * Handle webauthenticator cancellation gracefully * updates & bugfixes * Added state validation to web auth response handling * SSO auth, account registration, and environment settings support for iOS extensions * Added SSO prevalidation to auth process * prevalidation now hitting identity service base url * additional error handling * Requested changes * fixed case
This commit is contained in:
parent
3af08a4727
commit
f1419a75f6
46 changed files with 4368 additions and 4072 deletions
|
@ -78,7 +78,7 @@
|
||||||
<Version>1.8.6.7</Version>
|
<Version>1.8.6.7</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.5.3.1</Version>
|
<Version>1.5.3.2</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||||
<Version>71.1740.0</Version>
|
<Version>71.1740.0</Version>
|
||||||
|
@ -142,6 +142,7 @@
|
||||||
<Compile Include="Tiles\MyVaultTileService.cs" />
|
<Compile Include="Tiles\MyVaultTileService.cs" />
|
||||||
<Compile Include="Utilities\AndroidHelpers.cs" />
|
<Compile Include="Utilities\AndroidHelpers.cs" />
|
||||||
<Compile Include="Utilities\AppCenterHelper.cs" />
|
<Compile Include="Utilities\AppCenterHelper.cs" />
|
||||||
|
<Compile Include="WebAuthCallbackActivity.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidAsset Include="Assets\FontAwesome.ttf" />
|
<AndroidAsset Include="Assets\FontAwesome.ttf" />
|
||||||
|
|
|
@ -144,6 +144,7 @@ namespace Bit.Droid
|
||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
{
|
{
|
||||||
base.OnResume();
|
base.OnResume();
|
||||||
|
Xamarin.Essentials.Platform.OnResume();
|
||||||
if (_deviceActionService.SupportsNfc())
|
if (_deviceActionService.SupportsNfc())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
11
src/Android/WebAuthCallbackActivity.cs
Normal file
11
src/Android/WebAuthCallbackActivity.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content.PM;
|
||||||
|
|
||||||
|
namespace Bit.Droid
|
||||||
|
{
|
||||||
|
[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
|
||||||
|
[IntentFilter(new[] { Android.Content.Intent.ActionView },
|
||||||
|
Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
|
||||||
|
DataScheme = "bitwarden")]
|
||||||
|
public class WebAuthCallbackActivity : Xamarin.Essentials.WebAuthenticatorCallbackActivity { }
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="3.2.1" />
|
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="3.2.1" />
|
||||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.1" />
|
<PackageReference Include="Plugin.Fingerprint" Version="2.1.1" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.5.3.1" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.5.3.2" />
|
||||||
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
||||||
<PackageReference Include="Xamarin.Forms" Version="4.5.0.725" />
|
<PackageReference Include="Xamarin.Forms" Version="4.5.0.725" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||||
|
@ -111,6 +111,14 @@
|
||||||
<Compile Update="Pages\Vault\GroupingsPage\GroupingsPage.xaml.cs">
|
<Compile Update="Pages\Vault\GroupingsPage\GroupingsPage.xaml.cs">
|
||||||
<DependentUpon>GroupingsPage.xaml</DependentUpon>
|
<DependentUpon>GroupingsPage.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="Pages\Accounts\LoginSsoPage.xaml.cs">
|
||||||
|
<DependentUpon>LoginSsoPage.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Pages\Accounts\SetPasswordPage.xaml.cs">
|
||||||
|
<DependentUpon>ResetMasterPasswordPage.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -19,5 +19,28 @@ namespace Bit.App.Models
|
||||||
public string SaveCardExpYear { get; set; }
|
public string SaveCardExpYear { get; set; }
|
||||||
public string SaveCardCode { get; set; }
|
public string SaveCardCode { get; set; }
|
||||||
public bool IosExtension { get; set; }
|
public bool IosExtension { get; set; }
|
||||||
|
|
||||||
|
public void SetAllFrom(AppOptions o)
|
||||||
|
{
|
||||||
|
if (o == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MyVaultTile = o.MyVaultTile;
|
||||||
|
GeneratorTile = o.GeneratorTile;
|
||||||
|
FromAutofillFramework = o.FromAutofillFramework;
|
||||||
|
FillType = o.FillType;
|
||||||
|
Uri = o.Uri;
|
||||||
|
SaveType = o.SaveType;
|
||||||
|
SaveName = o.SaveName;
|
||||||
|
SaveUsername = o.SaveUsername;
|
||||||
|
SavePassword = o.SavePassword;
|
||||||
|
SaveCardName = o.SaveCardName;
|
||||||
|
SaveCardNumber = o.SaveCardNumber;
|
||||||
|
SaveCardExpMonth = o.SaveCardExpMonth;
|
||||||
|
SaveCardExpYear = o.SaveCardExpYear;
|
||||||
|
SaveCardCode = o.SaveCardCode;
|
||||||
|
IosExtension = o.IosExtension;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Resources;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public partial class EnvironmentPage : BaseContentPage
|
public partial class EnvironmentPage : BaseContentPage
|
||||||
{
|
{
|
||||||
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly EnvironmentPageViewModel _vm;
|
private readonly EnvironmentPageViewModel _vm;
|
||||||
|
|
||||||
public EnvironmentPage()
|
public EnvironmentPage()
|
||||||
{
|
{
|
||||||
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_messagingService.Send("showStatusBar", true);
|
_messagingService.Send("showStatusBar", true);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@ -28,6 +32,12 @@ namespace Bit.App.Pages
|
||||||
_apiEntry.ReturnCommand = new Command(() => _identityEntry.Focus());
|
_apiEntry.ReturnCommand = new Command(() => _identityEntry.Focus());
|
||||||
_identityEntry.ReturnType = ReturnType.Next;
|
_identityEntry.ReturnType = ReturnType.Next;
|
||||||
_identityEntry.ReturnCommand = new Command(() => _iconsEntry.Focus());
|
_identityEntry.ReturnCommand = new Command(() => _iconsEntry.Focus());
|
||||||
|
_vm.SubmitSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync());
|
||||||
|
_vm.CloseAction = async () =>
|
||||||
|
{
|
||||||
|
_messagingService.Send("showStatusBar", false);
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Submit_Clicked(object sender, EventArgs e)
|
private async void Submit_Clicked(object sender, EventArgs e)
|
||||||
|
@ -38,12 +48,17 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, EventArgs e)
|
private async Task SubmitSuccessAsync()
|
||||||
|
{
|
||||||
|
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
_messagingService.Send("showStatusBar", false);
|
_vm.CloseAction();
|
||||||
await Navigation.PopModalAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
@ -8,12 +9,10 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class EnvironmentPageViewModel : BaseViewModel
|
public class EnvironmentPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
|
||||||
private readonly IEnvironmentService _environmentService;
|
private readonly IEnvironmentService _environmentService;
|
||||||
|
|
||||||
public EnvironmentPageViewModel()
|
public EnvironmentPageViewModel()
|
||||||
{
|
{
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
|
||||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||||
|
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
|
@ -33,6 +32,8 @@ namespace Bit.App.Pages
|
||||||
public string WebVaultUrl { get; set; }
|
public string WebVaultUrl { get; set; }
|
||||||
public string IconsUrl { get; set; }
|
public string IconsUrl { get; set; }
|
||||||
public string NotificationsUrls { get; set; }
|
public string NotificationsUrls { get; set; }
|
||||||
|
public Action SubmitSuccessAction { get; set; }
|
||||||
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
|
@ -54,8 +55,7 @@ namespace Bit.App.Pages
|
||||||
IconsUrl = resUrls.Icons;
|
IconsUrl = resUrls.Icons;
|
||||||
NotificationsUrls = resUrls.Notifications;
|
NotificationsUrls = resUrls.Notifications;
|
||||||
|
|
||||||
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
|
SubmitSuccessAction?.Invoke();
|
||||||
await Page.Navigation.PopModalAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,15 @@
|
||||||
<pages:HomeViewModel />
|
<pages:HomeViewModel />
|
||||||
</ContentPage.BindingContext>
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<StackLayout Spacing="0" Padding="10, 5">
|
<StackLayout Spacing="0" Padding="10, 5">
|
||||||
<controls:FaButton Text=""
|
<controls:FaButton Text=""
|
||||||
StyleClass="btn-muted, btn-icon, btn-icon-platform"
|
StyleClass="btn-muted, btn-icon, btn-icon-platform"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
Clicked="Settings_Clicked"
|
Clicked="Environment_Clicked"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}">
|
AutomationProperties.Name="{u:I18n Options}">
|
||||||
<controls:FaButton.Margin>
|
<controls:FaButton.Margin>
|
||||||
|
@ -39,6 +43,8 @@
|
||||||
Clicked="LogIn_Clicked"></Button>
|
Clicked="LogIn_Clicked"></Button>
|
||||||
<Button Text="{u:I18n CreateAccount}"
|
<Button Text="{u:I18n CreateAccount}"
|
||||||
Clicked="Register_Clicked"></Button>
|
Clicked="Register_Clicked"></Button>
|
||||||
|
<Button Text="{u:I18n LogInSso}"
|
||||||
|
Clicked="LogInSso_Clicked"></Button>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public partial class HomePage : BaseContentPage
|
public partial class HomePage : BaseContentPage
|
||||||
{
|
{
|
||||||
|
private readonly HomeViewModel _vm;
|
||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
private IMessagingService _messagingService;
|
private IMessagingService _messagingService;
|
||||||
|
|
||||||
|
@ -19,6 +20,12 @@ namespace Bit.App.Pages
|
||||||
_messagingService.Send("showStatusBar", false);
|
_messagingService.Send("showStatusBar", false);
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_vm = BindingContext as HomeViewModel;
|
||||||
|
_vm.Page = this;
|
||||||
|
_vm.StartLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartLoginAsync());
|
||||||
|
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
|
||||||
|
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
|
||||||
|
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
|
||||||
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
|
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,28 +41,68 @@ namespace Bit.App.Pages
|
||||||
_messagingService.Send("showStatusBar", false);
|
_messagingService.Send("showStatusBar", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Close_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
_vm.CloseAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void LogIn_Clicked(object sender, EventArgs e)
|
private void LogIn_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
Navigation.PushModalAsync(new NavigationPage(new LoginPage(null, _appOptions)));
|
_vm.StartLoginAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartLoginAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginPage(null, _appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
private void Register_Clicked(object sender, EventArgs e)
|
private void Register_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
Navigation.PushModalAsync(new NavigationPage(new RegisterPage(this)));
|
_vm.StartRegisterAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Settings_Clicked(object sender, EventArgs e)
|
private async Task StartRegisterAsync()
|
||||||
|
{
|
||||||
|
var page = new RegisterPage(this);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogInSso_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
Navigation.PushModalAsync(new NavigationPage(new EnvironmentPage()));
|
_vm.StartSsoLoginAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartSsoLoginAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginSsoPage(_appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Environment_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
_vm.StartEnvironmentAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartEnvironmentAsync()
|
||||||
|
{
|
||||||
|
var page = new EnvironmentPage();
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
|
@ -6,7 +7,13 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public HomeViewModel()
|
public HomeViewModel()
|
||||||
{
|
{
|
||||||
PageTitle = "Home Page";
|
PageTitle = AppResources.Bitwarden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Action StartLoginAction { get; set; }
|
||||||
|
public Action StartRegisterAction { get; set; }
|
||||||
|
public Action StartSsoLoginAction { get; set; }
|
||||||
|
public Action StartEnvironmentAction { get; set; }
|
||||||
|
public Action CloseAction { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
|
@ -99,24 +99,11 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
private async Task UnlockedAsync()
|
private async Task UnlockedAsync()
|
||||||
{
|
{
|
||||||
if (_appOptions != null)
|
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||||
{
|
{
|
||||||
if (_appOptions.FromAutofillFramework && _appOptions.SaveType.HasValue)
|
|
||||||
{
|
|
||||||
Application.Current.MainPage = new NavigationPage(new AddEditPage(appOptions: _appOptions));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_appOptions.Uri != null)
|
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||||
{
|
|
||||||
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(_appOptions));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var previousPage = await _storageService.GetAsync<PreviousPageInfo>(Constants.PreviousPageKey);
|
|
||||||
if (previousPage != null)
|
|
||||||
{
|
|
||||||
await _storageService.RemoveAsync(Constants.PreviousPageKey);
|
|
||||||
}
|
|
||||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,14 @@ using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class LockPageViewModel : BaseViewModel
|
public class LockPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
|
private readonly IApiService _apiService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
@ -39,6 +41,7 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
public LockPageViewModel()
|
public LockPageViewModel()
|
||||||
{
|
{
|
||||||
|
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
|
@ -224,18 +227,33 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
|
||||||
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||||
|
var passwordValid = false;
|
||||||
|
if (keyHash != null)
|
||||||
|
{
|
||||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||||
if (storedKeyHash == null)
|
if (storedKeyHash != null)
|
||||||
{
|
{
|
||||||
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
passwordValid = storedKeyHash == keyHash;
|
||||||
if (key.KeyB64 == oldKey)
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await _secureStorageService.RemoveAsync("oldKey");
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
var request = new PasswordVerificationRequest();
|
||||||
|
request.MasterPasswordHash = keyHash;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||||
|
passwordValid = true;
|
||||||
await _cryptoService.SetKeyHashAsync(keyHash);
|
await _cryptoService.SetKeyHashAsync(keyHash);
|
||||||
storedKeyHash = keyHash;
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
|
||||||
|
}
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (storedKeyHash != null && keyHash != null && storedKeyHash == keyHash)
|
if (passwordValid)
|
||||||
{
|
{
|
||||||
if (_pinSet.Item1)
|
if (_pinSet.Item1)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
|
@ -25,7 +25,7 @@ namespace Bit.App.Pages
|
||||||
_vm = BindingContext as LoginPageViewModel;
|
_vm = BindingContext as LoginPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||||
_vm.LoggedInAction = () => Device.BeginInvokeOnMainThread(async () => await LoggedInAsync());
|
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
||||||
_vm.CloseAction = async () =>
|
_vm.CloseAction = async () =>
|
||||||
{
|
{
|
||||||
_messagingService.Send("showStatusBar", false);
|
_messagingService.Send("showStatusBar", false);
|
||||||
|
@ -74,7 +74,7 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, EventArgs e)
|
private void Close_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
|
@ -84,30 +84,17 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
private async Task StartTwoFactorAsync()
|
private async Task StartTwoFactorAsync()
|
||||||
{
|
{
|
||||||
var page = new TwoFactorPage();
|
var page = new TwoFactorPage(false, _appOptions);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoggedInAsync()
|
private async Task LogInSuccessAsync()
|
||||||
{
|
{
|
||||||
if (_appOptions != null)
|
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||||
{
|
{
|
||||||
if (_appOptions.FromAutofillFramework && _appOptions.SaveType.HasValue)
|
|
||||||
{
|
|
||||||
Application.Current.MainPage = new NavigationPage(new AddEditPage(appOptions: _appOptions));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_appOptions.Uri != null)
|
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||||
{
|
|
||||||
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(_appOptions));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var previousPage = await _storageService.GetAsync<PreviousPageInfo>(Constants.PreviousPageKey);
|
|
||||||
if (previousPage != null)
|
|
||||||
{
|
|
||||||
await _storageService.RemoveAsync(Constants.PreviousPageKey);
|
|
||||||
}
|
|
||||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ namespace Bit.App.Pages
|
||||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
public bool RememberEmail { get; set; }
|
public bool RememberEmail { get; set; }
|
||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action LoggedInAction { get; set; }
|
public Action LogInSuccessAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
public bool HideHintButton
|
public bool HideHintButton
|
||||||
|
@ -142,7 +142,7 @@ namespace Bit.App.Pages
|
||||||
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
|
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
|
||||||
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
|
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
|
||||||
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
||||||
LoggedInAction?.Invoke();
|
LogInSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
|
|
42
src/App/Pages/Accounts/LoginSsoPage.xaml
Normal file
42
src/App/Pages/Accounts/LoginSsoPage.xaml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?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.LoginSsoPage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
x:DataType="pages:LoginSsoPageViewModel"
|
||||||
|
Title="{Binding PageTitle}">
|
||||||
|
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:LoginSsoPageViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
|
<ToolbarItem Text="{u:I18n LogIn}" Clicked="LogIn_Clicked" />
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<ScrollView>
|
||||||
|
<StackLayout Spacing="20" Margin="0,10,0,0">
|
||||||
|
<StackLayout StyleClass="box">
|
||||||
|
<Label Text="{u:I18n LogInSsoSummary}"
|
||||||
|
StyleClass="text-md"
|
||||||
|
HorizontalTextAlignment="Start"></Label>
|
||||||
|
<StackLayout StyleClass="box-row">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n OrgIdentifier}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Entry
|
||||||
|
x:Name="_orgIdentifier"
|
||||||
|
Text="{Binding OrgIdentifier}"
|
||||||
|
Keyboard="Default"
|
||||||
|
StyleClass="box-value"
|
||||||
|
ReturnType="Go"
|
||||||
|
ReturnCommand="{Binding LogInCommand}" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</pages:BaseContentPage>
|
114
src/App/Pages/Accounts/LoginSsoPage.xaml.cs
Normal file
114
src/App/Pages/Accounts/LoginSsoPage.xaml.cs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class LoginSsoPage : BaseContentPage
|
||||||
|
{
|
||||||
|
private readonly IStorageService _storageService;
|
||||||
|
private readonly IMessagingService _messagingService;
|
||||||
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
private readonly LoginSsoPageViewModel _vm;
|
||||||
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
|
private AppOptions _appOptionsCopy;
|
||||||
|
|
||||||
|
public LoginSsoPage(AppOptions appOptions = null)
|
||||||
|
{
|
||||||
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
|
_messagingService.Send("showStatusBar", true);
|
||||||
|
_appOptions = appOptions;
|
||||||
|
InitializeComponent();
|
||||||
|
_vm = BindingContext as LoginSsoPageViewModel;
|
||||||
|
_vm.Page = this;
|
||||||
|
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||||
|
_vm.StartSetPasswordAction = () =>
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
|
||||||
|
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
|
||||||
|
_vm.CloseAction = async () =>
|
||||||
|
{
|
||||||
|
_messagingService.Send("showStatusBar", false);
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
};
|
||||||
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
ToolbarItems.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnAppearing()
|
||||||
|
{
|
||||||
|
base.OnAppearing();
|
||||||
|
await _vm.InitAsync();
|
||||||
|
if (string.IsNullOrWhiteSpace(_vm.OrgIdentifier))
|
||||||
|
{
|
||||||
|
RequestFocus(_orgIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyAppOptions()
|
||||||
|
{
|
||||||
|
if (_appOptions != null)
|
||||||
|
{
|
||||||
|
// create an object copy of _appOptions to persist values when app is exited during web auth flow
|
||||||
|
_appOptionsCopy = new AppOptions();
|
||||||
|
_appOptionsCopy.SetAllFrom(_appOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreAppOptionsFromCopy()
|
||||||
|
{
|
||||||
|
if (_appOptions != null)
|
||||||
|
{
|
||||||
|
// restore values to original readonly _appOptions object from copy
|
||||||
|
_appOptions.SetAllFrom(_appOptionsCopy);
|
||||||
|
_appOptionsCopy = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void LogIn_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
CopyAppOptions();
|
||||||
|
await _vm.LogInAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
_vm.CloseAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartTwoFactorAsync()
|
||||||
|
{
|
||||||
|
RestoreAppOptionsFromCopy();
|
||||||
|
var page = new TwoFactorPage(true, _appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartSetPasswordAsync()
|
||||||
|
{
|
||||||
|
RestoreAppOptionsFromCopy();
|
||||||
|
var page = new SetPasswordPage(_appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SsoAuthSuccessAsync()
|
||||||
|
{
|
||||||
|
RestoreAppOptionsFromCopy();
|
||||||
|
await AppHelpers.ClearPreviousPage();
|
||||||
|
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
218
src/App/Pages/Accounts/LoginSsoPageViewModel.cs
Normal file
218
src/App/Pages/Accounts/LoginSsoPageViewModel.cs
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class LoginSsoPageViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
private const string Keys_RememberedOrgIdentifier = "rememberedOrgIdentifier";
|
||||||
|
private const string Keys_RememberOrgIdentifier = "rememberOrgIdentifier";
|
||||||
|
|
||||||
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
private readonly IAuthService _authService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
|
private readonly IStorageService _storageService;
|
||||||
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
|
||||||
|
private string _orgIdentifier;
|
||||||
|
|
||||||
|
public LoginSsoPageViewModel()
|
||||||
|
{
|
||||||
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
_authService = ServiceContainer.Resolve<IAuthService>("authService");
|
||||||
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||||
|
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||||
|
_passwordGenerationService =
|
||||||
|
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
|
||||||
|
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>("cryptoFunctionService");
|
||||||
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
|
|
||||||
|
PageTitle = AppResources.Bitwarden;
|
||||||
|
LogInCommand = new Command(async () => await LogInAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string OrgIdentifier
|
||||||
|
{
|
||||||
|
get => _orgIdentifier;
|
||||||
|
set => SetProperty(ref _orgIdentifier, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command LogInCommand { get; }
|
||||||
|
public bool RememberOrgIdentifier { get; set; }
|
||||||
|
public Action StartTwoFactorAction { get; set; }
|
||||||
|
public Action StartSetPasswordAction { get; set; }
|
||||||
|
public Action SsoAuthSuccessAction { get; set; }
|
||||||
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(OrgIdentifier))
|
||||||
|
{
|
||||||
|
OrgIdentifier = await _storageService.GetAsync<string>(Keys_RememberedOrgIdentifier);
|
||||||
|
}
|
||||||
|
var rememberOrgIdentifier = await _storageService.GetAsync<bool?>(Keys_RememberOrgIdentifier);
|
||||||
|
RememberOrgIdentifier = rememberOrgIdentifier.GetValueOrDefault(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogInAsync()
|
||||||
|
{
|
||||||
|
if (Connectivity.NetworkAccess == NetworkAccess.None)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
||||||
|
AppResources.InternetConnectionRequiredTitle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(OrgIdentifier))
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(
|
||||||
|
string.Format(AppResources.ValidationFieldRequired, AppResources.OrgIdentifier),
|
||||||
|
AppResources.AnErrorHasOccurred,
|
||||||
|
AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _apiService.PreValidateSso(OrgIdentifier);
|
||||||
|
}
|
||||||
|
catch (ApiException e)
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
await _platformUtilsService.ShowDialogAsync(
|
||||||
|
(e?.Error != null ? e.Error.GetSingleMessage() : AppResources.LoginSsoError),
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var passwordOptions = new PasswordGenerationOptions(true);
|
||||||
|
passwordOptions.Length = 64;
|
||||||
|
|
||||||
|
var codeVerifier = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions);
|
||||||
|
var codeVerifierHash = await _cryptoFunctionService.HashAsync(codeVerifier, CryptoHashAlgorithm.Sha256);
|
||||||
|
var codeChallenge = CoreHelpers.Base64UrlEncode(codeVerifierHash);
|
||||||
|
|
||||||
|
var state = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions);
|
||||||
|
|
||||||
|
var redirectUri = "bitwarden://sso-callback";
|
||||||
|
|
||||||
|
var url = _apiService.IdentityBaseUrl + "/connect/authorize?" +
|
||||||
|
"client_id=" + _platformUtilsService.IdentityClientId + "&" +
|
||||||
|
"redirect_uri=" + Uri.EscapeDataString(redirectUri) + "&" +
|
||||||
|
"response_type=code&scope=api%20offline_access&" +
|
||||||
|
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
||||||
|
"code_challenge_method=S256&response_mode=query&" +
|
||||||
|
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
|
||||||
|
|
||||||
|
WebAuthenticatorResult authResult = null;
|
||||||
|
bool cancelled = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
|
||||||
|
new Uri(redirectUri));
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException taskCanceledException)
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
cancelled = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// WebAuthenticator throws NSErrorException if iOS flow is cancelled - by setting cancelled to true
|
||||||
|
// here we maintain the appearance of a clean cancellation (we don't want to do this across the board
|
||||||
|
// because we still want to present legitimate errors). If/when this is fixed, we can remove this
|
||||||
|
// particular catch block (catching taskCanceledException above must remain)
|
||||||
|
// https://github.com/xamarin/Essentials/issues/1240
|
||||||
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
cancelled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cancelled)
|
||||||
|
{
|
||||||
|
var code = GetResultCode(authResult, state);
|
||||||
|
if (!string.IsNullOrEmpty(code))
|
||||||
|
{
|
||||||
|
await LogIn(code, codeVerifier, redirectUri);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetResultCode(WebAuthenticatorResult authResult, string state)
|
||||||
|
{
|
||||||
|
string code = null;
|
||||||
|
if (authResult != null)
|
||||||
|
{
|
||||||
|
authResult.Properties.TryGetValue("state", out var resultState);
|
||||||
|
if (resultState == state)
|
||||||
|
{
|
||||||
|
authResult.Properties.TryGetValue("code", out var resultCode);
|
||||||
|
code = resultCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LogIn(string code, string codeVerifier, string redirectUri)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri);
|
||||||
|
if (RememberOrgIdentifier)
|
||||||
|
{
|
||||||
|
await _storageService.SaveAsync(Keys_RememberedOrgIdentifier, OrgIdentifier);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _storageService.RemoveAsync(Keys_RememberedOrgIdentifier);
|
||||||
|
}
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
if (response.TwoFactor)
|
||||||
|
{
|
||||||
|
StartTwoFactorAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (response.ResetMasterPassword)
|
||||||
|
{
|
||||||
|
StartSetPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
|
||||||
|
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
|
||||||
|
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
||||||
|
SsoAuthSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
|
@ -17,12 +18,11 @@ namespace Bit.App.Pages
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_vm = BindingContext as RegisterPageViewModel;
|
_vm = BindingContext as RegisterPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.RegistrationSuccess = async () =>
|
_vm.RegistrationSuccess = () => Device.BeginInvokeOnMainThread(async () => await RegistrationSuccessAsync(homePage));
|
||||||
|
_vm.CloseAction = async () =>
|
||||||
{
|
{
|
||||||
if (homePage != null)
|
_messagingService.Send("showStatusBar", false);
|
||||||
{
|
await Navigation.PopModalAsync();
|
||||||
await homePage.DismissRegisterPageAndLogInAsync(_vm.Email);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
MasterPasswordEntry = _masterPassword;
|
MasterPasswordEntry = _masterPassword;
|
||||||
ConfirmMasterPasswordEntry = _confirmMasterPassword;
|
ConfirmMasterPasswordEntry = _confirmMasterPassword;
|
||||||
|
@ -56,12 +56,19 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, EventArgs e)
|
private async Task RegistrationSuccessAsync(HomePage homePage)
|
||||||
|
{
|
||||||
|
if (homePage != null)
|
||||||
|
{
|
||||||
|
await homePage.DismissRegisterPageAndLogInAsync(_vm.Email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
_messagingService.Send("showStatusBar", false);
|
_vm.CloseAction();
|
||||||
await Navigation.PopModalAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ namespace Bit.App.Pages
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
public Action RegistrationSuccess { get; set; }
|
public Action RegistrationSuccess { get; set; }
|
||||||
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
|
|
146
src/App/Pages/Accounts/SetPasswordPage.xaml
Normal file
146
src/App/Pages/Accounts/SetPasswordPage.xaml
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
<?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.SetPasswordPage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
x:DataType="pages:SetPasswordPageViewModel"
|
||||||
|
Title="{Binding PageTitle}">
|
||||||
|
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:SetPasswordPageViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
|
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<ScrollView>
|
||||||
|
<StackLayout Spacing="20">
|
||||||
|
<StackLayout StyleClass="box">
|
||||||
|
<StackLayout StyleClass="box-row">
|
||||||
|
<Label Text="{u:I18n SetMasterPasswordSummary}"
|
||||||
|
StyleClass="text-md"
|
||||||
|
HorizontalTextAlignment="Start"></Label>
|
||||||
|
</StackLayout>
|
||||||
|
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
||||||
|
RowSpacing="0"
|
||||||
|
ColumnSpacing="0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Frame Padding="10"
|
||||||
|
Margin="0"
|
||||||
|
HasShadow="False"
|
||||||
|
BackgroundColor="Transparent"
|
||||||
|
BorderColor="Accent">
|
||||||
|
<Label
|
||||||
|
Text="{Binding PolicySummary}"
|
||||||
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
|
HorizontalTextAlignment="Start" />
|
||||||
|
</Frame>
|
||||||
|
</Grid>
|
||||||
|
<Grid StyleClass="box-row">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n MasterPassword}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<controls:MonoEntry
|
||||||
|
x:Name="_masterPassword"
|
||||||
|
Text="{Binding MasterPassword}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
IsSpellCheckEnabled="False"
|
||||||
|
IsTextPredictionEnabled="False"
|
||||||
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<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"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||||
|
</Grid>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
|
StyleClass="box-footer-label" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout StyleClass="box">
|
||||||
|
<Grid StyleClass="box-row">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n RetypeMasterPassword}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<controls:MonoEntry
|
||||||
|
x:Name="_confirmMasterPassword"
|
||||||
|
Text="{Binding ConfirmMasterPassword}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
IsSpellCheckEnabled="False"
|
||||||
|
IsTextPredictionEnabled="False"
|
||||||
|
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 ToggleConfirmPasswordCommand}"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||||
|
</Grid>
|
||||||
|
<StackLayout StyleClass="box-row">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n MasterPasswordHint}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Entry
|
||||||
|
x:Name="_hint"
|
||||||
|
Text="{Binding Hint}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
ReturnType="Go"
|
||||||
|
ReturnCommand="{Binding SubmitCommand}" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n MasterPasswordHintDescription}"
|
||||||
|
StyleClass="box-footer-label" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</pages:BaseContentPage>
|
82
src/App/Pages/Accounts/SetPasswordPage.xaml.cs
Normal file
82
src/App/Pages/Accounts/SetPasswordPage.xaml.cs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class SetPasswordPage : BaseContentPage
|
||||||
|
{
|
||||||
|
private readonly IMessagingService _messagingService;
|
||||||
|
private readonly SetPasswordPageViewModel _vm;
|
||||||
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
|
public SetPasswordPage(AppOptions appOptions = null)
|
||||||
|
{
|
||||||
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
_messagingService.Send("showStatusBar", true);
|
||||||
|
_appOptions = appOptions;
|
||||||
|
InitializeComponent();
|
||||||
|
_vm = BindingContext as SetPasswordPageViewModel;
|
||||||
|
_vm.Page = this;
|
||||||
|
_vm.SetPasswordSuccessAction =
|
||||||
|
() => Device.BeginInvokeOnMainThread(async () => await SetPasswordSuccessAsync());
|
||||||
|
_vm.CloseAction = async () =>
|
||||||
|
{
|
||||||
|
_messagingService.Send("showStatusBar", false);
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
};
|
||||||
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
ToolbarItems.RemoveAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MasterPasswordEntry = _masterPassword;
|
||||||
|
ConfirmMasterPasswordEntry = _confirmMasterPassword;
|
||||||
|
|
||||||
|
_masterPassword.ReturnType = ReturnType.Next;
|
||||||
|
_masterPassword.ReturnCommand = new Command(() => _confirmMasterPassword.Focus());
|
||||||
|
_confirmMasterPassword.ReturnType = ReturnType.Next;
|
||||||
|
_confirmMasterPassword.ReturnCommand = new Command(() => _hint.Focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry MasterPasswordEntry { get; set; }
|
||||||
|
public Entry ConfirmMasterPasswordEntry { get; set; }
|
||||||
|
|
||||||
|
protected override async void OnAppearing()
|
||||||
|
{
|
||||||
|
base.OnAppearing();
|
||||||
|
await _vm.InitAsync();
|
||||||
|
RequestFocus(_masterPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Submit_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
await _vm.SubmitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Close_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
_vm.CloseAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetPasswordSuccessAsync()
|
||||||
|
{
|
||||||
|
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||||
|
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
259
src/App/Pages/Accounts/SetPasswordPageViewModel.cs
Normal file
259
src/App/Pages/Accounts/SetPasswordPageViewModel.cs
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class SetPasswordPageViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
private readonly IPolicyService _policyService;
|
||||||
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
|
private readonly II18nService _i18nService;
|
||||||
|
|
||||||
|
private bool _showPassword;
|
||||||
|
private bool _isPolicyInEffect;
|
||||||
|
private string _policySummary;
|
||||||
|
private MasterPasswordPolicyOptions _policy;
|
||||||
|
|
||||||
|
public SetPasswordPageViewModel()
|
||||||
|
{
|
||||||
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||||
|
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||||
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||||
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
|
_passwordGenerationService =
|
||||||
|
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
|
||||||
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
|
|
||||||
|
PageTitle = AppResources.SetMasterPassword;
|
||||||
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
|
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
|
||||||
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowPassword
|
||||||
|
{
|
||||||
|
get => _showPassword;
|
||||||
|
set => SetProperty(ref _showPassword, value,
|
||||||
|
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPolicyInEffect
|
||||||
|
{
|
||||||
|
get => _isPolicyInEffect;
|
||||||
|
set => SetProperty(ref _isPolicyInEffect, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PolicySummary
|
||||||
|
{
|
||||||
|
get => _policySummary;
|
||||||
|
set => SetProperty(ref _policySummary, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MasterPasswordPolicyOptions Policy
|
||||||
|
{
|
||||||
|
get => _policy;
|
||||||
|
set => SetProperty(ref _policy, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command SubmitCommand { get; }
|
||||||
|
public Command TogglePasswordCommand { get; }
|
||||||
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
|
public string MasterPassword { get; set; }
|
||||||
|
public string ConfirmMasterPassword { get; set; }
|
||||||
|
public string Hint { get; set; }
|
||||||
|
public Action SetPasswordSuccessAction { get; set; }
|
||||||
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
await CheckPasswordPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SubmitAsync()
|
||||||
|
{
|
||||||
|
if (Connectivity.NetworkAccess == NetworkAccess.None)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
||||||
|
AppResources.InternetConnectionRequiredTitle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(MasterPassword))
|
||||||
|
{
|
||||||
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||||
|
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
||||||
|
AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsPolicyInEffect)
|
||||||
|
{
|
||||||
|
var userInput = await GetPasswordStrengthUserInput();
|
||||||
|
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput);
|
||||||
|
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
|
||||||
|
{
|
||||||
|
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
|
||||||
|
AppResources.MasterPasswordPolicyValidationMessage, AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (MasterPassword.Length < 8)
|
||||||
|
{
|
||||||
|
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
|
||||||
|
AppResources.MasterPasswordLengthValMessage, AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (MasterPassword != ConfirmMasterPassword)
|
||||||
|
{
|
||||||
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||||
|
AppResources.MasterPasswordConfirmationValMessage, AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var kdf = KdfType.PBKDF2_SHA256;
|
||||||
|
var kdfIterations = 100000;
|
||||||
|
var email = await _userService.GetEmailAsync();
|
||||||
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
|
||||||
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||||
|
|
||||||
|
Tuple<SymmetricCryptoKey, CipherString> encKey;
|
||||||
|
var existingEncKey = await _cryptoService.GetEncKeyAsync();
|
||||||
|
if (existingEncKey == null)
|
||||||
|
{
|
||||||
|
encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||||
|
var request = new SetPasswordRequest
|
||||||
|
{
|
||||||
|
MasterPasswordHash = masterPasswordHash,
|
||||||
|
Key = encKey.Item2.EncryptedString,
|
||||||
|
MasterPasswordHint = Hint,
|
||||||
|
Kdf = kdf,
|
||||||
|
KdfIterations = kdfIterations,
|
||||||
|
Keys = new KeysRequest
|
||||||
|
{
|
||||||
|
PublicKey = keys.Item1,
|
||||||
|
EncryptedPrivateKey = keys.Item2.EncryptedString
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
|
||||||
|
await _apiService.SetPasswordAsync(request);
|
||||||
|
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
|
||||||
|
await _userService.GetEmailAsync(), kdf, kdfIterations);
|
||||||
|
await _cryptoService.SetKeyAsync(key);
|
||||||
|
await _cryptoService.SetKeyHashAsync(masterPasswordHash);
|
||||||
|
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||||
|
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|
||||||
|
SetPasswordSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
|
catch (ApiException e)
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
if (e?.Error != null)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TogglePassword()
|
||||||
|
{
|
||||||
|
ShowPassword = !ShowPassword;
|
||||||
|
(Page as SetPasswordPage).MasterPasswordEntry.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleConfirmPassword()
|
||||||
|
{
|
||||||
|
ShowPassword = !ShowPassword;
|
||||||
|
(Page as SetPasswordPage).ConfirmMasterPasswordEntry.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckPasswordPolicy()
|
||||||
|
{
|
||||||
|
Policy = await _policyService.GetMasterPasswordPolicyOptions();
|
||||||
|
IsPolicyInEffect = Policy?.InEffect() ?? false;
|
||||||
|
if (!IsPolicyInEffect)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bullet = "\n" + "".PadLeft(4) + "\u2022 ";
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(_i18nService.T("MasterPasswordPolicyInEffect"));
|
||||||
|
if (Policy.MinComplexity > 0)
|
||||||
|
{
|
||||||
|
sb.Append(bullet)
|
||||||
|
.Append(string.Format(_i18nService.T("PolicyInEffectMinComplexity"), Policy.MinComplexity));
|
||||||
|
}
|
||||||
|
if (Policy.MinLength > 0)
|
||||||
|
{
|
||||||
|
sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectMinLength"), Policy.MinLength));
|
||||||
|
}
|
||||||
|
if (Policy.RequireUpper)
|
||||||
|
{
|
||||||
|
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectUppercase"));
|
||||||
|
}
|
||||||
|
if (Policy.RequireLower)
|
||||||
|
{
|
||||||
|
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectLowercase"));
|
||||||
|
}
|
||||||
|
if (Policy.RequireNumbers)
|
||||||
|
{
|
||||||
|
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectNumbers"));
|
||||||
|
}
|
||||||
|
if (Policy.RequireSpecial)
|
||||||
|
{
|
||||||
|
sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectSpecial"), "!@#$%^&*"));
|
||||||
|
}
|
||||||
|
PolicySummary = sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<string>> GetPasswordStrengthUserInput()
|
||||||
|
{
|
||||||
|
var email = await _userService.GetEmailAsync();
|
||||||
|
List<string> userInput = null;
|
||||||
|
var atPosition = email.IndexOf('@');
|
||||||
|
if (atPosition > -1)
|
||||||
|
{
|
||||||
|
var rx = new Regex("/[^A-Za-z0-9]/", RegexOptions.Compiled);
|
||||||
|
var data = rx.Split(email.Substring(0, atPosition).Trim().ToLower());
|
||||||
|
userInput = new List<string>(data);
|
||||||
|
}
|
||||||
|
return userInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
|
@ -14,22 +14,29 @@ namespace Bit.App.Pages
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
private TwoFactorPageViewModel _vm;
|
private TwoFactorPageViewModel _vm;
|
||||||
private bool _inited;
|
private bool _inited;
|
||||||
|
private bool _authingWithSso;
|
||||||
|
|
||||||
public TwoFactorPage(AppOptions appOptions = null)
|
public TwoFactorPage(bool? authingWithSso = false, AppOptions appOptions = null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
SetActivityIndicator();
|
SetActivityIndicator();
|
||||||
|
_authingWithSso = authingWithSso ?? false;
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
_vm = BindingContext as TwoFactorPageViewModel;
|
_vm = BindingContext as TwoFactorPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.TwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthAsync());
|
_vm.StartSetPasswordAction = () =>
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
|
||||||
|
_vm.TwoFactorAuthSuccessAction = () =>
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
|
||||||
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
||||||
DuoWebView = _duoWebView;
|
DuoWebView = _duoWebView;
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
|
@ -141,7 +148,7 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
private void Close_Clicked(object sender, System.EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
|
@ -160,27 +167,28 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TwoFactorAuthAsync()
|
private async Task StartSetPasswordAsync()
|
||||||
{
|
{
|
||||||
if (_appOptions != null)
|
_vm.CloseAction();
|
||||||
|
var page = new SetPasswordPage(_appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TwoFactorAuthSuccessAsync()
|
||||||
{
|
{
|
||||||
if (_appOptions.FromAutofillFramework && _appOptions.SaveType.HasValue)
|
if (_authingWithSso)
|
||||||
|
{
|
||||||
|
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||||
{
|
{
|
||||||
Application.Current.MainPage = new NavigationPage(new AddEditPage(appOptions: _appOptions));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_appOptions.Uri != null)
|
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||||
{
|
|
||||||
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(_appOptions));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var previousPage = await _storageService.GetAsync<PreviousPageInfo>(Constants.PreviousPageKey);
|
|
||||||
if (previousPage != null)
|
|
||||||
{
|
|
||||||
await _storageService.RemoveAsync(Constants.PreviousPageKey);
|
|
||||||
}
|
|
||||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace Bit.App.Pages
|
||||||
private TwoFactorProviderType? _selectedProviderType;
|
private TwoFactorProviderType? _selectedProviderType;
|
||||||
private string _totpInstruction;
|
private string _totpInstruction;
|
||||||
private string _webVaultUrl = "https://vault.bitwarden.com";
|
private string _webVaultUrl = "https://vault.bitwarden.com";
|
||||||
|
private bool _authingWithSso = false;
|
||||||
|
|
||||||
public TwoFactorPageViewModel()
|
public TwoFactorPageViewModel()
|
||||||
{
|
{
|
||||||
|
@ -89,19 +90,21 @@ namespace Bit.App.Pages
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Action TwoFactorAction { get; set; }
|
public Action TwoFactorAuthSuccessAction { get; set; }
|
||||||
|
public Action StartSetPasswordAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_authService.Email) ||
|
if ((!_authService.AuthingWithSso() && !_authService.AuthingWithPassword()) ||
|
||||||
string.IsNullOrWhiteSpace(_authService.MasterPasswordHash) ||
|
|
||||||
_authService.TwoFactorProvidersData == null)
|
_authService.TwoFactorProvidersData == null)
|
||||||
{
|
{
|
||||||
// TODO: dismiss modal?
|
// TODO: dismiss modal?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_authingWithSso = _authService.AuthingWithSso();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
|
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
|
||||||
{
|
{
|
||||||
_webVaultUrl = _environmentService.BaseUrl;
|
_webVaultUrl = _environmentService.BaseUrl;
|
||||||
|
@ -204,14 +207,21 @@ namespace Bit.App.Pages
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
||||||
await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
|
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
_messagingService.Send("listenYubiKeyOTP", false);
|
_messagingService.Send("listenYubiKeyOTP", false);
|
||||||
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
||||||
|
if (_authingWithSso && result.ResetMasterPassword)
|
||||||
|
{
|
||||||
|
StartSetPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
|
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
|
||||||
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
|
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
|
||||||
TwoFactorAction?.Invoke();
|
TwoFactorAuthSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
|
|
6453
src/App/Resources/AppResources.Designer.cs
generated
6453
src/App/Resources/AppResources.Designer.cs
generated
File diff suppressed because it is too large
Load diff
|
@ -1683,4 +1683,52 @@
|
||||||
<data name="EnableSyncOnRefreshDescription" xml:space="preserve">
|
<data name="EnableSyncOnRefreshDescription" xml:space="preserve">
|
||||||
<value>Syncing vault with pull down gesture.</value>
|
<value>Syncing vault with pull down gesture.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LogInSso" xml:space="preserve">
|
||||||
|
<value>Enterprise Single Sign-On</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogInSsoSummary" xml:space="preserve">
|
||||||
|
<value>Quickly log in using your organization's single sign-on portal. Please enter your organization's identifier to begin.</value>
|
||||||
|
</data>
|
||||||
|
<data name="OrgIdentifier" xml:space="preserve">
|
||||||
|
<value>Organization Identifier</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginSsoError" xml:space="preserve">
|
||||||
|
<value>Currently unable to login with SSO</value>
|
||||||
|
</data>
|
||||||
|
<data name="SetMasterPassword" xml:space="preserve">
|
||||||
|
<value>Set Master Password</value>
|
||||||
|
</data>
|
||||||
|
<data name="SetMasterPasswordSummary" xml:space="preserve">
|
||||||
|
<value>In order to complete logging in with SSO, please set a master password to access and protect your vault.</value>
|
||||||
|
</data>
|
||||||
|
<data name="MasterPasswordPolicyInEffect" xml:space="preserve">
|
||||||
|
<value>One or more organization policies require your master password to meet the following requirements:</value>
|
||||||
|
</data>
|
||||||
|
<data name="PolicyInEffectMinComplexity" xml:space="preserve">
|
||||||
|
<value>Minimum complexity score of {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="PolicyInEffectMinLength" xml:space="preserve">
|
||||||
|
<value>Minimum length of {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="PolicyInEffectUppercase" xml:space="preserve">
|
||||||
|
<value>Contain one or more uppercase characters</value>
|
||||||
|
</data>
|
||||||
|
<data name="PolicyInEffectLowercase" xml:space="preserve">
|
||||||
|
<value>Contain one or more lowercase characters</value>
|
||||||
|
</data>
|
||||||
|
<data name="PolicyInEffectNumbers" xml:space="preserve">
|
||||||
|
<value>Contain one or more numbers</value>
|
||||||
|
</data>
|
||||||
|
<data name="PolicyInEffectSpecial" xml:space="preserve">
|
||||||
|
<value>Contain one or more of the following special characters: {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="MasterPasswordPolicyValidationTitle" xml:space="preserve">
|
||||||
|
<value>Invalid Password</value>
|
||||||
|
</data>
|
||||||
|
<data name="MasterPasswordPolicyValidationMessage" xml:space="preserve">
|
||||||
|
<value>Password does not meet organization requirements. Please check the policy information and try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Loading" xml:space="preserve">
|
||||||
|
<value>Loading</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -8,6 +8,7 @@ using Bit.Core.Utilities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Utilities
|
namespace Bit.App.Utilities
|
||||||
|
@ -192,5 +193,34 @@ namespace Bit.App.Utilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool SetAlternateMainPage(AppOptions appOptions)
|
||||||
|
{
|
||||||
|
if (appOptions != null)
|
||||||
|
{
|
||||||
|
if (appOptions.FromAutofillFramework && appOptions.SaveType.HasValue)
|
||||||
|
{
|
||||||
|
Application.Current.MainPage = new NavigationPage(new AddEditPage(appOptions: appOptions));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (appOptions.Uri != null)
|
||||||
|
{
|
||||||
|
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(appOptions));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<PreviousPageInfo> ClearPreviousPage()
|
||||||
|
{
|
||||||
|
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
|
var previousPage = await storageService.GetAsync<PreviousPageInfo>(Constants.PreviousPageKey);
|
||||||
|
if (previousPage != null)
|
||||||
|
{
|
||||||
|
await storageService.RemoveAsync(Constants.PreviousPageKey);
|
||||||
|
}
|
||||||
|
return previousPage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,13 @@ namespace Bit.Core.Abstractions
|
||||||
Task<ProfileResponse> GetProfileAsync();
|
Task<ProfileResponse> GetProfileAsync();
|
||||||
Task<SyncResponse> GetSyncAsync();
|
Task<SyncResponse> GetSyncAsync();
|
||||||
Task PostAccountKeysAsync(KeysRequest request);
|
Task PostAccountKeysAsync(KeysRequest request);
|
||||||
|
Task PostAccountVerifyPasswordAsync(PasswordVerificationRequest request);
|
||||||
Task<CipherResponse> PostCipherAsync(CipherRequest request);
|
Task<CipherResponse> PostCipherAsync(CipherRequest request);
|
||||||
Task<CipherResponse> PostCipherCreateAsync(CipherCreateRequest request);
|
Task<CipherResponse> PostCipherCreateAsync(CipherCreateRequest request);
|
||||||
Task<FolderResponse> PostFolderAsync(FolderRequest request);
|
Task<FolderResponse> PostFolderAsync(FolderRequest request);
|
||||||
Task<Tuple<IdentityTokenResponse, IdentityTwoFactorResponse>> PostIdentityTokenAsync(TokenRequest request);
|
Task<Tuple<IdentityTokenResponse, IdentityTwoFactorResponse>> PostIdentityTokenAsync(TokenRequest request);
|
||||||
Task PostPasswordHintAsync(PasswordHintRequest request);
|
Task PostPasswordHintAsync(PasswordHintRequest request);
|
||||||
|
Task SetPasswordAsync(SetPasswordRequest request);
|
||||||
Task<PreloginResponse> PostPreloginAsync(PreloginRequest request);
|
Task<PreloginResponse> PostPreloginAsync(PreloginRequest request);
|
||||||
Task PostRegisterAsync(RegisterRequest request);
|
Task PostRegisterAsync(RegisterRequest request);
|
||||||
Task<CipherResponse> PutCipherAsync(string id, CipherRequest request);
|
Task<CipherResponse> PutCipherAsync(string id, CipherRequest request);
|
||||||
|
@ -40,6 +42,7 @@ namespace Bit.Core.Abstractions
|
||||||
Task PutDeleteCipherAsync(string id);
|
Task PutDeleteCipherAsync(string id);
|
||||||
Task PutRestoreCipherAsync(string id);
|
Task PutRestoreCipherAsync(string id);
|
||||||
Task RefreshIdentityTokenAsync();
|
Task RefreshIdentityTokenAsync();
|
||||||
|
Task<object> PreValidateSso(string identifier);
|
||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse);
|
TRequest body, bool authed, bool hasResponse);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrls urls);
|
||||||
|
|
|
@ -10,13 +10,19 @@ namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
string Email { get; set; }
|
string Email { get; set; }
|
||||||
string MasterPasswordHash { get; set; }
|
string MasterPasswordHash { get; set; }
|
||||||
|
string Code { get; set; }
|
||||||
|
string CodeVerifier { get; set; }
|
||||||
|
string SsoRedirectUrl { get; set; }
|
||||||
TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; }
|
TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; }
|
||||||
Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
|
Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
|
||||||
Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
|
Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
|
||||||
|
|
||||||
TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported);
|
TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported);
|
||||||
|
bool AuthingWithSso();
|
||||||
|
bool AuthingWithPassword();
|
||||||
List<TwoFactorProvider> GetSupportedTwoFactorProviders();
|
List<TwoFactorProvider> GetSupportedTwoFactorProviders();
|
||||||
Task<AuthResult> LogInAsync(string email, string masterPassword);
|
Task<AuthResult> LogInAsync(string email, string masterPassword);
|
||||||
|
Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl);
|
||||||
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
|
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
|
||||||
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
|
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
|
||||||
void LogOut(Action callback);
|
void LogOut(Action callback);
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace Bit.Core.Abstractions
|
||||||
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> GetOptionsAsync();
|
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> GetOptionsAsync();
|
||||||
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)>
|
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)>
|
||||||
EnforcePasswordGeneratorPoliciesOnOptionsAsync(PasswordGenerationOptions options);
|
EnforcePasswordGeneratorPoliciesOnOptionsAsync(PasswordGenerationOptions options);
|
||||||
Task<object> PasswordStrength(string password, List<string> userInputs = null);
|
Zxcvbn.Result PasswordStrength(string password, List<string> userInputs = null);
|
||||||
Task SaveOptionsAsync(PasswordGenerationOptions options);
|
Task SaveOptionsAsync(PasswordGenerationOptions options);
|
||||||
void NormalizeOptions(PasswordGenerationOptions options, PasswordGeneratorPolicyOptions enforcedPolicyOptions);
|
void NormalizeOptions(PasswordGenerationOptions options, PasswordGeneratorPolicyOptions enforcedPolicyOptions);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,5 +12,8 @@ namespace Bit.Core.Abstractions
|
||||||
Task<IEnumerable<Policy>> GetAll(PolicyType? type);
|
Task<IEnumerable<Policy>> GetAll(PolicyType? type);
|
||||||
Task Replace(Dictionary<string, PolicyData> policies);
|
Task Replace(Dictionary<string, PolicyData> policies);
|
||||||
Task Clear(string userId);
|
Task Clear(string userId);
|
||||||
|
Task<MasterPasswordPolicyOptions> GetMasterPasswordPolicyOptions(IEnumerable<Policy> policies = null);
|
||||||
|
Task<bool> EvaluateMasterPassword(int passwordStrength, string newPassword,
|
||||||
|
MasterPasswordPolicyOptions enforcedPolicyOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
<PackageReference Include="LiteDB" Version="5.0.8" />
|
<PackageReference Include="LiteDB" Version="5.0.8" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="PCLCrypto" Version="2.0.147" />
|
<PackageReference Include="PCLCrypto" Version="2.0.147" />
|
||||||
|
<PackageReference Include="zxcvbn-core" Version="2.1.44" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Bit.Core.Models.Domain
|
||||||
public class AuthResult
|
public class AuthResult
|
||||||
{
|
{
|
||||||
public bool TwoFactor { get; set; }
|
public bool TwoFactor { get; set; }
|
||||||
|
public bool ResetMasterPassword { get; set; }
|
||||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
22
src/Core/Models/Domain/MasterPasswordPolicyOptions.cs
Normal file
22
src/Core/Models/Domain/MasterPasswordPolicyOptions.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Bit.Core.Models.Domain
|
||||||
|
{
|
||||||
|
public class MasterPasswordPolicyOptions
|
||||||
|
{
|
||||||
|
public int MinComplexity { get; set; }
|
||||||
|
public int MinLength { get; set; }
|
||||||
|
public bool RequireUpper { get; set; }
|
||||||
|
public bool RequireLower { get; set; }
|
||||||
|
public bool RequireNumbers { get; set; }
|
||||||
|
public bool RequireSpecial { get; set; }
|
||||||
|
|
||||||
|
public bool InEffect()
|
||||||
|
{
|
||||||
|
return MinComplexity > 0 ||
|
||||||
|
MinLength > 0 ||
|
||||||
|
RequireUpper ||
|
||||||
|
RequireLower ||
|
||||||
|
RequireNumbers ||
|
||||||
|
RequireSpecial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/Core/Models/Request/PasswordVerificationRequest.cs
Normal file
7
src/Core/Models/Request/PasswordVerificationRequest.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Bit.Core.Models.Request
|
||||||
|
{
|
||||||
|
public class PasswordVerificationRequest
|
||||||
|
{
|
||||||
|
public string MasterPasswordHash { get; set; }
|
||||||
|
}
|
||||||
|
}
|
14
src/Core/Models/Request/SetPasswordRequest.cs
Normal file
14
src/Core/Models/Request/SetPasswordRequest.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Request
|
||||||
|
{
|
||||||
|
public class SetPasswordRequest
|
||||||
|
{
|
||||||
|
public string MasterPasswordHash { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string MasterPasswordHint { get; set; }
|
||||||
|
public KeysRequest Keys { get; set; }
|
||||||
|
public KdfType Kdf { get; set; }
|
||||||
|
public int KdfIterations { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,21 +9,60 @@ namespace Bit.Core.Models.Request
|
||||||
{
|
{
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string MasterPasswordHash { get; set; }
|
public string MasterPasswordHash { get; set; }
|
||||||
|
public string Code { get; set; }
|
||||||
|
public string CodeVerifier { get; set; }
|
||||||
|
public string RedirectUri { get; set; }
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
public TwoFactorProviderType? Provider { get; set; }
|
public TwoFactorProviderType? Provider { get; set; }
|
||||||
public bool Remember { get; set; }
|
public bool? Remember { get; set; }
|
||||||
public DeviceRequest Device { get; set; }
|
public DeviceRequest Device { get; set; }
|
||||||
|
|
||||||
|
public TokenRequest(string[] credentials, string[] codes, TwoFactorProviderType? provider, string token,
|
||||||
|
bool? remember, DeviceRequest device = null)
|
||||||
|
{
|
||||||
|
if (credentials != null && credentials.Length > 1)
|
||||||
|
{
|
||||||
|
Email = credentials[0];
|
||||||
|
MasterPasswordHash = credentials[1];
|
||||||
|
}
|
||||||
|
else if (codes != null && codes.Length > 2)
|
||||||
|
{
|
||||||
|
Code = codes[0];
|
||||||
|
CodeVerifier = codes[1];
|
||||||
|
RedirectUri = codes[2];
|
||||||
|
}
|
||||||
|
Token = token;
|
||||||
|
Provider = provider;
|
||||||
|
Remember = remember;
|
||||||
|
Device = device;
|
||||||
|
}
|
||||||
|
|
||||||
public Dictionary<string, string> ToIdentityToken(string clientId)
|
public Dictionary<string, string> ToIdentityToken(string clientId)
|
||||||
{
|
{
|
||||||
var obj = new Dictionary<string, string>
|
var obj = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["grant_type"] = "password",
|
|
||||||
["username"] = Email,
|
|
||||||
["password"] = MasterPasswordHash,
|
|
||||||
["scope"] = "api offline_access",
|
["scope"] = "api offline_access",
|
||||||
["client_id"] = clientId
|
["client_id"] = clientId
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (MasterPasswordHash != null && Email != null)
|
||||||
|
{
|
||||||
|
obj.Add("grant_type", "password");
|
||||||
|
obj.Add("username", Email);
|
||||||
|
obj.Add("password", MasterPasswordHash);
|
||||||
|
}
|
||||||
|
else if (Code != null && CodeVerifier != null && RedirectUri != null)
|
||||||
|
{
|
||||||
|
obj.Add("grant_type", "authorization_code");
|
||||||
|
obj.Add("code", Code);
|
||||||
|
obj.Add("code_verifier", CodeVerifier);
|
||||||
|
obj.Add("redirect_uri", RedirectUri);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("must provide credentials or codes");
|
||||||
|
}
|
||||||
|
|
||||||
if (Device != null)
|
if (Device != null)
|
||||||
{
|
{
|
||||||
obj.Add("deviceType", ((int)Device.Type).ToString());
|
obj.Add("deviceType", ((int)Device.Type).ToString());
|
||||||
|
@ -31,11 +70,11 @@ namespace Bit.Core.Models.Request
|
||||||
obj.Add("deviceName", Device.Name);
|
obj.Add("deviceName", Device.Name);
|
||||||
obj.Add("devicePushToken", Device.PushToken);
|
obj.Add("devicePushToken", Device.PushToken);
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(Token) && Provider != null)
|
if (!string.IsNullOrWhiteSpace(Token) && Provider != null && Remember.HasValue)
|
||||||
{
|
{
|
||||||
obj.Add("twoFactorToken", Token);
|
obj.Add("twoFactorToken", Token);
|
||||||
obj.Add("twoFactorProvider", ((int)Provider.Value).ToString());
|
obj.Add("twoFactorProvider", ((int)Provider.Value).ToString());
|
||||||
obj.Add("twoFactorRemember", Remember ? "1" : "0");
|
obj.Add("twoFactorRemember", Remember.GetValueOrDefault() ? "1" : "0");
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Newtonsoft.Json;
|
using Bit.Core.Enums;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Response
|
namespace Bit.Core.Models.Response
|
||||||
{
|
{
|
||||||
|
@ -12,8 +13,12 @@ namespace Bit.Core.Models.Response
|
||||||
public string RefreshToken { get; set; }
|
public string RefreshToken { get; set; }
|
||||||
[JsonProperty("token_type")]
|
[JsonProperty("token_type")]
|
||||||
public string TokenType { get; set; }
|
public string TokenType { get; set; }
|
||||||
|
|
||||||
|
public bool ResetMasterPassword { get; set; }
|
||||||
public string PrivateKey { get; set; }
|
public string PrivateKey { get; set; }
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
public string TwoFactorToken { get; set; }
|
public string TwoFactorToken { get; set; }
|
||||||
|
public KdfType Kdf { get; set; }
|
||||||
|
public int KdfIterations { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,12 @@ namespace Bit.Core.Services
|
||||||
request, false, false);
|
request, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SetPasswordAsync(SetPasswordRequest request)
|
||||||
|
{
|
||||||
|
return SendAsync<SetPasswordRequest, object>(HttpMethod.Post, "/accounts/set-password", request, true,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
public Task PostRegisterAsync(RegisterRequest request)
|
public Task PostRegisterAsync(RegisterRequest request)
|
||||||
{
|
{
|
||||||
return SendAsync<RegisterRequest, object>(HttpMethod.Post, "/accounts/register", request, false, false);
|
return SendAsync<RegisterRequest, object>(HttpMethod.Post, "/accounts/register", request, false, false);
|
||||||
|
@ -175,6 +181,12 @@ namespace Bit.Core.Services
|
||||||
return SendAsync<KeysRequest, object>(HttpMethod.Post, "/accounts/keys", request, true, false);
|
return SendAsync<KeysRequest, object>(HttpMethod.Post, "/accounts/keys", request, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task PostAccountVerifyPasswordAsync(PasswordVerificationRequest request)
|
||||||
|
{
|
||||||
|
return SendAsync<PasswordVerificationRequest, object>(HttpMethod.Post, "/accounts/verify-password", request,
|
||||||
|
true, false);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Folder APIs
|
#region Folder APIs
|
||||||
|
@ -365,6 +377,34 @@ namespace Bit.Core.Services
|
||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<object> PreValidateSso(string identifier)
|
||||||
|
{
|
||||||
|
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
||||||
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
|
{
|
||||||
|
requestMessage.Version = new Version(1, 0);
|
||||||
|
requestMessage.Method = HttpMethod.Get;
|
||||||
|
requestMessage.RequestUri = new Uri(string.Concat(IdentityBaseUrl, path));
|
||||||
|
requestMessage.Headers.Add("Accept", "application/json");
|
||||||
|
|
||||||
|
HttpResponseMessage response;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = await _httpClient.SendAsync(requestMessage);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new ApiException(HandleWebError(e));
|
||||||
|
}
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var error = await HandleErrorAsync(response, false);
|
||||||
|
throw new ApiException(error);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
|
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
|
||||||
bool authed, bool hasResponse)
|
bool authed, bool hasResponse)
|
||||||
{
|
{
|
||||||
|
@ -488,6 +528,8 @@ namespace Bit.Core.Services
|
||||||
await _logoutCallbackAsync(true);
|
await _logoutCallbackAsync(true);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
JObject responseJObject = null;
|
JObject responseJObject = null;
|
||||||
if (IsJsonResponse(response))
|
if (IsJsonResponse(response))
|
||||||
{
|
{
|
||||||
|
@ -496,6 +538,11 @@ namespace Bit.Core.Services
|
||||||
}
|
}
|
||||||
return new ErrorResponse(responseJObject, response.StatusCode, tokenError);
|
return new ErrorResponse(responseJObject, response.StatusCode, tokenError);
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsJsonResponse(HttpResponseMessage response)
|
private bool IsJsonResponse(HttpResponseMessage response)
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,8 +23,6 @@ namespace Bit.Core.Services
|
||||||
private readonly bool _setCryptoKeys;
|
private readonly bool _setCryptoKeys;
|
||||||
|
|
||||||
private SymmetricCryptoKey _key;
|
private SymmetricCryptoKey _key;
|
||||||
private KdfType? _kdf;
|
|
||||||
private int? _kdfIterations;
|
|
||||||
|
|
||||||
public AuthService(
|
public AuthService(
|
||||||
ICryptoService cryptoService,
|
ICryptoService cryptoService,
|
||||||
|
@ -95,6 +93,9 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string MasterPasswordHash { get; set; }
|
public string MasterPasswordHash { get; set; }
|
||||||
|
public string Code { get; set; }
|
||||||
|
public string CodeVerifier { get; set; }
|
||||||
|
public string SsoRedirectUrl { get; set; }
|
||||||
public Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
|
public Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
|
||||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
|
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
|
||||||
public TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; }
|
public TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; }
|
||||||
|
@ -122,13 +123,20 @@ namespace Bit.Core.Services
|
||||||
SelectedTwoFactorProviderType = null;
|
SelectedTwoFactorProviderType = null;
|
||||||
var key = await MakePreloginKeyAsync(masterPassword, email);
|
var key = await MakePreloginKeyAsync(masterPassword, email);
|
||||||
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
||||||
return await LogInHelperAsync(email, hashedPassword, key);
|
return await LogInHelperAsync(email, hashedPassword, null, null, null, key, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl)
|
||||||
|
{
|
||||||
|
SelectedTwoFactorProviderType = null;
|
||||||
|
return await LogInHelperAsync(null, null, code, codeVerifier, redirectUrl, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
|
public Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
|
||||||
bool? remember = null)
|
bool? remember = null)
|
||||||
{
|
{
|
||||||
return LogInHelperAsync(Email, MasterPasswordHash, _key, twoFactorProvider, twoFactorToken, remember);
|
return LogInHelperAsync(Email, MasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key,
|
||||||
|
twoFactorProvider, twoFactorToken, remember);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AuthResult> LogInCompleteAsync(string email, string masterPassword,
|
public async Task<AuthResult> LogInCompleteAsync(string email, string masterPassword,
|
||||||
|
@ -137,7 +145,16 @@ namespace Bit.Core.Services
|
||||||
SelectedTwoFactorProviderType = null;
|
SelectedTwoFactorProviderType = null;
|
||||||
var key = await MakePreloginKeyAsync(masterPassword, email);
|
var key = await MakePreloginKeyAsync(masterPassword, email);
|
||||||
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
||||||
return await LogInHelperAsync(email, hashedPassword, key, twoFactorProvider, twoFactorToken, remember);
|
return await LogInHelperAsync(email, hashedPassword, null, null, null, key, twoFactorProvider,
|
||||||
|
twoFactorToken, remember);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResult> LogInSsoCompleteAsync(string code, string codeVerifier, string redirectUrl,
|
||||||
|
TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null)
|
||||||
|
{
|
||||||
|
SelectedTwoFactorProviderType = null;
|
||||||
|
return await LogInHelperAsync(null, null, code, codeVerifier, redirectUrl, null, twoFactorProvider,
|
||||||
|
twoFactorToken, remember);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogOut(Action callback)
|
public void LogOut(Action callback)
|
||||||
|
@ -213,20 +230,30 @@ namespace Bit.Core.Services
|
||||||
return providerType;
|
return providerType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AuthingWithSso()
|
||||||
|
{
|
||||||
|
return Code != null && CodeVerifier != null && SsoRedirectUrl != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AuthingWithPassword()
|
||||||
|
{
|
||||||
|
return Email != null && MasterPasswordHash != null;
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email)
|
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email)
|
||||||
{
|
{
|
||||||
email = email.Trim().ToLower();
|
email = email.Trim().ToLower();
|
||||||
_kdf = null;
|
KdfType? kdf = null;
|
||||||
_kdfIterations = null;
|
int? kdfIterations = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var preloginResponse = await _apiService.PostPreloginAsync(new PreloginRequest { Email = email });
|
var preloginResponse = await _apiService.PostPreloginAsync(new PreloginRequest { Email = email });
|
||||||
if (preloginResponse != null)
|
if (preloginResponse != null)
|
||||||
{
|
{
|
||||||
_kdf = preloginResponse.Kdf;
|
kdf = preloginResponse.Kdf;
|
||||||
_kdfIterations = preloginResponse.KdfIterations;
|
kdfIterations = preloginResponse.KdfIterations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
|
@ -236,46 +263,64 @@ namespace Bit.Core.Services
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await _cryptoService.MakeKeyAsync(masterPassword, email, _kdf, _kdfIterations);
|
return await _cryptoService.MakeKeyAsync(masterPassword, email, kdf, kdfIterations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, SymmetricCryptoKey key,
|
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string code,
|
||||||
|
string codeVerifier, string redirectUrl, SymmetricCryptoKey key,
|
||||||
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null)
|
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null)
|
||||||
{
|
{
|
||||||
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
|
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
|
||||||
var appId = await _appIdService.GetAppIdAsync();
|
var appId = await _appIdService.GetAppIdAsync();
|
||||||
var deviceRequest = new DeviceRequest(appId, _platformUtilsService);
|
var deviceRequest = new DeviceRequest(appId, _platformUtilsService);
|
||||||
var request = new TokenRequest
|
|
||||||
|
string[] emailPassword;
|
||||||
|
string[] codeCodeVerifier;
|
||||||
|
if (email != null && hashedPassword != null)
|
||||||
{
|
{
|
||||||
Email = email,
|
emailPassword = new[] { email, hashedPassword };
|
||||||
MasterPasswordHash = hashedPassword,
|
}
|
||||||
Device = deviceRequest,
|
else
|
||||||
Remember = false
|
{
|
||||||
};
|
emailPassword = null;
|
||||||
|
}
|
||||||
|
if (code != null && codeVerifier != null && redirectUrl != null)
|
||||||
|
{
|
||||||
|
codeCodeVerifier = new[] { code, codeVerifier, redirectUrl };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
codeCodeVerifier = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenRequest request;
|
||||||
if (twoFactorToken != null && twoFactorProvider != null)
|
if (twoFactorToken != null && twoFactorProvider != null)
|
||||||
{
|
{
|
||||||
request.Provider = twoFactorProvider;
|
request = new TokenRequest(emailPassword, codeCodeVerifier, twoFactorProvider, twoFactorToken, remember,
|
||||||
request.Token = twoFactorToken;
|
deviceRequest);
|
||||||
request.Remember = remember.GetValueOrDefault();
|
|
||||||
}
|
}
|
||||||
else if (storedTwoFactorToken != null)
|
else if (storedTwoFactorToken != null)
|
||||||
{
|
{
|
||||||
request.Provider = TwoFactorProviderType.Remember;
|
request = new TokenRequest(emailPassword, codeCodeVerifier, TwoFactorProviderType.Remember,
|
||||||
request.Token = storedTwoFactorToken;
|
storedTwoFactorToken, false, deviceRequest);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request = new TokenRequest(emailPassword, codeCodeVerifier, null, null, false, deviceRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = await _apiService.PostIdentityTokenAsync(request);
|
var response = await _apiService.PostIdentityTokenAsync(request);
|
||||||
ClearState();
|
ClearState();
|
||||||
var result = new AuthResult
|
var result = new AuthResult { TwoFactor = response.Item2 != null };
|
||||||
{
|
|
||||||
TwoFactor = response.Item2 != null
|
|
||||||
};
|
|
||||||
if (result.TwoFactor)
|
if (result.TwoFactor)
|
||||||
{
|
{
|
||||||
// Two factor required.
|
// Two factor required.
|
||||||
var twoFactorResponse = response.Item2;
|
var twoFactorResponse = response.Item2;
|
||||||
Email = email;
|
Email = email;
|
||||||
MasterPasswordHash = hashedPassword;
|
MasterPasswordHash = hashedPassword;
|
||||||
|
Code = code;
|
||||||
|
CodeVerifier = codeVerifier;
|
||||||
|
SsoRedirectUrl = redirectUrl;
|
||||||
_key = _setCryptoKeys ? key : null;
|
_key = _setCryptoKeys ? key : null;
|
||||||
TwoFactorProvidersData = twoFactorResponse.TwoFactorProviders2;
|
TwoFactorProvidersData = twoFactorResponse.TwoFactorProviders2;
|
||||||
result.TwoFactorProviders = twoFactorResponse.TwoFactorProviders2;
|
result.TwoFactorProviders = twoFactorResponse.TwoFactorProviders2;
|
||||||
|
@ -283,13 +328,14 @@ namespace Bit.Core.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenResponse = response.Item1;
|
var tokenResponse = response.Item1;
|
||||||
|
result.ResetMasterPassword = tokenResponse.ResetMasterPassword;
|
||||||
if (tokenResponse.TwoFactorToken != null)
|
if (tokenResponse.TwoFactorToken != null)
|
||||||
{
|
{
|
||||||
await _tokenService.SetTwoFactorTokenAsync(tokenResponse.TwoFactorToken, email);
|
await _tokenService.SetTwoFactorTokenAsync(tokenResponse.TwoFactorToken, email);
|
||||||
}
|
}
|
||||||
await _tokenService.SetTokensAsync(tokenResponse.AccessToken, tokenResponse.RefreshToken);
|
await _tokenService.SetTokensAsync(tokenResponse.AccessToken, tokenResponse.RefreshToken);
|
||||||
await _userService.SetInformationAsync(_tokenService.GetUserId(), _tokenService.GetEmail(),
|
await _userService.SetInformationAsync(_tokenService.GetUserId(), _tokenService.GetEmail(),
|
||||||
_kdf.Value, _kdfIterations.Value);
|
tokenResponse.Kdf, tokenResponse.KdfIterations);
|
||||||
if (_setCryptoKeys)
|
if (_setCryptoKeys)
|
||||||
{
|
{
|
||||||
await _cryptoService.SetKeyAsync(key);
|
await _cryptoService.SetKeyAsync(key);
|
||||||
|
@ -322,8 +368,12 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
private void ClearState()
|
private void ClearState()
|
||||||
{
|
{
|
||||||
|
_key = null;
|
||||||
Email = null;
|
Email = null;
|
||||||
MasterPasswordHash = null;
|
MasterPasswordHash = null;
|
||||||
|
Code = null;
|
||||||
|
CodeVerifier = null;
|
||||||
|
SsoRedirectUrl = null;
|
||||||
TwoFactorProvidersData = null;
|
TwoFactorProvidersData = null;
|
||||||
SelectedTwoFactorProviderType = null;
|
SelectedTwoFactorProviderType = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Zxcvbn;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
|
@ -481,9 +482,28 @@ namespace Bit.Core.Services
|
||||||
await _storageService.RemoveAsync(Keys_History);
|
await _storageService.RemoveAsync(Keys_History);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<object> PasswordStrength(string password, List<string> userInputs = null)
|
public Result PasswordStrength(string password, List<string> userInputs = null)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (string.IsNullOrEmpty(password))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var globalUserInputs = new List<string>
|
||||||
|
{
|
||||||
|
"bitwarden",
|
||||||
|
"bit",
|
||||||
|
"warden"
|
||||||
|
};
|
||||||
|
if (userInputs != null && userInputs.Any())
|
||||||
|
{
|
||||||
|
globalUserInputs.AddRange(userInputs);
|
||||||
|
}
|
||||||
|
// Use a hash set to get rid of any duplicate user inputs
|
||||||
|
var hashSet = new HashSet<string>(globalUserInputs);
|
||||||
|
var finalUserInputs = new string[hashSet.Count];
|
||||||
|
hashSet.CopyTo(finalUserInputs);
|
||||||
|
var result = Zxcvbn.Zxcvbn.MatchPassword(password, finalUserInputs);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NormalizeOptions(PasswordGenerationOptions options,
|
public void NormalizeOptions(PasswordGenerationOptions options,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
@ -66,5 +67,156 @@ namespace Bit.Core.Services
|
||||||
await _storageService.RemoveAsync(string.Format(Keys_PoliciesPrefix, userId));
|
await _storageService.RemoveAsync(string.Format(Keys_PoliciesPrefix, userId));
|
||||||
_policyCache = null;
|
_policyCache = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MasterPasswordPolicyOptions> GetMasterPasswordPolicyOptions(
|
||||||
|
IEnumerable<Policy> policies = null)
|
||||||
|
{
|
||||||
|
MasterPasswordPolicyOptions enforcedOptions = null;
|
||||||
|
|
||||||
|
if (policies == null)
|
||||||
|
{
|
||||||
|
policies = await GetAll(PolicyType.MasterPassword);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
policies = policies.Where(p => p.Type == PolicyType.MasterPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (policies == null || !policies.Any())
|
||||||
|
{
|
||||||
|
return enforcedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var currentPolicy in policies)
|
||||||
|
{
|
||||||
|
if (!currentPolicy.Enabled || currentPolicy.Data == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enforcedOptions == null)
|
||||||
|
{
|
||||||
|
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
var minComplexity = GetPolicyInt(currentPolicy, "minComplexity");
|
||||||
|
if (minComplexity != null && (int)(long)minComplexity > enforcedOptions.MinComplexity)
|
||||||
|
{
|
||||||
|
enforcedOptions.MinComplexity = (int)(long)minComplexity;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minLength = GetPolicyInt(currentPolicy, "minLength");
|
||||||
|
if (minLength != null && (int)(long)minLength > enforcedOptions.MinLength)
|
||||||
|
{
|
||||||
|
enforcedOptions.MinLength = (int)(long)minLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requireUpper = GetPolicyBool(currentPolicy, "requireUpper");
|
||||||
|
if (requireUpper != null && (bool)requireUpper)
|
||||||
|
{
|
||||||
|
enforcedOptions.RequireUpper = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requireLower = GetPolicyBool(currentPolicy, "requireLower");
|
||||||
|
if (requireLower != null && (bool)requireLower)
|
||||||
|
{
|
||||||
|
enforcedOptions.RequireLower = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requireNumbers = GetPolicyBool(currentPolicy, "requireNumbers");
|
||||||
|
if (requireNumbers != null && (bool)requireNumbers)
|
||||||
|
{
|
||||||
|
enforcedOptions.RequireNumbers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requireSpecial = GetPolicyBool(currentPolicy, "requireSpecial");
|
||||||
|
if (requireSpecial != null && (bool)requireSpecial)
|
||||||
|
{
|
||||||
|
enforcedOptions.RequireSpecial = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enforcedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> EvaluateMasterPassword(int passwordStrength, string newPassword,
|
||||||
|
MasterPasswordPolicyOptions enforcedPolicyOptions)
|
||||||
|
{
|
||||||
|
if (enforcedPolicyOptions == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enforcedPolicyOptions.MinComplexity > 0 && enforcedPolicyOptions.MinComplexity > passwordStrength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enforcedPolicyOptions.MinLength > 0 && enforcedPolicyOptions.MinLength > newPassword.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enforcedPolicyOptions.RequireUpper && newPassword.ToLower() == newPassword)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enforcedPolicyOptions.RequireLower && newPassword.ToUpper() == newPassword)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enforcedPolicyOptions.RequireNumbers && !newPassword.Any(char.IsDigit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enforcedPolicyOptions.RequireSpecial && !Regex.IsMatch(newPassword, "^.*[!@#$%\\^&*].*$"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? GetPolicyInt(Policy policy, string key)
|
||||||
|
{
|
||||||
|
if (policy.Data.ContainsKey(key))
|
||||||
|
{
|
||||||
|
var value = policy.Data[key];
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
return (int)(long)value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool? GetPolicyBool(Policy policy, string key)
|
||||||
|
{
|
||||||
|
if (policy.Data.ContainsKey(key))
|
||||||
|
{
|
||||||
|
var value = policy.Data[key];
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
return (bool)value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPolicyString(Policy policy, string key)
|
||||||
|
{
|
||||||
|
if (policy.Data.ContainsKey(key))
|
||||||
|
{
|
||||||
|
var value = policy.Data[key];
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
return (string)value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ namespace Bit.iOS.Autofill
|
||||||
}
|
}
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchLoginFlow();
|
LaunchHomePage();
|
||||||
}
|
}
|
||||||
else if (await IsLocked())
|
else if (await IsLocked())
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@ namespace Bit.iOS.Autofill
|
||||||
InitAppIfNeeded();
|
InitAppIfNeeded();
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchLoginFlow();
|
LaunchHomePage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_context.CredentialIdentity = credentialIdentity;
|
_context.CredentialIdentity = credentialIdentity;
|
||||||
|
@ -107,7 +107,7 @@ namespace Bit.iOS.Autofill
|
||||||
_context.Configuring = true;
|
_context.Configuring = true;
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchLoginFlow();
|
LaunchHomePage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CheckLock(() => PerformSegue("setupSegue", this));
|
CheckLock(() => PerformSegue("setupSegue", this));
|
||||||
|
@ -294,17 +294,74 @@ namespace Bit.iOS.Autofill
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchLoginFlow()
|
private void LaunchHomePage()
|
||||||
{
|
{
|
||||||
var loginPage = new LoginPage();
|
var homePage = new HomePage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(homePage);
|
||||||
|
if (homePage.BindingContext is HomeViewModel vm)
|
||||||
|
{
|
||||||
|
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
||||||
|
vm.StartRegisterAction = () => DismissViewController(false, () => LaunchRegisterFlow());
|
||||||
|
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
|
vm.StartEnvironmentAction = () => DismissViewController(false, () => LaunchEnvironmentFlow());
|
||||||
|
vm.CloseAction = () => CompleteRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(homePage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchEnvironmentFlow()
|
||||||
|
{
|
||||||
|
var environmentPage = new EnvironmentPage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(environmentPage);
|
||||||
|
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(environmentPage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchRegisterFlow()
|
||||||
|
{
|
||||||
|
var registerPage = new RegisterPage(null);
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(registerPage);
|
||||||
|
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(registerPage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchLoginFlow(string email = null)
|
||||||
|
{
|
||||||
|
var loginPage = new LoginPage(email);
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
ThemeManager.SetTheme(false, app.Resources);
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
ThemeManager.ApplyResourcesToPage(loginPage);
|
ThemeManager.ApplyResourcesToPage(loginPage);
|
||||||
if (loginPage.BindingContext is LoginPageViewModel vm)
|
if (loginPage.BindingContext is LoginPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow());
|
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
||||||
vm.LoggedInAction = () => DismissLockAndContinue();
|
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => CompleteRequest();
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
vm.HideHintButton = true;
|
vm.HideHintButton = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,22 +371,68 @@ namespace Bit.iOS.Autofill
|
||||||
PresentViewController(loginController, true, null);
|
PresentViewController(loginController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchTwoFactorFlow()
|
private void LaunchLoginSsoFlow()
|
||||||
{
|
{
|
||||||
var twoFactorPage = new TwoFactorPage();
|
var loginPage = new LoginSsoPage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(loginPage);
|
||||||
|
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
||||||
|
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||||
|
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(loginPage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchTwoFactorFlow(bool authingWithSso)
|
||||||
|
{
|
||||||
|
var twoFactorPage = new TwoFactorPage(authingWithSso);
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
ThemeManager.SetTheme(false, app.Resources);
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
ThemeManager.ApplyResourcesToPage(twoFactorPage);
|
ThemeManager.ApplyResourcesToPage(twoFactorPage);
|
||||||
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.TwoFactorAction = () => DismissLockAndContinue();
|
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
|
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||||
|
if (authingWithSso)
|
||||||
|
{
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(twoFactorPage);
|
var navigationPage = new NavigationPage(twoFactorPage);
|
||||||
var twoFactorController = navigationPage.CreateViewController();
|
var twoFactorController = navigationPage.CreateViewController();
|
||||||
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
PresentViewController(twoFactorController, true, null);
|
PresentViewController(twoFactorController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LaunchSetPasswordFlow()
|
||||||
|
{
|
||||||
|
var setPasswordPage = new SetPasswordPage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(setPasswordPage);
|
||||||
|
if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.SetPasswordSuccessAction = () => DismissLockAndContinue();
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(setPasswordPage);
|
||||||
|
var setPasswordController = navigationPage.CreateViewController();
|
||||||
|
setPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(setPasswordController, true, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ namespace Bit.iOS.Extension
|
||||||
}
|
}
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchLoginFlow();
|
LaunchHomePage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (await IsLocked())
|
else if (await IsLocked())
|
||||||
|
@ -420,16 +420,73 @@ namespace Bit.iOS.Extension
|
||||||
return userService.IsAuthenticatedAsync();
|
return userService.IsAuthenticatedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchLoginFlow()
|
private void LaunchHomePage()
|
||||||
{
|
{
|
||||||
var loginPage = new LoginPage();
|
var homePage = new HomePage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(homePage);
|
||||||
|
if (homePage.BindingContext is HomeViewModel vm)
|
||||||
|
{
|
||||||
|
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
||||||
|
vm.StartRegisterAction = () => DismissViewController(false, () => LaunchRegisterFlow());
|
||||||
|
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
|
vm.StartEnvironmentAction = () => DismissViewController(false, () => LaunchEnvironmentFlow());
|
||||||
|
vm.CloseAction = () => CompleteRequest(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(homePage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchEnvironmentFlow()
|
||||||
|
{
|
||||||
|
var environmentPage = new EnvironmentPage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(environmentPage);
|
||||||
|
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(environmentPage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchRegisterFlow()
|
||||||
|
{
|
||||||
|
var registerPage = new RegisterPage(null);
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(registerPage);
|
||||||
|
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(registerPage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchLoginFlow(string email = null)
|
||||||
|
{
|
||||||
|
var loginPage = new LoginPage(email);
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
ThemeManager.SetTheme(false, app.Resources);
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
ThemeManager.ApplyResourcesToPage(loginPage);
|
ThemeManager.ApplyResourcesToPage(loginPage);
|
||||||
if (loginPage.BindingContext is LoginPageViewModel vm)
|
if (loginPage.BindingContext is LoginPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow());
|
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
||||||
vm.LoggedInAction = () => DismissLockAndContinue();
|
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => CompleteRequest(null, null);
|
vm.CloseAction = () => CompleteRequest(null, null);
|
||||||
vm.HideHintButton = true;
|
vm.HideHintButton = true;
|
||||||
}
|
}
|
||||||
|
@ -440,7 +497,27 @@ namespace Bit.iOS.Extension
|
||||||
PresentViewController(loginController, true, null);
|
PresentViewController(loginController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchTwoFactorFlow()
|
private void LaunchLoginSsoFlow()
|
||||||
|
{
|
||||||
|
var loginPage = new LoginSsoPage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(loginPage);
|
||||||
|
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
||||||
|
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||||
|
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(loginPage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchTwoFactorFlow(bool authingWithSso)
|
||||||
{
|
{
|
||||||
var twoFactorPage = new TwoFactorPage();
|
var twoFactorPage = new TwoFactorPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
@ -448,14 +525,40 @@ namespace Bit.iOS.Extension
|
||||||
ThemeManager.ApplyResourcesToPage(twoFactorPage);
|
ThemeManager.ApplyResourcesToPage(twoFactorPage);
|
||||||
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.TwoFactorAction = () => DismissLockAndContinue();
|
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
|
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||||
|
if (authingWithSso)
|
||||||
|
{
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(twoFactorPage);
|
var navigationPage = new NavigationPage(twoFactorPage);
|
||||||
var twoFactorController = navigationPage.CreateViewController();
|
var twoFactorController = navigationPage.CreateViewController();
|
||||||
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
PresentViewController(twoFactorController, true, null);
|
PresentViewController(twoFactorController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LaunchSetPasswordFlow()
|
||||||
|
{
|
||||||
|
var setPasswordPage = new SetPasswordPage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(false, app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(setPasswordPage);
|
||||||
|
if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.SetPasswordSuccessAction = () => DismissLockAndContinue();
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(setPasswordPage);
|
||||||
|
var setPasswordController = navigationPage.CreateViewController();
|
||||||
|
setPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(setPasswordController, true, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,6 +241,15 @@ namespace Bit.iOS
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
|
||||||
|
{
|
||||||
|
if (Xamarin.Essentials.Platform.OpenUrl(app, url, options))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return base.OpenUrl(app, url, options);
|
||||||
|
}
|
||||||
|
|
||||||
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
|
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
|
||||||
{
|
{
|
||||||
_pushHandler?.OnErrorReceived(error);
|
_pushHandler?.OnErrorReceived(error);
|
||||||
|
|
|
@ -156,7 +156,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.5.3.1</Version>
|
<Version>1.5.3.2</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||||
|
|
Loading…
Reference in a new issue