copy totp code on autofill

This commit is contained in:
Kyle Spearrin 2017-07-21 11:39:22 -04:00
parent 98e429505c
commit 1124c48c8d
10 changed files with 171 additions and 22 deletions

View file

@ -29,6 +29,8 @@ namespace Bit.Android
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
private DateTime? _lastAction;
private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
private IDeviceActionService _deviceActionService;
private ISettings _settings;
protected override void OnCreate(Bundle bundle)
{
@ -65,6 +67,8 @@ namespace Bit.Android
typeof(Color).GetProperty("Accent", BindingFlags.Public | BindingFlags.Static)
.SetValue(null, Color.FromHex("d2d6de"));
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_settings = Resolver.Resolve<ISettings>();
LoadApplication(new App.App(
uri,
Resolver.Resolve<IAuthService>(),
@ -72,13 +76,13 @@ namespace Bit.Android
Resolver.Resolve<IUserDialogs>(),
Resolver.Resolve<IDatabaseService>(),
Resolver.Resolve<ISyncService>(),
Resolver.Resolve<ISettings>(),
_settings,
Resolver.Resolve<ILockService>(),
Resolver.Resolve<IGoogleAnalyticsService>(),
Resolver.Resolve<ILocalizeService>(),
Resolver.Resolve<IAppInfoService>(),
Resolver.Resolve<IAppSettingsService>(),
Resolver.Resolve<IDeviceActionService>()));
_deviceActionService));
MessagingCenter.Subscribe<Xamarin.Forms.Application>(
Xamarin.Forms.Application.Current, "DismissKeyboard", (sender) =>
@ -129,6 +133,13 @@ namespace Bit.Android
}
else
{
var isPremium = Resolver.Resolve<ITokenService>()?.TokenPremium ?? false;
var autoCopyEnabled = !_settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false);
if(isPremium && autoCopyEnabled && _deviceActionService != null && login.Totp.Value != null)
{
_deviceActionService.CopyToClipboard(App.Utilities.Crypto.Totp(login.Totp.Value));
}
data.PutExtra("uri", login.Uri.Value);
data.PutExtra("username", login.Username);
data.PutExtra("password", login.Password.Value);

View file

@ -8,6 +8,7 @@
public const string SettingPinUnlockOn = "setting:pinUnlockOn";
public const string SettingLockSeconds = "setting:lockSeconds";
public const string SettingGaOptOut = "setting:googleAnalyticsOptOut";
public const string SettingDisableTotpCopy = "setting:disableAutoCopyTotp";
public const string AutofillPersistNotification = "setting:persistNotification";
public const string AutofillPasswordField = "setting:autofillPasswordField";

View file

@ -17,6 +17,7 @@ namespace Bit.App.Models.Page
Username = login.Username?.Decrypt(login.OrganizationId) ?? " ";
Password = new Lazy<string>(() => login.Password?.Decrypt(login.OrganizationId));
Uri = new Lazy<string>(() => login.Uri?.Decrypt(login.OrganizationId));
Totp = new Lazy<string>(() => login.Totp?.Decrypt(login.OrganizationId));
}
public string Id { get; set; }
@ -26,6 +27,7 @@ namespace Bit.App.Models.Page
public string Username { get; set; }
public Lazy<string> Password { get; set; }
public Lazy<string> Uri { get; set; }
public Lazy<string> Totp { get; set; }
}
public class AutofillLogin : Login

View file

