mirror of
https://github.com/bitwarden/android.git
synced 2024-12-24 18:08:26 +03:00
copy totp code on autofill
This commit is contained in:
parent
98e429505c
commit
1124c48c8d
10 changed files with 171 additions and 22 deletions
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
40
src/App/Resources/AppResources.Designer.cs
generated
40
src/App/Resources/AppResources.Designer.cs
generated
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
_controller.LoadingController.CompleteUsernamePasswordRequest(item.Username, item.Password, totp);
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(item.Username) || !string.IsNullOrWhiteSpace(item.Password))
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue