multibutton alert, autofill and save new uri

This commit is contained in:
Kyle Spearrin 2018-04-02 13:37:46 -04:00
parent be11933c60
commit 10df9e7cd5
12 changed files with 313 additions and 40 deletions

View file

@ -6506,17 +6506,17 @@ namespace Bit.Android
// aapt resource value: 0x7f0a0051
public const int ApplicationName = 2131361873;
// aapt resource value: 0x7f0a00ab
public const int AutoFillServiceDescription = 2131361963;
// aapt resource value: 0x7f0a00b2
public const int AutoFillServiceDescription = 2131361970;
// aapt resource value: 0x7f0a00aa
public const int AutoFillServiceSummary = 2131361962;
// aapt resource value: 0x7f0a00b1
public const int AutoFillServiceSummary = 2131361969;
// aapt resource value: 0x7f0a0050
public const int Hello = 2131361872;
// aapt resource value: 0x7f0a00ac
public const int MyVault = 2131361964;
// aapt resource value: 0x7f0a00b3
public const int MyVault = 2131361971;
// aapt resource value: 0x7f0a0027
public const int abc_action_bar_home_description = 2131361831;
@ -6671,6 +6671,27 @@ namespace Bit.Android
// aapt resource value: 0x7f0a000f
public const int common_signin_button_text_long = 2131361807;
// aapt resource value: 0x7f0a00ac
public const int default_web_client_id = 2131361964;
// aapt resource value: 0x7f0a00ad
public const int firebase_database_url = 2131361965;
// aapt resource value: 0x7f0a00aa
public const int gcm_defaultSenderId = 2131361962;
// aapt resource value: 0x7f0a00ae
public const int google_api_key = 2131361966;
// aapt resource value: 0x7f0a00ab
public const int google_app_id = 2131361963;
// aapt resource value: 0x7f0a00af
public const int google_crash_reporting_api_key = 2131361967;
// aapt resource value: 0x7f0a00b0
public const int google_storage_bucket = 2131361968;
// aapt resource value: 0x7f0a0052
public const int hockeyapp_crash_dialog_app_name_fallback = 2131361874;

View file

@ -514,5 +514,78 @@ namespace Bit.Android.Services
alert.Show();
return result.Task;
}
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
{
var activity = (MainActivity)CurrentContext;
if(activity == null)
{
return Task.FromResult<string>(null);
}
var result = new TaskCompletionSource<string>();
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetTitle(title);
if(!string.IsNullOrWhiteSpace(message))
{
if(buttons != null && buttons.Length > 2)
{
if(!string.IsNullOrWhiteSpace(title))
{
alertBuilder.SetTitle($"{title}: {message}");
}
else
{
alertBuilder.SetTitle(message);
}
}
else
{
alertBuilder.SetMessage(message);
}
}
if(buttons != null)
{
if(buttons.Length > 2)
{
alertBuilder.SetItems(buttons, (sender, args) =>
{
result.TrySetResult(buttons[args.Which]);
});
}
else
{
if(buttons.Length > 0)
{
alertBuilder.SetPositiveButton(buttons[0], (sender, args) =>
{
result.TrySetResult(buttons[0]);
});
}
if(buttons.Length > 1)
{
alertBuilder.SetNeutralButton(buttons[1], (sender, args) =>
{
result.TrySetResult(buttons[1]);
});
}
}
}
if(!string.IsNullOrWhiteSpace(cancel))
{
alertBuilder.SetNegativeButton(cancel, (sender, args) =>
{
result.TrySetResult(cancel);
});
}
var alert = alertBuilder.Create();
alert.CancelEvent += (o, args) => { result.TrySetResult(null); };
alert.Show();
return result.Task;
}
}
}

View file

@ -23,5 +23,6 @@ namespace Bit.App.Abstractions
void OpenAutofillSettings();
Task LaunchAppAsync(string appName, Page page);
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null);
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
}
}

View file

