File Coverage

blib/lib/Google/Ads/GoogleAds/Common/OAuth2BaseHandler.pm
Criterion Covered Total %
statement 73 90 81.1
branch 13 22 59.0
condition 8 12 66.6
subroutine 16 20 80.0
pod 3 6 50.0
total 113 150 75.3


line stmt bran cond sub pod time code
1             # Copyright 2019, Google LLC
2             #
3             # Licensed under the Apache License, Version 2.0 (the "License");
4             # you may not use this file except in compliance with the License.
5             # You may obtain a copy of the License at
6             #
7             # http://www.apache.org/licenses/LICENSE-2.0
8             #
9             # Unless required by applicable law or agreed to in writing, software
10             # distributed under the License is distributed on an "AS IS" BASIS,
11             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12             # See the License for the specific language governing permissions and
13             # limitations under the License.
14              
15              
16             use strict;
17 11     11   64 use warnings;
  11         13  
  11         222  
18 11     11   42 use version;
  11         17  
  11         177  
19 11     11   55 use base qw(Google::Ads::GoogleAds::Common::AuthHandlerInterface);
  11         20  
  11         38  
20 11     11   497  
  11         20  
  11         3872  
21             # The following needs to be on one line because CPAN uses a particularly hacky
22             # eval() to determine module versions.
23             use Google::Ads::GoogleAds::Constants; our $VERSION = ${Google::Ads::GoogleAds::Constants::VERSION};
24 11     11   2907  
  11         22  
  11         354  
25             use Class::Std::Fast;
26 11     11   4360 use HTTP::Request::Common;
  11         125259  
  11         64  
27 11     11   5729 use LWP::UserAgent;
  11         190610  
  11         742  
28 11     11   5838 use URI::Escape;
  11         236314  
  11         333  
29 11     11   82  
  11         18  
  11         1901  
30             # Class::Std-style attributes. Need to be kept in the same line.
31             # These need to go in the same line for older Perl interpreters to understand.
32             my %api_client_of : ATTR(:name<api_client> :default<>);
33             my %client_id_of : ATTR(:name<client_id> :default<>);
34             my %access_token_of : ATTR(:init_arg<access_token> :default<>);
35             my %access_token_expires_of : ATTR(:name<access_token_expires> :default<>);
36             my %__lwp_agent_of : ATTR(:name<__lwp_agent> :default<>);
37              
38             # Constructor.
39             my ($self, $ident) = @_;
40              
41 18     18 0 6980 $__lwp_agent_of{$ident} ||= LWP::UserAgent->new();
42             }
43 18   66     139  
44             # Methods from Google::Ads::GoogleAds::Common::AuthHandlerInterface.
45             my ($self, $api_client, $properties) = @_;
46             my $ident = ident $self;
47              
48 18     0 1 345 $api_client_of{$ident} = $api_client;
49 18         48 $client_id_of{$ident} = $properties->{clientId}
50             || $client_id_of{$ident};
51 18         164 $access_token_of{$ident} = $properties->{accessToken}
52             || $access_token_of{$ident};
53 18   66     108  
54             # Set up proxy for __lwp_agent.
55 18   66     73 my $proxy = $api_client->get_proxy();
56             $proxy
57             ? $__lwp_agent_of{$ident}->proxy(['http', 'https'], $proxy)
58 18         66 : $__lwp_agent_of{$ident}->env_proxy;
59             }
60              
61 18 100       218 my ($self, $http_method, $request_url, $http_headers, $request_content) = @_;
62 11     11   62  
  11         18  
  11         81  
63             my $access_token = $self->get_access_token();
64              
65 0     0 1 0 if (!$access_token) {
66             my $api_client = $self->get_api_client();
67 0         0 my $err_msg =
68             "Unable to prepare a request, authorization info is " .
69 0 0       0 "incomplete or invalid.";
70 0         0 $api_client->get_die_on_faults() ? die($err_msg) : warn($err_msg);
71 0         0 return;
72             }
73              
74 0 0       0 push @$http_headers, ("Authorization", "Bearer ${access_token}");
75 0         0  
76             return HTTP::Request->new($http_method, $request_url, $http_headers,
77             $request_content);
78 0         0 }
79              
80 0         0 my $self = shift;
81              
82             return $self->get_access_token();
83             }
84              
85 3     3 1 1996 # Custom getter and setter for the access_token with logic to auto-refresh.
86             my $self = shift;
87 3         15 my $ident = ident $self;
88              
89             if (!$self->__is_access_token_valid()) {
90             if (!$self->_refresh_access_token()) {
91             return undef;
92 6     6 0 3460 }
93 6         19  
94             return $access_token_of{$ident};
95 6 100       37 }
96 2 50       8  
97 2         9 return $access_token_of{$ident};
98             }
99              
100 0         0 my ($self, $token) = @_;
101              
102             $access_token_of{ident $self} = $token;
103 4         38 $access_token_expires_of{ident $self} = undef;
104             }
105              
106             # Internal methods.
107 1     1 0 2  
108             # Checks if:
109 1         4 # - the access token is set
110 1         7 # - if the token has no expiration set then assumes it was manually set and:
111             # - checks the token info, if it is valid then sets its expiration
112             # - checks the token scopes
113             # - checks the token has not expired
114             my $self = shift;
115             my $ident = ident $self;
116              
117             my $access_token = $access_token_of{$ident};
118             if (!$access_token) {
119             return 0;
120             }
121              
122 6     6   10 if (!$self->get_access_token_expires()) {
123 6         12 my $url =
124             Google::Ads::GoogleAds::Constants::OAUTH2_TOKEN_INFO_URL .
125 6         24 "?access_token=" . uri_escape($access_token);
126 6 100       18 my $response = $self->get___lwp_agent()->request(GET $url);
127 2         5 if (!$response->is_success()) {
128             my $err_msg = $response->decoded_content();
129             $self->get_api_client()->get_die_on_faults()
130 4 100       20 ? die($err_msg)
131 2         15 : warn($err_msg);
132             return 0;
133             }
134 2         52 my $content_hash =
135 2 50       11153 $self->__parse_auth_response($response->decoded_content());
136 0         0 my %token_scopes = map { $_ => 1 } split(" ", $content_hash->{scope});
137 0 0       0  
138             foreach my $required_scope ($self->_scope()) {
139             if (!exists($token_scopes{$required_scope})) {
140 0         0 return 0;
141             }
142 2         28 }
143             $self->set_access_token_expires(time + $content_hash->{expires_in});
144 2         7 }
  4         13  
145              
146 2         11 return time < ($self->get_access_token_expires() - 10);
147 4 50       10 }
148 0         0  
149             my ($self, $response_content) = @_;
150              
151 2         15 my %content_hash = ();
152              
153             # Use below regex to parse the token info response into hash.
154 4         42 # The sample token info response is as below:
155             # {
156             # "issued_to": "1234567890-abcdefg.apps.googleusercontent.com",
157             # "audience": "1234567890-abcdefg.apps.googleusercontent.com",
158 3     3   825 # "scope": "https://www.googleapis.com/auth/adwords",
159             # "expires_in": 3548
160 3         5 # }
161             while (
162             $response_content =~ m/([^"]+)"\s*:\s*"([^"]+)|([^"]+)"\s*:\s*([0-9]+)/g)
163             {
164             if ($1 && $2) {
165             $content_hash{$1} = $2;
166             } else {
167             $content_hash{$3} = $4;
168             }
169             }
170 3         24  
171             return \%content_hash;
172             }
173 7 100 66     32  
174 4         22 # Meant to be implemented by a concrete class, which should return the required
175             # API scopes in an array for the OAuth2 protocol.
176 3         14 my $self = shift;
177             die "Need to be implemented by subclass";
178             }
179              
180 3         8 # Method called to refresh the stored OAuth2 access token. Implementors will issue
181             # an access token refresh request to the OAuth2 server.
182             die "Need to be implemented by subclass";
183             }
184              
185             1;
186 0     0      
187 0           =pod
188              
189             =head1 NAME
190              
191             Google::Ads::GoogleAds::Common::OAuth2BaseHandler
192              
193 0     0     =head1 DESCRIPTION
194              
195             An abstract base implementation that defines part of the logic required to use
196             OAuth2 against Google APIs.
197              
198             It is meant to be specialized and its L</_scope>, L</_refresh_access_token>
199             methods should be properly implemented.
200              
201             =head1 ATTRIBUTES
202              
203             Each of these attributes can be set via
204             Google::Ads::GoogleAds::Common::OAuth2BaseHandler->new().
205              
206             Alternatively, there is a get_ and set_ method associated with each attribute
207             for retrieving or setting them dynamically.
208              
209             my %api_client_of : ATTR(:name<api_client> :default<>);
210             my %client_id_of : ATTR(:name<client_id> :default<>);
211             my %access_token_of : ATTR(:init_arg<access_token> :default<>);
212             my %access_token_expires_of : ATTR(:name<access_token_expires> :default<>);
213              
214             =head2 api_client
215              
216             A reference to the API client used to handle the API requests.
217              
218             =head2 client_id
219              
220             OAuth2 client id obtained from the Google APIs console.
221              
222             =head2 access_token
223              
224             Stores an OAuth2 access token after the authorization flow is followed or for
225             you to manually set it in case you had it previously stored. If this is manually
226             set this handler will verify its validity before preparing a request.
227              
228             =head1 METHODS
229              
230             =head2 initialize
231              
232             Initializes the handler with the API client object and the properties such as
233             client_id and access_token.
234              
235             =head3 Parameters
236              
237             =over
238              
239             =item *
240              
241             A required I<api_client> with a reference to the API client object handling the
242             requests against the API.
243              
244             =item *
245              
246             A hash reference with the following keys.
247              
248             {
249             clientId => "client-id",
250             accessToken => "access-token"
251             }
252              
253             Refer to the documentation of the L</client_id> and L</access_token> properties.
254              
255             =back
256              
257             =head2 prepare_request
258              
259             Refer to L<Google::Ads::GoogleAds::Common::AuthHandlerInterface> documentation
260             of this method.
261              
262             =head2 is_auth_enabled
263              
264             Refer to L<Google::Ads::GoogleAds::Common::AuthHandlerInterface> documentation
265             of this method.
266              
267             =head2 _scope
268              
269             Meant to be implemented by a concrete class, which should return the required
270             API scopes in an array for the OAuth2 protocol.
271              
272             =head2 _refresh_access_token
273              
274             Method called to refresh the stored OAuth2 access token. Implementors will issue
275             an access token refresh request to the OAuth2 server.
276              
277             =head1 LICENSE AND COPYRIGHT
278              
279             Copyright 2019 Google LLC
280              
281             Licensed under the Apache License, Version 2.0 (the "License");
282             you may not use this file except in compliance with the License.
283             You may obtain a copy of the License at
284              
285             http://www.apache.org/licenses/LICENSE-2.0
286              
287             Unless required by applicable law or agreed to in writing, software
288             distributed under the License is distributed on an "AS IS" BASIS,
289             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
290             See the License for the specific language governing permissions and
291             limitations under the License.
292              
293             =head1 REPOSITORY INFORMATION
294              
295             $Rev: $
296             $LastChangedBy: $
297             $Id: $
298              
299             =cut