3

perl で Google のサービス アカウント フローを使用して、アプリケーションを認証および承認する必要があります。Google は、サポートされている言語として perl をドキュメントに記載していないようです。この問題に直面した人はいますか?そこにコードへのポインタはありますか?

4

1 に答える 1

0

perl Google OAUTH を検索すると、さまざまなアプローチが見つかります。

適切に構成された Google Cloud API プロジェクトの OAUTH トークンを収集するために使用できるクイック ウェブサーバーの例については、次を参照してください。

#!perl

use strict; use warnings; ## required because I can't work out how to get percritic to use my modern config
package goauth;

# ABSTRACT: CLI tool with mini http server for negotiating Google OAuth2 Authorisation access tokens that allow offline access to Google API Services on behalf of the user. 

# 
# Supports multiple users
# similar to that installed as part of the WebService::Google module
# probably originally based on https://gist.github.com/throughnothing/3726907

# OAuth2 for Google. You can find the key (CLIENT ID) and secret (CLIENT SECRET) from the app console here under "APIs & Auth" 
# and "Credentials" in the menu at https://console.developers.google.com/project.

# See also https://developers.google.com/+/quickstart/.

use strict;
use warnings;
use Carp;
use Mojolicious::Lite;
use Data::Dumper;
use Config::JSON;
use Tie::File;
use feature 'say';
use Net::EmptyPort qw(empty_port);

use Crypt::JWT qw(decode_jwt);

my $filename;
if ( $ARGV[0] )
{
  $filename = $ARGV[0];
}
else
{
  $filename = './gapi.json';
}

if ( -e $filename )
{
  say "File $filename exists";
  input_if_not_exists( ['gapi/client_id', 'gapi/client_secret', 'gapi/scopes'] ); ## this potentially allows mreging with a json file with data external
                                                                                  ## to the app or to augment missing scope from file generated from 
                                                                                  ##  earlier versions of goauth from other libs
  runserver();
}
else
{
  say "JSON file '$filename' with OAUTH App Secrets and user tokens not found. Creating new file...";
  setup();
  runserver();
}

sub setup
{
  ## TODO: consider allowing the gapi.json to be either seeded or to extend the credentials.json provided by Google
  my $oauth = {};
  say "Obtain project app client_id and client_secret from http://console.developers.google.com/";
  print "client_id: ";

  $oauth->{ client_id } = _stdin() || croak( 'client_id is required and has no default' );
  print "client_secret: ";

  $oauth->{ client_secret } = _stdin() || croak( 'client secret is required and has no default' );

  print 'scopes ( space sep list): eg - email profile https://www.googleapis.com/auth/plus.profile.emails.read '
    . "https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/contacts.readonly https://mail.google.com\n";

  $oauth->{ scopes } = _stdin();    ## no croak because empty string is allowed an will evoke defaults

  ## set default scope if empty string provided
  if ( $oauth->{ scopes } eq '' )
  {
    $oauth->{ scopes }
      = 'email profile https://www.googleapis.com/auth/plus.profile.emails.read '
      . 'https://www.googleapis.com/auth/calendar '
      . 'https://www.googleapis.com/auth/contacts.readonly https://mail.google.com';
  }

  my $tokensfile = Config::JSON->create( $filename );
  $tokensfile->set( 'gapi/client_id',     $oauth->{ client_id } );
  $tokensfile->set( 'gapi/client_secret', $oauth->{ client_secret } );
  $tokensfile->set( 'gapi/scopes',        $oauth->{ scopes } );
  say 'OAuth details  updated!';

  # Remove comment for Mojolicious::Plugin::JSONConfig compatibility
  tie my @array, 'Tie::File', $filename or croak $!;
  shift @array;
  untie @array;
  return 1;
}

sub input_if_not_exists
{
  my $fields = shift;
  my $config = Config::JSON->new( $filename );
  for my $i ( @$fields )
  {
    if ( !defined $config->get( $i ) )
    {
      print "$i: ";

      #chomp( my $val = <STDIN> );
      my $val = _stdin();
      $config->set( $i, $val );
    }
  }
  return 1;
}

