diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index 02ca313bb..de238c41f 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -32,6 +32,8 @@ namespace Bit.Droid private IBroadcasterService _broadcasterService; private PendingIntent _lockAlarmPendingIntent; private AppOptions _appOptions; + private Java.Util.Regex.Pattern _otpPattern = + Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$"); protected override void OnCreate(Bundle savedInstanceState) { @@ -70,9 +72,38 @@ namespace Bit.Droid { Finish(); } + else if(message.Command == "listenYubiKeyOTP") + { + ListenYubiKey((bool)message.Data); + } }); } + protected override void OnPause() + { + base.OnPause(); + ListenYubiKey(false); + } + + protected override void OnResume() + { + base.OnResume(); + if(_deviceActionService.SupportsNfc()) + { + try + { + _messagingService.Send("resumeYubiKey"); + } + catch { } + } + } + + protected override void OnNewIntent(Intent intent) + { + base.OnNewIntent(intent); + ParseYubiKey(intent.DataString); + } + public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults) { @@ -191,5 +222,19 @@ namespace Bit.Droid } return options; } + + private void ParseYubiKey(string data) + { + if(data == null) + { + return; + } + var otpMatch = _otpPattern.Matcher(data); + if(otpMatch.Matches()) + { + var otp = otpMatch.Group(1); + _messagingService.Send("gotYubiKeyOTP", otp); + } + } } } diff --git a/src/App/Pages/Accounts/TwoFactorPage.xaml.cs b/src/App/Pages/Accounts/TwoFactorPage.xaml.cs index ce072a95d..783ada8cc 100644 --- a/src/App/Pages/Accounts/TwoFactorPage.xaml.cs +++ b/src/App/Pages/Accounts/TwoFactorPage.xaml.cs @@ -1,20 +1,28 @@ using Bit.App.Controls; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; using System; using System.Threading.Tasks; +using Xamarin.Forms; namespace Bit.App.Pages { public partial class TwoFactorPage : BaseContentPage { + private readonly IBroadcasterService _broadcasterService; + private readonly IMessagingService _messagingService; + private TwoFactorPageViewModel _vm; public TwoFactorPage() { InitializeComponent(); + SetActivityIndicator(); + _broadcasterService = ServiceContainer.Resolve("broadcasterService"); + _messagingService = ServiceContainer.Resolve("messagingService"); _vm = BindingContext as TwoFactorPageViewModel; _vm.Page = this; DuoWebView = _duoWebView; - SetActivityIndicator(); } public HybridWebView DuoWebView { get; set; } @@ -34,9 +42,28 @@ namespace Bit.App.Pages ToolbarItems.Remove(_continueItem); } } + protected async override void OnAppearing() { base.OnAppearing(); + _broadcasterService.Subscribe(nameof(TwoFactorPage), async (message) => + { + if(message.Command == "gotYubiKeyOTP") + { + if(_vm.YubikeyMethod) + { + _vm.Token = (string)message.Data; + await _vm.SubmitAsync(); + } + } + else if(message.Command == "resumeYubiKey") + { + if(_vm.YubikeyMethod) + { + _messagingService.Send("listenYubiKeyOTP", true); + } + } + }); await LoadOnAppearedAsync(_scrollView, true, () => { _vm.Init(); @@ -44,6 +71,31 @@ namespace Bit.App.Pages }); } + protected override void OnDisappearing() + { + base.OnDisappearing(); + if(!_vm.YubikeyMethod) + { + _messagingService.Send("listenYubiKeyOTP", false); + _broadcasterService.Unsubscribe(nameof(TwoFactorPage)); + } + } + + protected override bool OnBackButtonPressed() + { + // ref: https://github.com/bitwarden/mobile/issues/350 + if(_vm.YubikeyMethod) + { + if(Device.RuntimePlatform == Device.Android) + { + return true; + } + _messagingService.Send("listenYubiKeyOTP", false); + _broadcasterService.Unsubscribe(nameof(TwoFactorPage)); + } + return base.OnBackButtonPressed(); + } + private async void Continue_Clicked(object sender, EventArgs e) { if(DoOnce()) diff --git a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs index cfed16d58..ebbab4057 100644 --- a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs +++ b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs @@ -21,6 +21,8 @@ namespace Bit.App.Pages private readonly IApiService _apiService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IEnvironmentService _environmentService; + private readonly IMessagingService _messagingService; + private readonly IBroadcasterService _broadcasterService; private bool _u2fSupported = false; private TwoFactorProviderType? _selectedProviderType; @@ -36,6 +38,8 @@ namespace Bit.App.Pages _apiService = ServiceContainer.Resolve("apiService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _environmentService = ServiceContainer.Resolve("environmentService"); + _messagingService = ServiceContainer.Resolve("messagingService"); + _broadcasterService = ServiceContainer.Resolve("broadcasterService"); PageTitle = AppResources.TwoStepLogin; } @@ -122,9 +126,11 @@ namespace Bit.App.Pages case TwoFactorProviderType.U2f: // TODO break; + case TwoFactorProviderType.YubiKey: + _messagingService.Send("listenYubiKeyOTP", true); + break; case TwoFactorProviderType.Duo: case TwoFactorProviderType.OrganizationDuo: - page.RemoveContinueButton(); 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}"; @@ -135,7 +141,6 @@ namespace Bit.App.Pages }); break; case TwoFactorProviderType.Email: - page.AddContinueButton(); TwoFactorEmail = providerData["Email"] as string; if(_authService.TwoFactorProvidersData.Count > 1) { @@ -143,9 +148,21 @@ namespace Bit.App.Pages } break; default: - page.AddContinueButton(); break; } + + if(!YubikeyMethod) + { + _messagingService.Send("listenYubiKeyOTP", false); + } + if(DuoMethod) + { + page.RemoveContinueButton(); + } + else + { + page.AddContinueButton(); + } } public async Task SubmitAsync() @@ -172,6 +189,8 @@ namespace Bit.App.Pages await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember); await _deviceActionService.HideLoadingAsync(); var task = Task.Run(() => _syncService.FullSyncAsync(true)); + _messagingService.Send("listenYubiKeyOTP", false); + _broadcasterService.Unsubscribe(nameof(TwoFactorPage)); Application.Current.MainPage = new TabsPage(); } catch(ApiException e) @@ -202,7 +221,7 @@ namespace Bit.App.Pages public async Task SendEmailAsync(bool showLoading, bool doToast) { - if(SelectedProviderType != TwoFactorProviderType.Email) + if(!EmailMethod) { return false; } diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs index 59b028041..3df964d9e 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs @@ -47,7 +47,6 @@ namespace Bit.App.Pages protected async override void OnAppearing() { base.OnAppearing(); - // await _syncService.FullSyncAsync(true); _broadcasterService.Subscribe(_pageName, async (message) => { if(message.Command == "syncCompleted")