File Coverage

blib/lib/Mojo/UserAgent/Role/AWSSignature4.pm
Criterion Covered Total %
statement 66 67 98.5
branch 6 6 100.0
condition n/a
subroutine 20 21 95.2
pod 14 14 100.0
total 106 108 98.1


line stmt bran cond sub pod time code
1             package Mojo::UserAgent::Role::AWSSignature4;
2 1     1   465729 use Mojo::Base -role, -signatures;
  1         3  
  1         6  
3              
4 1     1   3974 use Digest::SHA;
  1         2  
  1         42  
5 1     1   6 use Mojo::Collection;
  1         1  
  1         26  
6 1     1   555 use Time::Piece;
  1         6676  
  1         4  
7              
8             our $VERSION = '0.01';
9              
10             has access_key => sub { $ENV{AWS_ACCESS_KEY} or die 'missing "access_key"' };
11             has aws_algorithm => 'AWS4-HMAC-SHA256';
12             has content => undef;
13             has debug => 0;
14             has expires => 86_400;
15             has region => 'us-east-1';
16             has secret_key => sub { $ENV{AWS_SECRET_KEY} or die 'missing "secret_key"' };
17             has service => sub { die 'missing "service"' };
18             has session_token => sub { $ENV{AWS_SESSION_TOKEN} || undef };
19             has unsigned_payload => 0;
20             has _tx => sub {die};
21              
22             around build_tx => sub ($orig, $self, @args) {
23             $self->transactor->add_generator(
24             awssig4 => sub {
25             my ($transactor, $tx, $config) = @_;
26             my $aws = $self->new({%$config, _tx => $tx});
27             $tx->req->content->asset(Mojo::Asset::File->new(path => $aws->content)) if $aws->content;
28             $tx->req->headers->host($tx->req->url->host || 'localhost');
29             $tx->req->headers->header('X-Amz-Date' => $aws->date_timestamp);
30             $tx->req->headers->header('X-Amz-Content-Sha256' => $aws->hashed_payload);
31             $tx->req->headers->header('X-Amz-Expires' => $aws->expires) if $aws->expires;
32             $tx->req->headers->authorization($aws->authorization);
33             }
34             );
35             $orig->($self, @args);
36             };
37              
38 3     3 1 50 sub authorization ($self) {
  3         6  
  3         7  
39 3         11 sprintf '%s Credential=%s/%s, SignedHeaders=%s, Signature=%s', $self->aws_algorithm, $self->access_key,
40             $self->credential_scope, $self->signed_header_list, $self->signature;
41             }
42              
43 7     7 1 634 sub canonical_headers ($self) {
  7         13  
  7         11  
44 7         10 join '', map { lc($_) . ":" . $self->_tx->req->headers->to_hash->{$_} . "\n" } @{$self->header_list};
  41         3399  
  7         18  
45             }
46              
47 7     7 1 988 sub canonical_qstring { shift->_tx->req->url->query->to_string }
48              
49 7     7 1 3693 sub canonical_request ($self) {
  7         11  
  7         10  
50             Mojo::Collection->new(
51             $self->_tx->req->method,
52             $self->_tx->req->url->path->to_abs_string,
53             $self->canonical_qstring, $self->canonical_headers, $self->signed_header_list, $self->hashed_payload
54             )->tap(sub {
55 7 100   7   810 warn $_->map(sub {"CR:$_"})->join("\n") if $self->debug;
  30         180  
56 7         20 })->join("\n");
57             }
58              
59 8     8 1 754 sub credential_scope ($self) {
  8         16  
  8         13  
60 8         21 join '/', $self->date, $self->region, $self->service, 'aws4_request';
61             }
62              
63 13     13 1 41 sub date { shift->time->ymd('') }
64              
65 6     6 1 82 sub date_timestamp { $_[0]->time->ymd('') . 'T' . $_[0]->time->hms('') . 'Z' }
66              
67 10     10 1 4662 sub hashed_payload ($self) {
  10         15  
  10         19  
68 10 100       35 $self->unsigned_payload ? 'UNSIGNED-PAYLOAD' : Digest::SHA::sha256_hex($self->_tx->req->body);
69             }
70              
71 18     18 1 28 sub header_list { [sort keys %{shift->_tx->req->headers->to_hash}] }
  18         47  
72              
73 4     4 1 22 sub signature ($self) {
  4         8  
  4         6  
74 4         13 Digest::SHA::hmac_sha256_hex($self->string_to_sign, $self->signing_key);
75             }
76              
77             sub signed_header_list {
78 11     11 1 1558 join ';', map { lc($_) } @{shift->header_list};
  64         1251  
  11         30  
79             }
80              
81 1     1 1 2840 sub signed_qstring ($self) {
  1         3  
  1         3  
82 1         6 $self->_tx->req->url->query(['X-Amz-Signature' => $self->signature]);
83             }
84              
85 5     5 1 213 sub signing_key ($self) {
  5         11  
  5         12  
86 5         18 my $kSecret = "AWS4" . $self->secret_key;
87 5         38 my $kDate = Digest::SHA::hmac_sha256($self->date, $kSecret);
88 5         158 my $kRegion = Digest::SHA::hmac_sha256($self->region, $kDate);
89 5         63 my $kService = Digest::SHA::hmac_sha256($self->service, $kRegion);
90 5         132 Digest::SHA::hmac_sha256("aws4_request", $kService);
91             }
92              
93 4     4 1 6 sub string_to_sign ($self) {
  4         6  
  4         6  
94             Mojo::Collection->new($self->aws_algorithm, $self->date_timestamp, $self->credential_scope,
95             Digest::SHA::sha256_hex($self->canonical_request))->tap(sub {
96 4 100   4   472 warn $_->map(sub {"STS:$_"})->join("\n") if $self->debug;
  12         92  
97 4         14 })->join("\n");
98             }
99              
100 0     0     sub time {gmtime}
101              
102             1;
103              
104             __END__