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