File Coverage

blib/lib/GitHub/Apps/Auth.pm
Criterion Covered Total %
statement 72 79 91.1
branch 9 14 64.2
condition 3 9 33.3
subroutine 19 21 90.4
pod 2 2 100.0
total 105 125 84.0


line stmt bran cond sub pod time code
1             package GitHub::Apps::Auth;
2 4     4   261291 use 5.008001;
  4         35  
3 4     4   23 use strict;
  4         9  
  4         80  
4 4     4   20 use warnings;
  4         7  
  4         280  
5              
6             our $VERSION = "0.02";
7              
8             use Class::Accessor::Lite (
9 4         42 rw => [qw/token expires _prefix _suffix/],
10             ro => [qw/_furl private_key app_id installation_id/],
11 4     4   1986 );
  4         5026  
12              
13 4     4   659 use Carp;
  4         17  
  4         238  
14 4     4   2414 use Crypt::PK::RSA;
  4         77909  
  4         205  
15 4     4   2517 use Crypt::JWT qw/encode_jwt/;
  4         106515  
  4         271  
16 4     4   2072 use Furl;
  4         99115  
  4         186  
17 4     4   2796 use JSON qw/decode_json/;
  4         34282  
  4         27  
18 4     4   2413 use Time::Moment;
  4         4560  
  4         586  
19              
20             use overload
21 0     0   0 "\"\"" => sub { shift->issued_token },
22             "." => sub {
23 2     2   876 my $self = shift;
24 2         5 my $other = shift;
25 2         4 my $reverse = shift;
26              
27 2 50       7 $other = "" unless defined $other;
28              
29 2         6 my $new_self = bless {}, ref $self;
30 2         16 %$new_self = %$self;
31              
32 2 100       10 $reverse ?
33             $new_self->_prefix($other . $new_self->_prefix) :
34             $new_self->_suffix($new_self->_suffix . $other);
35 2         23 return $new_self;
36             },
37 4     4   34 "eq" => sub { shift->issued_token eq shift };
  4     10   8  
  4         42  
  10         6583  
38              
39             sub new {
40 3     3 1 1294588 my ($class, %args) = @_;
41 3 50 33     88 if (!exists $args{private_key} || !$args{private_key}) {
42 0         0 croak "private_key is required.";
43             }
44 3 50 33     63 if (!exists $args{app_id} || !$args{app_id}) {
45 0         0 croak "app_id is required.";
46             }
47 3 50 33     27 if (!exists $args{installation_id} || !$args{installation_id}) {
48 0         0 croak "installation_id is required.";
49             }
50              
51 3         22 my $pk = Crypt::PK::RSA->new($args{private_key});
52              
53             my $self = {
54             private_key => $pk,
55             installation_id => $args{installation_id},
56             app_id => $args{app_id},
57 3         1090 expires => 0,
58             _furl => Furl->new,
59             _prefix => "",
60             _suffix => "",
61             };
62 3         295 return bless $self, $class;
63             }
64              
65             sub _generate_jwt {
66 4     4   8 my $self = shift;
67              
68 4         13 my $jwt = encode_jwt(
69             payload => {
70             iat => time(),
71             exp => time() + 60,
72             iss => $self->app_id,
73             },
74             alg => "RS256",
75             key => $self->private_key,
76             );
77              
78 4         22826 return $jwt;
79             }
80              
81             sub _generate_request_header {
82 4     4   7 my $self = shift;
83 4         11 my $jwt = $self->_generate_jwt();
84              
85             return [
86 4         22 Authorization => 'Bearer ' . $jwt,
87             Accept => "application/vnd.github.machine-man-preview+json",
88             ];
89             }
90              
91             sub _fetch_access_token {
92 4     4   21 my $self = shift;
93              
94 4         13 my $installation_id = $self->installation_id;
95 4         32 my $header = $self->_generate_request_header();
96 4         19 my $resp = $self->_post_to_access_token($installation_id, $header);
97              
98 4 50       386 if (!$resp->is_success) {
99 0         0 croak "cannot fetch access_token: ". $resp->content;
100             }
101              
102 4         65 my $content = decode_json $resp->content;
103 4         49 my $token = $content->{token};
104 4         18 $self->token($token);
105 4         30 my $expires = $content->{expires_at};
106 4         39 my $tm = Time::Moment->from_string($expires);
107 4         24 $self->expires($tm->epoch);
108              
109 4         24 return $self->_prefix . $token . $self->_suffix;
110             }
111              
112             sub _post_to_access_token {
113 0     0   0 my ($self, $installation_id, $header) = @_;
114              
115 0         0 return $self->_furl->post(
116             "https://api.github.com/app/installations/$installation_id/access_tokens",
117             $header,
118             );
119             }
120              
121             sub _is_expired_token {
122 15     15   27 my $self = shift;
123              
124 15         53 return time() > $self->expires;
125             }
126              
127             sub issued_token {
128 15     15 1 4015 my $self = shift;
129              
130 15 100       35 if ($self->_is_expired_token) {
131 4         99 return $self->_prefix . $self->_fetch_access_token . $self->_suffix;
132             }
133              
134 11         121 return $self->_prefix . $self->token . $self->_suffix;
135             }
136              
137             1;
138             __END__