Google-Auth

 view release on metacpan or  search on metacpan

XS.xs  view on Meta::CPAN

        if (ec_sig) ECDSA_SIG_free(ec_sig);
        if (der_buf) Safefree(der_buf);

        if (verify_res > 0) {
            XSRETURN_YES;
        } else {
            XSRETURN_NO;
        }

SV *
rsa_sign_sha256(SV *private_key_pem_sv, SV *message_sv)
    PREINIT:
        char *pem_str = NULL;
        char *msg_str = NULL;
        STRLEN pem_len, msg_len;
        BIO *bio = NULL;
        EVP_PKEY *pkey = NULL;
        EVP_MD_CTX *mdctx = NULL;
        unsigned char sig[4096];
        unsigned int sig_len = 0;
        SV *retval = NULL;
    CODE:
        pem_str = SvPV(private_key_pem_sv, pem_len);
        msg_str = SvPV(message_sv, msg_len);

        bio = BIO_new_mem_buf(pem_str, pem_len);
        if (!bio) XSRETURN_UNDEF;

        pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
        BIO_free(bio);
        if (!pkey) XSRETURN_UNDEF;

        mdctx = EVP_MD_CTX_new();

lib/Google/Auth/DefaultCredentials.pm  view on Meta::CPAN

      scope    => $scopes,
      %options
    );
  }

  # If no explicit json info was provided, fallback to checking environment variables
  my $env = $self->environment;
  if ($ENV{GOOGLE_PRIVATE_KEY} && $ENV{GOOGLE_CLIENT_EMAIL}) {
    require Google::Auth::ServiceAccountCredentials;
    return Google::Auth::ServiceAccountCredentials->new(
      private_key  => $ENV{GOOGLE_PRIVATE_KEY},
      client_email => $ENV{GOOGLE_CLIENT_EMAIL},
      scope        => $scopes,
      %options
    );
  }

  return;
}

sub from_env {

lib/Google/Auth/ServiceAccountCredentials.pm  view on Meta::CPAN

use Google::Auth;
use Google::Auth::Exceptions;
use Google::Auth::RetryHelper;
use Log::Any qw($log);

has json_key => (
  is       => 'ro',
  required => 0,
);

has private_key_id => (
  is       => 'ro',
  required => 0,
);

has private_key => (
  is       => 'ro',
  required => 0,
);

has client_email => (
  is       => 'ro',
  required => 0,
);

has client_id => (

lib/Google/Auth/ServiceAccountCredentials.pm  view on Meta::CPAN

    return $ua;
  },
);

