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 )