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}) {
    # default (linux) location of gpg-preset-passphrase program is in
    # /usr/libexec (not on PATH)

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//04Gei0xdQmoxKCgYIARAAGAQSNwF-L9IrizgSeuBmjQf7RNSPpAKUK-wsOFcDicS8jZEmusXSppx09bFyehICh4WkGqRrUj73OH0

=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 '<client_id>' | \
    pass insert --echo emdis/ecs/oauth/client_id

script/ecs_token  view on Meta::CPAN

  echo -n 'https://google.github.io/gmail-oauth2-tools/html/oauth2.dance.html' | \
    pass insert --echo emdis/ecs/oauth/redirect_uri

  echo -n '<refresh_token>' | \
    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 Gmail Setup Notes

The following are notes on setting up an app and getting an OAuth 2.0 access
token for use with Gmail SMTP/IMAP/POP3.

=over



( run in 0.925 second using v1.01-cache-2.11-cpan-2b1a40005be )