diff --git a/src/App/Models/Page/VaultListPageModel.cs b/src/App/Models/Page/VaultListPageModel.cs index 7992027dc..08c4cae27 100644 --- a/src/App/Models/Page/VaultListPageModel.cs +++ b/src/App/Models/Page/VaultListPageModel.cs @@ -8,8 +8,6 @@ namespace Bit.App.Models.Page { public class Login { - private string _baseDomain; - public Login(Models.Login login) { Id = login.Id; @@ -28,6 +26,17 @@ namespace Bit.App.Models.Page public Lazy Uri { get; set; } } + public class AutofillLogin : Login + { + public AutofillLogin(Models.Login login, bool fuzzy = false) + : base(login) + { + Fuzzy = fuzzy; + } + + public bool Fuzzy { get; set; } + } + public class Folder : List { public Folder(Models.Folder folder) @@ -45,9 +54,9 @@ namespace Bit.App.Models.Page public string Name { get; set; } = AppResources.FolderNone; } - public class AutofillGrouping : List + public class AutofillGrouping : List { - public AutofillGrouping(List logins, string name) + public AutofillGrouping(List logins, string name) { AddRange(logins); Name = name; diff --git a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs index 8735a9f8e..9c86d5f67 100644 --- a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs +++ b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs @@ -152,25 +152,35 @@ namespace Bit.App.Pages Task.Run(async () => { + var autofillGroupings = new List(); var logins = await _loginService.GetAllAsync(Uri); - var normalLogins = logins.Item1.Select(l => new VaultListPageModel.Login(l)) - .OrderBy(s => s.Name) - .ThenBy(s => s.Username) - .ToList(); - var fuzzyLogins = logins.Item2.Select(l => new VaultListPageModel.Login(l)) - .OrderBy(s => s.Name) - .ThenBy(s => s.Username) - .ToList(); - var autofillGroupings = new List + var normalLogins = logins?.Item1.Select(l => new VaultListPageModel.AutofillLogin(l, false)) + .OrderBy(s => s.Name) + .ThenBy(s => s.Username) + .ToList(); + if(normalLogins?.Any() ?? false) { - new VaultListPageModel.AutofillGrouping(normalLogins, "Matching"), - new VaultListPageModel.AutofillGrouping(fuzzyLogins, "Possible Matches") - }; + autofillGroupings.Add(new VaultListPageModel.AutofillGrouping(normalLogins, AppResources.MatchingLogins)); + } + + var fuzzyLogins = logins?.Item2.Select(l => new VaultListPageModel.AutofillLogin(l, true)) + .OrderBy(s => s.Name) + .ThenBy(s => s.Username) + .ToList(); + if(fuzzyLogins?.Any() ?? false) + { + autofillGroupings.Add(new VaultListPageModel.AutofillGrouping(fuzzyLogins, + AppResources.PossibleMatchingLogins)); + } Device.BeginInvokeOnMainThread(() => { - PresentationLogins.ResetWithRange(autofillGroupings); + if(autofillGroupings.Any()) + { + PresentationLogins.ResetWithRange(autofillGroupings); + } + AdjustContent(); }); }, cts.Token); @@ -178,18 +188,36 @@ namespace Bit.App.Pages return cts; } - private void LoginSelected(object sender, SelectedItemChangedEventArgs e) + private async void LoginSelected(object sender, SelectedItemChangedEventArgs e) { - var login = e.SelectedItem as VaultListPageModel.Login; + var login = e.SelectedItem as VaultListPageModel.AutofillLogin; + if(login == null) + { + return; + } if(Uri.StartsWith("http") && _deviceInfoService.Version < 21) { MoreClickedAsync(login); - return; + } + else + { + bool doAutofill = true; + if(login.Fuzzy) + { + doAutofill = await _userDialogs.ConfirmAsync( + string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, _name), + okText: AppResources.Yes, cancelText: AppResources.No); + } + + if(doAutofill) + { + GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App"); + MessagingCenter.Send(Application.Current, "Autofill", login as VaultListPageModel.Login); + } } - GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App"); - MessagingCenter.Send(Application.Current, "Autofill", login); + ((ListView)sender).SelectedItem = null; } private async void AddLoginAsync() @@ -272,7 +300,8 @@ namespace Bit.App.Pages private void ClickedItem(object sender, EventArgs e) { - _page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch", _page.Uri.StartsWith("http") ? "Website" : "App"); + _page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch", + _page.Uri.StartsWith("http") ? "Website" : "App"); Application.Current.MainPage = new MainPage(_page.Uri); } } diff --git a/src/App/Pages/Vault/VaultListLoginsPage.cs b/src/App/Pages/Vault/VaultListLoginsPage.cs index 9c972c9bc..3907cab1b 100644 --- a/src/App/Pages/Vault/VaultListLoginsPage.cs +++ b/src/App/Pages/Vault/VaultListLoginsPage.cs @@ -28,6 +28,7 @@ namespace Bit.App.Pages private readonly IPushNotification _pushNotification; private readonly IDeviceInfoService _deviceInfoService; private readonly ISettings _settings; + private readonly IGoogleAnalyticsService _googleAnalyticsService; private readonly bool _favorites; private bool _loadExistingData; private CancellationTokenSource _filterResultsCancellationTokenSource; @@ -45,6 +46,7 @@ namespace Bit.App.Pages _pushNotification = Resolver.Resolve(); _deviceInfoService = Resolver.Resolve(); _settings = Resolver.Resolve(); + _googleAnalyticsService = Resolver.Resolve(); var cryptoService = Resolver.Resolve(); _loadExistingData = !_settings.GetValueOrDefault(Constants.FirstVaultLoad, true) || !cryptoService.KeyChanged; @@ -272,7 +274,7 @@ namespace Bit.App.Pages return false; } - //GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App"); + _googleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App"); MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Login)null); return true; } @@ -378,12 +380,16 @@ namespace Bit.App.Pages private async void LoginSelected(object sender, SelectedItemChangedEventArgs e) { var login = e.SelectedItem as VaultListPageModel.Login; + if(login == null) + { + return; + } string selection = null; if(!string.IsNullOrWhiteSpace(Uri)) { - selection = await DisplayActionSheet("Auto-fill or view?", AppResources.Cancel, null, - "Autofill", AppResources.View); + selection = await DisplayActionSheet(AppResources.AutofillOrView, AppResources.Cancel, null, + AppResources.Autofill, AppResources.View); } if(selection == AppResources.View || string.IsNullOrWhiteSpace(Uri)) @@ -391,17 +397,20 @@ namespace Bit.App.Pages var page = new VaultViewLoginPage(login.Id); await Navigation.PushForDeviceAsync(page); } - else if(selection == "Autofill") + else if(selection == AppResources.Autofill) { if(Uri.StartsWith("http") && _deviceInfoService.Version < 21) { MoreClickedAsync(login); - return; } - - //GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App"); - MessagingCenter.Send(Application.Current, "Autofill", login); + else + { + _googleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App"); + MessagingCenter.Send(Application.Current, "Autofill", login); + } } + + ((ListView)sender).SelectedItem = null; } private async void MoreClickedAsync(VaultListPageModel.Login login) diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index b549d374f..0bf87df66 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -142,6 +142,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Auto-fill. + /// + public static string Autofill { + get { + return ResourceManager.GetString("Autofill", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use the bitwarden accessibility service to auto-fill your logins across apps and the web.. /// @@ -151,6 +160,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Do you want to auto-fill or view this login?. + /// + public static string AutofillOrView { + get { + return ResourceManager.GetString("AutofillOrView", resourceCulture); + } + } + /// /// Looks up a localized string similar to Auto-fill Service. /// @@ -250,6 +268,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Are you sure you want to auto-fill this login? It is not a complete match for "{0}".. + /// + public static string BitwardenAutofillServiceMatchConfirm { + get { + return ResourceManager.GetString("BitwardenAutofillServiceMatchConfirm", resourceCulture); + } + } + /// /// Looks up a localized string similar to When you see a bitwarden auto-fill notification, you can tap it to launch the auto-fill service.. /// @@ -1204,6 +1231,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Matching Logins. + /// + public static string MatchingLogins { + get { + return ResourceManager.GetString("MatchingLogins", resourceCulture); + } + } + /// /// Looks up a localized string similar to Minimum Numbers. /// @@ -1456,6 +1492,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Possible Matching Logins. + /// + public static string PossibleMatchingLogins { + get { + return ResourceManager.GetString("PossibleMatchingLogins", resourceCulture); + } + } + /// /// Looks up a localized string similar to bitwarden keeps your vault automatically synced by using push notifications. For the best possible experience, please select "Ok" on the following prompt when asked to enable push notifications.. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index d23dc4d51..441912a14 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -795,4 +795,19 @@ The easiest way to add new logins to your vault is from the bitwarden Auto-fill Service. Learn more about using the bitwarden Auto-fill Service by navigating to the "Tools" screen. + + Auto-fill + + + Do you want to auto-fill or view this login? + + + Are you sure you want to auto-fill this login? It is not a complete match for "{0}". + + + Matching Logins + + + Possible Matching Logins + \ No newline at end of file diff --git a/src/App/Services/LoginService.cs b/src/App/Services/LoginService.cs index 8ef5a8f61..b9dd18d66 100644 --- a/src/App/Services/LoginService.cs +++ b/src/App/Services/LoginService.cs @@ -70,7 +70,7 @@ namespace Bit.App.Services { if(domainName == null) { - androidApp = uriString.StartsWith(Constants.AndroidAppProtocol); + androidApp = UriIsAndroidApp(uriString); } } @@ -79,16 +79,7 @@ namespace Bit.App.Services return null; } - string androidAppWebUriString = null; - if(androidApp) - { - var androidUriParts = uriString.Replace(Constants.AndroidAppProtocol, string.Empty).Split('.'); - if(androidUriParts.Length >= 2) - { - androidAppWebUriString = string.Join(".", androidUriParts[1], androidUriParts[0]); - } - } - + var androidAppWebUriString = WebUriFromAndroidAppUri(uriString); var eqDomains = (await _settingsService.GetEquivalentDomainsAsync()).Select(d => d.ToArray()); var matchingDomains = new List(); var matchingFuzzyDomains = new List(); @@ -150,6 +141,11 @@ namespace Bit.App.Services matchingFuzzyLogins.Add(new Login(login)); continue; } + else if(!androidApp && Array.IndexOf(matchingDomainsArray, WebUriFromAndroidAppUri(loginUriString)) >= 0) + { + matchingFuzzyLogins.Add(new Login(login)); + continue; + } Uri loginUri; DomainName loginDomainName; @@ -223,5 +219,26 @@ namespace Bit.App.Services return response; } + + private string WebUriFromAndroidAppUri(string androidAppUriString) + { + if(!UriIsAndroidApp(androidAppUriString)) + { + return null; + } + + var androidUriParts = androidAppUriString.Replace(Constants.AndroidAppProtocol, string.Empty).Split('.'); + if(androidUriParts.Length >= 2) + { + return string.Join(".", androidUriParts[1], androidUriParts[0]); + } + + return null; + } + + private bool UriIsAndroidApp(string uriString) + { + return uriString.StartsWith(Constants.AndroidAppProtocol); + } } }