File Coverage

blib/lib/Akamai/Open/Request/EdgeGridV1.pm
Criterion Covered Total %
statement 8 10 80.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 12 14 85.7


line stmt bran cond sub pod time code
1             package Akamai::Open::Request::EdgeGridV1;
2             BEGIN {
3 3     3   73962 $Akamai::Open::Request::EdgeGridV1::AUTHORITY = 'cpan:PROBST';
4             }
5             # ABSTRACT: Creates the signed authentication header for the Akamai Open API Perl clients
6             $Akamai::Open::Request::EdgeGridV1::VERSION = '0.03';
7 3     3   33 use strict;
  3         7  
  3         104  
8 3     3   18 use warnings;
  3         5  
  3         102  
9              
10 3     3   1540 use Moose;
  0            
  0            
11             use Digest::SHA qw(sha256 hmac_sha256 hmac_sha256_base64);
12             use MIME::Base64;
13             use URL::Encode qw/:all/;
14             use URI;
15              
16             use constant {
17             EDGEGRIDV1ALGO => 'EG1-HMAC-SHA256',
18             HEADER_NAME => 'Authorization',
19             CLIENT_TOKEN => 'client_token=',
20             ACCESS_TOKEN => 'access_token=',
21             TIMESTAMP_TOKEN => 'timestamp=',
22             NONCE_TOKEN => 'nonce=',
23             SIGNATURE_TOKEN => 'signature='
24             };
25              
26             extends 'Akamai::Open::Request';
27              
28             has 'client' => (is => 'rw', trigger => \&Akamai::Open::Debug::debugger);
29             has 'signed_headers' => (is => 'rw', trigger => \&Akamai::Open::Debug::debugger);
30             has 'signature' => (is => 'rw', trigger => \&Akamai::Open::Debug::debugger);
31             has 'signing_key' => (is => 'rw', isa => 'Str', trigger => \&Akamai::Open::Debug::debugger);
32              
33             before 'sign_request' => sub {
34             my $self = shift;
35             my $tmp_key;
36             $self->debug->logger->debug(sprintf('Calculating signing key from %s and %s', $self->timestamp(),$self->client->client_secret()));
37             $tmp_key = encode_base64(hmac_sha256($self->timestamp(),$self->client->client_secret()));
38             chomp($tmp_key);
39             $self->signing_key($tmp_key);
40             return;
41             };
42              
43             after 'sign_request' => sub {
44             my $self = shift;
45              
46             if(defined($self->signature)) {
47             my $header_name = HEADER_NAME;
48             my $auth_header = sprintf('%s %s', EDGEGRIDV1ALGO,
49             join(';', CLIENT_TOKEN . $self->client->client_token(),
50             ACCESS_TOKEN . $self->client->access_token(),
51             TIMESTAMP_TOKEN . $self->timestamp(),
52             NONCE_TOKEN . $self->nonce(),
53             SIGNATURE_TOKEN . $self->signature()));
54              
55             $self->debug->logger->debug("Setting Authorization header to $auth_header");
56             $self->request->header($header_name => $auth_header);
57             }
58              
59             if(defined($self->signed_headers)) {
60             my $headers = $self->signed_headers;
61             $self->request->header($_ => $headers->{$_}) foreach(keys(%{$headers}));
62             }
63             };
64              
65              
66             sub sign_request {
67             my $self = shift;
68              
69             # to create a valid auth header, we'll need
70             # the http request method (i.e. GET, POST, PUT)
71             my $http_method = $self->request->method;
72             # the http scheme in lowercases (i.e. http or https)
73             my $http_scheme = $self->request->uri->scheme;
74             # the http host header
75             my $http_host = $self->request->uri->host;
76             # the encoded uri including the query string if present
77             my $http_uri = $self->request->uri->path_query;
78             # the canonicalized headers which are choosed for signing
79             my $http_headers = $self->canonicalize_headers;
80             # the content hash for POST/PUT requests
81             my $content_hash = $self->content_hash;
82             # and the authorization header content
83             my $auth_header = sprintf('%s %s;', EDGEGRIDV1ALGO,
84             join(';', CLIENT_TOKEN . $self->client->client_token,
85             ACCESS_TOKEN . $self->client->access_token,
86             TIMESTAMP_TOKEN . $self->timestamp,
87             NONCE_TOKEN . $self->nonce));
88             # now create the token to sign
89             my $token = join("\t", $http_method, $http_scheme, $http_host, $http_uri, $http_headers, $content_hash, $auth_header);
90              
91             $self->debug->logger->info("Signing token is $token");
92             if($self->debug->logger->is_debug()) {
93             my $dbg = $token;
94             $dbg =~ s#\t#\\t#g;
95             $self->debug->logger->debug("Quoted sigining token is $dbg");
96             }
97              
98             # and sign the token
99             $self->debug->logger->info(sprintf('signing with key %s', $self->signing_key()));
100             my $tmp_stoken = encode_base64(hmac_sha256($token, $self->signing_key()));
101             chomp($tmp_stoken);
102             $self->signature($tmp_stoken);
103             return;
104             }
105              
106             sub content_hash {
107             my $self = shift;
108             my $content_hash = '';
109              
110             if($self->request->method eq 'POST' && length($self->request->content) > 0) {
111             $content_hash = encode_base64(sha256($self->request->content));
112             chomp($content_hash);
113             }
114              
115             return($content_hash);
116             }
117              
118             sub canonicalize_headers {
119             my $self = shift;
120             my $sign_headers = $self->signed_headers || {};
121             return(join("\t", map {
122             my $header = lc($_);
123             my $value = $sign_headers->{$_};
124              
125             # trim leading and trailing whitespaces
126             $value =~ s{^\s+}{};
127             $value =~ s{\s$}{};
128             # replace repeated whitespaces
129             $value =~ s/\s{2,}/ /g;
130              
131             "$header:$value";
132             } sort(keys(%{$sign_headers}))));
133             }
134              
135             1;
136              
137             __END__
138              
139             =pod
140              
141             =encoding utf-8
142              
143             =head1 NAME
144              
145             Akamai::Open::Request::EdgeGridV1 - Creates the signed authentication header for the Akamai Open API Perl clients
146              
147             =head1 VERSION
148              
149             version 0.03
150              
151             =head1 ABOUT
152              
153             I<Akamai::Open::Request::EdgeGridV1> provides the signing functionality,
154             which is needed to authenticated the client against the I<Akamai::Open>
155             API.
156              
157             The algorithm to sign a header for a request against the API, is
158             provided and described by Akamai and can be found L<here|https://developer.akamai.com/stuff/Getting_Started_with_OPEN_APIs/Client_Auth.html>.
159              
160             =head1 AUTHOR
161              
162             Martin Probst <internet+cpan@megamaddin.org>
163              
164             =head1 COPYRIGHT AND LICENSE
165              
166             This software is copyright (c) 2014 by Martin Probst.
167              
168             This is free software; you can redistribute it and/or modify it under
169             the same terms as the Perl 5 programming language system itself.
170              
171             =cut