Firefox-Marionette
view release on metacpan or search on metacpan
lib/Firefox/Marionette.pm view on Meta::CPAN
$self->_send_request(
[
_COMMAND(),
$message_id,
$self->_command('WebAuthn:SetUserVerified'),
{
authenticatorId => $authenticator->id(),
isUserVerified => $boolean,
}
]
);
my $response = $self->_get_response($message_id);
return $self;
}
sub delete_webauthn_authenticator {
my ( $self, $parameter_authenticator ) = @_;
my $authenticator = $self->_get_webauthn_authenticator(
authenticator => $parameter_authenticator );
my $message_id = $self->_new_message_id();
$self->_send_request(
[
_COMMAND(),
$message_id,
$self->_command('WebAuthn:RemoveVirtualAuthenticator'),
{
authenticatorId => $authenticator->id(),
}
]
);
my $response = $self->_get_response($message_id);
if ( ( $self->webauthn_authenticator() )
&& ( $self->webauthn_authenticator()->id() eq $authenticator->id() ) )
{
delete $self->{$webauthn_default_authenticator_key_name};
}
return $self;
}
sub add_webauthn_credential {
my ( $self, %parameters ) = @_;
foreach my $key (
qw(
is_resident
)
)
{
$parameters{$key} = $self->_translate_to_json_boolean(
$self->_default_to_true( $parameters{$key} ) );
}
if ( !defined $parameters{id} ) {
my $credential_id = MIME::Base64::encode_base64url(
Crypt::URandom::urandom( _CREDENTIAL_ID_LENGTH() ) );
$parameters{id} = $credential_id;
}
if ( defined $parameters{user} ) {
$parameters{user} =
MIME::Base64::encode_base64url( $parameters{user} );
}
if ( !defined $parameters{private_key} ) {
$parameters{private_key} = {};
}
if ( ref $parameters{private_key} eq 'HASH' ) {
my $script = <<'_JS_';
let privateKeyArguments = {};
if (arguments[0]["name"]) {
privateKeyArguments["name"] = arguments[0]["name"];
} else {
privateKeyArguments["name"] = "RSA-PSS";
}
if ((privateKeyArguments["name"] == "RSA-PSS") || (privateKeyArguments["name"] == "RSASSA-PKCS1-v1_5")) {
privateKeyArguments["modulusLength"] = arguments[0]["size"] || 8192;
privateKeyArguments["publicExponent"] = new Uint8Array([1, 0, 1]);
privateKeyArguments["hash"] = arguments[0]["hash"] || "SHA-512";
} else if ((privateKeyArguments["name"] == "ECDSA") || (privateKeyArguments["name"] == "ECDH")) {
privateKeyArguments["namedCurve"] = arguments[0]["curve"] || "P-384";
}
let privateKey = (async function() {
let keyPair = await window.crypto.subtle.generateKey(
privateKeyArguments,
true,
["sign"]
);
let exportedKey = await window.crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
let CHUNK_SZ = 0x8000;
let c = [];
let array = new Uint8Array(exportedKey);
for (let i = 0; i < array.length; i += CHUNK_SZ) {
c.push(String.fromCharCode.apply(null, array.subarray(i, i + CHUNK_SZ)));
}
let b64Key = window.btoa(c.join(""));
let urlSafeKey = b64Key.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
return urlSafeKey;
})();
return privateKey;
_JS_
my $old = $self->_context('chrome');
$parameters{private_key} = $self->script(
$self->_compress_script($script),
args => [ $parameters{private_key} ]
);
$self->_context($old);
}
if ( !defined $parameters{sign_count} ) {
$parameters{sign_count} = 0;
}
my $authenticator = $self->_get_webauthn_authenticator(%parameters);
my %credential_parameters = (
authenticatorId => $authenticator->id(),
credentialId => $parameters{id},
isResidentCredential => $parameters{is_resident},
rpId => $parameters{host},
privateKey => $parameters{private_key},
signCount => $parameters{sign_count},
userHandle => $parameters{user},
);
my $message_id = $self->_new_message_id();
$self->_send_request(
[
_COMMAND(), $message_id, $self->_command('WebAuthn:AddCredential'),
\%credential_parameters,
]
);
if ( defined $credential_parameters{userHandle} ) {
$credential_parameters{userHandle} =
MIME::Base64::decode_base64url( $credential_parameters{userHandle} );
}
my $response = $self->_get_response($message_id);
return Firefox::Marionette::WebAuthn::Credential->new(
%credential_parameters);
}
sub _get_webauthn_authenticator {
my ( $self, %parameters ) = @_;
my $authenticator;
if ( $parameters{authenticator} ) {
$authenticator = $parameters{authenticator};
}
else {
$authenticator = $self->webauthn_authenticator();
}
return $authenticator;
}
sub webauthn_credentials {
my ( $self, $parameter_authenticator ) = @_;
my $authenticator = $self->_get_webauthn_authenticator(
authenticator => $parameter_authenticator );
my $message_id = $self->_new_message_id();
$self->_send_request(
[
_COMMAND(),
$message_id,
$self->_command('WebAuthn:GetCredentials'),
{
authenticatorId => $authenticator->id(),
}
]
);
my $response = $self->_get_response($message_id);
return map { Firefox::Marionette::WebAuthn::Credential->new( %{$_} ) }
map { $self->_decode_credential_user_handle($_) }
@{ $self->_response_result_value($response) };
}
sub _decode_credential_user_handle {
my ( $self, $credential ) = @_;
if ( $credential->{userHandle} eq q[] ) {
$credential->{userHandle} = undef;
}
else {
$credential->{userHandle} =
MIME::Base64::decode_base64url( $credential->{userHandle} );
lib/Firefox/Marionette.pm view on Meta::CPAN
use Firefox::Marionette();
use UUID();
my $firefox = Firefox::Marionette->new();
my $uuid = UUID::uuid();
$firefox->add_site_header( 'metacpan.org', 'Track-my-automated-tests' => $uuid );
$firefox->go('https://metacpan.org/');
these headers are added to any existing headers going to the metacpan.org site, but no other site. To clear site headers, see the L<delete_site_header|/delete_site_header> method
=head2 add_webauthn_authenticator
accepts a hash of the following keys;
=over 4
=item * has_resident_key - boolean value to indicate if the authenticator will support L<client side discoverable credentials|https://www.w3.org/TR/webauthn-2/#client-side-discoverable-credential>
=item * has_user_verification - boolean value to determine if the L<authenticator|https://www.w3.org/TR/webauthn-2/#virtual-authenticators> supports L<user verification|https://www.w3.org/TR/webauthn-2/#user-verification>.
=item * is_user_consenting - boolean value to determine the result of all L<user consent|https://www.w3.org/TR/webauthn-2/#user-consent> L<authorization gestures|https://www.w3.org/TR/webauthn-2/#authorization-gesture>, and by extension, any L<test o...
=item * is_user_verified - boolean value to determine the result of L<User Verification|https://www.w3.org/TR/webauthn-2/#user-verification> performed on the L<Virtual Authenticator|https://www.w3.org/TR/webauthn-2/#virtual-authenticators>. If set to...
=item * protocol - the L<protocol|Firefox::Marionette::WebAuthn::Authenticator#protocol> spoken by the authenticator. This may be L<CTAP1_U2F|Firefox::Marionette::WebAuthn::Authenticator#CTAP1_U2F>, L<CTAP2|Firefox::Marionette::WebAuthn::Authenticat...
=item * transport - the L<transport|Firefox::Marionette::WebAuthn::Authenticator#transport> simulated by the authenticator. This may be L<BLE|Firefox::Marionette::WebAuthn::Authenticator#BLE>, L<HYBRID|Firefox::Marionette::WebAuthn::Authenticator#HY...
=back
It returns the newly created L<authenticator|Firefox::Marionette::WebAuthn::Authenticator>.
use Firefox::Marionette();
use Crypt::URandom();
my $user_name = MIME::Base64::encode_base64( Crypt::URandom::urandom( 10 ), q[] ) . q[@example.com];
my $firefox = Firefox::Marionette->new( webauthn => 0 );
my $authenticator = $firefox->add_webauthn_authenticator( transport => Firefox::Marionette::WebAuthn::Authenticator::INTERNAL(), protocol => Firefox::Marionette::WebAuthn::Authenticator::CTAP2() );
$firefox->go('https://webauthn.io');
$firefox->find_id('input-email')->type($user_name);
$firefox->find_id('register-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('alert-success'); });
$firefox->find_id('login-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });
=head2 add_webauthn_credential
accepts a hash of the following keys;
=over 4
=item * authenticator - contains the L<authenticator|Firefox::Marionette::WebAuthn::Authenticator> that the credential will be added to. If this parameter is not supplied, the credential will be added to the default authenticator, if one exists.
=item * host - contains the domain that this credential is to be used for. In the language of L<WebAuthn|https://www.w3.org/TR/webauthn-2>, this field is referred to as the L<relying party identifier|https://www.w3.org/TR/webauthn-2/#relying-party-i...
=item * id - contains the unique id for this credential, also known as the L<Credential ID|https://www.w3.org/TR/webauthn-2/#credential-id>. If this is not supplied, one will be generated.
=item * is_resident - contains a boolean that if set to true, a L<client-side discoverable credential|https://w3c.github.io/webauthn/#client-side-discoverable-credential> is created. If set to false, a L<server-side credential|https://w3c.github.io/w...
=item * private_key - either a L<RFC5958|https://www.rfc-editor.org/rfc/rfc5958> encoded private key encoded using L<encode_base64url|MIME::Base64::encode_base64url> or a hash containing the following keys;
=over 8
=item * name - contains the name of the private key algorithm, such as "RSA-PSS" (the default), "RSASSA-PKCS1-v1_5", "ECDSA" or "ECDH".
=item * size - contains the modulus length of the private key. This is only valid for "RSA-PSS" or "RSASSA-PKCS1-v1_5" private keys.
=item * hash - contains the name of the hash algorithm, such as "SHA-512" (the default). This is only valid for "RSA-PSS" or "RSASSA-PKCS1-v1_5" private keys.
=item * curve - contains the name of the curve for the private key, such as "P-384" (the default). This is only valid for "ECDSA" or "ECDH" private keys.
=back
=item * sign_count - contains the initial value for a L<signature counter|https://w3c.github.io/webauthn/#signature-counter> associated to the L<public key credential source|https://w3c.github.io/webauthn/#public-key-credential-source>. It will defa...
=item * user - contains the L<userHandle|https://w3c.github.io/webauthn/#public-key-credential-source-userhandle> associated to the credential encoded using L<encode_base64url|MIME::Base64::encode_base64url>. This property is optional.
=back
It returns the newly created L<credential|Firefox::Marionette::WebAuthn::Credential>. If of course, the credential is just created, it probably won't be much good by itself. However, you can use it to recreate a credential, so long as you know all ...
use Firefox::Marionette();
use Crypt::URandom();
my $user_name = MIME::Base64::encode_base64( Crypt::URandom::urandom( 10 ), q[] ) . q[@example.com];
my $firefox = Firefox::Marionette->new();
$firefox->go('https://webauthn.io');
$firefox->find_id('input-email')->type($user_name);
$firefox->find_id('register-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('alert-success'); });
$firefox->find_id('login-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });
foreach my $credential ($firefox->webauthn_credentials()) {
$firefox->delete_webauthn_credential($credential);
# ... time passes ...
$firefox->add_webauthn_credential(
id => $credential->id(),
host => $credential->host(),
user => $credential->user(),
private_key => $credential->private_key(),
is_resident => $credential->is_resident(),
sign_count => $credential->sign_count(),
);
}
$firefox->go('about:blank');
$firefox->clear_cache(Firefox::Marionette::Cache::CLEAR_COOKIES());
$firefox->go('https://webauthn.io');
$firefox->find_id('input-email')->type($user_name);
$firefox->find_id('login-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });
=head2 addons
returns if pre-existing addons (extensions/themes) are allowed to run. This will be true for Firefox versions less than 55, as L<-safe-mode|http://kb.mozillazine.org/Command_line_arguments#List_of_command_line_arguments_.28incomplete.29> cannot be a...
=head2 agent
accepts an optional value for the L<User-Agent|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent> header and sets this using the profile preferences and inserting L<javascript|/script> into the current page. It returns the current ...
This method can be used to set a user agent string like so;
use Firefox::Marionette();
use strict;
# useragents.me should only be queried once a month or less.
# these UA strings should be cached locally.
my %user_agent_strings = map { $_->{ua} => $_->{pct} } @{$firefox->json("https://www.useragents.me/api")->{data}};
my ($user_agent) = reverse sort { $user_agent_strings{$a} <=> $user_agent_strings{$b} } keys %user_agent_strings;
my $firefox = Firefox::Marionette->new();
$firefox->agent($user_agent); # agent is now the most popular agent from useragents.me
If the user agent string that is passed as a parameter looks like a L<Chrome|https://www.google.com/chrome/>, L<Edge|https://microsoft.com/edge> or L<Safari|https://www.apple.com/safari/> user agent string, then this method will also try and change o...
=over 4
=item * general.appversion.override
=item * general.oscpu.override
=item * general.platform.override
=item * network.http.accept
=item * network.http.accept-encoding
=item * network.http.accept-encoding.secure
=item * privacy.donottrackheader.enabled
=back
In addition, this method will accept a hash of values as parameters as well. When a hash is provided, this method will alter specific parts of the normal Firefox User Agent. These hash parameters are;
=over 4
=item * os - The desired operating system, known values are "linux", "win32", "darwin", "freebsd", "netbsd", "openbsd" and "dragonfly"
=item * version - A specific version of firefox, such as 120.
( run in 1.546 second using v1.01-cache-2.11-cpan-354807fb38d )