around BUILDARGS => sub {
  my ($orig, $class, @args) = @_;
  my $args = $class->$orig(@args);

  if (my $json = $args->{json_key}) {
    $args->{project_id}     //= $json->{project_id};
    $args->{private_key_id} //= $json->{private_key_id};
    $args->{private_key}    //= $json->{private_key};
    $args->{client_email}   //= $json->{client_email};
    $args->{client_id}      //= $json->{client_id};
    $args->{auth_uri}       //= $json->{auth_uri};
    $args->{token_uri}      //= $json->{token_uri};
    $args->{auth_provider_x509_cert_url} //=
      $json->{auth_provider_x509_cert_url};
    $args->{client_x509_cert_url} //= $json->{client_x509_cert_url};
  }

  return $args;

lib/Google/Auth/ServiceAccountCredentials.pm  view on Meta::CPAN

sub _encode_base64url {
  my ($data) = @_;
  my $s = MIME::Base64::encode_base64url($data);
  $s =~ s/=+$//;
  return $s;
}

sub fetch_access_token {
  my ($self, %options) = @_;

  my $private_key  = $self->private_key;
  my $client_email = $self->client_email;
  my $token_uri    = $self->token_uri;

  $self->_validate_url($token_uri, 'token_uri');

  if (!defined $private_key || !defined $client_email) {
    $log->errorf(
'Missing private_key or client_email for ServiceAccountCredentials token exchange'
    );
    Google::Auth::Error->throw(
      'Missing private_key or client_email to sign and fetch token');
  }

  my $now    = time();
  my $header = {
    alg => 'RS256',
    typ => 'JWT',
  };
  $header->{kid} = $self->private_key_id if defined $self->private_key_id;

  my $scope = $self->scope;
  if (ref($scope) eq 'ARRAY') {
    $scope = join(' ', @$scope);
  }

  my $payload = {
    iss => $client_email,
    sub => $client_email,
    aud => $token_uri,
    exp => $now + 3600,
    iat => $now,
  };
  $payload->{scope} = $scope if defined $scope;

  my $header_b64  = _encode_base64url(encode_json($header));
  my $payload_b64 = _encode_base64url(encode_json($payload));
  my $message     = $header_b64 . '.' . $payload_b64;

  $log->tracef('Signing JWT assertion for service account %s (key ID: %s)...',
    $client_email, $self->private_key_id // 'N/A');
  my $signature_raw = Google::Auth::rsa_sign_sha256($private_key, $message);
  if (!defined $signature_raw) {
    $log->errorf('Failed to sign JWT assertion for service account %s',
      $client_email);
    Google::Auth::Error->throw(
      'Failed to sign JWT assertion using private key');
  }
  my $signature_b64 = _encode_base64url($signature_raw);
  my $assertion     = $message . '.' . $signature_b64;

  my $ua        = $self->ua;

t/05-default-credentials.t  view on Meta::CPAN

  && ($tmpdir eq '\\' || $tmpdir eq '/' || $tmpdir =~ /^[a-zA-Z]:\\?$/))
{
  $tmpdir = $ENV{RUNNER_TEMP} || $ENV{TEMP} || $ENV{TMP} || '.';
}
($tmpdir) = $tmpdir =~ /^(.*)$/;

subtest 'from_env loading valid service account credentials' => sub {
  my $sa_data = {
    type           => 'service_account',
    project_id     => 'test-project-123',
    private_key_id => 'abcd1234efgh5678',
    private_key    =>
      '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----',
    client_email => 'test-sa@test-project-123.iam.gserviceaccount.com',
    client_id    => '1234567890',
    auth_uri     => 'https://accounts.google.com/o/oauth2/auth',
    token_uri    => 'https://oauth2.googleapis.com/token',
    auth_provider_x509_cert_url => 'https://www.googleapis.com/oauth2/v1/certs',
    client_x509_cert_url        =>
'https://www.googleapis.com/robot/v1/metadata/x509/test-sa%40test-project-123.iam.gserviceaccount.com',
  };
  my $sa_json = encode_json($sa_data);

t/05-default-credentials.t  view on Meta::CPAN

  ok(defined $creds, 'credentials returned');
  isa_ok($creds, 'Google::Auth::ServiceAccountCredentials');

  if ($creds) {
    is($creds->project_id, 'test-project-123', 'project_id matches');
    is(
      $creds->client_email,
      'test-sa@test-project-123.iam.gserviceaccount.com',
      'client_email matches'
    );
    is($creds->private_key_id, 'abcd1234efgh5678', 'private_key_id matches');
  }

  delete $ENV{GOOGLE_APPLICATION_CREDENTIALS};
};

subtest 'from_env loading invalid JSON' => sub {
  my ($fh, $filename) = tempfile(UNLINK => 1, DIR => $tmpdir);
  print $fh 'not a valid json string';
  close($fh);

t/13-impersonated-credentials.t  view on Meta::CPAN

subtest 'Load from JSON configuration' => sub {
  my $json_config = {
    type                              => 'impersonated_service_account',
    service_account_impersonation_url =>
'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/target-sa@google.com:generateAccessToken',
    scopes             => ['https://www.googleapis.com/auth/compute'],
    source_credentials => {
      type         => 'service_account',
      project_id   => 'test-project',
      client_email => 'source-sa@google.com',
      private_key  => 'some-private-key',
    }};

  require Google::Auth::DefaultCredentials;
  my $creds =
    Google::Auth::DefaultCredentials->make_creds(json_key => $json_config,);

  ok(defined $creds, 'loaded credentials successfully');
  isa_ok($creds, 'Google::Auth::ImpersonatedServiceAccountCredentials');
  is(
    $creds->impersonation_url,

t/17-logging-verification.t  view on Meta::CPAN

use Google::Auth::RetryHelper;

subtest 'ServiceAccountCredentials Logging' => sub {
  my $mock_ua = Test::LWP::UserAgent->new();

  my $keypair    = Google::Auth::generate_self_signed_cert();
  my $valid_pkey = $keypair->{key};

  my $creds = Google::Auth::ServiceAccountCredentials->new(
    project_id     => 'my-project',
    private_key    => $valid_pkey,
    client_email   => 'test-sa@google.com',
    private_key_id => 'key-12345',
    token_uri      => 'https://oauth2.googleapis.com/token',
    ua             => $mock_ua,
  );

  $mock_ua->map_response(
    qr/oauth2.googleapis.com\/token/,
    HTTP::Response->new(
      200, 'OK',
      ['Content-Type' => 'application/json'],
      encode_json({

t/17-logging-verification.t  view on Meta::CPAN

};

subtest 'RetryHelper and Error Logging' => sub {
  my $mock_ua = Test::LWP::UserAgent->new();

  my $keypair    = Google::Auth::generate_self_signed_cert();
  my $valid_pkey = $keypair->{key};

  my $creds = Google::Auth::ServiceAccountCredentials->new(
    project_id   => 'my-project',
    private_key  => $valid_pkey,
    client_email => 'test-sa@google.com',
    token_uri    => 'https://oauth2.googleapis.com/token',
    ua           => $mock_ua,
  );

  $mock_ua->map_response(
    qr/oauth2.googleapis.com\/token/,
    HTTP::Response->new(
      503,
      'Service Unavailable',

t/18-sa-token-uri-ssrf.t  view on Meta::CPAN

# 1. Test invalid domain (SSRF Sink)
subtest 'ServiceAccountCredentials token_uri SSRF Validation' => sub {
  plan tests => 2;

  my $mock_ua = Test::LWP::UserAgent->new();

  my $sa = Google::Auth::DefaultCredentials->make_creds(
    json_key => {
      type           => 'service_account',
      project_id     => 'victim-project',
      private_key_id => 'victim-key-id',
      private_key    => $valid_pkey,
      client_email   => 'victim-sa@victim-project.iam.gserviceaccount.com',
      token_uri      => 'http://evil.com/token',    # Attacker URL
    },
    scope => 'https://www.googleapis.com/auth/cloud-platform',
    ua    => $mock_ua,
  );

  # It should throw before even using UA if validation works
  my $token = eval { $sa->fetch_access_token() };
  my $err   = $@;

t/18-sa-token-uri-ssrf.t  view on Meta::CPAN

      ['Content-Type' => 'application/json'],
      encode_json({
          access_token => 'valid-token',
          expires_in   => 3600
        })));

  my $sa = Google::Auth::DefaultCredentials->make_creds(
    json_key => {
      type           => 'service_account',
      project_id     => 'victim-project',
      private_key_id => 'victim-key-id',
      private_key    => $valid_pkey,
      client_email   => 'victim-sa@victim-project.iam.gserviceaccount.com',
      token_uri      => 'https://oauth2.googleapis.com/token',    # Valid URL
    },
    scope => 'https://www.googleapis.com/auth/cloud-platform',
    ua    => $mock_ua,
  );

  my $token = eval { $sa->fetch_access_token() };
  is($@,     '',            'no exception thrown for valid URL');
  is($token, 'valid-token', 'token fetched successfully');



( run in 1.137 second using v1.01-cache-2.11-cpan-8dfa8b56332 )