@ -13,6 +13,7 @@ using Bit.App.Models;
using System.Collections.Generic;
using Bit.App.Enums;
using static Bit.App.Models.Page.VaultListPageModel;
using Plugin.Connectivity.Abstractions;
namespace Bit.App.Pages
{
@ -22,6 +23,7 @@ namespace Bit.App.Pages
private readonly IDeviceInfoService _deviceInfoService;
private readonly ISettingsService _settingsService;
private readonly IAppSettingsService _appSettingsService;
public readonly IConnectivity _connectivity;
private CancellationTokenSource _filterResultsCancellationTokenSource;
private readonly string _name;
private readonly AppOptions _appOptions;
@ -47,6 +49,7 @@ namespace Bit.App.Pages
_settingsService = Resolver.Resolve<ISettingsService>();
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
GoogleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_connectivity = Resolver.Resolve<IConnectivity>();
Init();
}
@ -237,17 +240,57 @@ namespace Bit.App.Pages
}
else
{
bool doAutofill = true;
var autofillResponse = AppResources.Yes;
if(cipher.Fuzzy)
{
doAutofill = await DisplayAlert(null,
string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, _name),
AppResources.Yes, AppResources.No);
var options = new List<string> { AppResources.Yes };
if(cipher.Type == CipherType.Login && _connectivity.IsConnected)
{
options.Add(AppResources.YesAndSave);
}
autofillResponse = await DeviceActionService.DisplayAlertAsync(null,
string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, _name), AppResources.No,
options.ToArray());
}
if(doAutofill)
if(autofillResponse == AppResources.YesAndSave && cipher.Type == CipherType.Login)
{
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
if(!_connectivity.IsConnected)
{
Helpers.AlertNoConnection(this);
}
else
{
var uris = cipher.CipherModel.Login?.Uris?.ToList();
if(uris == null)
{
uris = new List<LoginUri>();
}
uris.Add(new LoginUri
{
Uri = Uri.Encrypt(cipher.CipherModel.OrganizationId),
Match = null
});
cipher.CipherModel.Login.Uris = uris;
await DeviceActionService.ShowLoadingAsync(AppResources.Saving);
var saveTask = await _cipherService.SaveAsync(cipher.CipherModel);
await DeviceActionService.HideLoadingAsync();
if(saveTask.Succeeded)
{
GoogleAnalyticsService.TrackAppEvent("AddedLoginUriDuringAutofill");
}
}
}
if(autofillResponse == AppResources.Yes || autofillResponse == AppResources.YesAndSave)
{
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled",
Uri.StartsWith("http") ? "Website" : "App");
DeviceActionService.Autofill(cipher);
}
}

View file

