1
0
mirror of https://github.com/bitwarden/mobile.git synced 2025-01-24 21:31:31 +01:00

PM-1575 Added non-discoverable passkeys to login UI

This commit is contained in:
Federico Maccaroni 2023-05-19 17:24:25 +03:00
parent b88c0ea61f
commit 2aeb9b6d31
No known key found for this signature in database
GPG Key ID: 5D233F8F2B034536
10 changed files with 106 additions and 32 deletions

View File

@ -208,6 +208,19 @@
IsVisible="{Binding Cipher.ViewPassword}" /> IsVisible="{Binding Cipher.ViewPassword}" />
</Grid> </Grid>
<Label
Text="{u:I18n Passkey}"
StyleClass="box-label"
Margin="0,10,0,0"
IsVisible="{Binding ShowPasskeyCreationDate}"/>
<!--TODO: Update binding to display the true creation date of the login's Fido2Key.
For now, displaying the creation date of the cipher to check the UI-->
<Entry
Text="{Binding CreationDate}"
IsEnabled="False"
StyleClass="box-value,text-muted"
IsVisible="{Binding ShowPasskeyCreationDate}" />
<Grid StyleClass="box-row, box-row-input"> <Grid StyleClass="box-row, box-row-input">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />

View File

@ -88,7 +88,6 @@ namespace Bit.App.Pages
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>(); _watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
_accountsManager = ServiceContainer.Resolve<IAccountsManager>(); _accountsManager = ServiceContainer.Resolve<IAccountsManager>();
GeneratePasswordCommand = new Command(GeneratePassword); GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardNumberCommand = new Command(ToggleCardNumber);
@ -310,6 +309,7 @@ namespace Bit.App.Pages
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow; public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp); public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}"; public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
public bool ShowPasskeyCreationDate => Cipher?.Login?.Fido2Key != null && !CloneMode;
public void Init() public void Init()
{ {
@ -368,6 +368,11 @@ namespace Bit.App.Pages
{ {
Cipher.OrganizationId = OrganizationId; Cipher.OrganizationId = OrganizationId;
} }
if (Cipher.Type == CipherType.Login)
{
// passkeys can't be cloned
Cipher.Login.Fido2Key = null;
}
} }
if (appOptions?.OtpData != null && Cipher.Type == CipherType.Login) if (appOptions?.OtpData != null && Cipher.Type == CipherType.Login)
{ {

View File

@ -45,7 +45,7 @@
x:Name="_attachmentsItem" x:Key="attachmentsItem" /> x:Name="_attachmentsItem" x:Key="attachmentsItem" />
<ToolbarItem Text="{u:I18n Delete}" Clicked="Delete_Clicked" Order="Secondary" IsDestructive="True" <ToolbarItem Text="{u:I18n Delete}" Clicked="Delete_Clicked" Order="Secondary" IsDestructive="True"
x:Name="_deleteItem" x:Key="deleteItem" /> x:Name="_deleteItem" x:Key="deleteItem" />
<ToolbarItem Text="{u:I18n Clone}" Clicked="Clone_Clicked" Order="Secondary" <ToolbarItem Text="{u:I18n Clone}" Command="{Binding CloneCommand}" Order="Secondary"
x:Name="_cloneItem" x:Key="cloneItem" /> x:Name="_cloneItem" x:Key="cloneItem" />
<DataTemplate x:Key="TextCustomFieldDataTemplate"> <DataTemplate x:Key="TextCustomFieldDataTemplate">
@ -182,6 +182,18 @@
</Grid> </Grid>
<BoxView StyleClass="box-row-separator" <BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}" /> IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}" />
<Label
Text="{u:I18n Passkey}"
StyleClass="box-label"
Margin="0,10,0,0"
IsVisible="{Binding Cipher.Login.Fido2Key}"/>
<!--TODO: Update binding to display the true creation date of the login's Fido2Key.
For now, displaying the creation date of the cipher to check the UI-->
<Entry
Text="{Binding CreationDate}"
IsEnabled="False"
StyleClass="box-value,text-muted"
IsVisible="{Binding Cipher.Login.Fido2Key}" />
<Grid StyleClass="box-row" IsVisible="{Binding ShowTotp}"> <Grid StyleClass="box-row" IsVisible="{Binding ShowTotp}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />

View File

@ -204,19 +204,6 @@ namespace Bit.App.Pages
} }
} }
private async void Clone_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())
{
if (!await _vm.PromptPasswordAsync())
{
return;
}
var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async void More_Clicked(object sender, System.EventArgs e) private async void More_Clicked(object sender, System.EventArgs e)
{ {
if (!DoOnce()) if (!DoOnce())
@ -267,8 +254,7 @@ namespace Bit.App.Pages
} }
else if (selection == AppResources.Clone) else if (selection == AppResources.Clone)
{ {
var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this); _vm.CloneCommand.Execute(null);
await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }

View File

@ -69,6 +69,7 @@ namespace Bit.App.Pages
CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyFieldCommand = new AsyncCommand<FieldView>(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyFieldCommand = new AsyncCommand<FieldView>(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
LaunchUriCommand = new Command<ILaunchableView>(LaunchUri); LaunchUriCommand = new Command<ILaunchableView>(LaunchUri);
CloneCommand = new AsyncCommand(CloneAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false);
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode); ToggleCardCodeCommand = new Command(ToggleCardCode);
@ -81,6 +82,7 @@ namespace Bit.App.Pages
public ICommand CopyUriCommand { get; set; } public ICommand CopyUriCommand { get; set; }
public ICommand CopyFieldCommand { get; set; } public ICommand CopyFieldCommand { get; set; }
public Command LaunchUriCommand { get; set; } public Command LaunchUriCommand { get; set; }
public ICommand CloneCommand { get; set; }
public Command TogglePasswordCommand { get; set; } public Command TogglePasswordCommand { get; set; }
public Command ToggleCardNumberCommand { get; set; } public Command ToggleCardNumberCommand { get; set; }
public Command ToggleCardCodeCommand { get; set; } public Command ToggleCardCodeCommand { get; set; }
@ -677,6 +679,17 @@ namespace Bit.App.Pages
} }
} }
private async Task CloneAsync()
{
if (!await CanCloneAsync() || !await PromptPasswordAsync())
{
return;
}
var page = new CipherAddEditPage(CipherId, cloneMode: true, cipherDetailsPage: Page as CipherDetailsPage);
await Page.Navigation.PushModalAsync(new NavigationPage(page));
}
public async Task<bool> PromptPasswordAsync() public async Task<bool> PromptPasswordAsync()
{ {
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted) if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
@ -686,5 +699,15 @@ namespace Bit.App.Pages
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync(); return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
} }
private async Task<bool> CanCloneAsync()
{
if (Cipher.Type == CipherType.Login && Cipher.Login?.Fido2Key != null)
{
return await _platformUtilsService.ShowDialogAsync(AppResources.ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem, AppResources.PasskeyWillNotBeCopied, AppResources.Yes, AppResources.No);
}
return true;
}
} }
} }

