File Coverage

blib/lib/Google/Ads/GoogleAds/OAuth2ApplicationsHandler.pm
Criterion Covered Total %
statement 68 80 85.0
branch 5 12 41.6
condition 15 26 57.6
subroutine 14 15 93.3
pod 3 3 100.0
total 105 136 77.2


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   59 use warnings;
  11         20  
  11         270  
18 11     11   47 use version;
  11         15  
  11         234  
19 11     11   44 use base qw(Google::Ads::GoogleAds::Common::OAuth2BaseHandler
  11         15  
  11         43  
20 11         4141 Google::Ads::GoogleAds::Common::OAuthApplicationsHandlerInterface);
21 11     11   576  
  11         17  
22             # The following needs to be on one line because CPAN uses a particularly hacky
23             # eval() to determine module versions.
24             use Google::Ads::GoogleAds::Constants; our $VERSION = ${Google::Ads::GoogleAds::Constants::VERSION};
25 11     11   68  
  11         19  
  11         357  
26             use Class::Std::Fast;
27 11     11   57 use HTTP::Request::Common;
  11         18  
  11         74  
28 11     11   1156 use URI::Escape;
  11         19  
  11         677  
29 11     11   56  
  11         17  
  11         2445  
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 %client_secret_of : ATTR(:name<client_secret> :default<>);
33             my %access_type_of : ATTR(:name<access_type> :default<offline>);
34             my %prompt_of : ATTR(:name<prompt> :default<consent>);
35             my %refresh_token_of : ATTR(:name<refresh_token> :default<>);
36             my %redirect_uri_of :
37             ATTR(:name<redirect_uri> :default<urn:ietf:wg:oauth:2.0:oob>);
38             my %additional_scopes_of : ATTR(:name<additional_scopes> :default<>);
39              
40             # Methods from Google::Ads::GoogleAds::Common::AuthHandlerInterface.
41             my ($self, $api_client, $properties) = @_;
42             my $ident = ident $self;
43 9     0 1 5267  
44 9         28 $client_secret_of{$ident} = $properties->{clientSecret}
45             || $client_secret_of{$ident};
46             $refresh_token_of{$ident} = $properties->{refreshToken}
47 9   66     72 || $refresh_token_of{$ident};
48              
49 9   66     32 # Below attributes are not in the googleads.properties configuration.
50             $access_type_of{$ident} = $properties->{accessType}
51             || $access_type_of{$ident};
52             $prompt_of{$ident} = $properties->{approvalPrompt}
53 9   66     38 || $prompt_of{$ident};
54             $redirect_uri_of{$ident} = $properties->{redirectUri}
55 9   66     66 || $redirect_uri_of{$ident};
56             $additional_scopes_of{$ident} = $properties->{additionalScopes}
57 9   66     34 || $additional_scopes_of{$ident};
58             }
59 9   66     38  
60 11     11   65 # Methods from Google::Ads::GoogleAds::Common::OAuthApplicationsHandlerInterface.
  11         214  
  11         57  
