File Coverage

blib/lib/Akamai/Edgegrid.pm
Criterion Covered Total %
statement 96 102 94.1
branch 11 12 91.6
condition 3 3 100.0
subroutine 21 22 95.4
pod 1 1 100.0
total 132 140 94.2


line stmt bran cond sub pod time code
1             package Akamai::Edgegrid;
2              
3 7     7   141978 use 5.006;
  7         25  
  7         301  
4 7     7   39 use strict;
  7         9  
  7         291  
5 7     7   41 use warnings FATAL => 'all';
  7         20  
  7         383  
6              
7 7     7   45 use base 'LWP::UserAgent';
  7         16  
  7         5561  
8 7     7   305636 use Data::Dumper;
  7         45514  
  7         605  
9 7     7   4226 use Digest::SHA qw(hmac_sha256_base64 sha256_base64);
  7         26663  
  7         790  
10 7     7   4094 use POSIX qw(strftime);
  7         39239  
  7         45  
11 7     7   11042 use Data::UUID;
  7         6090  
  7         8800  
12              
13             =head1 NAME
14              
15             Akamai::Edgegrid - User agent for Akamai {OPEN} Edgegrid
16              
17             =head1 VERSION
18              
19             Version 1.0
20              
21             =cut
22              
23             our $VERSION = '1.0.4';
24              
25             =head1 SYNOPSIS
26              
27             use Akamai::Edgegrid;
28              
29             my $baseurl = 'https://akaa-bbbbbbbbbbbbb.luna.akamaiapis.net';
30             my $agent = new Akamai::Edgegrid(
31             client_token => 'cccccccccccccccc',
32             client_secret => 'sssssssssssssssss',
33             access_token => 'aaaaaaaaaaaaaaaaaaaa'
34             );
35              
36             my $resp = $agent->get("$baseurl/diagnostic-tools/v1/locations");
37             print $resp->content;
38              
39             =head1 DESCRIPTION
40              
41             This module implements the Akamai {OPEN} Edgegrid Authentication scheme as specified by L.
42              
43             =cut
44              
45             sub _eg_timestamp {
46 1     1   225 return strftime('%Y%m%dT%H:%M:%S+0000', gmtime(time));
47             }
48              
49             sub _new_nonce {
50 100     100   28788 my $ug = new Data::UUID;
51 100         14097 return $ug->create_str;
52             }
53              
54             # see http://search.cpan.org/~mshelor/Digest-SHA-5.88/lib/Digest/SHA.pm#PADDING_OF_BASE64_DIGESTS
55             sub _pad_digest {
56 27     27   42 my $digest = shift;
57 27         70 while (length($digest) % 4) {
58 27         74 $digest .= '=';
59             }
60 27         92 return $digest;
61             }
62              
63             sub _padded_hmac_sha256_base64 {
64 24     24   34 my ($data, $key) = @_;
65 24         290 return _pad_digest(hmac_sha256_base64($data, $key));
66             }
67              
68             sub _padded_sha256_base64 {
69 3     3   4 my ($data) = @_;
70 3         59 return _pad_digest(sha256_base64($data));
71             }
72              
73             ## methods
74              
75             sub _debug {
76 50     50   73 my ($self, $msg) = @_;
77 50 50       136 if ($self->{debug}) {
78 0         0 $msg =~ s/\n$//;
79 0         0 warn "$msg\n";
80             }
81             }
82              
83             sub _make_signing_key {
84 12     12   15 my ($self, $timestamp) = @_;
85 12         27 my $signing_key = _padded_hmac_sha256_base64($timestamp, $self->{client_secret});
86 12         37 $self->_debug("signing_key: $signing_key");
87              
88 12         25 return $signing_key;
89             }
90              
91             sub _canonicalize_headers {
92 12     12   783 my ($self, $r) = @_;
93 9         199 return join("\t",
94             map {
95 36         1050 my $header_name = lc($_);
96 9         23 my $header_val = $r->header($_);
97 9         305 $header_val =~ s/^\s+//g;
98 9         15 $header_val =~ s/\s+$//g;
99 9         16 $header_val =~ s/\s+/ /g;
100              
101 9         40 "$header_name:$header_val";
102              
103             } grep {
104 12         19 defined $r->header($_)
105 12         13 } @{$self->{headers_to_sign}}
106             );
107             }
108              
109             sub _make_content_hash {
110 12     12   301 my ($self, $r) = @_;
111 12 100 100     26 if ($r->method eq 'POST' and length($r->content) > 0) {
112 3         82 my $body = $r->content;
113 3 100       29 if (length($body) > $self->{max_body}) {
114 1         8 $self->_debug(
115             "data length " . length($body) . " is larger than maximum " . $self->{max_body}
116             );
117              
118 1         4 $body = substr($body, 0, $self->{max_body});
119              
120 1         11 $self->_debug(
121             "data truncated to " . length($body) . " for computing the hash"
122             );
123             }
124 3         7 return _padded_sha256_base64($body);
125             }
126 9         136 return "";
127             }
128              
129             sub _make_data_to_sign {
130 12     12   15 my ($self, $r, $auth_header) = @_;
131 12         31 my $data_to_sign = join("\t", (
132             $r->method,
133             $r->url->scheme,
134             $r->url->host,
135             $r->url->path_query,
136             $self->_canonicalize_headers($r),
137             $self->_make_content_hash($r),
138             $auth_header
139             ));
140              
141 12         32 my $display_to_sign = $data_to_sign;
142 12         63 $display_to_sign =~ s/\t/\\t/g;
143 12         37 $self->_debug("data to sign: $display_to_sign");
144              
145 12         39 return $data_to_sign;
146             }
147              
148             sub _sign_request {
149 12     12   20 my ($self, $r, $timestamp, $auth_header) = @_;
150              
151 12         26 return _padded_hmac_sha256_base64(
152             $self->_make_data_to_sign($r, $auth_header),
153             $self->_make_signing_key($timestamp)
154             );
155             }
156              
157             sub _make_auth_header {
158 12     12   12772 my ($self, $r, $timestamp, $nonce) = @_;
159 12         73 my @kvps = (
160             ['client_token' => $self->{client_token}],
161             ['access_token' => $self->{access_token}],
162             ['timestamp' => $timestamp],
163             ['nonce' => $nonce]
164             );
165 48         68 my $auth_header = "EG1-HMAC-SHA256 " . join(';', map {
166 12         18 my ($k,$v) = @$_;
167 48         150 "$k=$v";
168             } @kvps) . ';';
169              
170 12         47 $self->_debug("unsigned authorization header: $auth_header");
171              
172 12         33 my $signed_auth_header =
173             $auth_header . 'signature=' . $self->_sign_request($r, $timestamp, $auth_header);
174              
175 12         42 $self->_debug("signed authorization header: $signed_auth_header");
176              
177 12         69 return $signed_auth_header;
178             }
179              
180             =head1 CONSTRUCTOR METHOD
181              
182             =over 2
183              
184             =item $ua = Akamai::Edgegrid->new( %options )
185              
186             This method constructs a new C object and returns it. This
187             is a subclass of C and accepts all Key/value pair arguments
188             accepted by the parent class. In addition The following required key/value
189             pairs must be provided:
190              
191             KEY SOURCE
192             ------------- -----------------------------------------------
193             client_token from "Credentials" section of Manage APIs UI
194             client_secret from "Credentials" section of Manage APIs UI
195             access_token from "Authorizations" section of Manage APIs UI
196              
197             The following optional key/value pairs may be provided:
198              
199             KEY DESCRIPTION
200             --------------- -------------------------------------------------------
201             debug if true enables additional logging
202             headers_to_sign listref of header names to sign (in order) (default [])
203             max_body maximum body size for POSTS (default 2048)
204              
205             =cut
206              
207             sub new {
208 17     17 1 10101 my $class = shift @_;
209 17         79 my %args = @_;
210              
211 17         46 my @local_args = qw(client_token client_secret access_token headers_to_sign max_body debug);
212 17         26 my @required_args = qw(client_token client_secret access_token);
213 17         26 my %local = ();
214              
215 17         29 for my $arg (@local_args) {
216 102         193 $local{$arg} = delete $args{$arg};
217             }
218            
219 17         81 my $self = LWP::UserAgent::new($class, %args);
220              
221 17         10630 for my $arg (@local_args) {
222 102         209 $self->{$arg} = $local{$arg};
223             }
224              
225 17         28 for my $arg (@required_args) {
226 46 100       111 unless ($self->{$arg}) {
227 4         71 die "missing required argument $arg";
228             }
229             }
230              
231             # defaults
232 13 100       36 unless ($self->{headers_to_sign}) {
233 1         2 $self->{headers_to_sign} = [];
234             }
235 13 100       38 unless ($self->{max_body}) {
236 1         2 $self->{max_body} = 2048;
237             }
238              
239             $self->add_handler('request_prepare' => sub {
240 0     0   0 my ($r, $ua, $h) = @_;
241              
242 0         0 my $nonce = _new_nonce();
243 0         0 my $timestamp = _eg_timestamp();
244              
245 0         0 $r->header('Authorization', $ua->_make_auth_header($r, $timestamp, $nonce));
246 13         66 });
247              
248 13         294 return $self;
249             }
250              
251             =back
252              
253             =head1 AUTHOR
254              
255             Jonathan Landis, C<< >>
256              
257             =head1 BUGS
258              
259             Please report any bugs or feature requests to the web interface at L.
260              
261             =head1 SUPPORT
262              
263             You can find documentation for this module with the perldoc command.
264              
265             perldoc Akamai::Edgegrid
266              
267              
268             You can also look for information at:
269              
270             =over 4
271              
272             =item * Akamai's OPEN Developer Community
273              
274             L
275              
276             =item * Github issues (report bugs here)
277              
278             L
279              
280             =item * AnnoCPAN: Annotated CPAN documentation
281              
282             L
283              
284             =item * CPAN Ratings
285              
286             L
287              
288             =item * Search CPAN
289              
290             L
291              
292             =back
293              
294              
295             =head1 LICENSE AND COPYRIGHT
296              
297             Copyright 2014 Akamai Technologies, Inc. All rights reserved
298              
299             Licensed under the Apache License, Version 2.0 (the "License");
300             you may not use this file except in compliance with the License.
301             You may obtain a copy of the License at
302              
303             L
304              
305             Unless required by applicable law or agreed to in writing, software
306             distributed under the License is distributed on an "AS IS" BASIS,
307             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
308             See the License for the specific language governing permissions and
309             limitations under the License.
310              
311              
312             =cut
313              
314             1; # End of Akamai::Edgegrid