View File

@ -4696,6 +4696,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Passkey will not be copied.
/// </summary>
public static string PasskeyWillNotBeCopied {
get {
return ResourceManager.GetString("PasskeyWillNotBeCopied", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Passphrase. /// Looks up a localized string similar to Passphrase.
/// </summary> /// </summary>
@ -6092,6 +6101,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to The passkey will not be copied to the cloned item. Do you want to continue cloning this item?.
/// </summary>
public static string ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem {
get {
return ResourceManager.GetString("ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to There are no items in your vault that match &quot;{0}&quot;. /// Looks up a localized string similar to There are no items in your vault that match &quot;{0}&quot;.
/// </summary> /// </summary>

View File

@ -2632,4 +2632,10 @@ Do you want to switch to this account?</value>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve"> <data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>You cannot edit passkey application because it would invalidate the passkey</value> <value>You cannot edit passkey application because it would invalidate the passkey</value>
</data> </data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Passkey will not be copied</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>The passkey will not be copied to the cloned item. Do you want to continue cloning this item?</value>
</data>
</root> </root>

View File

@ -1,7 +1,28 @@
namespace Bit.Core.Models.Api using Bit.Core.Models.Domain;
using Bit.Core.Models.Export;
namespace Bit.Core.Models.Api
{ {
public class Fido2KeyApi public class Fido2KeyApi
{ {
public Fido2KeyApi()
{
}
public Fido2KeyApi(Fido2Key fido2Key)
{
NonDiscoverableId = fido2Key.NonDiscoverableId?.EncryptedString;
KeyType = fido2Key.KeyType?.EncryptedString;
KeyAlgorithm = fido2Key.KeyAlgorithm?.EncryptedString;
KeyCurve = fido2Key.KeyCurve?.EncryptedString;
KeyValue = fido2Key.KeyValue?.EncryptedString;
RpId = fido2Key.RpId?.EncryptedString;
RpName = fido2Key.RpName?.EncryptedString;
UserHandle = fido2Key.UserHandle?.EncryptedString;
UserName = fido2Key.UserName?.EncryptedString;
Counter = fido2Key.Counter?.EncryptedString;
}
public string NonDiscoverableId { get; set; } public string NonDiscoverableId { get; set; }
public string KeyType { get; set; } = Constants.DefaultFido2KeyType; public string KeyType { get; set; } = Constants.DefaultFido2KeyType;
public string KeyAlgorithm { get; set; } = Constants.DefaultFido2KeyAlgorithm; public string KeyAlgorithm { get; set; } = Constants.DefaultFido2KeyAlgorithm;

View File

@ -15,6 +15,7 @@ namespace Bit.Core.Models.Domain
{ {
PasswordRevisionDate = obj.PasswordRevisionDate; PasswordRevisionDate = obj.PasswordRevisionDate;
Uris = obj.Uris?.Select(u => new LoginUri(u, alreadyEncrypted)).ToList(); Uris = obj.Uris?.Select(u => new LoginUri(u, alreadyEncrypted)).ToList();
Fido2Key = obj.Fido2Key != null ? new Fido2Key(obj.Fido2Key, alreadyEncrypted) : null;
BuildDomainModel(this, obj, new HashSet<string> BuildDomainModel(this, obj, new HashSet<string>
{ {
"Username", "Username",

View File

@ -30,7 +30,8 @@ namespace Bit.Core.Models.Request
Username = cipher.Login.Username?.EncryptedString, Username = cipher.Login.Username?.EncryptedString,
Password = cipher.Login.Password?.EncryptedString, Password = cipher.Login.Password?.EncryptedString,
PasswordRevisionDate = cipher.Login.PasswordRevisionDate, PasswordRevisionDate = cipher.Login.PasswordRevisionDate,
Totp = cipher.Login.Totp?.EncryptedString Totp = cipher.Login.Totp?.EncryptedString,
Fido2Key = cipher.Login.Fido2Key != null ? new Fido2KeyApi(cipher.Login.Fido2Key) : null
}; };
break; break;
case CipherType.Card: case CipherType.Card:
@ -74,19 +75,7 @@ namespace Bit.Core.Models.Request
}; };
break; break;
case CipherType.Fido2Key: case CipherType.Fido2Key:
Fido2Key = new Fido2KeyApi Fido2Key = new Fido2KeyApi(cipher.Fido2Key);
{
NonDiscoverableId = cipher.Fido2Key.NonDiscoverableId?.EncryptedString,
KeyType = cipher.Fido2Key.KeyType?.EncryptedString,
KeyAlgorithm = cipher.Fido2Key.KeyAlgorithm?.EncryptedString,
KeyCurve = cipher.Fido2Key.KeyCurve?.EncryptedString,
KeyValue = cipher.Fido2Key.KeyValue?.EncryptedString,
RpId = cipher.Fido2Key.RpId?.EncryptedString,
RpName = cipher.Fido2Key.RpName?.EncryptedString,
UserHandle = cipher.Fido2Key.UserHandle?.EncryptedString,
UserName = cipher.Fido2Key.UserName?.EncryptedString,
Counter = cipher.Fido2Key.Counter?.EncryptedString
};
break; break;
default: default:
break; break;