From e5d5d8b43439f9af7b69495a0a37879453422ebb Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 20 Dec 2017 11:55:16 -0500 Subject: [PATCH] add autofill service support for web browsers --- src/Android/Autofill/Field.cs | 5 +++ src/Android/Autofill/FieldCollection.cs | 17 ++++++---- src/Android/Autofill/Parser.cs | 43 +++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/Android/Autofill/Field.cs b/src/Android/Autofill/Field.cs index ca23b5f8b..8e2bb6815 100644 --- a/src/Android/Autofill/Field.cs +++ b/src/Android/Autofill/Field.cs @@ -5,6 +5,7 @@ using Android.Views; using Android.Views.Autofill; using static Android.App.Assist.AssistStructure; using Android.Text; +using static Android.Views.ViewStructure; namespace Bit.Android.Autofill { @@ -24,7 +25,9 @@ namespace Bit.Android.Autofill Clickable = node.IsClickable; Visible = node.Visibility == ViewStates.Visible; Hints = FilterForSupportedHints(node.GetAutofillHints()); + Hint = node.Hint; AutofillOptions = node.GetAutofillOptions()?.ToList(); + HtmlInfo = node.HtmlInfo; Node = node; if(node.AutofillValue != null) @@ -63,6 +66,7 @@ namespace Bit.Android.Autofill UpdateSaveTypeFromHints(); } } + public string Hint { get; set; } public int Id { get; private set; } public string IdEntry { get; set; } public AutofillId AutofillId { get; private set; } @@ -77,6 +81,7 @@ namespace Bit.Android.Autofill public long? DateValue { get; set; } public int? ListValue { get; set; } public bool? ToggleValue { get; set; } + public HtmlInfo HtmlInfo { get; private set; } public ViewNode Node { get; private set; } private void UpdateSaveTypeFromHints() diff --git a/src/Android/Autofill/FieldCollection.cs b/src/Android/Autofill/FieldCollection.cs index 88dd5c425..275454a4b 100644 --- a/src/Android/Autofill/FieldCollection.cs +++ b/src/Android/Autofill/FieldCollection.cs @@ -61,7 +61,7 @@ namespace Bit.Android.Autofill _passwordFields = Fields .Where(f => (!f.IdEntry?.ToLowerInvariant().Contains("search") ?? true) && - (!f.Node?.Hint?.ToLowerInvariant().Contains("search") ?? true) && + (!f.Hint?.ToLowerInvariant().Contains("search") ?? true) && ( f.InputType.HasFlag(InputTypes.TextVariationPassword) || f.InputType.HasFlag(InputTypes.TextVariationVisiblePassword) || @@ -71,7 +71,8 @@ namespace Bit.Android.Autofill if(!_passwordFields.Any()) { _passwordFields = Fields.Where(f => - f.IdEntry?.ToLowerInvariant().Contains("password") ?? false).ToList(); + (f.IdEntry?.ToLowerInvariant().Contains("password") ?? false) + || (f.Hint?.ToLowerInvariant().Contains("password") ?? false)).ToList(); } } @@ -104,7 +105,7 @@ namespace Bit.Android.Autofill { foreach(var passwordField in PasswordFields) { - var usernameField = Fields.TakeWhile(f => f.Id != passwordField.Id).LastOrDefault(); + var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault(); if(usernameField != null) { _usernameFields.Add(usernameField); @@ -137,10 +138,14 @@ namespace Bit.Android.Autofill _passwordFields = _usernameFields = null; - Ids.Add(field.Id); + if(field.Id > -1) + { + Ids.Add(field.Id); + IdToFieldMap.Add(field.Id, field); + } + Fields.Add(field); AutofillIds.Add(field.AutofillId); - IdToFieldMap.Add(field.Id, field); if(field.Hints != null) { @@ -181,7 +186,7 @@ namespace Bit.Android.Autofill } }; - var usernameField = Fields.TakeWhile(f => f.Id != passwordField.Id).LastOrDefault(); + var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault(); savedItem.Login.Username = GetFieldValue(usernameField); return savedItem; diff --git a/src/Android/Autofill/Parser.cs b/src/Android/Autofill/Parser.cs index 08a0a4b93..fcbc09a14 100644 --- a/src/Android/Autofill/Parser.cs +++ b/src/Android/Autofill/Parser.cs @@ -1,14 +1,26 @@ using static Android.App.Assist.AssistStructure; using Android.App.Assist; using Bit.App; +using System.Collections.Generic; namespace Bit.Android.Autofill { public class Parser { + public static HashSet TrustedBrowsers = new HashSet + { + "org.mozilla.focus","org.mozilla.firefox","org.mozilla.firefox_beta","com.microsoft.emmx", + "com.android.chrome","com.chrome.beta","com.android.browser","com.brave.browser","com.opera.browser", + "com.opera.browser.beta","com.opera.mini.native","com.chrome.dev","com.chrome.canary", + "com.google.android.apps.chrome","com.google.android.apps.chrome_dev","com.yandex.browser", + "com.sec.android.app.sbrowser","com.sec.android.app.sbrowser.beta","org.codeaurora.swe.browser", + "com.amazon.cloud9" + }; + private readonly AssistStructure _structure; private string _uri; private string _packageName; + private string _webDomain; public Parser(AssistStructure structure) { @@ -25,10 +37,14 @@ namespace Bit.Android.Autofill return _uri; } - if(string.IsNullOrWhiteSpace(PackageName)) + if(string.IsNullOrWhiteSpace(WebDomain) && string.IsNullOrWhiteSpace(PackageName)) { _uri = null; } + else if(!string.IsNullOrWhiteSpace(WebDomain)) + { + _uri = string.Concat("http://", WebDomain); + } else { _uri = string.Concat(Constants.AndroidAppProtocol, PackageName); @@ -50,6 +66,19 @@ namespace Bit.Android.Autofill _packageName = value; } } + public string WebDomain + { + get => _webDomain; + set + { + if(string.IsNullOrWhiteSpace(value)) + { + _webDomain = _uri = null; + } + + _webDomain = value; + } + } public void Parse() { @@ -63,17 +92,27 @@ namespace Bit.Android.Autofill private void ParseNode(ViewNode node) { var hints = node.GetAutofillHints(); - var isEditText = node.ClassName == "android.widget.EditText"; + var isEditText = node.ClassName == "android.widget.EditText" || node?.HtmlInfo?.Tag == "input"; if(isEditText || (hints?.Length ?? 0) > 0) { if(PackageName == null) { PackageName = node.IdPackage; } + if(WebDomain == null && TrustedBrowsers.Contains(node.IdPackage)) + { + WebDomain = node.WebDomain; + } + FieldCollection.Add(new Field(node)); } else { + if(WebDomain == null && TrustedBrowsers.Contains(node.IdPackage)) + { + WebDomain = node.WebDomain; + } + FieldCollection.IgnoreAutofillIds.Add(node.AutofillId); }