File Coverage

blib/lib/WWW/Google/Cloud/Auth/ServiceAccount.pm
Criterion Covered Total %
statement 48 50 96.0
branch 4 6 66.6
condition 3 3 100.0
subroutine 12 12 100.0
pod 1 1 100.0
total 68 72 94.4


line stmt bran cond sub pod time code
1             package WWW::Google::Cloud::Auth::ServiceAccount;
2              
3 1     1   69307 use Moose;
  1         478595  
  1         9  
4 1     1   8332 use MooseX::HasDefaults::RO;
  1         13027  
  1         5  
5 1     1   11640 use MooseX::StrictConstructor;
  1         24716  
  1         5  
6 1     1   9814 use namespace::autoclean;
  1         2  
  1         6  
7              
8 1     1   67 use Carp;
  1         3  
  1         86  
9 1     1   743 use JSON;
  1         12121  
  1         18  
10 1     1   832 use LWP::UserAgent;
  1         44003  
  1         43  
11 1     1   638 use Crypt::JWT qw(encode_jwt);
  1         47765  
  1         82  
12              
13 1     1   575 use version; our $VERSION = version->declare('v1.0.0');
  1         2089  
  1         5  
14              
15             has credentials_path => (
16             isa => 'Str',
17             required => 1,
18             );
19              
20             has auth_url => (
21             isa => 'Str',
22             default => 'https://www.googleapis.com/oauth2/v4/token',
23             );
24              
25             has grant_type => (
26             isa => 'Str',
27             default => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
28             );
29              
30             has scope => (
31             isa => 'Str',
32             default => 'https://www.googleapis.com/auth/cloud-platform',
33             );
34              
35             # so that the token doesn't expire after checking it but before the request
36             # is processed on the other side.
37             has token_expiry_shift => (
38             isa => 'Str',
39             required => 0,
40             default => 10,
41             );
42              
43             has clock => (
44             isa => 'CodeRef',
45             default => sub {sub {time}}
46             );
47              
48             has ua_string => (
49             isa => 'Str',
50             default => "WWW-Google-Cloud-Auth/$VERSION",
51             );
52              
53             has _token => (
54             is => 'rw',
55             isa => 'Maybe[Str]',
56             default => undef,
57             init_arg => undef,
58             );
59              
60             has _token_expiry => (
61             is => 'rw',
62             isa => 'Int',
63             default => 0,
64             init_arg => undef,
65             );
66              
67             has _ua => (
68             isa => 'LWP::UserAgent',
69             builder => '_build_ua',
70             lazy => 1,
71             init_arg => undef,
72             );
73              
74             sub _build_ua {
75 2     2   5 my $self = shift;
76 2         76 my $ua = LWP::UserAgent->new(
77             agent => $self->ua_string,
78             );
79 2         3175 return $ua;
80             }
81              
82             sub _generate_jwt {
83 3     3   6 my $self = shift;
84 3 50       120 open (my $fh, '<', $self->credentials_path) or die("Can't open credentials file: $!");
85 3         11 my $creds_json = do {local $/; <$fh>};
  3         14  
  3         81  
86 3         57 my $creds = JSON::decode_json($creds_json);
87             my $payload = {
88             iss => $creds->{client_email},
89 3         137 scope => $self->scope,
90             aud => 'https://www.googleapis.com/oauth2/v4/token',
91             exp => $self->clock->() + 600,
92             iat => $self->clock->(),
93             };
94 3         26 my $key = $creds->{private_key};
95 3         15 return encode_jwt(alg => 'RS256', payload => $payload, key => \$key);
96             }
97              
98             sub get_token {
99 4     4 1 18 my $self = shift;
100 4 100 100     169 return $self->_token if($self->_token && $self->clock->() < $self->_token_expiry);
101              
102 3         11 my $jwt = $self->_generate_jwt();
103 3         5607 my $response = $self->_ua->post(
104             $self->auth_url,
105             {
106             grant_type => $self->grant_type,
107             assertion => $jwt,
108             }
109             );
110              
111 3 50       7164 if ($response->is_success) {
112 3         41 my $r = decode_json($response->decoded_content);
113 3         572 $self->_token($r->{access_token});
114 3         114 $self->_token_expiry($self->clock->() + $r->{expires_in} - $self->token_expiry_shift);
115 3         107 return $self->_token;
116             } else {
117 0           my @err = ($response->code, $response->message, $response->decoded_content);
118 0           croak "@err";
119             }
120             }
121              
122             __PACKAGE__->meta->make_immutable;
123              
124             1;
125              
126             =pod
127              
128             =encoding utf8
129              
130             =head1 NAME
131              
132             WWW::Google::Cloud::Auth::ServiceAccount - Service account based OAuth authentication for Google Cloud APIs
133              
134             =head1 SYNOPSIS
135              
136             my $auth = WWW::Google::Cloud::Auth::ServiceAccount>new(
137             credentials_path => '/home/myapp/priv/google_some_service.json',
138             );
139              
140             my $response = $ua->post(
141             $some_google_cloud_api_endpoint,
142             'Content-Type' => 'application/json; charset=utf-8',
143             'Authorization' => 'Bearer ' . $auth->get_token,
144             Content => $arguments,
145             );
146              
147             =head1 DESCRIPTION
148              
149             This is a library for service account based OAuth authentication with Google Cloud API endpoints for server to server applications.
150             See: L<https://developers.google.com/identity/protocols/OAuth2ServiceAccount>
151              
152             =head1 FUNCTIONS
153              
154             =head2 WWW::Google::Cloud::Auth::ServiceAccount->new(credentials_path => $credentials_path);
155              
156             Instantiate a new WWW::Google::Cloud::Auth::ServiceAccount object.
157              
158             Arguments:
159              
160             =over 4
161              
162             =item
163              
164             credentials path [required]
165              
166             The path to the JSON-encoded credentials file provided by Google.
167              
168             =item
169              
170             auth_url [optional]
171              
172             The URL to get the OAuth token from. Defaults to https://www.googleapis.com/oauth2/v4/token. You probably don't need to change this.
173              
174             =back
175              
176             Returns:
177              
178             =over 4
179              
180             A new WWW::Google::Cloud::Auth::ServiceAccount instance.
181              
182             =back
183              
184             =head2 $auth->get_token()
185              
186             Get a valid token to use for authorization. If there is a valid cached token return that.
187              
188             Arguments:
189              
190             =over 4
191              
192             None
193              
194             =back
195              
196             Returns:
197              
198             =over 4
199              
200             The OAuth token
201              
202             =back
203              
204             =head1 AUTHOR
205              
206             This module is written by Larion Garaczi <larion@cpan.org> (2019)
207              
208             =head1 SOURCE CODE
209              
210             The source code for this module is hosted on GitHub L<https://github.com/larion/www-google-cloud-auth>.
211              
212             Feel free to contribute :)
213              
214             =head1 LICENSE AND COPYRIGHT
215              
216             This module is free software and is published under the same
217             terms as Perl itself.
218              
219             =cut