@ -403,8 +403,14 @@ namespace Bit.App.Pages
string selection = null;
if(!string.IsNullOrWhiteSpace(_uri))
{
var options = new List<string> { AppResources.Autofill };
if(cipher.Type == Enums.CipherType.Login && _connectivity.IsConnected)
{
options.Add(AppResources.AutofillAndSave);
}
options.Add(AppResources.View);
selection = await DisplayActionSheet(AppResources.AutofillOrView, AppResources.Cancel, null,
AppResources.Autofill, AppResources.View);
options.ToArray());
}
if(selection == AppResources.View || string.IsNullOrWhiteSpace(_uri))
@ -412,8 +418,40 @@ namespace Bit.App.Pages
var page = new VaultViewCipherPage(cipher.Type, cipher.Id);
await Navigation.PushForDeviceAsync(page);
}
else if(selection == AppResources.Autofill)
else if(selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
{
if(selection == AppResources.AutofillAndSave)
{
if(!_connectivity.IsConnected)
{
Helpers.AlertNoConnection(this);
}
else
{
var uris = cipher.CipherModel.Login?.Uris?.ToList();
if(uris == null)
{
uris = new List<Models.LoginUri>();
}
uris.Add(new Models.LoginUri
{
Uri = _uri.Encrypt(cipher.CipherModel.OrganizationId),
Match = null
});
cipher.CipherModel.Login.Uris = uris;
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
var saveTask = await _cipherService.SaveAsync(cipher.CipherModel);
await _deviceActionService.HideLoadingAsync();
if(saveTask.Succeeded)
{
_googleAnalyticsService.TrackAppEvent("AddedLoginUriDuringAutofill");
}
}
}
if(_deviceInfoService.Version < 21)
{
Helpers.CipherMoreClickedAsync(this, cipher, !string.IsNullOrWhiteSpace(_uri));

View file

@ -330,6 +330,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Auto-fill and save.
/// </summary>
public static string AutofillAndSave {
get {
return ResourceManager.GetString("AutofillAndSave", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Do you want to auto-fill or view this item?.
/// </summary>
@ -3291,6 +3300,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Yes, and Save.
/// </summary>
public static string YesAndSave {
get {
return ResourceManager.GetString("YesAndSave", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device&apos;s USB port, then touch its button..
/// </summary>

View file

@ -1283,4 +1283,10 @@
<value>Match Detection</value>
<comment>URI match detection for auto-fill.</comment>
</data>
<data name="YesAndSave" xml:space="preserve">
<value>Yes, and Save</value>
</data>
<data name="AutofillAndSave" xml:space="preserve">
<value>Auto-fill and save</value>
</data>
</root>

View file

@ -93,7 +93,8 @@ namespace Bit.App.Services
return ciphers.Where(c => cipherIds.Contains(c.Id));
}
public async Task<Tuple<IEnumerable<Cipher>, IEnumerable<Cipher>, IEnumerable<Cipher>>> GetAllAsync(string uriString)
public async Task<Tuple<IEnumerable<Cipher>, IEnumerable<Cipher>, IEnumerable<Cipher>>> GetAllAsync(
string uriString)
{
if(string.IsNullOrWhiteSpace(uriString))
{
@ -173,42 +174,42 @@ namespace Bit.App.Services
break;
}
var added = false;
var match = false;
switch(u.Match)
{
case null:
case Enums.UriMatchType.Domain:
added = CheckDefaultUriMatch(cipher, loginUriString, matchingLogins, matchingFuzzyLogins,
match = CheckDefaultUriMatch(cipher, loginUriString, matchingLogins, matchingFuzzyLogins,
matchingDomainsArray, matchingFuzzyDomainsArray, mobileApp, mobileAppSearchTerms);
break;
case Enums.UriMatchType.Host:
var urlHost = Helpers.GetUrlHost(uriString);
added = urlHost != null && urlHost == Helpers.GetUrlHost(loginUriString);
if(added)
match = urlHost != null && urlHost == Helpers.GetUrlHost(loginUriString);
if(match)
{
matchingLogins.Add(cipher);
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
}
break;
case Enums.UriMatchType.Exact:
added = uriString == loginUriString;
if(added)
match = uriString == loginUriString;
if(match)
{
matchingLogins.Add(cipher);
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
}
break;
case Enums.UriMatchType.StartsWith:
added = uriString.StartsWith(loginUriString);
if(added)
match = uriString.StartsWith(loginUriString);
if(match)
{
matchingLogins.Add(cipher);
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
}
break;
case Enums.UriMatchType.RegularExpression:
var regex = new Regex(loginUriString, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
added = regex.IsMatch(uriString);
if(added)
match = regex.IsMatch(uriString);
if(match)
{
matchingLogins.Add(cipher);
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
}
break;
case Enums.UriMatchType.Never:
@ -216,7 +217,7 @@ namespace Bit.App.Services
break;
}
if(added)
if(match)
{
break;
}
@ -436,21 +437,21 @@ namespace Bit.App.Services
{
if(Array.IndexOf(matchingDomainsArray, loginUriString) >= 0)
{
matchingLogins.Add(cipher);
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
return true;
}
else if(mobileApp && Array.IndexOf(matchingFuzzyDomainsArray, loginUriString) >= 0)
{
matchingFuzzyLogins.Add(cipher);
return true;
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
return false;
}
else if(!mobileApp)
{
var info = InfoFromMobileAppUri(loginUriString);
if(info?.Item1 != null && Array.IndexOf(matchingDomainsArray, info.Item1) >= 0)
{
matchingFuzzyLogins.Add(cipher);
return true;
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
return false;
}
}
@ -462,13 +463,13 @@ namespace Bit.App.Services
if(Array.IndexOf(matchingDomainsArray, loginDomainName) >= 0)
{
matchingLogins.Add(cipher);
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
return true;
}
else if(mobileApp && Array.IndexOf(matchingFuzzyDomainsArray, loginDomainName) >= 0)
{
matchingFuzzyLogins.Add(cipher);
return true;
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
return false;
}
}
@ -491,13 +492,31 @@ namespace Bit.App.Services
if(addedFromSearchTerm)
{
matchingFuzzyLogins.Add(cipher);
return true;
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
return false;
}
}
}
return false;
}
private void AddMatchingLogin(Cipher cipher, List<Cipher> matchingLogins, List<Cipher> matchingFuzzyLogins)
{
if(matchingFuzzyLogins.Contains(cipher))
{
matchingFuzzyLogins.Remove(cipher);
}
matchingLogins.Add(cipher);
}
private void AddMatchingFuzzyLogin(Cipher cipher, List<Cipher> matchingLogins, List<Cipher> matchingFuzzyLogins)
{
if(!matchingFuzzyLogins.Contains(cipher) && !matchingLogins.Contains(cipher))
{
matchingFuzzyLogins.Add(cipher);
}
}
}
}

View file

@ -521,5 +521,11 @@ namespace Bit.App.Utilities
return host;
}
public static void AlertNoConnection(Page page)
{
page.DisplayAlert(AppResources.InternetConnectionRequiredTitle,
AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
}
}
}

View file

@ -182,5 +182,22 @@ namespace Bit.UWP.Services
return result.Ok ? result.Value ?? string.Empty : null;
}
public async Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
{
if(!string.IsNullOrWhiteSpace(message))
{
if(string.IsNullOrWhiteSpace(title))
{
title = message;
}
else
{
title = $"{title}: {message}";
}
}
return await _userDialogs.ActionSheetAsync(title, cancel, null, null, buttons.ToArray());
}
}
}

View file

@ -43,6 +43,11 @@ namespace Bit.iOS.Core.Services
// do nothing
}
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
{
return Task.FromResult<string>(null);
}
public Task<string> DisplayPromptAync(string title = null, string description = null, string text = null)
{
return Task.FromResult<string>(null);

View file

@ -351,6 +351,32 @@ namespace Bit.iOS.Services
return result.Task;
}
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
{
var result = new TaskCompletionSource<string>();
var alert = UIAlertController.Create(title ?? string.Empty, message, UIAlertControllerStyle.Alert);
if(!string.IsNullOrWhiteSpace(cancel))
{
alert.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Cancel, x =>
{
result.TrySetResult(cancel);
}));
}
foreach(var button in buttons)
{
alert.AddAction(UIAlertAction.Create(button, UIAlertActionStyle.Default, x =>
{
result.TrySetResult(button);
}));
}
var vc = GetPresentedViewController();
vc?.PresentViewController(alert, true, null);
return result.Task;
}
private UIViewController GetPresentedViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;