read yubikey and log in
@ -860,6 +860,18 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Resources\drawable-xxxhdpi\share_tools.png" />
|
<AndroidResource Include="Resources\drawable-xxxhdpi\share_tools.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AndroidResource Include="Resources\drawable\yubikey.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AndroidResource Include="Resources\drawable-hdpi\yubikey.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AndroidResource Include="Resources\drawable-xhdpi\yubikey.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AndroidResource Include="Resources\drawable-xxhdpi\yubikey.png" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||||
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
|
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
|
||||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
@ -51,7 +51,7 @@ namespace Bit.Android
|
|||||||
|
|
||||||
Console.WriteLine("A OnCreate");
|
Console.WriteLine("A OnCreate");
|
||||||
Window.SetSoftInputMode(SoftInput.StateHidden);
|
Window.SetSoftInputMode(SoftInput.StateHidden);
|
||||||
Window.AddFlags(WindowManagerFlags.Secure);
|
//Window.AddFlags(WindowManagerFlags.Secure);
|
||||||
|
|
||||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||||
var authService = Resolver.Resolve<IAuthService>();
|
var authService = Resolver.Resolve<IAuthService>();
|
||||||
@ -105,10 +105,10 @@ namespace Bit.Android
|
|||||||
LaunchApp(args);
|
LaunchApp(args);
|
||||||
});
|
});
|
||||||
|
|
||||||
MessagingCenter.Subscribe<Xamarin.Forms.Application>(
|
MessagingCenter.Subscribe<Xamarin.Forms.Application, bool>(
|
||||||
Xamarin.Forms.Application.Current, "ListenYubiKeyOTP", (sender) =>
|
Xamarin.Forms.Application.Current, "ListenYubiKeyOTP", (sender, listen) =>
|
||||||
{
|
{
|
||||||
ListenYubiKey();
|
ListenYubiKey(listen);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +142,7 @@ namespace Bit.Android
|
|||||||
{
|
{
|
||||||
Console.WriteLine("A OnPause");
|
Console.WriteLine("A OnPause");
|
||||||
base.OnPause();
|
base.OnPause();
|
||||||
|
ListenYubiKey(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
@ -176,6 +177,18 @@ namespace Bit.Android
|
|||||||
// workaround for app compat bug
|
// workaround for app compat bug
|
||||||
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
|
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
|
||||||
Task.Delay(10).Wait();
|
Task.Delay(10).Wait();
|
||||||
|
|
||||||
|
if(Utilities.NfcEnabled())
|
||||||
|
{
|
||||||
|
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ResumeYubiKey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnNewIntent(Intent intent)
|
||||||
|
{
|
||||||
|
base.OnNewIntent(intent);
|
||||||
|
Console.WriteLine("A OnNewIntent");
|
||||||
|
ParseYubiKey(intent.DataString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RateApp()
|
public void RateApp()
|
||||||
@ -237,35 +250,47 @@ namespace Bit.Android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ListenYubiKey()
|
private void ListenYubiKey(bool listen)
|
||||||
{
|
{
|
||||||
var intent = new Intent(this, Class);
|
if(!Utilities.NfcEnabled())
|
||||||
intent.AddFlags(ActivityFlags.SingleTop);
|
|
||||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
|
||||||
|
|
||||||
// register for all NDEF tags starting with http och https
|
|
||||||
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
|
||||||
ndef.AddDataScheme("http");
|
|
||||||
ndef.AddDataScheme("https");
|
|
||||||
|
|
||||||
// register for foreground dispatch so we'll receive tags according to our intent filters
|
|
||||||
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
|
||||||
adapter.EnableForegroundDispatch(this, pendingIntent, new IntentFilter[] { ndef }, null);
|
|
||||||
|
|
||||||
var data = Intent.DataString;
|
|
||||||
if(data != null)
|
|
||||||
{
|
{
|
||||||
var otpMatch = _otpPattern.Matcher(data);
|
return;
|
||||||
if(otpMatch.Matches())
|
}
|
||||||
{
|
|
||||||
var otp = otpMatch.Group(1);
|
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
||||||
Console.WriteLine("Got OTP: " + otp);
|
if(listen)
|
||||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "GotYubiKeyOTP", otp);
|
{
|
||||||
}
|
var intent = new Intent(this, Class);
|
||||||
else
|
intent.AddFlags(ActivityFlags.SingleTop);
|
||||||
{
|
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
||||||
Console.WriteLine("Data from ndef didn't match, it was: " + data);
|
|
||||||
}
|
// register for all NDEF tags starting with http och https
|
||||||
|
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
||||||
|
ndef.AddDataScheme("http");
|
||||||
|
ndef.AddDataScheme("https");
|
||||||
|
var filters = new IntentFilter[] { ndef };
|
||||||
|
|
||||||
|
// register for foreground dispatch so we'll receive tags according to our intent filters
|
||||||
|
adapter.EnableForegroundDispatch(this, pendingIntent, filters, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adapter.DisableForegroundDispatch(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseYubiKey(string data)
|
||||||
|
{
|
||||||
|
if(data == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var otpMatch = _otpPattern.Matcher(data);
|
||||||
|
if(otpMatch.Matches())
|
||||||
|
{
|
||||||
|
var otp = otpMatch.Group(1);
|
||||||
|
MessagingCenter.Send(Xamarin.Forms.Application.Current, "GotYubiKeyOTP", otp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
src/Android/Resources/Resource.Designer.cs
generated
@ -2744,8 +2744,8 @@ namespace Bit.Android
|
|||||||
// aapt resource value: 0x7f0200e4
|
// aapt resource value: 0x7f0200e4
|
||||||
public const int notification_sm = 2130837732;
|
public const int notification_sm = 2130837732;
|
||||||
|
|
||||||
// aapt resource value: 0x7f0200f3
|
// aapt resource value: 0x7f0200f4
|
||||||
public const int notification_template_icon_bg = 2130837747;
|
public const int notification_template_icon_bg = 2130837748;
|
||||||
|
|
||||||
// aapt resource value: 0x7f0200e5
|
// aapt resource value: 0x7f0200e5
|
||||||
public const int plus = 2130837733;
|
public const int plus = 2130837733;
|
||||||
@ -2789,6 +2789,9 @@ namespace Bit.Android
|
|||||||
// aapt resource value: 0x7f0200f2
|
// aapt resource value: 0x7f0200f2
|
||||||
public const int user = 2130837746;
|
public const int user = 2130837746;
|
||||||
|
|
||||||
|
// aapt resource value: 0x7f0200f3
|
||||||
|
public const int yubikey = 2130837747;
|
||||||
|
|
||||||
static Drawable()
|
static Drawable()
|
||||||
{
|
{
|
||||||
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
|
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
|
||||||
|
BIN
src/Android/Resources/drawable-hdpi/yubikey.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
src/Android/Resources/drawable-xhdpi/yubikey.png
Normal file
After Width: | Height: | Size: 187 KiB |
BIN
src/Android/Resources/drawable-xxhdpi/yubikey.png
Normal file
After Width: | Height: | Size: 381 KiB |
BIN
src/Android/Resources/drawable/yubikey.png
Normal file
After Width: | Height: | Size: 51 KiB |
@ -1,5 +1,4 @@
|
|||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Nfc;
|
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
|
||||||
@ -42,14 +41,6 @@ namespace Bit.Android.Services
|
|||||||
return 1f;
|
return 1f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public bool NfcEnabled
|
public bool NfcEnabled => Utilities.NfcEnabled();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var manager = (NfcManager)Application.Context.GetSystemService("nfc");
|
|
||||||
var adapter = manager.DefaultAdapter;
|
|
||||||
return adapter != null && adapter.IsEnabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,19 @@ using Android.App;
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Java.Security;
|
using Java.Security;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Android.Nfc;
|
||||||
|
|
||||||
namespace Bit.Android
|
namespace Bit.Android
|
||||||
{
|
{
|
||||||
public static class Utilities
|
public static class Utilities
|
||||||
{
|
{
|
||||||
|
public static bool NfcEnabled()
|
||||||
|
{
|
||||||
|
var manager = (NfcManager)Application.Context.GetSystemService("nfc");
|
||||||
|
var adapter = manager.DefaultAdapter;
|
||||||
|
return adapter != null && adapter.IsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public static void SendCrashEmail(Exception e, bool includeSecurityProviders = true)
|
public static void SendCrashEmail(Exception e, bool includeSecurityProviders = true)
|
||||||
{
|
{
|
||||||
SendCrashEmail(e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);
|
SendCrashEmail(e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);
|
||||||
|
@ -13,14 +13,17 @@ using Bit.App.Enums;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using FFImageLoading.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class LoginTwoFactorPage : ExtendedContentPage
|
public class LoginTwoFactorPage : ExtendedContentPage
|
||||||
{
|
{
|
||||||
|
private DateTime? _lastAction;
|
||||||
private IAuthService _authService;
|
private IAuthService _authService;
|
||||||
private IUserDialogs _userDialogs;
|
private IUserDialogs _userDialogs;
|
||||||
private ISyncService _syncService;
|
private ISyncService _syncService;
|
||||||
|
private IDeviceInfoService _deviceInfoService;
|
||||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||||
private IPushNotification _pushNotification;
|
private IPushNotification _pushNotification;
|
||||||
private readonly string _email;
|
private readonly string _email;
|
||||||
@ -33,6 +36,8 @@ namespace Bit.App.Pages
|
|||||||
public LoginTwoFactorPage(string email, FullLoginResult result, TwoFactorProviderType? type = null)
|
public LoginTwoFactorPage(string email, FullLoginResult result, TwoFactorProviderType? type = null)
|
||||||
: base(updateActivity: false)
|
: base(updateActivity: false)
|
||||||
{
|
{
|
||||||
|
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||||
|
|
||||||
_email = email;
|
_email = email;
|
||||||
_result = result;
|
_result = result;
|
||||||
_masterPasswordHash = result.MasterPasswordHash;
|
_masterPasswordHash = result.MasterPasswordHash;
|
||||||
@ -47,26 +52,54 @@ namespace Bit.App.Pages
|
|||||||
_pushNotification = Resolver.Resolve<IPushNotification>();
|
_pushNotification = Resolver.Resolve<IPushNotification>();
|
||||||
|
|
||||||
Init();
|
Init();
|
||||||
|
|
||||||
|
SubscribeYubiKey(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormEntryCell TokenCell { get; set; }
|
public FormEntryCell TokenCell { get; set; }
|
||||||
public ExtendedSwitchCell RememberCell { get; set; }
|
public ExtendedSwitchCell RememberCell { get; set; }
|
||||||
public HybridWebView WebView { get; set; }
|
|
||||||
|
|
||||||
private void Init()
|
private void Init()
|
||||||
{
|
{
|
||||||
var scrollView = new ScrollView();
|
var scrollView = new ScrollView();
|
||||||
|
|
||||||
|
var anotherMethodButton = new ExtendedButton
|
||||||
|
{
|
||||||
|
Text = "Use another two-step login method",
|
||||||
|
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
||||||
|
Margin = new Thickness(15, 0, 15, 25),
|
||||||
|
Command = new Command(() => AnotherMethodAsync()),
|
||||||
|
Uppercase = false,
|
||||||
|
BackgroundColor = Color.Transparent
|
||||||
|
};
|
||||||
|
|
||||||
|
var instruction = new Label
|
||||||
|
{
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap,
|
||||||
|
Margin = new Thickness(15),
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center
|
||||||
|
};
|
||||||
|
|
||||||
|
RememberCell = new ExtendedSwitchCell
|
||||||
|
{
|
||||||
|
Text = "Remember me",
|
||||||
|
On = false
|
||||||
|
};
|
||||||
|
|
||||||
if(!_providerType.HasValue)
|
if(!_providerType.HasValue)
|
||||||
{
|
{
|
||||||
var noProviderLabel = new Label
|
instruction.Text = "No providers available.";
|
||||||
|
|
||||||
|
var layout = new StackLayout
|
||||||
{
|
{
|
||||||
Text = "No provider.",
|
Children = { instruction, anotherMethodButton },
|
||||||
LineBreakMode = LineBreakMode.WordWrap,
|
Spacing = 0
|
||||||
Margin = new Thickness(15),
|
|
||||||
HorizontalTextAlignment = TextAlignment.Center
|
|
||||||
};
|
};
|
||||||
scrollView.Content = noProviderLabel;
|
|
||||||
|
scrollView.Content = layout;
|
||||||
|
|
||||||
|
Title = "Login Unavailable";
|
||||||
|
Content = scrollView;
|
||||||
}
|
}
|
||||||
else if(_providerType.Value == TwoFactorProviderType.Authenticator ||
|
else if(_providerType.Value == TwoFactorProviderType.Authenticator ||
|
||||||
_providerType.Value == TwoFactorProviderType.Email)
|
_providerType.Value == TwoFactorProviderType.Email)
|
||||||
@ -88,54 +121,12 @@ namespace Bit.App.Pages
|
|||||||
TokenCell.Entry.Keyboard = Keyboard.Numeric;
|
TokenCell.Entry.Keyboard = Keyboard.Numeric;
|
||||||
TokenCell.Entry.ReturnType = ReturnType.Go;
|
TokenCell.Entry.ReturnType = ReturnType.Go;
|
||||||
|
|
||||||
RememberCell = new ExtendedSwitchCell
|
var table = new TwoFactorTable(
|
||||||
{
|
new TableSection(" ")
|
||||||
Text = "Remember me",
|
|
||||||
On = false
|
|
||||||
};
|
|
||||||
|
|
||||||
var table = new ExtendedTableView
|
|
||||||
{
|
|
||||||
Intent = TableIntent.Settings,
|
|
||||||
EnableScrolling = false,
|
|
||||||
HasUnevenRows = true,
|
|
||||||
EnableSelection = true,
|
|
||||||
NoFooter = true,
|
|
||||||
NoHeader = true,
|
|
||||||
VerticalOptions = LayoutOptions.Start,
|
|
||||||
Root = new TableRoot
|
|
||||||
{
|
{
|
||||||
new TableSection(" ")
|
TokenCell,
|
||||||
{
|
RememberCell
|
||||||
TokenCell,
|
});
|
||||||
RememberCell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if(Device.RuntimePlatform == Device.iOS)
|
|
||||||
{
|
|
||||||
table.RowHeight = -1;
|
|
||||||
table.EstimatedRowHeight = 70;
|
|
||||||
}
|
|
||||||
|
|
||||||
var instruction = new Label
|
|
||||||
{
|
|
||||||
Text = AppResources.EnterVerificationCode,
|
|
||||||
LineBreakMode = LineBreakMode.WordWrap,
|
|
||||||
Margin = new Thickness(15),
|
|
||||||
HorizontalTextAlignment = TextAlignment.Center
|
|
||||||
};
|
|
||||||
|
|
||||||
var anotherMethodButton = new ExtendedButton
|
|
||||||
{
|
|
||||||
Text = "Use another two-step login method",
|
|
||||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
|
|
||||||
Margin = new Thickness(15, 0, 15, 25),
|
|
||||||
Command = new Command(() => AnotherMethodAsync()),
|
|
||||||
Uppercase = false,
|
|
||||||
BackgroundColor = Color.Transparent
|
|
||||||
};
|
|
||||||
|
|
||||||
var layout = new StackLayout
|
var layout = new StackLayout
|
||||||
{
|
{
|
||||||
@ -189,25 +180,57 @@ namespace Bit.App.Pages
|
|||||||
var host = WebUtility.UrlEncode(duoParams["Host"].ToString());
|
var host = WebUtility.UrlEncode(duoParams["Host"].ToString());
|
||||||
var req = WebUtility.UrlEncode(duoParams["Signature"].ToString());
|
var req = WebUtility.UrlEncode(duoParams["Signature"].ToString());
|
||||||
|
|
||||||
WebView = new HybridWebView
|
var webView = new HybridWebView
|
||||||
{
|
{
|
||||||
Uri = $"http://192.168.1.6:4001/duo-mobile.html?host={host}&request={req}",
|
Uri = $"http://192.168.1.6:4001/duo-mobile.html?host={host}&request={req}",
|
||||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||||
VerticalOptions = LayoutOptions.FillAndExpand
|
VerticalOptions = LayoutOptions.FillAndExpand
|
||||||
};
|
};
|
||||||
WebView.RegisterAction(async (sig) =>
|
webView.RegisterAction(async (sig) =>
|
||||||
{
|
{
|
||||||
await LogInAsync(sig, false);
|
await LogInAsync(sig, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
Title = "Duo";
|
Title = "Duo";
|
||||||
Content = WebView;
|
Content = webView;
|
||||||
|
}
|
||||||
|
else if(_providerType == TwoFactorProviderType.YubiKey)
|
||||||
|
{
|
||||||
|
instruction.Text = "Hold your YubiKey NEO against the back of the device to continue.";
|
||||||
|
|
||||||
|
var image = new CachedImage
|
||||||
|
{
|
||||||
|
Source = "yubikey",
|
||||||
|
VerticalOptions = LayoutOptions.Start,
|
||||||
|
HorizontalOptions = LayoutOptions.Center,
|
||||||
|
WidthRequest = 266,
|
||||||
|
HeightRequest = 160,
|
||||||
|
Margin = new Thickness(0, 0, 0, 25)
|
||||||
|
};
|
||||||
|
|
||||||
|
var table = new TwoFactorTable(
|
||||||
|
new TableSection(" ")
|
||||||
|
{
|
||||||
|
RememberCell
|
||||||
|
});
|
||||||
|
|
||||||
|
var layout = new StackLayout
|
||||||
|
{
|
||||||
|
Children = { instruction, image, table, anotherMethodButton },
|
||||||
|
Spacing = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
scrollView.Content = layout;
|
||||||
|
|
||||||
|
Title = "YubiKey";
|
||||||
|
Content = scrollView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAppearing()
|
protected override void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
ListenYubiKey(true);
|
||||||
|
|
||||||
if(TokenCell != null)
|
if(TokenCell != null)
|
||||||
{
|
{
|
||||||
@ -220,6 +243,7 @@ namespace Bit.App.Pages
|
|||||||
protected override void OnDisappearing()
|
protected override void OnDisappearing()
|
||||||
{
|
{
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
|
ListenYubiKey(false);
|
||||||
|
|
||||||
if(TokenCell != null)
|
if(TokenCell != null)
|
||||||
{
|
{
|
||||||
@ -251,6 +275,12 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task LogInAsync(string token, bool remember)
|
private async Task LogInAsync(string token, bool remember)
|
||||||
{
|
{
|
||||||
|
if(_lastAction.LastActionWasRecent())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_lastAction = DateTime.UtcNow;
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(token))
|
if(string.IsNullOrWhiteSpace(token))
|
||||||
{
|
{
|
||||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||||
@ -264,6 +294,7 @@ namespace Bit.App.Pages
|
|||||||
_userDialogs.HideLoading();
|
_userDialogs.HideLoading();
|
||||||
if(!response.Success)
|
if(!response.Success)
|
||||||
{
|
{
|
||||||
|
ListenYubiKey(true);
|
||||||
await DisplayAlert(AppResources.AnErrorHasOccurred, response.ErrorMessage, AppResources.Ok);
|
await DisplayAlert(AppResources.AnErrorHasOccurred, response.ErrorMessage, AppResources.Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -299,7 +330,7 @@ namespace Bit.App.Pages
|
|||||||
switch(p.Key)
|
switch(p.Key)
|
||||||
{
|
{
|
||||||
case TwoFactorProviderType.Authenticator:
|
case TwoFactorProviderType.Authenticator:
|
||||||
if(provider == TwoFactorProviderType.Duo)
|
if(provider == TwoFactorProviderType.Duo || provider == TwoFactorProviderType.YubiKey)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -311,6 +342,16 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TwoFactorProviderType.Duo:
|
case TwoFactorProviderType.Duo:
|
||||||
|
if(provider == TwoFactorProviderType.YubiKey)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TwoFactorProviderType.YubiKey:
|
||||||
|
if(!_deviceInfoService.NfcEnabled)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
continue;
|
continue;
|
||||||
@ -322,5 +363,76 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ListenYubiKey(bool listen)
|
||||||
|
{
|
||||||
|
if(_providerType == TwoFactorProviderType.YubiKey)
|
||||||
|
{
|
||||||
|
MessagingCenter.Send(Application.Current, "ListenYubiKeyOTP", listen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeYubiKey(bool subscribe)
|
||||||
|
{
|
||||||
|
if(_providerType != TwoFactorProviderType.YubiKey)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
|
||||||
|
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
|
||||||
|
if(!subscribe)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessagingCenter.Subscribe<Application, string>(Application.Current, "GotYubiKeyOTP", async (sender, otp) =>
|
||||||
|
{
|
||||||
|
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
|
||||||
|
if(_providerType == TwoFactorProviderType.YubiKey)
|
||||||
|
{
|
||||||
|
await LogInAsync(otp, RememberCell.On);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
SubscribeYubiKeyResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeYubiKeyResume()
|
||||||
|
{
|
||||||
|
MessagingCenter.Subscribe<Application>(Application.Current, "ResumeYubiKey", (sender) =>
|
||||||
|
{
|
||||||
|
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
|
||||||
|
if(_providerType == TwoFactorProviderType.YubiKey)
|
||||||
|
{
|
||||||
|
MessagingCenter.Send(Application.Current, "ListenYubiKeyOTP", true);
|
||||||
|
SubscribeYubiKeyResume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TwoFactorTable : ExtendedTableView
|
||||||
|
{
|
||||||
|
public TwoFactorTable(TableSection section)
|
||||||
|
{
|
||||||
|
Intent = TableIntent.Settings;
|
||||||
|
EnableScrolling = false;
|
||||||
|
HasUnevenRows = true;
|
||||||
|
EnableSelection = true;
|
||||||
|
NoFooter = true;
|
||||||
|
NoHeader = true;
|
||||||
|
VerticalOptions = LayoutOptions.Start;
|
||||||
|
Root = Root = new TableRoot
|
||||||
|
{
|
||||||
|
section
|
||||||
|
};
|
||||||
|
|
||||||
|
if(Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
RowHeight = -1;
|
||||||
|
EstimatedRowHeight = 70;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
src/iOS/Resources/yubikey.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
src/iOS/Resources/yubikey@2x.png
Normal file
After Width: | Height: | Size: 187 KiB |
BIN
src/iOS/Resources/yubikey@3x.png
Normal file
After Width: | Height: | Size: 381 KiB |
@ -716,6 +716,15 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BundleResource Include="Resources\share_tools%403x.png" />
|
<BundleResource Include="Resources\share_tools%403x.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<BundleResource Include="Resources\yubikey.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<BundleResource Include="Resources\yubikey%402x.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<BundleResource Include="Resources\yubikey%403x.png" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Mono.Android" />
|
<Reference Include="Mono.Android" />
|
||||||
|
<Reference Include="Mono.Android.Export" />
|
||||||
<Reference Include="mscorlib" />
|
<Reference Include="mscorlib" />
|
||||||
<Reference Include="PCLCrypto, Version=2.0.0.0, Culture=neutral, PublicKeyToken=d4421c8a4786956c, processorArchitecture=MSIL">
|
<Reference Include="PCLCrypto, Version=2.0.0.0, Culture=neutral, PublicKeyToken=d4421c8a4786956c, processorArchitecture=MSIL">
|
||||||
<HintPath>..\..\packages\PCLCrypto.2.0.147\lib\MonoAndroid23\PCLCrypto.dll</HintPath>
|
<HintPath>..\..\packages\PCLCrypto.2.0.147\lib\MonoAndroid23\PCLCrypto.dll</HintPath>
|
||||||
|