Akamai-Edgegrid
view release on metacpan or search on metacpan
lib/Akamai/Edgegrid.pm view on Meta::CPAN
=head1 NAME
Akamai::Edgegrid - User agent for Akamai {OPEN} Edgegrid
=head1 VERSION
Version 1.0
=cut
our $VERSION = '1.0.5';
=head1 SYNOPSIS
use Akamai::Edgegrid;
my $agent = new Akamai::Edgegrid(
config_file => "$ENV{HOME}/.edgerc",
section => "default");
my $baseurl = "https://" . $agent->{host};
my $resp = $agent->get("$baseurl/diagnostic-tools/v1/locations");
print $resp->content;
=head1 DESCRIPTION
This module implements the Akamai {OPEN} Edgegrid Authentication scheme as specified by L<https://developer.akamai.com/introduction/Client_Auth.html>.
=cut
sub _eg_timestamp {
return strftime('%Y%m%dT%H:%M:%S+0000', gmtime(time));
}
sub _new_nonce {
my $ug = new Data::UUID;
return $ug->create_str;
}
# see http://search.cpan.org/~mshelor/Digest-SHA-5.88/lib/Digest/SHA.pm#PADDING_OF_BASE64_DIGESTS
sub _pad_digest {
my $digest = shift;
while (length($digest) % 4) {
$digest .= '=';
}
return $digest;
}
sub _padded_hmac_sha256_base64 {
my ($data, $key) = @_;
return _pad_digest(hmac_sha256_base64($data, $key));
}
sub _padded_sha256_base64 {
my ($data) = @_;
return _pad_digest(sha256_base64($data));
}
## methods
sub _debug {
my ($self, $msg) = @_;
if ($self->{debug}) {
$msg =~ s/\n$//;
warn "$msg\n";
}
}
sub _make_signing_key {
my ($self, $timestamp) = @_;
my $signing_key = _padded_hmac_sha256_base64($timestamp, $self->{client_secret});
$self->_debug("signing_key: $signing_key");
return $signing_key;
}
sub _canonicalize_headers {
my ($self, $r) = @_;
return join("\t",
map {
my $header_name = lc($_);
my $header_val = $r->header($_);
$header_val =~ s/^\s+//g;
$header_val =~ s/\s+$//g;
$header_val =~ s/\s+/ /g;
"$header_name:$header_val";
} grep {
defined $r->header($_)
} @{$self->{headers_to_sign}}
);
}
sub _make_content_hash {
my ($self, $r) = @_;
if ($r->method eq 'POST' and length($r->content) > 0) {
my $body = $r->content;
if (length($body) > $self->{max_body}) {
$self->_debug(
"data length " . length($body) . " is larger than maximum " . $self->{max_body}
);
$body = substr($body, 0, $self->{max_body});
$self->_debug(
"data truncated to " . length($body) . " for computing the hash"
);
}
return _padded_sha256_base64($body);
}
return "";
}
sub _make_data_to_sign {
my ($self, $r, $auth_header) = @_;
my $data_to_sign = join("\t", (
$r->method,
$r->url->scheme,
$r->url->host,
$r->url->path_query,
$self->_canonicalize_headers($r),
$self->_make_content_hash($r),
$auth_header
));
my $display_to_sign = $data_to_sign;
$display_to_sign =~ s/\t/\\t/g;
$self->_debug("data to sign: $display_to_sign");
return $data_to_sign;
}
sub _sign_request {
my ($self, $r, $timestamp, $auth_header) = @_;
return _padded_hmac_sha256_base64(
$self->_make_data_to_sign($r, $auth_header),
$self->_make_signing_key($timestamp)
);
}
sub _make_auth_header {
my ($self, $r, $timestamp, $nonce) = @_;
my @kvps = (
['client_token' => $self->{client_token}],
['access_token' => $self->{access_token}],
['timestamp' => $timestamp],
['nonce' => $nonce]
);
my $auth_header = "EG1-HMAC-SHA256 " . join(';', map {
my ($k,$v) = @$_;
"$k=$v";
} @kvps) . ';';
$self->_debug("unsigned authorization header: $auth_header");
my $signed_auth_header =
$auth_header . 'signature=' . $self->_sign_request($r, $timestamp, $auth_header);
$self->_debug("signed authorization header: $signed_auth_header");
return $signed_auth_header;
}
=head1 CONSTRUCTOR METHOD
=over 2
=item $ua = Akamai::Edgegrid->new( %options )
This method constructs a new C<Akamai::EdgeGrid> object and returns it. This
is a subclass of C<LWP::UserAgent> and accepts all Key/value pair arguments
accepted by the parent class. In addition The following required key/value
pairs must be provided:
KEY SOURCE
------------- -----------------------------------------------
client_token from "Credentials" section of Manage APIs UI
client_secret from "Credentials" section of Manage APIs UI
access_token from "Authorizations" section of Manage APIs UI
The following optional key/value pairs may be provided:
KEY DESCRIPTION
--------------- -------------------------------------------------------
debug if true enables additional logging
headers_to_sign listref of header names to sign (in order) (default [])
max_body maximum body size for POSTS (default 2048)
=cut
sub new {
my $class = shift @_;
my %args = @_;
my @local_args = qw(config_file section client_token client_secret access_token headers_to_sign max_body debug);
my @required_args = qw(client_token client_secret access_token);
my @cred_args = qw(client_token client_secret access_token host);
my %local = ();
for my $arg (@local_args) {
$local{$arg} = delete $args{$arg};
}
my $self = LWP::UserAgent::new($class, %args);
for my $arg (@local_args) {
$self->{$arg} = $local{$arg};
}
# defaults
unless ($self->{config_file}) {
$self->{config_file} = "$ENV{HOME}/.edgerc";
}
if (-f $self->{config_file} and $self->{section} ) {
my $cfg = Config::IniFiles->new( -file => $self->{config_file} );
for my $variable (@cred_args) {
if ($cfg->val($self->{section}, $variable)) {
$self->{$variable} = $cfg->val($self->{section}, $variable);
} else {
die ("Config file " . $self->{config_file} .
" is missing required argument " . $variable .
" in section " . $self->{section} );
}
}
if ( $cfg->val($self->{section}, "max_body") ) {
$self->{max_body} = $cfg->val($self->{section}, "max_body");
}
}
for my $arg (@required_args) {
unless ($self->{$arg}) {
die "missing required argument $arg";
}
}
unless ($self->{headers_to_sign}) {
$self->{headers_to_sign} = [];
}
unless ($self->{max_body}) {
$self->{max_body} = 131072;
}
$self->add_handler('request_prepare' => sub {
my ($r, $ua, $h) = @_;
my $nonce = _new_nonce();
my $timestamp = _eg_timestamp();
$r->header('Authorization', $ua->_make_auth_header($r, $timestamp, $nonce));
});
return $self;
}
=back
( run in 2.753 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )