bitwarden-android/src/App/Pages/Accounts/TwoFactorPageViewModel.cs

296 lines
12 KiB
C#
Raw Normal View History

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