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