mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Password reprompt (#1365)
* Make card number hidden * Add support for password reprompt * Rename PasswordPrompt to Reprompt * Protect autofill * Use Enums.CipherRepromptType * Fix iOS not building * Protect iOS autofill * Update to match jslib * Fix failing build
This commit is contained in:
parent
e61bcd2785
commit
976eeab6d7
36 changed files with 401 additions and 55 deletions
|
@ -140,13 +140,14 @@ namespace Bit.Droid.Autofill
|
|||
{
|
||||
var allCiphers = ciphers.Item1.ToList();
|
||||
allCiphers.AddRange(ciphers.Item2.ToList());
|
||||
return allCiphers.Select(c => new FilledItem(c)).ToList();
|
||||
var nonPromptCiphers = allCiphers.Where(cipher => cipher.Reprompt == CipherRepromptType.None);
|
||||
return nonPromptCiphers.Select(c => new FilledItem(c)).ToList();
|
||||
}
|
||||
}
|
||||
else if (parser.FieldCollection.FillableForCard)
|
||||
{
|
||||
var ciphers = await cipherService.GetAllDecryptedAsync();
|
||||
return ciphers.Where(c => c.Type == CipherType.Card).Select(c => new FilledItem(c)).ToList();
|
||||
return ciphers.Where(c => c.Type == CipherType.Card && c.Reprompt == CipherRepromptType.None).Select(c => new FilledItem(c)).ToList();
|
||||
}
|
||||
return new List<FilledItem>();
|
||||
}
|
||||
|
|
|
@ -99,6 +99,9 @@ namespace Bit.Droid
|
|||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||
broadcasterService);
|
||||
var biometricService = new BiometricService();
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
||||
|
||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
||||
|
@ -110,6 +113,9 @@ namespace Bit.Droid
|
|||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||
|
||||
// Push
|
||||
#if FDROID
|
||||
|
|
|
@ -307,7 +307,7 @@ namespace Bit.Droid.Services
|
|||
|
||||
public Task<string> DisplayPromptAync(string title = null, string description = null,
|
||||
string text = null, string okButtonText = null, string cancelButtonText = null,
|
||||
bool numericKeyboard = false, bool autofocus = true)
|
||||
bool numericKeyboard = false, bool autofocus = true, bool password = false)
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
if (activity == null)
|
||||
|
@ -333,6 +333,10 @@ namespace Bit.Droid.Services
|
|||
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
if (password)
|
||||
{
|
||||
input.InputType = InputTypes.TextVariationPassword | InputTypes.ClassText;
|
||||
}
|
||||
|
||||
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
|
||||
(ImeAction)ImeFlags.NoExtractUi;
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Bit.App.Abstractions
|
|||
Task SelectFileAsync();
|
||||
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
|
||||
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
|
||||
bool autofocus = true);
|
||||
bool autofocus = true, bool password = false);
|
||||
void RateApp();
|
||||
bool SupportsFaceBiometric();
|
||||
Task<bool> SupportsFaceBiometricAsync();
|
||||
|
|
11
src/App/Abstractions/IPasswordRepromptService.cs
Normal file
11
src/App/Abstractions/IPasswordRepromptService.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IPasswordRepromptService
|
||||
{
|
||||
string[] ProtectedFields { get; }
|
||||
|
||||
Task<bool> ShowPasswordPromptAsync();
|
||||
}
|
||||
}
|
|
@ -219,15 +219,40 @@
|
|||
Text="{Binding Cipher.Card.CardholderName}"
|
||||
StyleClass="box-value" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row, box-row-input">
|
||||
<Grid StyleClass="box-row, box-row-input">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{u:I18n Number}"
|
||||
StyleClass="box-label" />
|
||||
<Entry
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<controls:MonoEntry
|
||||
x:Name="_cardNumberEntry"
|
||||
Text="{Binding Cipher.Card.Number}"
|
||||
StyleClass="box-value" />
|
||||
</StackLayout>
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Keyboard="Numeric"
|
||||
IsPassword="{Binding ShowCardNumber, Converter={StaticResource inverseBool}}"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsTextPredictionEnabled="False" />
|
||||
<controls:FaButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowCardNumberIcon}"
|
||||
Command="{Binding ToggleCardNumberCommand}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
</Grid>
|
||||
<StackLayout StyleClass="box-row, box-row-input">
|
||||
<Label
|
||||
Text="{u:I18n Brand}"
|
||||
|
@ -530,6 +555,17 @@
|
|||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row, box-row-switch">
|
||||
<Label
|
||||
Text="{u:I18n PasswordPrompt}"
|
||||
StyleClass="box-label-regular"
|
||||
HorizontalOptions="StartAndExpand" />
|
||||
<Switch
|
||||
IsToggled="{Binding PasswordPrompt}"
|
||||
Toggled="PasswordPrompt_Toggled"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box">
|
||||
|
|
|
@ -376,5 +376,10 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PasswordPrompt_Toggled(object sender, ToggledEventArgs e)
|
||||
{
|
||||
_vm.Cipher.Reprompt = e.Value ? CipherRepromptType.Password : CipherRepromptType.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace Bit.App.Pages
|
|||
private CipherView _cipher;
|
||||
private bool _showNotesSeparator;
|
||||
private bool _showPassword;
|
||||
private bool _showCardNumber;
|
||||
private bool _showCardCode;
|
||||
private int _typeSelectedIndex;
|
||||
private int _cardBrandSelectedIndex;
|
||||
|
@ -82,6 +83,7 @@ namespace Bit.App.Pages
|
|||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
GeneratePasswordCommand = new Command(GeneratePassword);
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
||||
ToggleCardCodeCommand = new Command(ToggleCardCode);
|
||||
CheckPasswordCommand = new Command(CheckPasswordAsync);
|
||||
UriOptionsCommand = new Command<LoginUriView>(UriOptions);
|
||||
|
@ -141,6 +143,7 @@ namespace Bit.App.Pages
|
|||
|
||||
public Command GeneratePasswordCommand { get; set; }
|
||||
public Command TogglePasswordCommand { get; set; }
|
||||
public Command ToggleCardNumberCommand { get; set; }
|
||||
public Command ToggleCardCodeCommand { get; set; }
|
||||
public Command CheckPasswordCommand { get; set; }
|
||||
public Command UriOptionsCommand { get; set; }
|
||||
|
@ -246,6 +249,15 @@ namespace Bit.App.Pages
|
|||
nameof(ShowPasswordIcon)
|
||||
});
|
||||
}
|
||||
public bool ShowCardNumber
|
||||
{
|
||||
get => _showCardNumber;
|
||||
set => SetProperty(ref _showCardNumber, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowCardNumberIcon)
|
||||
});
|
||||
}
|
||||
public bool ShowCardCode
|
||||
{
|
||||
get => _showCardCode;
|
||||
|
@ -277,10 +289,12 @@ namespace Bit.App.Pages
|
|||
public bool ShowUris => IsLogin && Cipher.Login.HasUris;
|
||||
public bool ShowAttachments => Cipher.HasAttachments;
|
||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||
public string ShowCardNumberIcon => ShowCardNumber ? "" : "";
|
||||
public string ShowCardCodeIcon => ShowCardCode ? "" : "";
|
||||
public int PasswordFieldColSpan => Cipher.ViewPassword ? 1 : 4;
|
||||
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
||||
public bool AllowPersonal { get; set; }
|
||||
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
|
@ -691,6 +705,16 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
|
||||
public void ToggleCardNumber()
|
||||
{
|
||||
ShowCardNumber = !ShowCardNumber;
|
||||
if (EditMode && ShowCardNumber)
|
||||
{
|
||||
var task = _eventService.CollectAsync(
|
||||
Core.Enums.EventType.Cipher_ClientToggledCardNumberVisible, CipherId);
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleCardCode()
|
||||
{
|
||||
ShowCardCode = !ShowCardCode;
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace Bit.App.Pages
|
|||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
|
||||
private AppOptions _appOptions;
|
||||
private bool _showList;
|
||||
|
@ -35,6 +36,7 @@ namespace Bit.App.Pages
|
|||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
|
||||
GroupedItems = new ExtendedObservableCollection<GroupingsPageListGroup>();
|
||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||
|
@ -118,10 +120,14 @@ namespace Bit.App.Pages
|
|||
}
|
||||
if (_deviceActionService.SystemMajorVersion() < 21)
|
||||
{
|
||||
await AppHelpers.CipherListOptions(Page, cipher);
|
||||
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var autofillResponse = AppResources.Yes;
|
||||
if (fuzzy)
|
||||
{
|
||||
|
@ -175,7 +181,7 @@ namespace Bit.App.Pages
|
|||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
{
|
||||
await AppHelpers.CipherListOptions(Page, cipher);
|
||||
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace Bit.App.Pages
|
|||
private readonly ISearchService _searchService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private CancellationTokenSource _searchCancellationTokenSource;
|
||||
|
||||
private bool _showNoData;
|
||||
|
@ -35,6 +36,7 @@ namespace Bit.App.Pages
|
|||
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
|
||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||
|
@ -182,7 +184,7 @@ namespace Bit.App.Pages
|
|||
}
|
||||
if (_deviceActionService.SystemMajorVersion() < 21)
|
||||
{
|
||||
await Utilities.AppHelpers.CipherListOptions(Page, cipher);
|
||||
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -195,7 +197,7 @@ namespace Bit.App.Pages
|
|||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
{
|
||||
await Utilities.AppHelpers.CipherListOptions(Page, cipher);
|
||||
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ namespace Bit.App.Pages
|
|||
private readonly IMessagingService _messagingService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
|
||||
public GroupingsPageViewModel()
|
||||
{
|
||||
|
@ -60,6 +61,7 @@ namespace Bit.App.Pages
|
|||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
|
||||
Loading = true;
|
||||
PageTitle = AppResources.MyVault;
|
||||
|
@ -514,7 +516,7 @@ namespace Bit.App.Pages
|
|||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
{
|
||||
await AppHelpers.CipherListOptions(Page, cipher);
|
||||
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,24 +224,41 @@
|
|||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{u:I18n Number}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<Label
|
||||
<controls:MonoLabel
|
||||
Text="{Binding Cipher.Card.MaskedNumber, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding ShowCardNumber, Converter={StaticResource inverseBool}}" />
|
||||
<controls:MonoLabel
|
||||
Text="{Binding Cipher.Card.Number, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0" />
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding ShowCardNumber}" />
|
||||
<controls:FaButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowCardNumberIcon}"
|
||||
Command="{Binding ToggleCardNumberCommand}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
<controls:FaButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text=""
|
||||
Command="{Binding CopyCommand}"
|
||||
CommandParameter="CardNumber"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n CopyNumber}" />
|
||||
|
|
|
@ -128,6 +128,10 @@ namespace Bit.App.Pages
|
|||
}
|
||||
else
|
||||
{
|
||||
if (!await _vm.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_vm.CipherId)));
|
||||
}
|
||||
}
|
||||
|
@ -142,6 +146,10 @@ namespace Bit.App.Pages
|
|||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
if (!await _vm.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var page = new AttachmentsPage(_vm.CipherId);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
@ -151,6 +159,10 @@ namespace Bit.App.Pages
|
|||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
if (!await _vm.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var page = new SharePage(_vm.CipherId);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
@ -160,6 +172,10 @@ namespace Bit.App.Pages
|
|||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
if (!await _vm.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (await _vm.DeleteAsync())
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
|
@ -171,6 +187,10 @@ namespace Bit.App.Pages
|
|||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
if (!await _vm.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var page = new CollectionsPage(_vm.CipherId);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
@ -180,6 +200,10 @@ namespace Bit.App.Pages
|
|||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
if (!await _vm.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var page = new AddEditPage(_vm.CipherId, cloneMode: true, viewPage: this);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
|
@ -23,10 +24,12 @@ namespace Bit.App.Pages
|
|||
private readonly IAuditService _auditService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private CipherView _cipher;
|
||||
private List<ViewPageFieldViewModel> _fields;
|
||||
private bool _canAccessPremium;
|
||||
private bool _showPassword;
|
||||
private bool _showCardNumber;
|
||||
private bool _showCardCode;
|
||||
private string _totpCode;
|
||||
private string _totpCodeFormatted;
|
||||
|
@ -36,6 +39,7 @@ namespace Bit.App.Pages
|
|||
private string _previousCipherId;
|
||||
private byte[] _attachmentData;
|
||||
private string _attachmentFilename;
|
||||
private bool _passwordReprompted;
|
||||
|
||||
public ViewPageViewModel()
|
||||
{
|
||||
|
@ -47,11 +51,13 @@ namespace Bit.App.Pages
|
|||
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
CopyCommand = new Command<string>((id) => CopyAsync(id, null));
|
||||
CopyUriCommand = new Command<LoginUriView>(CopyUri);
|
||||
CopyFieldCommand = new Command<FieldView>(CopyField);
|
||||
LaunchUriCommand = new Command<LoginUriView>(LaunchUri);
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
||||
ToggleCardCodeCommand = new Command(ToggleCardCode);
|
||||
CheckPasswordCommand = new Command(CheckPasswordAsync);
|
||||
DownloadAttachmentCommand = new Command<AttachmentView>(DownloadAttachmentAsync);
|
||||
|
@ -64,6 +70,7 @@ namespace Bit.App.Pages
|
|||
public Command CopyFieldCommand { get; set; }
|
||||
public Command LaunchUriCommand { get; set; }
|
||||
public Command TogglePasswordCommand { get; set; }
|
||||
public Command ToggleCardNumberCommand { get; set; }
|
||||
public Command ToggleCardCodeCommand { get; set; }
|
||||
public Command CheckPasswordCommand { get; set; }
|
||||
public Command DownloadAttachmentCommand { get; set; }
|
||||
|
@ -109,6 +116,15 @@ namespace Bit.App.Pages
|
|||
nameof(ShowPasswordIcon)
|
||||
});
|
||||
}
|
||||
public bool ShowCardNumber
|
||||
{
|
||||
get => _showCardNumber;
|
||||
set => SetProperty(ref _showCardNumber, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowCardNumberIcon)
|
||||
});
|
||||
}
|
||||
public bool ShowCardCode
|
||||
{
|
||||
get => _showCardCode;
|
||||
|
@ -188,6 +204,7 @@ namespace Bit.App.Pages
|
|||
public bool ShowTotp => IsLogin && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
|
||||
!string.IsNullOrWhiteSpace(TotpCodeFormatted);
|
||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||
public string ShowCardNumberIcon => ShowCardNumber ? "" : "";
|
||||
public string ShowCardCodeIcon => ShowCardCode ? "" : "";
|
||||
public string TotpCodeFormatted
|
||||
{
|
||||
|
@ -226,7 +243,7 @@ namespace Bit.App.Pages
|
|||
}
|
||||
Cipher = await cipher.DecryptAsync();
|
||||
CanAccessPremium = await _userService.CanAccessPremiumAsync();
|
||||
Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(Cipher, f)).ToList();
|
||||
Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(this, Cipher, f)).ToList();
|
||||
|
||||
if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
|
||||
(Cipher.OrganizationUseTotp || CanAccessPremium))
|
||||
|
@ -259,8 +276,13 @@ namespace Bit.App.Pages
|
|||
_totpInterval = null;
|
||||
}
|
||||
|
||||
public void TogglePassword()
|
||||
public async void TogglePassword()
|
||||
{
|
||||
if (! await PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ShowPassword = !ShowPassword;
|
||||
if (ShowPassword)
|
||||
{
|
||||
|
@ -268,8 +290,26 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
|
||||
public void ToggleCardCode()
|
||||
public async void ToggleCardNumber()
|
||||
{
|
||||
if (!await PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ShowCardNumber = !ShowCardNumber;
|
||||
if (ShowCardNumber)
|
||||
{
|
||||
var task = _eventService.CollectAsync(
|
||||
Core.Enums.EventType.Cipher_ClientToggledCardNumberVisible, CipherId);
|
||||
}
|
||||
}
|
||||
|
||||
public async void ToggleCardCode()
|
||||
{
|
||||
if (!await PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ShowCardCode = !ShowCardCode;
|
||||
if (ShowCardCode)
|
||||
{
|
||||
|
@ -564,6 +604,11 @@ namespace Bit.App.Pages
|
|||
|
||||
private async void CopyAsync(string id, string text = null)
|
||||
{
|
||||
if (_passwordRepromptService.ProtectedFields.Contains(id) && !await PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string name = null;
|
||||
if (id == "LoginUsername")
|
||||
{
|
||||
|
@ -638,16 +683,28 @@ namespace Bit.App.Pages
|
|||
_platformUtilsService.LaunchUri(uri.LaunchUri);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<bool> PromptPasswordAsync()
|
||||
{
|
||||
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class ViewPageFieldViewModel : ExtendedViewModel
|
||||
{
|
||||
private ViewPageViewModel _vm;
|
||||
private FieldView _field;
|
||||
private CipherView _cipher;
|
||||
private bool _showHiddenValue;
|
||||
|
||||
public ViewPageFieldViewModel(CipherView cipher, FieldView field)
|
||||
public ViewPageFieldViewModel(ViewPageViewModel vm, CipherView cipher, FieldView field)
|
||||
{
|
||||
_vm = vm;
|
||||
_cipher = cipher;
|
||||
Field = field;
|
||||
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
|
||||
|
@ -688,8 +745,12 @@ namespace Bit.App.Pages
|
|||
public bool ShowCopyButton => _field.Type != Core.Enums.FieldType.Boolean &&
|
||||
!string.IsNullOrWhiteSpace(_field.Value) && !(IsHiddenType && !_cipher.ViewPassword);
|
||||
|
||||
public void ToggleHiddenValue()
|
||||
public async void ToggleHiddenValue()
|
||||
{
|
||||
if (!await _vm.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ShowHiddenValue = !ShowHiddenValue;
|
||||
if (ShowHiddenValue)
|
||||
{
|
||||
|
|
20
src/App/Resources/AppResources.Designer.cs
generated
20
src/App/Resources/AppResources.Designer.cs
generated
|
@ -3512,5 +3512,25 @@ namespace Bit.App.Resources {
|
|||
return ResourceManager.GetString("SendFileEmailVerificationRequired", resourceCulture);
|
||||
}
|
||||
}
|
||||
public static string PasswordPrompt
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResourceManager.GetString("PasswordPrompt", resourceCulture);
|
||||
}
|
||||
}
|
||||
public static string PasswordConfirmation
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResourceManager.GetString("PasswordConfirmation", resourceCulture);
|
||||
}
|
||||
}public static string PasswordConfirmationDesc
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResourceManager.GetString("PasswordConfirmationDesc", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1991,4 +1991,13 @@
|
|||
<value>You must verify your email to use files with Send. You can verify your email in the web vault.</value>
|
||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="PasswordPrompt" xml:space="preserve">
|
||||
<value>Master password re-prompt</value>
|
||||
</data>
|
||||
<data name="PasswordConfirmation" xml:space="preserve">
|
||||
<value>Master password confirmation</value>
|
||||
</data>
|
||||
<data name="PasswordConfirmationDesc" xml:space="preserve">
|
||||
<value>This action is protected, to continue please re-enter your master password to verify your identity.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
46
src/App/Services/MobilePasswordRepromptService.cs
Normal file
46
src/App/Services/MobilePasswordRepromptService.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
public class MobilePasswordRepromptService : IPasswordRepromptService
|
||||
{
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
|
||||
public MobilePasswordRepromptService(IPlatformUtilsService platformUtilsService, ICryptoService cryptoService)
|
||||
{
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_cryptoService = cryptoService;
|
||||
}
|
||||
|
||||
public string[] ProtectedFields { get; } = { "LoginTotp", "LoginPassword", "H_FieldValue", "CardNumber", "CardCode" };
|
||||
|
||||
public async Task<bool> ShowPasswordPromptAsync()
|
||||
{
|
||||
Func<string, Task<bool>> validator = async (string password) =>
|
||||
{
|
||||
// Assume user has canceled.
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
var keyHash = await _cryptoService.HashPasswordAsync(password, null);
|
||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||
|
||||
if (storedKeyHash == null || keyHash == null || storedKeyHash != keyHash)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return await _platformUtilsService.ShowPasswordDialogAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, validator);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -171,6 +171,21 @@ namespace Bit.App.Services
|
|||
return tcs.Task;
|
||||
}
|
||||
|
||||
public async Task<bool> ShowPasswordDialogAsync(string title, string body, Func<string, Task<bool>> validator)
|
||||
{
|
||||
var password = await _deviceActionService.DisplayPromptAync(AppResources.PasswordConfirmation,
|
||||
AppResources.PasswordConfirmationDesc, null, AppResources.Submit, AppResources.Cancel, password: true);
|
||||
|
||||
var valid = await validator(password);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
await ShowDialogAsync(AppResources.InvalidMasterPassword, null, AppResources.Ok);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
public bool IsDev()
|
||||
{
|
||||
return Core.Utilities.CoreHelpers.InDebugMode();
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Bit.App.Utilities
|
|||
{
|
||||
public static class AppHelpers
|
||||
{
|
||||
public static async Task<string> CipherListOptions(ContentPage page, CipherView cipher)
|
||||
public static async Task<string> CipherListOptions(ContentPage page, CipherView cipher, IPasswordRepromptService passwordRepromptService)
|
||||
{
|
||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
|
@ -92,20 +92,26 @@ namespace Bit.App.Utilities
|
|||
}
|
||||
else if (selection == AppResources.CopyPassword)
|
||||
{
|
||||
await platformUtilsService.CopyToClipboardAsync(cipher.Login.Password);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await platformUtilsService.CopyToClipboardAsync(cipher.Login.Password);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopyTotp)
|
||||
{
|
||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (!string.IsNullOrWhiteSpace(totp))
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await platformUtilsService.CopyToClipboardAsync(totp);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
|
||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (!string.IsNullOrWhiteSpace(totp))
|
||||
{
|
||||
await platformUtilsService.CopyToClipboardAsync(totp);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.Launch)
|
||||
|
@ -114,16 +120,22 @@ namespace Bit.App.Utilities
|
|||
}
|
||||
else if (selection == AppResources.CopyNumber)
|
||||
{
|
||||
await platformUtilsService.CopyToClipboardAsync(cipher.Card.Number);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Number));
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await platformUtilsService.CopyToClipboardAsync(cipher.Card.Number);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Number));
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopySecurityCode)
|
||||
{
|
||||
await platformUtilsService.CopyToClipboardAsync(cipher.Card.Code);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.SecurityCode));
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await platformUtilsService.CopyToClipboardAsync(cipher.Card.Code);
|
||||
platformUtilsService.ShowToast("info", null,
|
||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.SecurityCode));
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.CopyNotes)
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace Bit.Core.Abstractions
|
|||
void SaveFile();
|
||||
Task<bool> ShowDialogAsync(string text, string title = null, string confirmText = null,
|
||||
string cancelText = null, string type = null);
|
||||
Task<bool> ShowPasswordDialogAsync(string title, string body, Func<string, Task<bool>> validator);
|
||||
void ShowToast(string type, string title, string text, Dictionary<string, object> options = null);
|
||||
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
|
||||
bool SupportsU2f();
|
||||
|
|
8
src/Core/Enums/CipherRepromptType.cs
Normal file
8
src/Core/Enums/CipherRepromptType.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum CipherRepromptType : byte
|
||||
{
|
||||
None = 0,
|
||||
Password = 1,
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@
|
|||
Cipher_ClientAutofilled = 1114,
|
||||
Cipher_SoftDeleted = 1115,
|
||||
Cipher_Restored = 1116,
|
||||
Cipher_ClientToggledCardNumberVisible = 1117,
|
||||
|
||||
Collection_Created = 1300,
|
||||
Collection_Updated = 1301,
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace Bit.Core.Models.Data
|
|||
Name = response.Name;
|
||||
Notes = response.Notes;
|
||||
CollectionIds = collectionIds?.ToList() ?? response.CollectionIds;
|
||||
Reprompt = response.Reprompt;
|
||||
|
||||
try // Added to address Issue (https://github.com/bitwarden/mobile/issues/1006)
|
||||
{
|
||||
|
@ -84,5 +85,6 @@ namespace Bit.Core.Models.Data
|
|||
public List<PasswordHistoryData> PasswordHistory { get; set; }
|
||||
public List<string> CollectionIds { get; set; }
|
||||
public DateTime? DeletedDate { get; set; }
|
||||
public Enums.CipherRepromptType Reprompt { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.View;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -30,6 +31,7 @@ namespace Bit.Core.Models.Domain
|
|||
RevisionDate = obj.RevisionDate;
|
||||
CollectionIds = obj.CollectionIds != null ? new HashSet<string>(obj.CollectionIds) : null;
|
||||
LocalData = localData;
|
||||
Reprompt = obj.Reprompt;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
|
@ -75,8 +77,8 @@ namespace Bit.Core.Models.Domain
|
|||
public List<Field> Fields { get; set; }
|
||||
public List<PasswordHistory> PasswordHistory { get; set; }
|
||||
public HashSet<string> CollectionIds { get; set; }
|
||||
|
||||
public DateTime? DeletedDate { get; set; }
|
||||
public CipherRepromptType Reprompt { get; set; }
|
||||
|
||||
public async Task<CipherView> DecryptAsync()
|
||||
{
|
||||
|
@ -167,7 +169,8 @@ namespace Bit.Core.Models.Domain
|
|||
RevisionDate = RevisionDate,
|
||||
Type = Type,
|
||||
CollectionIds = CollectionIds.ToList(),
|
||||
DeletedDate = DeletedDate
|
||||
DeletedDate = DeletedDate,
|
||||
Reprompt = Reprompt,
|
||||
};
|
||||
BuildDataModel(this, c, new HashSet<string>
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Bit.Core.Models.Request
|
|||
Notes = cipher.Notes?.EncryptedString;
|
||||
Favorite = cipher.Favorite;
|
||||
LastKnownRevisionDate = cipher.RevisionDate;
|
||||
Reprompt = cipher.Reprompt;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
|
@ -121,5 +122,6 @@ namespace Bit.Core.Models.Request
|
|||
public Dictionary<string, string> Attachments { get; set; }
|
||||
public Dictionary<string, AttachmentRequest> Attachments2 { get; set; }
|
||||
public DateTime LastKnownRevisionDate { get; set; }
|
||||
public CipherRepromptType Reprompt { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
@ -26,5 +27,6 @@ namespace Bit.Core.Models.Response
|
|||
public List<PasswordHistoryResponse> PasswordHistory { get; set; }
|
||||
public List<string> CollectionIds { get; set; }
|
||||
public DateTime? DeletedDate { get; set; }
|
||||
public CipherRepromptType Reprompt { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Bit.Core.Models.View
|
|||
public string ExpYear { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string MaskedCode => Code != null ? new string('•', Code.Length) : null;
|
||||
public string MaskedNumber => Number != null ? new string('•', Number.Length) : null;
|
||||
|
||||
public string Brand
|
||||
{
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace Bit.Core.Models.View
|
|||
CollectionIds = c.CollectionIds;
|
||||
RevisionDate = c.RevisionDate;
|
||||
DeletedDate = c.DeletedDate;
|
||||
Reprompt = c.Reprompt;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
|
@ -47,7 +48,7 @@ namespace Bit.Core.Models.View
|
|||
public HashSet<string> CollectionIds { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
public DateTime? DeletedDate { get; set; }
|
||||
|
||||
public CipherRepromptType Reprompt { get; set; }
|
||||
|
||||
public string SubTitle
|
||||
{
|
||||
|
|
|
@ -180,7 +180,8 @@ namespace Bit.Core.Services
|
|||
OrganizationId = model.OrganizationId,
|
||||
Type = model.Type,
|
||||
CollectionIds = model.CollectionIds,
|
||||
RevisionDate = model.RevisionDate
|
||||
RevisionDate = model.RevisionDate,
|
||||
Reprompt = model.Reprompt
|
||||
};
|
||||
|
||||
if (key == null && cipher.OrganizationId != null)
|
||||
|
|
|
@ -23,14 +23,13 @@ namespace Bit.Core.Utilities
|
|||
var platformUtilsService = Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
var storageService = Resolve<IStorageService>("storageService");
|
||||
var secureStorageService = Resolve<IStorageService>("secureStorageService");
|
||||
var cryptoPrimitiveService = Resolve<ICryptoPrimitiveService>("cryptoPrimitiveService");
|
||||
var i18nService = Resolve<II18nService>("i18nService");
|
||||
var messagingService = Resolve<IMessagingService>("messagingService");
|
||||
var cryptoFunctionService = Resolve<ICryptoFunctionService>("cryptoFunctionService");
|
||||
var cryptoService = Resolve<ICryptoService>("cryptoService");
|
||||
SearchService searchService = null;
|
||||
|
||||
var stateService = new StateService();
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService);
|
||||
var tokenService = new TokenService(storageService);
|
||||
var apiService = new ApiService(tokenService, platformUtilsService, (bool expired) =>
|
||||
{
|
||||
|
@ -75,8 +74,6 @@ namespace Bit.Core.Utilities
|
|||
var eventService = new EventService(storageService, apiService, userService, cipherService);
|
||||
|
||||
Register<IStateService>("stateService", stateService);
|
||||
Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||
Register<ICryptoService>("cryptoService", cryptoService);
|
||||
Register<ITokenService>("tokenService", tokenService);
|
||||
Register<IApiService>("apiService", apiService);
|
||||
Register<IAppIdService>("appIdService", appIdService);
|
||||
|
|
|
@ -9,6 +9,7 @@ using Bit.iOS.Autofill.Utilities;
|
|||
using Bit.iOS.Core.Utilities;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
|
@ -18,10 +19,12 @@ namespace Bit.iOS.Autofill
|
|||
: base(handle)
|
||||
{
|
||||
DismissModalAction = Cancel;
|
||||
PasswordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
}
|
||||
|
||||
public Context Context { get; set; }
|
||||
public CredentialProviderViewController CPViewController { get; set; }
|
||||
public IPasswordRepromptService PasswordRepromptService { get; private set; }
|
||||
|
||||
public async override void ViewDidLoad()
|
||||
{
|
||||
|
@ -109,7 +112,7 @@ namespace Bit.iOS.Autofill
|
|||
public async override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
await AutofillHelpers.TableRowSelectedAsync(tableView, indexPath, this,
|
||||
_controller.CPViewController, _controller, "loginAddSegue");
|
||||
_controller.CPViewController, _controller, _controller.PasswordRepromptService, "loginAddSegue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ using Bit.App.Resources;
|
|||
using Bit.iOS.Core.Views;
|
||||
using Bit.iOS.Autofill.Utilities;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
|
@ -16,11 +18,13 @@ namespace Bit.iOS.Autofill
|
|||
: base(handle)
|
||||
{
|
||||
DismissModalAction = Cancel;
|
||||
PasswordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
}
|
||||
|
||||
public Context Context { get; set; }
|
||||
public CredentialProviderViewController CPViewController { get; set; }
|
||||
public bool FromList { get; set; }
|
||||
public IPasswordRepromptService PasswordRepromptService { get; private set; }
|
||||
|
||||
public async override void ViewDidLoad()
|
||||
{
|
||||
|
@ -107,7 +111,8 @@ namespace Bit.iOS.Autofill
|
|||
public async override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
await AutofillHelpers.TableRowSelectedAsync(tableView, indexPath, this,
|
||||
_controller.CPViewController, _controller, "loginAddFromSearchSegue");
|
||||
_controller.CPViewController, _controller, _controller.PasswordRepromptService,
|
||||
"loginAddFromSearchSegue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
@ -14,7 +15,8 @@ namespace Bit.iOS.Autofill.Utilities
|
|||
{
|
||||
public async static Task TableRowSelectedAsync(UITableView tableView, NSIndexPath indexPath,
|
||||
ExtensionTableSource tableSource, CredentialProviderViewController cpViewController,
|
||||
UITableViewController controller, string loginAddSegue)
|
||||
UITableViewController controller, IPasswordRepromptService passwordRepromptService,
|
||||
string loginAddSegue)
|
||||
{
|
||||
tableView.DeselectRow(indexPath, true);
|
||||
tableView.EndEditing(true);
|
||||
|
@ -31,6 +33,11 @@ namespace Bit.iOS.Autofill.Utilities
|
|||
return;
|
||||
}
|
||||
|
||||
if (item.Reprompt != Bit.Core.Enums.CipherRepromptType.None && !await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Username) && !string.IsNullOrWhiteSpace(item.Password))
|
||||
{
|
||||
string totp = null;
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Bit.iOS.Core.Models
|
|||
Totp = cipher.Login?.Totp;
|
||||
Uris = cipher.Login?.Uris?.Select(u => new LoginUriModel(u)).ToList();
|
||||
Fields = cipher.Fields?.Select(f => new Tuple<string, string>(f.Name, f.Value)).ToList();
|
||||
Reprompt = cipher.Reprompt;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
|
@ -28,6 +29,7 @@ namespace Bit.iOS.Core.Models
|
|||
public string Totp { get; set; }
|
||||
public List<Tuple<string, string>> Fields { get; set; }
|
||||
public CipherView CipherView { get; set; }
|
||||
public CipherRepromptType Reprompt { get; set; }
|
||||
|
||||
public class LoginUriModel
|
||||
{
|
||||
|
|
|
@ -200,7 +200,7 @@ namespace Bit.iOS.Core.Services
|
|||
|
||||
public Task<string> DisplayPromptAync(string title = null, string description = null,
|
||||
string text = null, string okButtonText = null, string cancelButtonText = null,
|
||||
bool numericKeyboard = false, bool autofocus = true)
|
||||
bool numericKeyboard = false, bool autofocus = true, bool password = false)
|
||||
{
|
||||
var result = new TaskCompletionSource<string>();
|
||||
var alert = UIAlertController.Create(title ?? string.Empty, description, UIAlertControllerStyle.Alert);
|
||||
|
@ -223,6 +223,9 @@ namespace Bit.iOS.Core.Services
|
|||
{
|
||||
input.KeyboardType = UIKeyboardType.NumberPad;
|
||||
}
|
||||
if (password) {
|
||||
input.SecureTextEntry = true;
|
||||
}
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
{
|
||||
input.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
|
|
|
@ -55,6 +55,9 @@ namespace Bit.iOS.Core.Utilities
|
|||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||
broadcasterService);
|
||||
var biometricService = new BiometricService(mobileStorageService);
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
||||
|
||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
||||
|
@ -66,6 +69,9 @@ namespace Bit.iOS.Core.Utilities
|
|||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||
}
|
||||
|
||||
public static void Bootstrap(Func<Task> postBootstrapFunc = null)
|
||||
|
|
Loading…
Reference in a new issue