EMDIS-ECS
view release on metacpan or search on metacpan
script/ecs_token view on Meta::CPAN
# following the __END__ marker in this file, or run "perldoc ecs_token".
use EMDIS::ECS qw(timelimit_cmd);
use Getopt::Long;
use JSON::PP qw(decode_json encode_json);
use LWP::UserAgent;
use Term::ReadLine;
use URI::Escape;
my $SECSTOR_LOCATION = {
auth_endpoint => 'emdis/ecs/oauth/auth_endpoint',
cached_token_response => 'emdis/ecs/oauth/cached_token_response',
cached_token_timestamp => 'emdis/ecs/oauth/cached_token_timestamp',
client_id => 'emdis/ecs/oauth/client_id',
client_secret => 'emdis/ecs/oauth/client_secret',
redirect_uri => 'emdis/ecs/oauth/redirect_uri',
refresh_token => 'emdis/ecs/oauth/refresh_token',
scope => 'emdis/ecs/oauth/scope',
token_endpoint => 'emdis/ecs/oauth/token_endpoint',
};
my $SECSTOR_TIMELIMIT = 3;
my $CACHED_TOKEN_EXPIRATION_MARGIN = 600;
# add --nocache option
my $USAGE =
"Usage:$/" .
" ecs_token <command> [options]$/" .
"Where:$/" .
" <command> is code, credentials, or refresh$/" .
" code [options] are:$/" .
" --auth_endpoint <auth_endpoint>$/" .
" --client_id <client_id>$/" .
" --client_secret <client_secret>$/" .
" --nocache$/" .
" --redirect_uri <redirect_uri>$/" .
" --scope <scope>$/" .
" --token_endpoint <token_endpoint>$/" .
" credentials [options] are:$/" .
" --client_id <client_id>$/" .
" --client_secret <client_secret>$/" .
" --nocache$/" .
" --scope <scope>$/" .
" --token_endpoint <token_endpoint>$/" .
" refresh [options] are:$/" .
" --client_id <client_id>$/" .
" --client_secret <client_secret>$/" .
" --nocache$/" .
" --refresh_token <refresh_token>$/" .
" --token_endpoint <token_endpoint>$/" .
" [options] not present on command line will be read from secure storage$/" .
"For details, refer to documentation:$/" .
" perldoc ecs_token$/";
my %options = ();
GetOptions(\%options, 'auth_endpoint=s', 'client_id=s', 'client_secret=s',
'nocache', 'redirect_uri=s', 'refresh_token=s', 'scope=s',
'token_endpoint=s')
or die "Error - Unrecognized command line option$/" . $USAGE;
my $command = ($#ARGV == 0 ? $ARGV[0] : '');
die "Error - unrecognized, invalid, or missing <command>$/" . $USAGE
unless $command eq 'code' or $command eq 'credentials' or $command eq 'refresh';
# if configured, have gpg-agent cache GnuPG passphrase used by "pass"
if(exists $ENV{PASS_GPG_KEYGRIP} and exists $ENV{PASS_GPG_PASSPHRASE}) {
# use gpgconf to ensure gpg-agent is started
script/ecs_token view on Meta::CPAN
}
# define LWP user agent
my $user_agent = LWP::UserAgent->new;
$user_agent->agent("PerlECS/$EMDIS::ECS::VERSION ");
if($command eq 'code') {
# using authorization code flow ...
# get configuration parameters
my $auth_endpoint = get_config_param('auth_endpoint');
my $client_id = get_config_param('client_id');
my $client_secret = get_config_param('client_secret');
my $nocache = exists $options{nocache};
my $redirect_uri = get_config_param('redirect_uri');
my $scope = get_config_param('scope');
my $token_endpoint = get_config_param('token_endpoint');
# fail fast if command line contains unsupported options
die "Error - Option(s) unsupported for \"code\" command$/" . $USAGE
if exists $options{refresh_token};
# construct Term::Readline object for interactive I/O
my $term = new Term::ReadLine("ECS New Access Token Dialog")
or die "Error - Unable to initialize Term::ReadLine.$/";
$term->ornaments(0);
my $OUT = $term->OUT || *STDOUT;
# Construct URL to request authorization code.
# uses client id, redirect uri, scope, and auth endpoint
my $url = $auth_endpoint .
'?client_id=' . uri_escape($client_id) .
'&redirect_uri=' . uri_escape($redirect_uri) .
'&scope=' . uri_escape($scope) .
'&response_type=code' .
'&access_type=offline' .
'&prompt=consent';
# Using a web browser, while logged in to the EMDIS email account, the
# user visits the authorization code URL and navigates the approval flow
# to obtain the authorization code and paste it here.
print $OUT "To authorize token, using a web browser logged in to the EMDIS email$/" .
"account, visit this url and follow the directions:$/";
print $OUT " $url$/";
my $authorization_code = $term->readline("Enter authorization code: ");
my $token_request_timestamp = time;
# use authorization code, client id, client secret, and redirect uri
# to request access token from token endpoint
my $response = $user_agent->post($token_endpoint, [
client_id => $client_id,
client_secret => $client_secret,
code => $authorization_code,
redirect_uri => $redirect_uri,
grant_type => 'authorization_code',
]);
die "Error - Access token request failed: " . $response->status_line . $/ .
$response->decoded_content . $/
unless $response->is_success;
script/ecs_token view on Meta::CPAN
}
if($command eq 'credentials') {
# using client credentials flow ... (with client secret, not cert-based JWT)
# get configuration parameters
my $client_id = get_config_param('client_id');
my $client_secret = get_config_param('client_secret');
my $nocache = exists $options{nocache};
my $scope = get_config_param('scope');
my $token_endpoint = get_config_param('token_endpoint');
# fail fast if command line contains unsupported options
die "Error - Option(s) unsupported for \"credentials\" command$/" . $USAGE
if exists $options{auth_endpoint} or exists $options{redirect_uri}
or exists $options{refresh_token};
if(not $nocache) {
# use cached token if available
my $cached_token = get_cached_token();
if($cached_token) {
print $cached_token, $/;
exit 0;
}
}
my $token_request_timestamp = time;
# use client id, client secret, and resource to request access token
# from token endpoint
my $response = $user_agent->post($token_endpoint, [
client_id => $client_id,
client_secret => $client_secret,
scope => $scope,
grant_type => 'client_credentials',
]);
die "Error - Access token request failed: " . $response->status_line . $/ .
$response->decoded_content . $/
unless $response->is_success;
script/ecs_token view on Meta::CPAN
}
if($command eq 'refresh') {
# using refresh token flow ...
# get configuration parameters
my $client_id = get_config_param('client_id');
my $client_secret = get_config_param('client_secret');
my $nocache = exists $options{nocache};
my $refresh_token = get_config_param('refresh_token');
my $token_endpoint = get_config_param('token_endpoint');
# fail fast if command line contains unsupported options
die "Error - Option(s) unsupported for \"refresh\" command$/" . $USAGE
if exists $options{auth_endpoint} or exists $options{redirect_uri}
or exists $options{scope};
if(not $nocache) {
# use cached token if available
my $cached_token = get_cached_token();
if($cached_token) {
print $cached_token, $/;
exit 0;
}
}
my $token_request_timestamp = time;
# use client id, client secret and refresh token to request access token
# from token endpoint
my $response = $user_agent->post($token_endpoint, [
client_id => $client_id,
client_secret => $client_secret,
refresh_token => $refresh_token,
grant_type => 'refresh_token',
]);
die "Error - Access token request failed: " . $response->status_line . $/ .
$response->decoded_content . $/
unless $response->is_success;
script/ecs_token view on Meta::CPAN
=back
=head2 Configuration Parameters
Configuration parameter values can be set by storing the value in secure
storage or by passing the value on the C<ecs_token> command line.
Example using C<pass> secure storage:
echo -n 'https://accounts.google.com/o/oauth2/auth' | \
pass insert --echo emdis/ecs/oauth/auth_endpoint
Example using command line parameter:
ecs_token code --auth_endpoint https://accounts.google.com/o/oauth2/auth
=over
=item auth_endpoint
OAuth 2.0 authorization code endpoint, for authorization code flow.
Required by the C<ecs_token code> command. Example value:
https://accounts.google.com/o/oauth2/auth
=item cached_token_response
Not a true configuration parameter, but only a secure storage location to
hold a copy of the most recent token response, so it can be reused until it
expires. To avoid a secure storage retrieval error, initialize this to the
value '' (empty string).
script/ecs_token view on Meta::CPAN
1//04Ge[...]3OH0
=item scope
OAuth 2.0 scope. Required by the C<ecs_token code> and
C<ecs_token credentials> commands. Example values:
https://mail.google.com/
https://outlook.office365.com/.default
=item token_endpoint
OAuth 2.0 token endpoint. Required by the C<ecs_token code>,
C<ecs_token credentials>, and C<ecs_token refresh> commands. Example
values:
https://accounts.google.com/o/oauth2/token
https://login.microsoftonline.com/[tenant_id]/oauth2/v2.0/token
=back
=head1 SETUP
script/ecs_token view on Meta::CPAN
Initialize password storage using the selected key.
pass init <gpg-key-fingerprint>
=item 3.
Populate the expected secure storage locations with information needed by
C<ecs_token>. E.g.:
echo -n 'https://accounts.google.com/o/oauth2/auth' | \
pass insert --echo emdis/ecs/oauth/auth_endpoint
echo -n '' | \
pass insert --echo emdis/ecs/oauth/cached_token_response
echo -n '0' | \
pass insert --echo emdis/ecs/oauth/cached_token_timestamp
echo -n '1083[...].apps.googleusercontent.com' | \
pass insert --echo emdis/ecs/oauth/client_id
script/ecs_token view on Meta::CPAN
'https://google.github.io/gmail-oauth2-tools/html/oauth2.dance.html' | \
pass insert --echo emdis/ecs/oauth/redirect_uri
echo -n '1//04Ge[...]3OH0' | \
pass insert --echo emdis/ecs/oauth/refresh_token
echo -n 'https://mail.google.com/' | \
pass insert --echo emdis/ecs/oauth/scope
echo -n 'https://accounts.google.com/o/oauth2/token' | \
pass insert --echo emdis/ecs/oauth/token_endpoint
=back
=head2 Environment Variables
The C<pass> program depends on C<gpg-agent> to supply the passphrase it
uses to decrypt its gpg-encrypted data. If C<PASS_GPG_KEYGRIP> and
C<PASS_GPG_PASSPHRASE> environment variables are defined, C<ecs_token>
uses the information they contain to preset the indicated passphrase.
( run in 0.239 second using v1.01-cache-2.11-cpan-beeb90c9504 )