diff --git a/dotnet/src/webdriver/VirtualAuth/Credential.cs b/dotnet/src/webdriver/VirtualAuth/Credential.cs
index 170677b12e035..57dbcbe76ff45 100644
--- a/dotnet/src/webdriver/VirtualAuth/Credential.cs
+++ b/dotnet/src/webdriver/VirtualAuth/Credential.cs
@@ -18,31 +18,30 @@
//
using OpenQA.Selenium.Internal;
+using System;
using System.Collections.Generic;
+#nullable enable
+
namespace OpenQA.Selenium.VirtualAuth
{
///
/// A credential stored in a virtual authenticator.
- /// Refer https://w3c.github.io/webauthn/#credential-parameters
+ /// Refer
///
- public class Credential
+ public sealed class Credential
{
private readonly byte[] id;
- private readonly bool isResidentCredential;
- private readonly string rpId;
- private readonly string privateKey;
- private readonly byte[] userHandle;
- private readonly int signCount;
+ private readonly byte[]? userHandle;
- private Credential(byte[] id, bool isResidentCredential, string rpId, string privateKey, byte[] userHandle, int signCount)
+ private Credential(byte[] id, bool isResidentCredential, string rpId, string privateKey, byte[]? userHandle, int signCount)
{
- this.id = id;
- this.isResidentCredential = isResidentCredential;
- this.rpId = rpId;
- this.privateKey = privateKey;
+ this.id = id ?? throw new ArgumentNullException(nameof(id));
+ this.IsResidentCredential = isResidentCredential;
+ this.RpId = rpId ?? throw new ArgumentNullException(nameof(rpId));
+ this.PrivateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey));
this.userHandle = userHandle;
- this.signCount = signCount;
+ this.SignCount = signCount;
}
///
@@ -53,6 +52,7 @@ private Credential(byte[] id, bool isResidentCredential, string rpId, string pri
/// The private Key for the credentials.
/// The signature counter for the credentials.
/// The created instance of the Credential class.
+ /// If , , or are .
public static Credential CreateNonResidentCredential(byte[] id, string rpId, string privateKey, int signCount)
{
return new Credential(id, false, rpId, privateKey, null, signCount);
@@ -67,6 +67,7 @@ public static Credential CreateNonResidentCredential(byte[] id, string rpId, str
/// The user handle associated to the credential.
/// The signature counter for the credentials.
/// The created instance of the Credential class.
+ /// If , , or are .
public static Credential CreateResidentCredential(byte[] id, string rpId, string privateKey, byte[] userHandle, int signCount)
{
return new Credential(id, true, rpId, privateKey, userHandle, signCount);
@@ -75,50 +76,32 @@ public static Credential CreateResidentCredential(byte[] id, string rpId, string
///
/// Gets the byte array of the ID of the credential.
///
- public byte[] Id
- {
- get { return (byte[])id.Clone(); }
- }
+ public byte[] Id => (byte[])id.Clone();
///
/// Gets a value indicating whether this Credential is a resident credential.
///
- public bool IsResidentCredential
- {
- get { return this.isResidentCredential; }
- }
+ public bool IsResidentCredential { get; }
///
/// Gets the ID of the relying party of this credential.
///
- public string RpId
- {
- get { return this.rpId; }
- }
+ public string RpId { get; }
///
/// Gets the private key of the credential.
///
- public string PrivateKey
- {
- get { return this.privateKey; }
- }
+ public string PrivateKey { get; }
///
/// Gets the user handle of the credential.
///
- public byte[] UserHandle
- {
- get { return userHandle == null ? null : (byte[])userHandle.Clone(); }
- }
+ public byte[]? UserHandle => (byte[]?)userHandle?.Clone();
///
/// Gets the signature counter associated to the public key credential source.
///
- public int SignCount
- {
- get { return this.signCount; }
- }
+ public int SignCount { get; }
///
/// Creates a Credential instance from a dictionary of values.
@@ -127,13 +110,14 @@ public int SignCount
/// The created instance of the Credential.
public static Credential FromDictionary(Dictionary dictionary)
{
- return new Credential(
- Base64UrlEncoder.DecodeBytes((string)dictionary["credentialId"]),
- (bool)dictionary["isResidentCredential"],
- dictionary.ContainsKey("rpId") ? (string)dictionary["rpId"] : null,
- (string)dictionary["privateKey"],
- dictionary.ContainsKey("userHandle") ? Base64UrlEncoder.DecodeBytes((string)dictionary["userHandle"]) : null,
- (int)((long)dictionary["signCount"]));
+ byte[] id = Base64UrlEncoder.DecodeBytes((string)dictionary["credentialId"]);
+ bool isResidentCredential = (bool)dictionary["isResidentCredential"];
+ string? rpId = dictionary.TryGetValue("rpId", out object? r) ? (string)r : null;
+ string privateKey = (string)dictionary["privateKey"];
+ byte[]? userHandle = dictionary.TryGetValue("userHandle", out object? u) ? Base64UrlEncoder.DecodeBytes((string)u) : null;
+ int signCount = (int)(long)dictionary["signCount"];
+
+ return new Credential(id, isResidentCredential, rpId, privateKey, userHandle, signCount);
}
///
@@ -145,11 +129,11 @@ public Dictionary ToDictionary()
Dictionary toReturn = new Dictionary();
toReturn["credentialId"] = Base64UrlEncoder.Encode(this.id);
- toReturn["isResidentCredential"] = this.isResidentCredential;
- toReturn["rpId"] = this.rpId;
- toReturn["privateKey"] = this.privateKey;
- toReturn["signCount"] = this.signCount;
- if (this.userHandle != null)
+ toReturn["isResidentCredential"] = this.IsResidentCredential;
+ toReturn["rpId"] = this.RpId;
+ toReturn["privateKey"] = this.PrivateKey;
+ toReturn["signCount"] = this.SignCount;
+ if (this.userHandle is not null)
{
toReturn["userHandle"] = Base64UrlEncoder.Encode(this.userHandle);
}
diff --git a/dotnet/src/webdriver/VirtualAuth/IHasVirtualAuthenticator.cs b/dotnet/src/webdriver/VirtualAuth/IHasVirtualAuthenticator.cs
index 92619ded68acb..2d8616416f90e 100644
--- a/dotnet/src/webdriver/VirtualAuth/IHasVirtualAuthenticator.cs
+++ b/dotnet/src/webdriver/VirtualAuth/IHasVirtualAuthenticator.cs
@@ -17,8 +17,11 @@
// under the License.
//
+using System;
using System.Collections.Generic;
+#nullable enable
+
namespace OpenQA.Selenium.VirtualAuth
{
///
@@ -31,18 +34,23 @@ public interface IHasVirtualAuthenticator
///
/// The VirtualAuthenticatorOptions to use in creating the authenticator.
/// The ID of the added virtual authenticator.
+ /// If is .
string AddVirtualAuthenticator(VirtualAuthenticatorOptions options);
///
/// Removes a virtual authenticator.
///
/// The ID of the virtual authenticator to remove.
+ /// If is .
+ /// If the specified virtual authenticator does not exist.
void RemoveVirtualAuthenticator(string id);
///
/// Adds a credential to the virtual authenticator.
///
/// The credential to add to the authenticator.
+ /// If is .
+ /// If a Virtual Authenticator has not been added yet.
void AddCredential(Credential credential);
///
@@ -55,23 +63,29 @@ public interface IHasVirtualAuthenticator
/// Removes a credential from the virtual authenticator.
///
/// A byte array representing the ID of the credential to remove.
+ /// If is .
+ /// If a Virtual Authenticator has not been added yet.
void RemoveCredential(byte[] credentialId);
///
/// Removes a credential from the virtual authenticator.
///
/// A string representing the ID of the credential to remove.
+ /// If is .
+ /// If a Virtual Authenticator has not been added yet.
void RemoveCredential(string credentialId);
///
/// Removes all credentials registered to this virtual authenticator.
///
+ /// If a Virtual Authenticator has not been added yet.
void RemoveAllCredentials();
///
/// Sets whether or not a user is verified in this virtual authenticator.
///
/// if the user is verified; otherwise .
+ /// If a Virtual Authenticator has not been added yet.
void SetUserVerified(bool verified);
}
}
diff --git a/dotnet/src/webdriver/VirtualAuth/VirtualAuthenticatorOptions.cs b/dotnet/src/webdriver/VirtualAuth/VirtualAuthenticatorOptions.cs
index 6e118df89e037..502d1368f9d0f 100644
--- a/dotnet/src/webdriver/VirtualAuth/VirtualAuthenticatorOptions.cs
+++ b/dotnet/src/webdriver/VirtualAuth/VirtualAuthenticatorOptions.cs
@@ -20,6 +20,8 @@
using System;
using System.Collections.Generic;
+#nullable enable
+
namespace OpenQA.Selenium.VirtualAuth
{
///
@@ -78,10 +80,13 @@ public static class Transport
private bool isUserVerified = false;
///
- /// Sets the protocol the Virtual Authenticator speaks
+ /// Sets the Client to Authenticator Protocol (CTAP) this Virtual Authenticator speaks.
///
- /// Valid protocol value
- /// VirtualAuthenticatorOptions
+ /// The CTAP protocol identifier.
+ /// This options instance for chaining.
+ /// Valid protocols are available on the type.
+ /// If is not a supported protocol value.
+ ///
public VirtualAuthenticatorOptions SetProtocol(string protocol)
{
if (string.Equals(Protocol.CTAP2, protocol) || string.Equals(Protocol.U2F, protocol))
@@ -92,15 +97,19 @@ public VirtualAuthenticatorOptions SetProtocol(string protocol)
else
{
throw new ArgumentException("Enter a valid protocol value." +
- "Refer to https://www.w3.org/TR/webauthn-2/#sctn-automation-virtual-authenticators for supported protocols.");
+ "Refer to https://www.w3.org/TR/webauthn-2/#sctn-automation-virtual-authenticators for supported protocols.");
}
}
///
- /// Sets the transport authenticator needs to implement to communicate with clients
+ /// Sets the Authenticator Transport this Virtual Authenticator needs to implement, to communicate with clients.
///
- /// Valid transport value
- /// VirtualAuthenticatorOptions
+ /// Valid transport value.
+ ///
+ /// This options instance for chaining.
+ /// Valid protocols are available on the type.
+ /// If is not a supported transport value.
+ ///
public VirtualAuthenticatorOptions SetTransport(string transport)
{
if (Transport.BLE == transport || Transport.INTERNAL == transport || Transport.NFC == transport || Transport.USB == transport)
@@ -111,16 +120,15 @@ public VirtualAuthenticatorOptions SetTransport(string transport)
else
{
throw new ArgumentException("Enter a valid transport value." +
- "Refer to https://www.w3.org/TR/webauthn-2/#enum-transport for supported transport values.");
+ "Refer to https://www.w3.org/TR/webauthn-2/#enum-transport for supported transport values.");
}
}
///
- /// If set to true the authenticator will support client-side discoverable credentials.
- /// Refer https://w3c.github.io/webauthn/#client-side-discoverable-credential
+ /// If set to , the authenticator will support Client-side discoverable Credentials.
///
- /// boolean value to set
- /// VirtualAuthenticatorOptions
+ /// Whether authenticator will support client-side discoverable credentials.
+ /// This options instance for chaining.
public VirtualAuthenticatorOptions SetHasResidentKey(bool hasResidentKey)
{
this.hasResidentKey = hasResidentKey;
@@ -128,11 +136,10 @@ public VirtualAuthenticatorOptions SetHasResidentKey(bool hasResidentKey)
}
///
- /// If set to true, the authenticator supports user verification.
- /// Refer https://w3c.github.io/webauthn/#user-verification.
+ /// If set to , the authenticator will support User Verification.
///
- /// boolean value to set
- ///
+ /// Whether the authenticator supports user verification.
+ /// This options instance for chaining.
public VirtualAuthenticatorOptions SetHasUserVerification(bool hasUserVerification)
{
this.hasUserVerification = hasUserVerification;
@@ -140,11 +147,10 @@ public VirtualAuthenticatorOptions SetHasUserVerification(bool hasUserVerificati
}
///
- /// If set to true, a user consent will always be granted.
- /// Refer https://w3c.github.io/webauthn/#user-consent
+ /// If set to , a User Consent will always be granted.
///
- /// boolean value to set
- /// VirtualAuthenticatorOptions
+ /// Whether a user consent will always be granted.
+ /// This options instance for chaining.
public VirtualAuthenticatorOptions SetIsUserConsenting(bool isUserConsenting)
{
this.isUserConsenting = isUserConsenting;
@@ -152,11 +158,10 @@ public VirtualAuthenticatorOptions SetIsUserConsenting(bool isUserConsenting)
}
///
- /// If set to true, User Verification will always succeed.
- /// Refer https://w3c.github.io/webauthn/#user-verification
+ /// If set to , User Verification will always succeed.
///
- /// boolean value to set
- /// VirtualAuthenticatorOptions
+ /// Whether User Verification will always succeed.
+ /// This options instance for chaining.
public VirtualAuthenticatorOptions SetIsUserVerified(bool isUserVerified)
{
this.isUserVerified = isUserVerified;
@@ -164,7 +169,7 @@ public VirtualAuthenticatorOptions SetIsUserVerified(bool isUserVerified)
}
///
- /// Serializes this set of options to a dictionary of key-value pairs.
+ /// Serializes this set of options into a dictionary of key-value pairs.
///
/// The dictionary containing the values of this set of options.
public Dictionary ToDictionary()
diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs
index d9960ea97b235..9bcca8b28f8b3 100644
--- a/dotnet/src/webdriver/WebDriver.cs
+++ b/dotnet/src/webdriver/WebDriver.cs
@@ -1036,44 +1036,68 @@ private object ParseJavaScriptReturnValue(object responseValue)
return returnValue;
}
+#nullable enable
+
///
/// Creates a Virtual Authenticator.
///
- /// VirtualAuthenticator Options (https://w3c.github.io/webauthn/#sctn-automation-virtual-authenticators)
+ /// Virtual Authenticator Options.
/// Authenticator id as string
+ /// If is .
public string AddVirtualAuthenticator(VirtualAuthenticatorOptions options)
{
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
Response commandResponse = this.Execute(DriverCommand.AddVirtualAuthenticator, options.ToDictionary());
- string id = commandResponse.Value.ToString();
+ string id = (string)commandResponse.Value!;
this.AuthenticatorId = id;
- return this.AuthenticatorId;
+ return id;
}
///
/// Removes the Virtual Authenticator
///
- /// Id as string that uniquely identifies a Virtual Authenticator
+ /// Id as string that uniquely identifies a Virtual Authenticator.
+ /// If is .
public void RemoveVirtualAuthenticator(string authenticatorId)
{
+ if (authenticatorId is null)
+ {
+ throw new ArgumentNullException(nameof(authenticatorId));
+ }
+
Dictionary parameters = new Dictionary();
- parameters.Add("authenticatorId", this.AuthenticatorId);
+ parameters.Add("authenticatorId", authenticatorId);
+
this.Execute(DriverCommand.RemoveVirtualAuthenticator, parameters);
this.AuthenticatorId = null;
}
///
- /// Gets the virtual authenticator ID for this WebDriver instance.
+ /// Gets the cached virtual authenticator ID, or if no authenticator ID is set.
///
- public string AuthenticatorId { get; private set; }
+ public string? AuthenticatorId { get; private set; }
///
/// Add a credential to the Virtual Authenticator/
///
/// The credential to be stored in the Virtual Authenticator
+ /// If is .
+ /// If a Virtual Authenticator has not been added yet.
public void AddCredential(Credential credential)
{
+ if (credential is null)
+ {
+ throw new ArgumentNullException(nameof(credential));
+ }
+
+ string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
+
Dictionary parameters = new Dictionary(credential.ToDictionary());
- parameters.Add("authenticatorId", this.AuthenticatorId);
+ parameters.Add("authenticatorId", authenticatorId);
this.Execute(driverCommandToExecute: DriverCommand.AddCredential, parameters);
}
@@ -1082,18 +1106,25 @@ public void AddCredential(Credential credential)
/// Retrieves all the credentials stored in the Virtual Authenticator
///
/// List of credentials
+ /// If a Virtual Authenticator has not been added yet.
public List GetCredentials()
{
+ string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
+
Dictionary parameters = new Dictionary();
- parameters.Add("authenticatorId", this.AuthenticatorId);
+ parameters.Add("authenticatorId", authenticatorId);
- object[] commandResponse = (object[])this.Execute(driverCommandToExecute: DriverCommand.GetCredentials, parameters).Value;
+ Response getCredentialsResponse = this.Execute(driverCommandToExecute: DriverCommand.GetCredentials, parameters);
- List credentials = new List();
+ if (getCredentialsResponse.Value is not object?[] credentialsList)
+ {
+ throw new WebDriverException($"Get credentials call succeeded, but the response was not a list of credentials: {getCredentialsResponse.Value}");
+ }
- foreach (object dictionary in commandResponse)
+ List credentials = new List(credentialsList.Length);
+ foreach (object? dictionary in credentialsList)
{
- Credential credential = Credential.FromDictionary((Dictionary)dictionary);
+ Credential credential = Credential.FromDictionary((Dictionary)dictionary!);
credentials.Add(credential);
}
@@ -1104,6 +1135,8 @@ public List GetCredentials()
/// Removes the credential identified by the credentialId from the Virtual Authenticator.
///
/// The id as byte array that uniquely identifies a credential
+ /// If is .
+ /// If a Virtual Authenticator has not been added yet.
public void RemoveCredential(byte[] credentialId)
{
RemoveCredential(Base64UrlEncoder.Encode(credentialId));
@@ -1113,10 +1146,19 @@ public void RemoveCredential(byte[] credentialId)
/// Removes the credential identified by the credentialId from the Virtual Authenticator.
///
/// The id as string that uniquely identifies a credential
+ /// If is .
+ /// If a Virtual Authenticator has not been added yet.
public void RemoveCredential(string credentialId)
{
+ if (credentialId is null)
+ {
+ throw new ArgumentNullException(nameof(credentialId));
+ }
+
+ string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
+
Dictionary parameters = new Dictionary();
- parameters.Add("authenticatorId", this.AuthenticatorId);
+ parameters.Add("authenticatorId", authenticatorId);
parameters.Add("credentialId", credentialId);
this.Execute(driverCommandToExecute: DriverCommand.RemoveCredential, parameters);
@@ -1125,10 +1167,13 @@ public void RemoveCredential(string credentialId)
///
/// Removes all the credentials stored in the Virtual Authenticator.
///
+ /// If a Virtual Authenticator has not been added yet.
public void RemoveAllCredentials()
{
+ string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
+
Dictionary parameters = new Dictionary();
- parameters.Add("authenticatorId", this.AuthenticatorId);
+ parameters.Add("authenticatorId", authenticatorId);
this.Execute(driverCommandToExecute: DriverCommand.RemoveAllCredentials, parameters);
}
@@ -1139,8 +1184,10 @@ public void RemoveAllCredentials()
/// The boolean value representing value to be set
public void SetUserVerified(bool verified)
{
+ string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
+
Dictionary parameters = new Dictionary();
- parameters.Add("authenticatorId", this.AuthenticatorId);
+ parameters.Add("authenticatorId", authenticatorId);
parameters.Add("isUserVerified", verified);
this.Execute(driverCommandToExecute: DriverCommand.SetUserVerified, parameters);
diff --git a/dotnet/test/common/VirtualAuthn/VirtualAuthenticatorTest.cs b/dotnet/test/common/VirtualAuthn/VirtualAuthenticatorTest.cs
index e6c16e11b673a..c46d541dcb548 100644
--- a/dotnet/test/common/VirtualAuthn/VirtualAuthenticatorTest.cs
+++ b/dotnet/test/common/VirtualAuthn/VirtualAuthenticatorTest.cs
@@ -194,6 +194,26 @@ public void ShouldRemoveAuthenticator()
Assert.IsNull(webDriver.AuthenticatorId);
}
+ [Test]
+ [NeedsFreshDriver(IsCreatedAfterTest = true)]
+ [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")]
+ [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")]
+ [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")]
+ public void ShouldSupportMultipleVirtualAuthenticatorsAtOnce()
+ {
+ VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions();
+
+ string authenticatorId1 = webDriver.AddVirtualAuthenticator(options);
+ Assert.That(webDriver.AuthenticatorId, Is.EqualTo(authenticatorId1));
+
+ string authenticatorId2 = webDriver.AddVirtualAuthenticator(options);
+
+ webDriver.RemoveVirtualAuthenticator(authenticatorId1);
+ webDriver.RemoveVirtualAuthenticator(authenticatorId2);
+
+ Assert.IsNull(webDriver.AuthenticatorId);
+ }
+
[Test]
[NeedsFreshDriver(IsCreatedAfterTest = true)]
[IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")]
@@ -302,7 +322,9 @@ public void AddResidentCredentialNotSupportedWhenAuthenticatorUsesU2FProtocol()
byte[] userHandle = { 1 };
Credential credential = Credential.CreateResidentCredential(
credentialId, "localhost", base64EncodedEC256PK, userHandle, /*signCount=*/0);
- Assert.Throws(() => webDriver.AddCredential(credential));
+ Assert.That(
+ () => webDriver.AddCredential(credential),
+ Throws.TypeOf().With.Message.Contains("The Authenticator does not support Resident Credentials."));
}
[Test]
@@ -492,5 +514,37 @@ public void testSetUserVerified()
Assert.True(error.StartsWith("NotAllowedError"));
}
+
+ [Test]
+ [NeedsFreshDriver(IsCreatedAfterTest = true)]
+ [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")]
+ [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")]
+ [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")]
+ public void ShouldThrowOnInvalidArguments()
+ {
+ Assert.That(
+ () => webDriver.AddVirtualAuthenticator(null),
+ Throws.ArgumentNullException);
+
+ Assert.That(
+ () => webDriver.RemoveVirtualAuthenticator(null),
+ Throws.ArgumentNullException);
+
+ Assert.That(
+ () => webDriver.AddCredential(null),
+ Throws.ArgumentNullException);
+
+ Assert.That(
+ () => webDriver.RemoveCredential((byte[])null),
+ Throws.ArgumentNullException);
+
+ Assert.That(
+ () => webDriver.RemoveCredential((string)null),
+ Throws.ArgumentNullException);
+
+ Assert.That(
+ () => webDriver.RemoveVirtualAuthenticator("non-existant"),
+ Throws.TypeOf());
+ }
}
}