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:
@@ -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" />
|
||||
|
||||
72
src/Android/Controls/HybridWebViewRenderer.cs
Normal file
72
src/Android/Controls/HybridWebViewRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
34
src/App/Controls/HybridWebView.cs
Normal file
34
src/App/Controls/HybridWebView.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
54
src/iOS/Controls/HybridWebViewRenderer.cs
Normal file
54
src/iOS/Controls/HybridWebViewRenderer.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user