@ -27,6 +27,8 @@ namespace Bit.App.Pages
}
private StackLayout StackLayout { get; set; }
private ExtendedSwitchCell CopyTotpCell { get; set; }
private Label CopyTotpLabel { get; set; }
private ExtendedSwitchCell AnalyticsCell { get; set; }
private Label AnalyticsLabel { get; set; }
private ExtendedSwitchCell AutofillPersistNotificationCell { get; set; }
@ -38,6 +40,23 @@ namespace Bit.App.Pages
private void Init()
{
CopyTotpCell = new ExtendedSwitchCell
{
Text = AppResources.DisableAutoTotpCopy,
On = _settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false)
};
var totpTable = new FormTableView(true)
{
Root = new TableRoot
{
new TableSection(" ")
{
CopyTotpCell
}
}
};
AnalyticsCell = new ExtendedSwitchCell
{
Text = AppResources.DisableGA,
@ -55,18 +74,19 @@ namespace Bit.App.Pages
}
};
AnalyticsLabel = new Label
CopyTotpLabel = new FormTableLabel(this)
{
Text = AppResources.DisbaleGADescription,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
Style = (Style)Application.Current.Resources["text-muted"],
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
Text = AppResources.DisableAutoTotpCopyDescription
};
AnalyticsLabel = new FormTableLabel(this)
{
Text = AppResources.DisableGADescription
};
StackLayout = new StackLayout
{
Children = { analyticsTable, AnalyticsLabel },
Children = { totpTable, CopyTotpLabel, analyticsTable, AnalyticsLabel },
Spacing = 0
};
@ -78,7 +98,7 @@ namespace Bit.App.Pages
On = !_appSettings.AutofillPersistNotification && !_appSettings.AutofillPasswordField
};
var autofillAlwaysTable = new FormTableView
var autofillAlwaysTable = new FormTableView(true)
{
Root = new TableRoot
{
@ -102,7 +122,6 @@ namespace Bit.App.Pages
var autofillPersistNotificationTable = new FormTableView
{
NoHeader = true,
Root = new TableRoot
{
new TableSection(" ")
@ -125,7 +144,6 @@ namespace Bit.App.Pages
var autofillPasswordFieldTable = new FormTableView
{
NoHeader = true,
Root = new TableRoot
{
new TableSection(" ")
@ -169,6 +187,7 @@ namespace Bit.App.Pages
base.OnAppearing();
AnalyticsCell.OnChanged += AnalyticsCell_Changed;
CopyTotpCell.OnChanged += CopyTotpCell_OnChanged;
StackLayout.LayoutChanged += Layout_LayoutChanged;
if(Device.RuntimePlatform == Device.Android)
@ -184,6 +203,7 @@ namespace Bit.App.Pages
base.OnDisappearing();
AnalyticsCell.OnChanged -= AnalyticsCell_Changed;
CopyTotpCell.OnChanged -= CopyTotpCell_OnChanged;
StackLayout.LayoutChanged -= Layout_LayoutChanged;
if(Device.RuntimePlatform == Device.Android)
@ -211,6 +231,17 @@ namespace Bit.App.Pages
_googleAnalyticsService.SetAppOptOut(cell.On);
}
private void CopyTotpCell_OnChanged(object sender, ToggledEventArgs e)
{
var cell = sender as ExtendedSwitchCell;
if(cell == null)
{
return;
}
_settings.AddOrUpdateValue(Constants.SettingDisableTotpCopy, cell.On);
}
private void AutofillAlwaysCell_OnChanged(object sender, ToggledEventArgs e)
{
var cell = sender as ExtendedSwitchCell;
@ -262,7 +293,7 @@ namespace Bit.App.Pages
private class FormTableView : ExtendedTableView
{
public FormTableView()
public FormTableView(bool header = false)
{
Intent = TableIntent.Settings;
EnableScrolling = false;
@ -270,6 +301,7 @@ namespace Bit.App.Pages
EnableSelection = true;
VerticalOptions = LayoutOptions.Start;
NoFooter = true;
NoHeader = !header;
}
}

View file

@ -574,6 +574,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Copied TOTP!.
/// </summary>
public static string CopiedTotp {
get {
return ResourceManager.GetString("CopiedTotp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copied username!.
/// </summary>
@ -601,6 +610,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Copy TOTP.
/// </summary>
public static string CopyTotp {
get {
return ResourceManager.GetString("CopyTotp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy Username.
/// </summary>
@ -664,6 +682,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Disable Automatic TOTP Copy.
/// </summary>
public static string DisableAutoTotpCopy {
get {
return ResourceManager.GetString("DisableAutoTotpCopy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to If your login has an authenticator key attached to it, the TOTP verification code is automatically copied to your clipboard whenever you auto-fill the login..
/// </summary>
public static string DisableAutoTotpCopyDescription {
get {
return ResourceManager.GetString("DisableAutoTotpCopyDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Disabled.
/// </summary>
@ -685,9 +721,9 @@ namespace Bit.App.Resources {
/// <summary>
/// Looks up a localized string similar to We use analytics to better learn how the app is being used so that we can make it better. All data collection is completely anonymous..
/// </summary>
public static string DisbaleGADescription {
public static string DisableGADescription {
get {
return ResourceManager.GetString("DisbaleGADescription", resourceCulture);
return ResourceManager.GetString("DisableGADescription", resourceCulture);
}
}

View file

@ -822,7 +822,7 @@
<data name="ShareVaultDescription" xml:space="preserve">
<value>Create an organization to securely share your logins with other users.</value>
</data>
<data name="DisbaleGADescription" xml:space="preserve">
<data name="DisableGADescription" xml:space="preserve">
<value>We use analytics to better learn how the app is being used so that we can make it better. All data collection is completely anonymous.</value>
</data>
<data name="Features" xml:space="preserve">
@ -950,4 +950,16 @@
<data name="Photos" xml:space="preserve">
<value>Photos</value>
</data>
<data name="CopiedTotp" xml:space="preserve">
<value>Copied TOTP!</value>
</data>
<data name="CopyTotp" xml:space="preserve">
<value>Copy TOTP</value>
</data>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>If your login has an authenticator key attached to it, the TOTP verification code is automatically copied to your clipboard whenever you auto-fill the login.</value>
</data>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Disable Automatic TOTP Copy</value>
</data>
</root>

View file

@ -191,7 +191,7 @@ namespace Bit.iOS.Extension
}
}
public void CompleteUsernamePasswordRequest(string username, string password)
public void CompleteUsernamePasswordRequest(string username, string password, string totp)
{
NSDictionary itemData = null;
if(_context.ProviderType == UTType.PropertyList)
@ -227,6 +227,11 @@ namespace Bit.iOS.Extension
Constants.AppExtensionOldPasswordKey, password);
}
if(!string.IsNullOrWhiteSpace(totp))
{
UIPasteboard.General.String = totp;
}
CompleteRequest(itemData);
}

View file

@ -176,7 +176,8 @@ namespace Bit.iOS.Extension
}
else if(LoadingController != null)
{
LoadingController.CompleteUsernamePasswordRequest(UsernameCell.TextField.Text, PasswordCell.TextField.Text);
LoadingController.CompleteUsernamePasswordRequest(UsernameCell.TextField.Text, PasswordCell.TextField.Text,
null);
}
}
else if(saveTask.Result.Errors.Count() > 0)

View file

@ -104,17 +104,22 @@ namespace Bit.iOS.Extension
private IEnumerable<LoginViewModel> _tableItems = new List<LoginViewModel>();
private Context _context;
private LoginListViewController _controller;
private ILoginService _loginService;
private ISettings _settings;
private bool _isPremium;
public TableSource(LoginListViewController controller)
{
_context = controller.Context;
_controller = controller;
_isPremium = Resolver.Resolve<ITokenService>()?.TokenPremium ?? false;
_loginService = Resolver.Resolve<ILoginService>();
_settings = Resolver.Resolve<ISettings>();
}
public async Task LoadItemsAsync()
{
var loginService = Resolver.Resolve<ILoginService>();
var logins = await loginService.GetAllAsync(_context.UrlString);
var logins = await _loginService.GetAllAsync(_context.UrlString);
_tableItems = logins?.Item1?.Select(s => new LoginViewModel(s))
.OrderBy(s => s.Name)
.ThenBy(s => s.Username)
@ -184,9 +189,16 @@ namespace Bit.iOS.Extension
if(_controller.CanAutoFill() && !string.IsNullOrWhiteSpace(item.Password))
{
_controller.LoadingController.CompleteUsernamePasswordRequest(item.Username, item.Password);
string totp = null;
if(!_settings.GetValueOrDefault(App.Constants.SettingDisableTotpCopy, false))
{
totp = GetTotp(item);
}
else if(!string.IsNullOrWhiteSpace(item.Username) || !string.IsNullOrWhiteSpace(item.Password))
_controller.LoadingController.CompleteUsernamePasswordRequest(item.Username, item.Password, totp);
}
else if(!string.IsNullOrWhiteSpace(item.Username) || !string.IsNullOrWhiteSpace(item.Password) ||
!string.IsNullOrWhiteSpace(item.Totp.Value))
{
var sheet = Dialogs.CreateActionSheet(item.Name, _controller);
if(!string.IsNullOrWhiteSpace(item.Username))
@ -217,6 +229,26 @@ namespace Bit.iOS.Extension
}));
}
if(!string.IsNullOrWhiteSpace(item.Totp.Value))
{
sheet.AddAction(UIAlertAction.Create(AppResources.CopyTotp, UIAlertActionStyle.Default, a =>
{
var totp = GetTotp(item);
if(string.IsNullOrWhiteSpace(totp))
{
return;
}
UIPasteboard clipboard = UIPasteboard.General;
clipboard.String = totp;
var alert = Dialogs.CreateMessageAlert(AppResources.CopiedTotp);
_controller.PresentViewController(alert, true, () =>
{
_controller.DismissViewController(true, null);
});
}));
}
sheet.AddAction(UIAlertAction.Create(AppResources.Cancel, UIAlertActionStyle.Cancel, null));
_controller.PresentViewController(sheet, true, null);
}
@ -226,6 +258,20 @@ namespace Bit.iOS.Extension
_controller.PresentViewController(alert, true, null);
}
}
private string GetTotp(LoginViewModel item)
{
string totp = null;
if(_isPremium)
{
if(item != null && !string.IsNullOrWhiteSpace(item.Totp.Value))
{
totp = App.Utilities.Crypto.Totp(item.Totp.Value);
}
}
return totp;
}
}
}
}

View file

@ -1,4 +1,5 @@
using Bit.App.Models;
using System;
namespace Bit.iOS.Extension.Models
{
@ -11,6 +12,7 @@ namespace Bit.iOS.Extension.Models
Username = login.Username?.Decrypt(login.OrganizationId);
Password = login.Password?.Decrypt(login.OrganizationId);
Uri = login.Uri?.Decrypt(login.OrganizationId);
Totp = new Lazy<string>(() => login.Totp?.Decrypt(login.OrganizationId));
}
public string Id { get; set; }
@ -18,5 +20,6 @@ namespace Bit.iOS.Extension.Models
public string Username { get; set; }
public string Password { get; set; }
public string Uri { get; set; }
public Lazy<string> Totp { get; set; }
}
}