2019-05-23 21:19:45 -04:00
|
|
|
|
using Bit.App.Abstractions;
|
|
|
|
|
using Bit.App.Resources;
|
2019-07-02 08:05:34 -04:00
|
|
|
|
using Bit.Core;
|
2019-05-23 21:19:45 -04:00
|
|
|
|
using Bit.Core.Abstractions;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
using Bit.Core.Enums;
|
2019-05-23 21:19:45 -04:00
|
|
|
|
using Bit.Core.Exceptions;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
using Bit.Core.Models.Request;
|
2019-05-23 21:19:45 -04:00
|
|
|
|
using Bit.Core.Utilities;
|
2020-04-13 11:32:23 -05:00
|
|
|
|
using System;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
using System.Linq;
|
2019-05-27 11:57:10 -04:00
|
|
|
|
using System.Net;
|
2019-05-23 21:19:45 -04:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Xamarin.Forms;
|
|
|
|
|
|
|
|
|
|
namespace Bit.App.Pages
|
|
|
|
|
{
|
|
|
|
|
public class TwoFactorPageViewModel : BaseViewModel
|
|
|
|
|
{
|
|
|
|
|
private readonly IDeviceActionService _deviceActionService;
|
|
|
|
|
private readonly IAuthService _authService;
|
|
|
|
|
private readonly ISyncService _syncService;
|
|
|
|
|
private readonly IStorageService _storageService;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
private readonly IApiService _apiService;
|
|
|
|
|
private readonly IPlatformUtilsService _platformUtilsService;
|
2019-05-27 11:57:10 -04:00
|
|
|
|
private readonly IEnvironmentService _environmentService;
|
2019-05-28 09:54:08 -04:00
|
|
|
|
private readonly IMessagingService _messagingService;
|
|
|
|
|
private readonly IBroadcasterService _broadcasterService;
|
2019-07-02 08:05:34 -04:00
|
|
|
|
private readonly IStateService _stateService;
|
2019-05-23 21:19:45 -04:00
|
|
|
|
|
2019-05-27 10:28:38 -04:00
|
|
|
|
private bool _u2fSupported = false;
|
|
|
|
|
private TwoFactorProviderType? _selectedProviderType;
|
2019-05-28 10:12:51 -04:00
|
|
|
|
private string _totpInstruction;
|
2019-05-27 11:57:10 -04:00
|
|
|
|
private string _webVaultUrl = "https://vault.bitwarden.com";
|
2020-09-03 12:30:40 -04:00
|
|
|
|
private bool _authingWithSso = false;
|
2019-05-23 21:19:45 -04:00
|
|
|
|
|
|
|
|
|
public TwoFactorPageViewModel()
|
|
|
|
|
{
|
|
|
|
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
|
|
|
|
_authService = ServiceContainer.Resolve<IAuthService>("authService");
|
|
|
|
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
|
|
|
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
2019-05-27 10:28:38 -04:00
|
|
|
|
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
|
|
|
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
2019-05-27 11:57:10 -04:00
|
|
|
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
2019-05-28 09:54:08 -04:00
|
|
|
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
|
|
|
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
2019-07-02 08:05:34 -04:00
|
|
|
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
2019-05-28 09:04:20 -04:00
|
|
|
|
|
|
|
|
|
PageTitle = AppResources.TwoStepLogin;
|
2019-05-31 12:13:14 -04:00
|
|
|
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
2019-05-23 21:19:45 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 10:12:51 -04:00
|
|
|
|
public string TotpInstruction
|
2019-05-23 21:19:45 -04:00
|
|
|
|
{
|
2019-05-28 10:12:51 -04:00
|
|
|
|
get => _totpInstruction;
|
|
|
|
|
set => SetProperty(ref _totpInstruction, value);
|
2019-05-23 21:19:45 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-27 10:28:38 -04:00
|
|
|
|
public bool Remember { get; set; }
|
|
|
|
|
|
|
|
|
|
public string Token { get; set; }
|
|
|
|
|
|
2019-05-28 09:04:20 -04:00
|
|
|
|
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
|
|
|
|
|
SelectedProviderType == TwoFactorProviderType.OrganizationDuo;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
|
|
|
|
|
public bool YubikeyMethod => SelectedProviderType == TwoFactorProviderType.YubiKey;
|
|
|
|
|
|
|
|
|
|
public bool AuthenticatorMethod => SelectedProviderType == TwoFactorProviderType.Authenticator;
|
|
|
|
|
|
|
|
|
|
public bool EmailMethod => SelectedProviderType == TwoFactorProviderType.Email;
|
|
|
|
|
|
2019-05-27 11:57:10 -04:00
|
|
|
|
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
|
|
|
|
|
|
2019-07-06 21:59:13 -04:00
|
|
|
|
public bool ShowTryAgain => YubikeyMethod && Device.RuntimePlatform == Device.iOS;
|
|
|
|
|
|
2019-05-27 11:57:10 -04:00
|
|
|
|
public string YubikeyInstruction => Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
|
|
|
|
|
AppResources.YubiKeyInstruction;
|
|
|
|
|
|
2019-05-27 10:28:38 -04:00
|
|
|
|
public TwoFactorProviderType? SelectedProviderType
|
2019-05-23 21:19:45 -04:00
|
|
|
|
{
|
2019-05-27 10:28:38 -04:00
|
|
|
|
get => _selectedProviderType;
|
|
|
|
|
set => SetProperty(ref _selectedProviderType, value, additionalPropertyNames: new string[]
|
|
|
|
|
{
|
|
|
|
|
nameof(EmailMethod),
|
|
|
|
|
nameof(DuoMethod),
|
|
|
|
|
nameof(YubikeyMethod),
|
2019-05-27 11:57:10 -04:00
|
|
|
|
nameof(AuthenticatorMethod),
|
|
|
|
|
nameof(TotpMethod),
|
2019-07-06 21:59:13 -04:00
|
|
|
|
nameof(ShowTryAgain),
|
2019-05-27 10:28:38 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
2019-05-31 12:13:14 -04:00
|
|
|
|
public Command SubmitCommand { get; }
|
2020-09-03 12:30:40 -04:00
|
|
|
|
public Action TwoFactorAuthSuccessAction { get; set; }
|
|
|
|
|
public Action StartSetPasswordAction { get; set; }
|
2020-05-29 11:26:36 -05:00
|
|
|
|
public Action CloseAction { get; set; }
|
2019-05-27 10:28:38 -04:00
|
|
|
|
|
|
|
|
|
public void Init()
|
|
|
|
|
{
|
2020-09-03 12:30:40 -04:00
|
|
|
|
if ((!_authService.AuthingWithSso() && !_authService.AuthingWithPassword()) ||
|
2019-05-27 10:28:38 -04:00
|
|
|
|
_authService.TwoFactorProvidersData == null)
|
|
|
|
|
{
|
|
|
|
|
// TODO: dismiss modal?
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-03 12:30:40 -04:00
|
|
|
|
_authingWithSso = _authService.AuthingWithSso();
|
|
|
|
|
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
|
2019-05-27 11:57:10 -04:00
|
|
|
|
{
|
|
|
|
|
_webVaultUrl = _environmentService.BaseUrl;
|
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
else if (!string.IsNullOrWhiteSpace(_environmentService.WebVaultUrl))
|
2019-05-27 11:57:10 -04:00
|
|
|
|
{
|
|
|
|
|
_webVaultUrl = _environmentService.WebVaultUrl;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-27 10:28:38 -04:00
|
|
|
|
// TODO: init U2F
|
|
|
|
|
_u2fSupported = false;
|
2019-05-23 21:19:45 -04:00
|
|
|
|
|
2019-05-27 11:57:10 -04:00
|
|
|
|
SelectedProviderType = _authService.GetDefaultTwoFactorProvider(_u2fSupported);
|
2019-05-27 10:28:38 -04:00
|
|
|
|
Load();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Load()
|
|
|
|
|
{
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (SelectedProviderType == null)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
PageTitle = AppResources.LoginUnavailable;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-05-28 09:12:05 -04:00
|
|
|
|
var page = Page as TwoFactorPage;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
PageTitle = _authService.TwoFactorProviders[SelectedProviderType.Value].Name;
|
|
|
|
|
var providerData = _authService.TwoFactorProvidersData[SelectedProviderType.Value];
|
2020-03-28 09:16:28 -04:00
|
|
|
|
switch (SelectedProviderType.Value)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
case TwoFactorProviderType.U2f:
|
|
|
|
|
// TODO
|
|
|
|
|
break;
|
2019-05-28 09:54:08 -04:00
|
|
|
|
case TwoFactorProviderType.YubiKey:
|
|
|
|
|
_messagingService.Send("listenYubiKeyOTP", true);
|
|
|
|
|
break;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
case TwoFactorProviderType.Duo:
|
|
|
|
|
case TwoFactorProviderType.OrganizationDuo:
|
2019-05-27 11:57:10 -04:00
|
|
|
|
var host = WebUtility.UrlEncode(providerData["Host"] as string);
|
|
|
|
|
var req = WebUtility.UrlEncode(providerData["Signature"] as string);
|
|
|
|
|
page.DuoWebView.Uri = $"{_webVaultUrl}/duo-connector.html?host={host}&request={req}";
|
2020-02-24 14:57:36 +01:00
|
|
|
|
page.DuoWebView.RegisterAction(sig =>
|
2019-05-27 11:57:10 -04:00
|
|
|
|
{
|
|
|
|
|
Token = sig;
|
2020-02-24 08:58:15 -05:00
|
|
|
|
Device.BeginInvokeOnMainThread(async () => await SubmitAsync());
|
2019-05-27 11:57:10 -04:00
|
|
|
|
});
|
2019-05-27 10:28:38 -04:00
|
|
|
|
break;
|
|
|
|
|
case TwoFactorProviderType.Email:
|
2019-05-28 10:12:51 -04:00
|
|
|
|
TotpInstruction = string.Format(AppResources.EnterVerificationCodeEmail,
|
|
|
|
|
providerData["Email"] as string);
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (_authService.TwoFactorProvidersData.Count > 1)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
var emailTask = Task.Run(() => SendEmailAsync(false, false));
|
|
|
|
|
}
|
|
|
|
|
break;
|
2019-05-28 10:12:51 -04:00
|
|
|
|
case TwoFactorProviderType.Authenticator:
|
|
|
|
|
TotpInstruction = AppResources.EnterVerificationCodeApp;
|
|
|
|
|
break;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-28 09:54:08 -04:00
|
|
|
|
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (!YubikeyMethod)
|
2019-05-28 09:54:08 -04:00
|
|
|
|
{
|
|
|
|
|
_messagingService.Send("listenYubiKeyOTP", false);
|
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (SelectedProviderType == null || DuoMethod)
|
2019-05-28 09:54:08 -04:00
|
|
|
|
{
|
|
|
|
|
page.RemoveContinueButton();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
page.AddContinueButton();
|
|
|
|
|
}
|
2019-05-23 21:19:45 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task SubmitAsync()
|
|
|
|
|
{
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (SelectedProviderType == null)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
2019-06-03 22:43:52 -04:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
|
|
|
|
AppResources.InternetConnectionRequiredTitle);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (string.IsNullOrWhiteSpace(Token))
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(
|
|
|
|
|
string.Format(AppResources.ValidationFieldRequired, AppResources.VerificationCode),
|
|
|
|
|
AppResources.AnErrorHasOccurred);
|
2019-06-03 22:43:52 -04:00
|
|
|
|
return;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (SelectedProviderType == TwoFactorProviderType.Email ||
|
2019-05-27 10:28:38 -04:00
|
|
|
|
SelectedProviderType == TwoFactorProviderType.Authenticator)
|
|
|
|
|
{
|
|
|
|
|
Token = Token.Replace(" ", string.Empty).Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2019-06-01 00:13:36 -04:00
|
|
|
|
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
2020-09-03 12:30:40 -04:00
|
|
|
|
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
|
2019-05-27 10:28:38 -04:00
|
|
|
|
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
2020-09-03 12:30:40 -04:00
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
2019-05-28 09:54:08 -04:00
|
|
|
|
_messagingService.Send("listenYubiKeyOTP", false);
|
|
|
|
|
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
2020-09-03 12:30:40 -04:00
|
|
|
|
if (_authingWithSso && result.ResetMasterPassword)
|
|
|
|
|
{
|
|
|
|
|
StartSetPasswordAction?.Invoke();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
|
|
|
|
|
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
|
|
|
|
|
TwoFactorAuthSuccessAction?.Invoke();
|
|
|
|
|
}
|
2019-05-27 10:28:38 -04:00
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
catch (ApiException e)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (e?.Error != null)
|
2019-10-22 16:37:40 -04:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
|
|
|
|
|
AppResources.AnErrorHasOccurred);
|
|
|
|
|
}
|
2019-05-27 10:28:38 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-23 21:19:45 -04:00
|
|
|
|
|
2019-05-27 10:28:38 -04:00
|
|
|
|
public async Task AnotherMethodAsync()
|
|
|
|
|
{
|
|
|
|
|
var supportedProviders = _authService.GetSupportedTwoFactorProviders();
|
|
|
|
|
var options = supportedProviders.Select(p => p.Name).ToList();
|
|
|
|
|
options.Add(AppResources.RecoveryCodeTitle);
|
2020-05-29 11:26:36 -05:00
|
|
|
|
var method = await _deviceActionService.DisplayActionSheetAsync(AppResources.TwoStepLoginOptions,
|
|
|
|
|
AppResources.Cancel, null, options.ToArray());
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (method == AppResources.RecoveryCodeTitle)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
_platformUtilsService.LaunchUri("https://help.bitwarden.com/article/lost-two-step-device/");
|
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
else if (method != AppResources.Cancel)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
2019-05-28 10:12:51 -04:00
|
|
|
|
var selected = supportedProviders.FirstOrDefault(p => p.Name == method)?.Type;
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (selected == SelectedProviderType)
|
2019-05-28 10:12:51 -04:00
|
|
|
|
{
|
|
|
|
|
// Nothing changed
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
SelectedProviderType = selected;
|
2019-05-27 10:28:38 -04:00
|
|
|
|
Load();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<bool> SendEmailAsync(bool showLoading, bool doToast)
|
|
|
|
|
{
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (!EmailMethod)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
2019-06-03 22:43:52 -04:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
|
|
|
|
AppResources.InternetConnectionRequiredTitle);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-05-27 10:28:38 -04:00
|
|
|
|
try
|
|
|
|
|
{
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (showLoading)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.ShowLoadingAsync(AppResources.Submitting);
|
|
|
|
|
}
|
|
|
|
|
var request = new TwoFactorEmailRequest
|
|
|
|
|
{
|
|
|
|
|
Email = _authService.Email,
|
|
|
|
|
MasterPasswordHash = _authService.MasterPasswordHash
|
|
|
|
|
};
|
|
|
|
|
await _apiService.PostTwoFactorEmailAsync(request);
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (showLoading)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (doToast)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
_platformUtilsService.ShowToast("success", null, AppResources.VerificationEmailSent);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-03-28 09:16:28 -04:00
|
|
|
|
catch (ApiException)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
2020-03-28 09:16:28 -04:00
|
|
|
|
if (showLoading)
|
2019-05-27 10:28:38 -04:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
|
|
|
|
}
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-05-23 21:19:45 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|