diff --git a/src/App/Pages/Vault/CipherAddEditPage.xaml b/src/App/Pages/Vault/CipherAddEditPage.xaml
index af2d45810..e5f5f968b 100644
--- a/src/App/Pages/Vault/CipherAddEditPage.xaml
+++ b/src/App/Pages/Vault/CipherAddEditPage.xaml
@@ -208,6 +208,19 @@
IsVisible="{Binding Cipher.ViewPassword}" />
+
+
+
+
diff --git a/src/App/Pages/Vault/CipherAddEditPageViewModel.cs b/src/App/Pages/Vault/CipherAddEditPageViewModel.cs
index ad303dac9..e51ba139c 100644
--- a/src/App/Pages/Vault/CipherAddEditPageViewModel.cs
+++ b/src/App/Pages/Vault/CipherAddEditPageViewModel.cs
@@ -88,7 +88,6 @@ namespace Bit.App.Pages
_watchDeviceService = ServiceContainer.Resolve();
_accountsManager = ServiceContainer.Resolve();
-
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
@@ -310,6 +309,7 @@ namespace Bit.App.Pages
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
+ public bool ShowPasskeyCreationDate => Cipher?.Login?.Fido2Key != null && !CloneMode;
public void Init()
{
@@ -368,6 +368,11 @@ namespace Bit.App.Pages
{
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)
{
diff --git a/src/App/Pages/Vault/CipherDetailsPage.xaml b/src/App/Pages/Vault/CipherDetailsPage.xaml
index 9666743fc..609af3f77 100644
--- a/src/App/Pages/Vault/CipherDetailsPage.xaml
+++ b/src/App/Pages/Vault/CipherDetailsPage.xaml
@@ -45,7 +45,7 @@
x:Name="_attachmentsItem" x:Key="attachmentsItem" />
-
@@ -182,6 +182,18 @@
+
+
+
diff --git a/src/App/Pages/Vault/CipherDetailsPage.xaml.cs b/src/App/Pages/Vault/CipherDetailsPage.xaml.cs
index 3ee45b927..d0566fe96 100644
--- a/src/App/Pages/Vault/CipherDetailsPage.xaml.cs
+++ b/src/App/Pages/Vault/CipherDetailsPage.xaml.cs
@@ -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)
{
if (!DoOnce())
@@ -267,8 +254,7 @@ namespace Bit.App.Pages
}
else if (selection == AppResources.Clone)
{
- var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this);
- await Navigation.PushModalAsync(new NavigationPage(page));
+ _vm.CloneCommand.Execute(null);
}
}
diff --git a/src/App/Pages/Vault/CipherDetailsPageViewModel.cs b/src/App/Pages/Vault/CipherDetailsPageViewModel.cs
index f2b462dc6..169c3cd71 100644
--- a/src/App/Pages/Vault/CipherDetailsPageViewModel.cs
+++ b/src/App/Pages/Vault/CipherDetailsPageViewModel.cs
@@ -69,6 +69,7 @@ namespace Bit.App.Pages
CopyUriCommand = new AsyncCommand(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyFieldCommand = new AsyncCommand(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
LaunchUriCommand = new Command(LaunchUri);
+ CloneCommand = new AsyncCommand(CloneAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode);
@@ -81,6 +82,7 @@ namespace Bit.App.Pages
public ICommand CopyUriCommand { get; set; }
public ICommand CopyFieldCommand { get; set; }
public Command LaunchUriCommand { get; set; }
+ public ICommand CloneCommand { get; set; }
public Command TogglePasswordCommand { get; set; }
public Command ToggleCardNumberCommand { 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 PromptPasswordAsync()
{
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
@@ -686,5 +699,15 @@ namespace Bit.App.Pages
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
}
+
+ private async Task CanCloneAsync()
+ {
+ if (Cipher.Type == CipherType.Login && Cipher.Login?.Fido2Key != null)
+ {
+ return await _platformUtilsService.ShowDialogAsync(AppResources.ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem, AppResources.PasskeyWillNotBeCopied, AppResources.Yes, AppResources.No);
+ }
+
+ return true;
+ }
}
}
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index 87122249b..f843e4cef 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -4696,6 +4696,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Passkey will not be copied.
+ ///
+ public static string PasskeyWillNotBeCopied {
+ get {
+ return ResourceManager.GetString("PasskeyWillNotBeCopied", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Passphrase.
///
@@ -6092,6 +6101,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// 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?.
+ ///
+ public static string ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem {
+ get {
+ return ResourceManager.GetString("ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to There are no items in your vault that match "{0}".
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index 93a2965a0..8330cecfb 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -2632,4 +2632,10 @@ Do you want to switch to this account?
You cannot edit passkey application because it would invalidate the passkey
+
+ Passkey will not be copied
+
+
+ The passkey will not be copied to the cloned item. Do you want to continue cloning this item?
+
diff --git a/src/Core/Models/Api/Fido2KeyApi.cs b/src/Core/Models/Api/Fido2KeyApi.cs
index 666f08456..61f2444af 100644
--- a/src/Core/Models/Api/Fido2KeyApi.cs
+++ b/src/Core/Models/Api/Fido2KeyApi.cs
@@ -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 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 KeyType { get; set; } = Constants.DefaultFido2KeyType;
public string KeyAlgorithm { get; set; } = Constants.DefaultFido2KeyAlgorithm;
diff --git a/src/Core/Models/Domain/Login.cs b/src/Core/Models/Domain/Login.cs
index 5501b043f..9968f43a6 100644
--- a/src/Core/Models/Domain/Login.cs
+++ b/src/Core/Models/Domain/Login.cs
@@ -15,6 +15,7 @@ namespace Bit.Core.Models.Domain
{
PasswordRevisionDate = obj.PasswordRevisionDate;
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
{
"Username",
diff --git a/src/Core/Models/Request/CipherRequest.cs b/src/Core/Models/Request/CipherRequest.cs
index ddb2dbc03..121abc2f8 100644
--- a/src/Core/Models/Request/CipherRequest.cs
+++ b/src/Core/Models/Request/CipherRequest.cs
@@ -30,7 +30,8 @@ namespace Bit.Core.Models.Request
Username = cipher.Login.Username?.EncryptedString,
Password = cipher.Login.Password?.EncryptedString,
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;
case CipherType.Card:
@@ -74,19 +75,7 @@ namespace Bit.Core.Models.Request
};
break;
case CipherType.Fido2Key:
- Fido2Key = new Fido2KeyApi
- {
- 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
- };
+ Fido2Key = new Fido2KeyApi(cipher.Fido2Key);
break;
default:
break;