1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-04 09:33:16 +00:00

hybrid web view and duo html/js

This commit is contained in:
Kyle Spearrin
2017-06-28 10:09:52 -04:00
parent 45c5801538
commit d71bc775d5
8 changed files with 801 additions and 136 deletions

View File

@@ -116,6 +116,7 @@
<Private>True</Private>
</Reference>
<Reference Include="Mono.Android" />
<Reference Include="Mono.Android.Export" />
<Reference Include="mscorlib" />
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
@@ -302,6 +303,7 @@
<Compile Include="AutofillCredentials.cs" />
<Compile Include="Controls\CustomSearchBarRenderer.cs" />
<Compile Include="Controls\CustomButtonRenderer.cs" />
<Compile Include="Controls\HybridWebViewRenderer.cs" />
<Compile Include="Controls\ExtendedButtonRenderer.cs" />
<Compile Include="Controls\ExtendedTabbedPageRenderer.cs" />
<Compile Include="Controls\ExtendedTableViewRenderer.cs" />

View File

@@ -0,0 +1,72 @@
using System;
using Bit.Android.Controls;
using Bit.App.Controls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Webkit;
using AWebkit = Android.Webkit;
using Java.Interop;
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace Bit.Android.Controls
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
{
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if(Control == null)
{
var webView = new AWebkit.WebView(Forms.Context);
webView.Settings.JavaScriptEnabled = true;
SetNativeControl(webView);
}
if(e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if(e.NewElement != null)
{
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(Element.Uri);
InjectJS(JSFunction);
}
}
private void InjectJS(string script)
{
if(Control != null)
{
Control.LoadUrl(string.Format("javascript: {0}", script));
}
}
public class JSBridge : Java.Lang.Object
{
private readonly WeakReference<HybridWebViewRenderer> _hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
_hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;
if(_hybridWebViewRenderer != null && _hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
hybridRenderer.Element.InvokeAction(data);
}
}
}
}
}

View File

@@ -59,6 +59,7 @@
<Compile Include="Abstractions\Services\ISecureStorageService.cs" />
<Compile Include="Abstractions\Services\ISqlService.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Controls\HybridWebView.cs" />
<Compile Include="Controls\ExtendedToolbarItem.cs" />
<Compile Include="Controls\DismissModalToolBarItem.cs" />
<Compile Include="Controls\ExtendedEditor.cs" />

View File

@@ -0,0 +1,34 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class HybridWebView : View
{
private Action<string> _func;
public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: nameof(Uri),
returnType: typeof(string), declaringType: typeof(HybridWebView), defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
_func = callback;
}
public void Cleanup()
{
_func = null;
}
public void InvokeAction(string data)
{
_func?.Invoke(data);
}
}
}

View File