61             my ($self, $state) = @_;
62              
63             $state ||= "";
64 1     1 1 348 my ($client_id, $redirect_uri, $access_type, $prompt) = (
65             $self->get_client_id(), $self->get_redirect_uri(),
66 1   50     4 $self->get_access_type(), $self->get_prompt());
67 1         3  
68             my $authorization_url =
69             Google::Ads::GoogleAds::Constants::OAUTH2_BASE_URL .
70             "/auth?response_type=code" .
71 1         16 "&client_id=" . uri_escape($client_id) . "&redirect_uri=" .
72             $redirect_uri . "&scope=" . $self->__formatted_scopes() .
73             "&access_type=" . $access_type . "&prompt=" . $prompt;
74              
75             $authorization_url .= ("&state=" . uri_escape($state)) if $state;
76              
77             return $authorization_url;
78 1 50       4 }
79              
80 1         15 my ($self, $authorization_code) = @_;
81              
82             my $body =
83             "code=" . uri_escape($authorization_code) .
84 2     2 1 780 "&client_id=" . uri_escape($self->get_client_id()) . "&client_secret=" .
85             uri_escape($self->get_client_secret()) . "&redirect_uri=" .
86 2         6 uri_escape($self->get_redirect_uri()) . "&grant_type=authorization_code";
87              
88             push my @headers, "Content-Type" => "application/x-www-form-urlencoded";
89             my $request =
90             HTTP::Request->new("POST",
91             Google::Ads::GoogleAds::Constants::OAUTH2_BASE_URL . "/token",
92 2         83 \@headers, $body);
93 2         8 my $response = $self->get___lwp_agent()->request($request);
94              
95             if (!$response->is_success()) {
96             return $response->decoded_content();
97 2         280 }
98              
99 2 100       4119 my $content_hash = $self->__parse_auth_response($response->decoded_content());
100 1         59  
101             $self->set_access_token($content_hash->{access_token});
102             $self->set_refresh_token($content_hash->{refresh_token});
103 1         59 $self->set_access_token_expires(time + $content_hash->{expires_in});
104              
105 1         8 return undef;
106 1         6 }
107 1         9  
108             # Methods from Google::Ads::GoogleAds::Common::OAuth2BaseHandler.
109 1         6 my $self = shift;
110              
111             if (
112             !(
113             $self->get_client_id()
114 1     1   2 && $self->get_client_secret()
115             && $self->get_refresh_token()))
116 1 0 33     4 {
      33        
117             return 0;
118             }
119              
120             my $body =
121             "refresh_token=" . uri_escape($self->get_refresh_token()) .
122 1         10 "&client_id=" . uri_escape($self->get_client_id()) . "&client_secret=" .
123             uri_escape($self->get_client_secret()) . "&grant_type=refresh_token";
124              
125 0         0 push my @headers, "Content-Type" => "application/x-www-form-urlencoded";
126              
127             my $request =
128             HTTP::Request->new("POST",
129             Google::Ads::GoogleAds::Constants::OAUTH2_BASE_URL . "/token",
130 0         0 \@headers, $body);
131             my $response = $self->get___lwp_agent()->request($request);
132 0         0  
133             if (!$response->is_success()) {
134             my $err_msg = $response->decoded_content();
135             $self->get_api_client()->get_die_on_faults()
136 0         0 ? die($err_msg)
137             : warn($err_msg);
138 0 0       0 return 0;
139 0         0 }
140 0 0       0  
141             my $content_hash = $self->__parse_auth_response($response->decoded_content());
142              
143 0         0 $self->set_access_token($content_hash->{access_token});
144             $self->set_access_token_expires(time + $content_hash->{expires_in});
145              
146 0         0 return 1;
147             }
148 0         0  
149 0         0 my $self = shift;
150             my @parsed_scopes = ();
151 0         0 my $additional_scopes = $self->get_additional_scopes();
152             if ($additional_scopes) {
153             @parsed_scopes = split(/\s*,\s*/, $additional_scopes);
154             }
155 6     6   4176 push @parsed_scopes, Google::Ads::GoogleAds::Constants::DEFAULT_OAUTH2_SCOPE;
156 6         8 return @parsed_scopes;
157 6         14 }
158 6 100       30  
159 4         12 # Retrieves the OAuth2 scopes defined in _scope as a list of encoded URLs
160             # separated by pluses. This is the format expected when sending the OAuth2
161 6         9 # request in a URL.
162 6         19 my $self = shift;
163             my @parsed_scopes = $self->_scope();
164             # Removes spaces and replaces commas with pluses. Encode the URI.
165             # Don't encode the plus!
166             # Example:
167             # https://www.googleapis.com/auth/adwords,https://
168             # www.googleapis.com/auth/analytics
169 3     3   580 # changes to
170 3         4 # https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fadwords+https%3A%2F%2F
171             # www.googleapis.com%2Fauth%2Fanalytics
172             foreach my $single_scope (@parsed_scopes) {
173             $single_scope = uri_escape($single_scope);
174             }
175             return join('+', @parsed_scopes);
176             }
177              
178             1;
179 3         5  
180 5         59 =pod
181              
182 3         93 =head1 NAME
183              
184             Google::Ads::GoogleAds::OAuth2ApplicationsHandler
185              
186             =head1 DESCRIPTION
187              
188             A concrete implementation of L<Google::Ads::GoogleAds::Common::OAuth2BaseHandler>
189             and L<Google::Ads::GoogleAds::Common::OAuthApplicationsHandlerInterface> that
190             supports OAuth2 for Web/Desktop applications and defines the scope required to
191             access the Google Ads API server.
192              
193             See L<https://developers.google.com/identity/protocols/oauth2> for details of
194             the protocol.
195              
196             =head1 ATTRIBUTES
197              
198             Each of these attributes can be set via
199             Google::Ads::GoogleAds::OAuth2ApplicationsHandler->new().
200              
201             Alternatively, there is a get_ and set_ method associated with each attribute
202             for retrieving or setting them dynamically.
203              
204             =head2 api_client
205              
206             A reference to the API client used to handle the API requests.
207              
208             =head2 client_id
209              
210             OAuth2 client id obtained from the Google APIs console.
211              
212             =head2 client_secret
213              
214             OAuth2 client secret obtained from the Google APIs console.
215              
216             =head2 access_type
217              
218             OAuth2 access type to be requested when following the authorization flow. It
219             defaults to offline but it can be set to online.
220              
221             =head2 prompt
222              
223             OAuth2 prompt to be used when following the authorization flow. It defaults to
224             consent.
225              
226             =head2 redirect_uri
227              
228             Redirect URI as set for you in the Google APIs console, to which the
229             authorization flow will callback with the authorization code. Defaults to
230             urn:ietf:wg:oauth:2.0:oob for the desktop applications flow.
231              
232             =head2 access_token
233              
234             Stores an OAuth2 access token after the authorization flow is followed or for
235             you to manually set it in case you had it previously stored. If this is manually
236             set this handler will verify its validity before preparing a request.
237              
238             =head2 refresh_token
239              
240             Stores an OAuth2 refresh token in case of an offline L</access_type> is
241             requested. It is automatically used by the handler to request new access tokens,
242             i.e. when they are expired or found invalid.
243              
244             =head2 additional_scopes
245              
246             Stores additional OAuth2 scopes as a comma-separated string.
247             These scopes define which services the tokens are allowed to access,
248             e.g. https://www.googleapis.com/auth/analytics.
249              
250             =head1 METHODS
251              
252             =head2 initialize
253              
254             Initializes the handler with the API client object and the properties such as
255             client_id and client_secret, used for generating authorization requests.
256              
257             =head3 Parameters
258              
259             =over
260              
261             =item *
262              
263             A required I<api_client> with a reference to the API client object handling the
264             requests against the API.
265              
266             =item *
267              
268             A hash reference with the following keys:
269              
270             {
271             clientId => "client-id",
272             clientSecret => "client-secret",
273             accessType => "access-type",
274             approvalPrompt => "approval-prompt",
275             redirectUri => "redirect-uri",
276             accessToken => "access-token",
277             refreshToken => "refresh-token",
278             additionalScopes => "additional-scopes",
279             }
280              
281             Refer to the documentation of the properties as L</client_id>, L</client_secret>,
282             L</access_type>, L</prompt>, L</redirect_uri>, L</access_token>, L</refresh_token>
283             and L</additional_scopes>.
284              
285             =back
286              
287             =head2 prepare_request
288              
289             Refer to L<Google::Ads::GoogleAds::Common::AuthHandlerInterface> documentation
290             of this method.
291              
292             =head2 is_auth_enabled
293              
294             Refer to L<Google::Ads::GoogleAds::Common::AuthHandlerInterface> documentation
295             of this method.
296              
297             =head2 get_authorization_url
298              
299             Refer to L<Google::Ads::GoogleAds::Common::OAuthApplicationsHandlerInterface>
300             documentation of this method.
301              
302             =head2 issue_access_token
303              
304             Refer to L<Google::Ads::GoogleAds::Common::OAuthApplicationsHandlerInterface>
305             documentation of this method.
306              
307             =head2 _scope
308              
309             Method defined by L<Google::Ads::GoogleAds::Common::OAuth2BaseHandler> and
310             implemented in this class to return the required OAuth2 scopes as an array.
311              
312             =head2 _refresh_access_token
313              
314             Method defined by L<Google::Ads::GoogleAds::Common::OAuth2BaseHandler> and
315             implemented in this class to refresh the stored OAuth2 access token.
316              
317             =head2 __formatted_scopes
318              
319             Private method to return the OAuth2 scopes as a list of encoded URLs separated
320             by pluses. This is the format expected when sending the OAuth2 request in a URL.
321              
322             =head3 Returns
323              
324             The encoded URL string of OAuth2 scopes separated by pluses.
325              
326             =head1 LICENSE AND COPYRIGHT
327              
328             Copyright 2019 Google LLC
329              
330             Licensed under the Apache License, Version 2.0 (the "License");
331             you may not use this file except in compliance with the License.
332             You may obtain a copy of the License at
333              
334             http://www.apache.org/licenses/LICENSE-2.0
335              
336             Unless required by applicable law or agreed to in writing, software
337             distributed under the License is distributed on an "AS IS" BASIS,
338             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
339             See the License for the specific language governing permissions and
340             limitations under the License.
341              
342             =head1 REPOSITORY INFORMATION
343              
344             $Rev: $
345             $LastChangedBy: $
346             $Id: $
347              
348             =cut