sub runserver
{
  my $port = empty_port( 3000 );
  say "Starting web server. Before authorization don't forget to allow redirect_uri to http://127.0.0.1 in your Google Console Project";
  $ENV{ 'GOAUTH_TOKENSFILE' } = $filename;

  my $config = Config::JSON->new( $ENV{ 'GOAUTH_TOKENSFILE' } );


  # authorize_url and token_url can be retrieved from OAuth discovery document
  # https://github.com/marcusramberg/Mojolicious-Plugin-OAuth2/issues/52
  plugin "OAuth2" => {
    google => {
      key           => $config->get( 'gapi/client_id' ),                                    # $config->{gapi}{client_id},
      secret        => $config->get( 'gapi/client_secret' ),                                #$config->{gapi}{client_secret},
      authorize_url => 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code',
      token_url     => 'https://www.googleapis.com/oauth2/v4/token' ## NB Google credentials.json specifies "https://www.googleapis.com/oauth2/v3/token" 
    }
  };

# Marked for decomission
#  helper get_email => sub {
#    my ( $c, $access_token ) = @_;
#   my %h = ( 'Authorization' => 'Bearer ' . $access_token );
#    $c->ua->get( 'https://www.googleapis.com/auth/plus.profile.emails.read' => form => \%h )->res->json;
#  };

  helper get_new_tokens => sub {
    my ( $c, $auth_code ) = @_;
    my $hash = {};
    $hash->{ code }          = $c->param( 'code' );
    $hash->{ redirect_uri }  = $c->url_for->to_abs->to_string;
    $hash->{ client_id }     = $config->get( 'gapi/client_id' );
    $hash->{ client_secret } = $config->get( 'gapi/client_secret' );
    $hash->{ grant_type }    = 'authorization_code';
    my $tokens = $c->ua->post( 'https://www.googleapis.com/oauth2/v4/token' => form => $hash )->res->json;
    return $tokens;
  };

  get "/" => sub {
    my $c = shift;
    $c->{ config } = $config;
    app->log->info( "Will store tokens in" . $config->getFilename( $config->pathToFile ) );

    if ( $c->param( 'code' ) ) ## postback from google 
    {
      app->log->info( "Authorization code was retrieved: " . $c->param( 'code' ) );
      my $tokens = $c->get_new_tokens( $c->param( 'code' ) );
      app->log->info( "App got new tokens: " . Dumper $tokens);
      if ( $tokens )
      {
        my $user_data;
        if ( $tokens->{ id_token } )
        {
          # my $jwt = Mojo::JWT->new(claims => $tokens->{id_token});
          # carp "Mojo header:".Dumper $jwt->header;

          # my $keys = $c->get_all_google_jwk_keys(); # arrayref
          # my ($header, $data) = decode_jwt( token => $tokens->{id_token}, decode_header => 1, key => '' ); # exctract kid
          # carp "Decode header :".Dumper $header;

          $user_data = decode_jwt( token => $tokens->{ id_token }, kid_keys => $c->ua->get( 'https://www.googleapis.com/oauth2/v3/certs' )->res->json, );
          #carp "Decoded user data:" . Dumper $user_data;
        }

        #$user_data->{email};
        #$user_data->{family_name}
        #$user_data->{given_name}
        # $tokensfile->set('tokens/'.$user_data->{email}, $tokens->{access_token});
        $config->addToHash( 'gapi/tokens/' . $user_data->{ email }, 'access_token', $tokens->{ access_token } );

        if ( $tokens->{ refresh_token } )
        {
          $config->addToHash( 'gapi/tokens/' . $user_data->{ email }, 'refresh_token', $tokens->{ refresh_token } );
        }
        else ## with access_type=offline set we should receive a refresh token unless user already has an active one.
        {
          carp('Google JWT Did not incude a refresh token - when the access token expires services will become inaccessible');
        }
      }
      $c->render( json => $config->get( 'gapi' ) );
    }
    else ## PRESENT USER DEFAULT PAGE TO REQUEST GOOGLE AUTH'D ACCESS TO SERVICES
    {
      $c->render( template => 'oauth' );
    }
  };
  app->secrets( ['putyourownsecretcookieseedhereforsecurity' . time] ); ## NB persistence cookies not required beyond server run
  app->start( 'daemon', '-l', "http://*:$port" );
  return 1;
}

## replacement for STDIN as per https://coderwall.com/p/l9-uvq/reading-from-stdin-the-good-way
sub _stdin
{
  my $io;
  my $string = q{};

  $io = IO::Handle->new();
  if ( $io->fdopen( fileno( STDIN ), 'r' ) )
  {
    $string = $io->getline();
    $io->close();
  }
  chomp $string;
  return $string;
}


=head2 TODO: Improve user interface of the HTML templates beneath DATA section

=over 1

=item * include Auth with Google button from Google Assets and advertise scopes reqeusted on the oauth.html

=item * More informative details on post-authentication page - perhaps include scopes, filename updated and instructions on revoking

=back

=cut

__DATA__

@@ oauth.html.ep

<%= link_to "Click here to get Google OAUTH2 tokens", $c->oauth2->auth_url("google",
    authorize_query => { access_type => 'offline'},
        scope => $c->{config}->get('gapi/scopes'), ## scope => "email profile https://www.googleapis.com/auth/plus.profile.emails.read https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/contacts.readonly",
         )
%>

<br>
<br>

<a href="https://developers.google.com/+/web/api/rest/oauth#authorization-scopes">
Check more about authorization scopes</a>
Once you have a token in your gapi.json you can check the available scopes with curl using <pre>curl https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=<YOUR_ACCESS_TOKEN></pre>

__END__
于 2018-10-09T02:28:51.573 に答える