@@ -12,6 +12,7 @@ using Bit.App.Utilities;
using Bit.App.Enums;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace Bit.App.Pages
{
@@ -50,17 +51,12 @@ namespace Bit.App.Pages
public FormEntryCell TokenCell { get; set; }
public ExtendedSwitchCell RememberCell { get; set; }
public HybridWebView WebView { get; set; }
private void Init()
{
var scrollView = new ScrollView();
var continueToolbarItem = new ToolbarItem(AppResources.Continue, null, async () =>
{
var token = TokenCell?.Entry.Text.Trim().Replace(" ", "");
await LogInAsync(token);
}, ToolbarItemOrder.Default, 0);
if(!_providerType.HasValue)
{
var noProviderLabel = new Label
@@ -72,8 +68,15 @@ namespace Bit.App.Pages
};
scrollView.Content = noProviderLabel;
}
else
else if(_providerType.Value == TwoFactorProviderType.Authenticator ||
_providerType.Value == TwoFactorProviderType.Email)
{
var continueToolbarItem = new ToolbarItem(AppResources.Continue, null, async () =>
{
var token = TokenCell?.Entry.Text.Trim().Replace(" ", "");
await LogInAsync(token, RememberCell.On);
}, ToolbarItemOrder.Default, 0);
var padding = Helpers.OnPlatform(
iOS: new Thickness(15, 20),
Android: new Thickness(15, 8),
@@ -110,7 +113,6 @@ namespace Bit.App.Pages
}
};
if(Device.RuntimePlatform == Device.iOS)
{
table.RowHeight = -1;
@@ -150,9 +152,6 @@ namespace Bit.App.Pages
layout.Children.Add(instruction);
layout.Children.Add(table);
layout.Children.Add(anotherMethodButton);
ToolbarItems.Add(continueToolbarItem);
Title = AppResources.VerificationCode;
break;
case TwoFactorProviderType.Email:
var emailParams = _providers[TwoFactorProviderType.Email];
@@ -173,18 +172,37 @@ namespace Bit.App.Pages
layout.Children.Add(table);
layout.Children.Add(resendEmailButton);
layout.Children.Add(anotherMethodButton);
ToolbarItems.Add(continueToolbarItem);
Title = AppResources.VerificationCode;
break;
case TwoFactorProviderType.Duo:
break;
default:
break;
}
}
Content = scrollView;
ToolbarItems.Add(continueToolbarItem);
Title = AppResources.VerificationCode;
Content = scrollView;
}
else if(_providerType == TwoFactorProviderType.Duo)
{
var duoParams = _providers[TwoFactorProviderType.Duo];
var host = WebUtility.UrlEncode(duoParams["Host"].ToString());
var req = WebUtility.UrlEncode(duoParams["Signature"].ToString());
WebView = new HybridWebView
{
Uri = $"http://192.168.1.6:4001/duo-mobile.html?host={host}&request={req}",
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
};
WebView.RegisterAction(async (sig) =>
{
await LogInAsync(sig, false);
});
Title = "Duo";
Content = WebView;
}
}
protected override void OnAppearing()
@@ -228,10 +246,10 @@ namespace Bit.App.Pages
private async void Entry_Completed(object sender, EventArgs e)
{
var token = TokenCell.Entry.Text.Trim().Replace(" ", "");
await LogInAsync(token);
await LogInAsync(token, RememberCell.On);
}
private async Task LogInAsync(string token)
private async Task LogInAsync(string token, bool remember)
{
if(string.IsNullOrWhiteSpace(token))
{
@@ -241,7 +259,7 @@ namespace Bit.App.Pages
}
_userDialogs.ShowLoading(AppResources.ValidatingCode, MaskType.Black);
var response = await _authService.TokenPostTwoFactorAsync(_providerType.Value, token, RememberCell.On,
var response = await _authService.TokenPostTwoFactorAsync(_providerType.Value, token, remember,
_email, _masterPasswordHash, _key);
_userDialogs.HideLoading();
if(!response.Success)
@@ -258,7 +276,11 @@ namespace Bit.App.Pages
}
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
Application.Current.MainPage = new MainPage();
Device.BeginInvokeOnMainThread(() =>
{
Application.Current.MainPage = new MainPage();
});
}
private TwoFactorProviderType? GetDefaultProvider()

View File

@@ -0,0 +1,54 @@
using System.IO;
using Foundation;
using WebKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using Bit.App.Controls;
using Bit.iOS.Controls;
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace Bit.iOS.Controls
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
{
private const string JSFunction =
"function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
private WKUserContentController _userController;
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if(Control == null)
{
_userController = new WKUserContentController();
var script = new WKUserScript(new NSString(JSFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
_userController.AddUserScript(script);
_userController.AddScriptMessageHandler(this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = _userController };
var webView = new WKWebView(Frame, config);
SetNativeControl(webView);
}
if(e.OldElement != null)
{
_userController.RemoveAllUserScripts();
_userController.RemoveScriptMessageHandler("invokeAction");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if(e.NewElement != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri, false)));
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
Element.InvokeAction(message.Body.ToString());
}
}
}

View File

@@ -217,6 +217,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Controls\ContentPageRenderer.cs" />
<Compile Include="Controls\HybridWebViewRenderer.cs" />
<Compile Include="Controls\ExtendedButtonRenderer.cs" />
<Compile Include="Controls\CustomButtonRenderer.cs" />
<Compile Include="Controls\CustomLabelRenderer.cs" />