EMDIS-ECS
view release on metacpan or search on metacpan
script/ecs_token view on Meta::CPAN
if $err;
}
# 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;
print $OUT $response->decoded_content . $/;
# parse JSON response content
my $parsed_content = decode_json($response->decoded_content);
die "Error - Unexpected response content: " . ref($parsed_content) . $/
unless ref($parsed_content) eq 'HASH';
die "Error - Refresh token not received$/"
if not exists $parsed_content->{refresh_token};
store_secret(
$SECSTOR_LOCATION->{refresh_token},
$parsed_content->{refresh_token});
print $OUT "New refresh token stored.$/";
die "Error - Access token not received$/"
unless exists $parsed_content->{access_token};
if(not $nocache) {
store_cached_token($response->decoded_content, $token_request_timestamp);
}
}
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;
# parse JSON response content
my $parsed_content = decode_json($response->decoded_content);
die "Error - Unexpected response content: " . ref($parsed_content) . $/
unless ref($parsed_content) eq 'HASH';
die "Error - Access token not received$/"
unless exists $parsed_content->{access_token};
if(not $nocache) {
store_cached_token($response->decoded_content, $token_request_timestamp);
}
# print access token
print $parsed_content->{access_token}, $/;
}
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;
# parse JSON response content
my $parsed_content = decode_json($response->decoded_content);
die "Error - Unexpected response content: " . ref($parsed_content) . $/
unless ref($parsed_content) eq 'HASH';
# if indicated, store new refresh token
if(exists $parsed_content->{refresh_token}) {
store_secret(
$SECSTOR_LOCATION->{refresh_token},
$parsed_content->{refresh_token});
}
die "Error - Access token not received$/"
unless exists $parsed_content->{access_token};
if(not $nocache) {
store_cached_token($response->decoded_content, $token_request_timestamp);
}
# print access token
print $parsed_content->{access_token}, $/;
}
exit 0;
# attempt to get cached access token
sub get_cached_token {
my $token = '';
eval {
my $cached_token_response = get_secret($SECSTOR_LOCATION->{cached_token_response});
my $cached_token_timestamp = get_secret($SECSTOR_LOCATION->{cached_token_timestamp});
my $current_timestamp = time;
my $parsed_token_response = decode_json($cached_token_response);
my $expires_in = (exists $parsed_token_response->{expires_in} ? $parsed_token_response->{expires_in} : 0);
my $expiration_timestamp = $cached_token_timestamp + $expires_in - $TOKEN_CACHE_EXPIRATION_MARGIN;
if($cached_token_timestamp < $current_timestamp and $current_timestamp < $expiration_timestamp) {
$token = $parsed_token_response->{access_token};
}
};
return $token;
}
# get configuration parameter value - get value from command-line option
# if defined, otherwise get value from secure storage
sub get_config_param {
my $param_name = shift;
die "Error - get_config_param(): param_name not specified$/"
unless $param_name;
# if defined, get value from command-line option
return $options{$param_name}
if exists $options{$param_name};
# get value from secure storage
return get_secret($SECSTOR_LOCATION->{$param_name});
}
# This subroutine uses "pass" to get the value of a secret.
#
# For this to work, the GnuPG passphrase needed by pass must be preloaded
# into the gpg-agent cache, e.g., using gpg-preset-passphrase.
#
# See also:
# - https://www.passwordstore.org/
# - https://www.gnupg.org/documentation/manuals/gnupg/gpg_002dpreset_002dpassphrase.html
# - embedded documentation below
#
sub get_secret {
my $location = shift;
die "Error - get_secret(): location not specified$/"
unless $location;
my $err = timelimit_cmd($SECSTOR_TIMELIMIT, "pass show $location");
die "Error - get_secret() - command failed: $err$/"
if $err;
my $retval = $EMDIS::ECS::cmd_output;
( run in 2.831 seconds using v1.01-cache-2.11-cpan-f52f0507bed )