File Coverage

lib/Google/Ads/Common/OAuth2BaseHandler.pm
Criterion Covered Total %
statement 33 92 35.8
branch 0 20 0.0
condition 0 12 0.0
subroutine 11 22 50.0
pod 3 6 50.0
total 47 152 30.9


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