File Coverage

blib/lib/Amazon/PAApi5/Signature.pm
Criterion Covered Total %
statement 75 76 98.6
branch 5 8 62.5
condition 9 16 56.2
subroutine 19 20 95.0
pod 6 6 100.0
total 114 126 90.4


line stmt bran cond sub pod time code
1             package Amazon::PAApi5::Signature;
2 2     2   73224 use strict;
  2         5  
  2         59  
3 2     2   20 use warnings;
  2         4  
  2         54  
4 2     2   11 use Carp qw/croak/;
  2         4  
  2         100  
5 2     2   1149 use POSIX qw/strftime/;
  2         13608  
  2         11  
6 2     2   4177 use Digest::SHA qw/sha256_hex hmac_sha256 hmac_sha256_hex/;
  2         6667  
  2         232  
7             use Class::Accessor::Lite (
8 2         29 rw => [qw/
9             access_key
10             secret_key
11             payload
12             resource_path
13             operation
14             host
15             region
16             aws_headers
17             str_signed_header
18             /],
19             ro => [qw/
20             service
21             http_method
22             hmac_algorithm
23             aws4_request
24             x_amz_date
25             current_date
26             /],
27 2     2   586 );
  2         1305  
28              
29             our $VERSION = '0.04';
30              
31             sub new {
32 2     2 1 3492 my $class = shift;
33 2 50       8 my $access_key = shift or croak 'access_key is required';
34 2 50       6 my $secret_key = shift or croak 'secret_key is required';
35 2 50       6 my $payload = shift or croak 'payload is required';
36 2   100     9 my $opt = shift || {};
37              
38 2   50     10 my $operation = $opt->{operation} || 'SearchItems';
39 2 100       9 my $resource_path = $opt->{resource_path} ? $opt->{resource_path} : '/paapi5/' . lc($operation);
40              
41             return bless {
42             access_key => $access_key,
43             secret_key => $secret_key,
44             payload => $payload,
45             resource_path => $resource_path,
46             operation => $operation,
47             host => $opt->{host} || 'webservices.amazon.com',
48             region => $opt->{region} || 'us-east-1',
49             service => $opt->{service} || 'ProductAdvertisingAPI',
50             http_method => $opt->{http_method} || 'POST',
51             hmac_algorithm => $opt->{hmac_algorithm} || 'AWS4-HMAC-SHA256',
52 2   50     30 aws4_request => $opt->{aws4_request} || 'aws4_request',
      50        
      50        
      50        
      50        
      50        
53             x_amz_date => $class->_get_time_stamp,
54             current_date => $class->_get_date,
55             aws_headers => {},
56             str_signed_header => '',
57             }, $class;
58             }
59              
60             sub req_url {
61 3     3 1 965 my ($self) = @_;
62              
63 3         11 return sprintf("https://%s%s", $self->host, $self->resource_path);
64             }
65              
66             sub _prepare_canonical_url {
67 2     2   5 my ($self) = @_;
68              
69 2         6 my $canonical_url = $self->http_method . "\n";
70              
71 2         14 $canonical_url .= $self->resource_path . "\n\n";
72              
73 2         9 my $signed_headers = '';
74 2         4 for my $key (grep { $_ !~ m!content-type! } sort keys %{$self->aws_headers}) {
  11         44  
  2         4  
75 9         38 $signed_headers .= lc($key) . ';';
76 9         20 $canonical_url .= lc($key) . ':' . $self->aws_headers->{$key} . "\n";
77             }
78              
79 2         11 $canonical_url .= "\n";
80              
81 2         8 $self->str_signed_header(substr($signed_headers, 0, -1)); # remove ';'
82 2         19 $canonical_url .= $self->str_signed_header . "\n";
83              
84 2         13 $canonical_url .= sha256_hex($self->payload);
85              
86 2         32 return $canonical_url;
87             }
88              
89             sub _prepare_string_to_sign {
90 2     2   6 my ($self, $canonical_url) = @_;
91              
92 2         5 return join("\n",
93             $self->hmac_algorithm,
94             $self->x_amz_date,
95             join('/', $self->current_date, $self->region, $self->service, $self->aws4_request),
96             sha256_hex($canonical_url),
97             );
98             }
99              
100             sub _calculate_signature {
101 2     2   6 my ($self, $string_to_sign) = @_;
102              
103 2         5 my $signature_key = $self->_get_signature_key;
104              
105 2         34 return lc(hmac_sha256_hex($string_to_sign, $signature_key));
106             }
107              
108             sub _build_authorization_string {
109 2     2   5 my ($self, $signature) = @_;
110              
111 2         5 return $self->hmac_algorithm . ' '
112             . 'Credential=' . join('/', $self->access_key, $self->_get_date, $self->region, $self->service, $self->aws4_request)
113             . ',SignedHeaders=' . $self->str_signed_header
114             . ',Signature=' . $signature
115             ;
116             }
117              
118             sub headers {
119 2     2 1 5 my ($self) = @_;
120              
121 2         7 my $aws_headers = $self->aws_headers;
122              
123 2         11 $aws_headers->{'content-encoding'} = 'amz-1.0';
124 2         4 $aws_headers->{'content-type'} = 'application/json; charset=UTF-8';
125 2         6 $aws_headers->{'host'} = $self->host;
126 2         10 $aws_headers->{'x-amz-date'} = $self->x_amz_date;
127 2         18 $aws_headers->{'x-amz-target'} = $self->_build_amz_target;
128              
129 2         14 my $canonical_url = $self->_prepare_canonical_url;
130              
131 2         6 my $string_to_sign = $self->_prepare_string_to_sign($canonical_url);
132              
133 2         72 my $signature = $self->_calculate_signature($string_to_sign);
134              
135 2         8 $aws_headers->{Authorization} = $self->_build_authorization_string($signature);
136              
137 2         50 $self->aws_headers($aws_headers);
138              
139 2         11 return %{$aws_headers};
  2         19  
140             }
141              
142             sub headers_as_arrayref {
143 0     0 1 0 return [shift->headers];
144             }
145              
146             sub headers_as_hashref {
147 2     2 1 1202 return {shift->headers};
148             }
149              
150             sub _get_signature_key {
151 2     2   4 my ($self) = @_;
152              
153 2         6 my $k_date = hmac_sha256($self->current_date, 'AWS4' . $self->secret_key);
154 2         35 my $k_region = hmac_sha256($self->region, $k_date);
155 2         25 my $k_service = hmac_sha256($self->service, $k_region);
156 2         25 my $k_signing = hmac_sha256($self->aws4_request, $k_service);
157              
158 2         23 return $k_signing;
159             }
160              
161             sub _build_amz_target {
162 2     2   5 return 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1.' . shift->operation;
163             }
164              
165             sub _get_time_stamp {
166 2     2   127 return strftime("%Y%m%dT%H%M%SZ", gmtime()); # 20191128T235650Z
167             }
168              
169             sub _get_date {
170 4     4   186 return strftime("%Y%m%d", gmtime()); # 20191128
171             }
172              
173             sub to_request {
174 1     1 1 1158 my ($self) = @_;
175              
176             return {
177 1         5 method => $self->http_method,
178             uri => $self->req_url,
179             headers => $self->headers_as_hashref,
180             content => $self->payload,
181             };
182             }
183              
184             1;
185              
186             __END__