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 )