File Coverage

lib/LWP/Authen/OAuth2.pm
Criterion Covered Total %
statement 61 188 32.4
branch 3 54 5.5
condition 1 21 4.7
subroutine 14 41 34.1
pod 23 30 76.6
total 102 334 30.5


line stmt bran cond sub pod time code
1             package LWP::Authen::OAuth2;
2              
3             # ABSTRACT: Make requests to OAuth2 APIs.
4             our $VERSION = '0.20'; # VERSION
5              
6 8     8   594792 use 5.006;
  8         120  
7 8     8   56 use strict;
  8         20  
  8         221  
8 8     8   43 use warnings;
  8         18  
  8         304  
9              
10 8     8   61 use Carp qw(croak confess);
  8         27  
  8         531  
11             # LWP::UserAgent lazyloads these, but we always need it.
12 8     8   4144 use HTTP::Request::Common;
  8         219120  
  8         690  
13 8     8   5381 use JSON qw(encode_json decode_json);
  8         85439  
  8         62  
14 8     8   7004 use LWP::UserAgent;
  8         235607  
  8         368  
15 8     8   4447 use Module::Load qw(load);
  8         9461  
  8         58  
16              
17             our @CARP_NOT = map "LWP::Authen::OAUth2::$_", qw(Args ServiceProvider);
18 8         546 use LWP::Authen::OAuth2::Args qw(
19             extract_option copy_option assert_options_empty
20 8     8   5202 );
  8         28  
21 8     8   4417 use LWP::Authen::OAuth2::ServiceProvider;
  8         34  
  8         17653  
22              
23             sub new {
24 7     7 0 4904 my ($class, %opts) = @_;
25              
26             # Constructing the service provider can consume my options.
27 7         37 my $service_provider = LWP::Authen::OAuth2::ServiceProvider->new(\%opts);
28 7         63 my $self
29             = bless {
30             service_provider => $service_provider
31             }, $service_provider->oauth2_class();
32 7         54 $self->init(%opts, service_provider => $service_provider);
33 7         28 return $self;
34             }
35              
36             sub init {
37 7     7 0 31 my ($self , %opts) = @_;
38              
39             # Collect arguments for the service providers.
40 7         33 my $service_provider = $self->{service_provider};
41 7         65 my $for_service_provider = LWP::Authen::OAuth2::Args->new();
42 7         18 my %is_seen;
43 7         14 for my $opt (@{ $service_provider->{required_init} }) {
  7         22  
44 13         32 $is_seen{$opt}++;
45 13         37 $for_service_provider->copy_option(\%opts, $opt);
46             }
47 7         388 for my $opt (@{ $service_provider->{optional_init} }) {
  7         29  
48 16 100       62 if (not $is_seen{$opt}) {
49 15         36 $is_seen{$opt}++;
50 15         46 $for_service_provider->copy_option(\%opts, $opt, undef);
51             }
52             }
53 7         30 $self->{for_service_provider} = $for_service_provider;
54              
55 7         45 $self->copy_option(\%opts, "early_refresh_time", 300);
56 7         26 $self->copy_option(\%opts, "error_handler", undef);
57 7         71 $self->copy_option(\%opts, "is_strict", 1);
58 7         39 $self->copy_option(\%opts, "prerefresh", undef);
59 7         44 $self->copy_option(\%opts, "save_tokens", undef);
60 7         24 $self->copy_option(\%opts, "save_tokens_args", undef);
61 7         25 $self->copy_option(\%opts, "token_string", undef);
62 7         23 $self->copy_option(\%opts, "user_agent", undef);
63              
64 7 50       44 if ($self->{token_string}) {
65 0         0 $self->load_token_string();
66             }
67             }
68              
69             # Standard shortcut request methods.
70             sub delete {
71 0     0 1 0 my ($self, @parameters) = @_;
72 0         0 my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1);
73 0         0 my $request = HTTP::Request::Common::DELETE(@parameters);
74 0         0 return $self->request($request, @rest);
75             }
76              
77             sub get {
78 0     0 1 0 my ($self, @parameters) = @_;
79 0         0 my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1);
80 0         0 my $request = HTTP::Request::Common::GET(@parameters);
81 0         0 return $self->request($request, @rest);
82             }
83              
84             sub head {
85 0     0 1 0 my ($self, @parameters) = @_;
86 0         0 my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1);
87 0         0 my $request = HTTP::Request::Common::HEAD(@parameters);
88 0         0 return $self->request($request, @rest);
89             }
90              
91             sub post {
92 0     0 1 0 my ($self, @parameters) = @_;
93 0 0       0 my @rest = $self->user_agent->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1));
94 0         0 my $request = HTTP::Request::Common::POST(@parameters);
95 0         0 return $self->request($request, @rest);
96             }
97              
98             sub put {
99 0     0 1 0 my ($self, @parameters) = @_;
100 0 0       0 my @rest = $self->user_agent->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1));
101 0         0 my $request = HTTP::Request::Common::PUT(@parameters);
102 0         0 return $self->request($request, @rest);
103             }
104              
105             sub request {
106 0     0 1 0 my ($self, $request, @rest) = @_;
107 0         0 return $self->access_token->request($self, $request, @rest);
108             }
109              
110             # Now all of the methods that I need.
111             sub token_string {
112 0     0 1 0 my $self = shift;
113 0 0       0 if ($self->{access_token}) {
114 0         0 my $ref = $self->{access_token}->to_ref;
115 0         0 $ref->{_class} = ref($self->{access_token});
116 0         0 return encode_json($ref);
117             }
118             else {
119 0         0 return undef;
120             }
121             }
122              
123             # This does the actual saving.
124             sub _set_tokens {
125 0     0   0 my ($self, %opts) = @_;
126              
127 0         0 my $tokens = $self->extract_option(\%opts, "tokens");
128 0         0 my $skip_save = $self->extract_option(\%opts, "skip_save", 0);
129 0         0 assert_options_empty(\%opts);
130              
131 0 0       0 if (ref($tokens)) {
132             # Assume we have tokens.
133 0         0 $self->{access_token} = $tokens;
134 0 0 0     0 if ($self->{save_tokens} and not $skip_save) {
135 0         0 my $as_string = $self->token_string;
136 0         0 $self->{save_tokens}->($as_string, @{$self->{save_tokens_args}});
  0         0  
137             }
138 0         0 return;
139             }
140             else {
141             # Assume we have an error message.
142 0         0 return $self->error($tokens);
143             }
144             }
145              
146             sub authorization_url {
147 0     0 1 0 my ($self, %opts) = @_;
148              
149             # If we get here, the service provider does it.
150 0         0 my $url = $self->{service_provider}->authorization_url($self, %opts);
151 0 0       0 if ($url =~ / /) {
152             # Assume an error.
153 0         0 return $self->error($url);
154             }
155             else {
156 0         0 return $url;
157             }
158             }
159              
160             sub api_url_base {
161 0     0 1 0 my $self = shift;
162 0   0     0 return $self->{service_provider}->api_url_base || '';
163             }
164              
165             sub make_api_call {
166 0     0 1 0 my ($self, $uri, $params, $headers) = @_;
167 0 0       0 my $url = $uri =~ m|^http| ? $uri : $self->api_url_base.$uri;
168 0 0       0 if ($self->{service_provider}->can('default_api_headers')) {
169 0         0 my $service_provider_headers = $self->{service_provider}->default_api_headers;
170 0 0 0     0 $headers = ref $headers eq 'HASH' ? { %$headers, %$service_provider_headers } : $service_provider_headers || {};
171             }
172              
173 0 0       0 my $response = $params ? $self->post($url, Content => encode_json($params), %$headers) : $self->get($url, %$headers);
174              
175 0 0       0 if (! $response->is_success()) {
176             #$self->error('failed call to: '.$url.'; status_line='.$response->status_line.'; full error='.$response->error_as_HTML.'; content='.$response->content);
177 0   0     0 $self->{'_api_call_error'} = $response->error_as_HTML || $response->status_line;
178 0         0 return undef;
179             }
180              
181 0         0 my $content = $response->content;
182 0 0       0 return 1 if ! $content; # success
183 0         0 return eval { decode_json($content) }; # return decoded JSON if response has a body
  0         0  
184             }
185              
186 0     0 1 0 sub api_call_error { return shift->{'_api_call_error'}; }
187              
188             sub request_tokens {
189 0     0 1 0 my ($self, %opts) = @_;
190              
191             # If we get here, the service provider does it.
192 0         0 my $tokens = $self->{service_provider}->request_tokens($self, %opts);
193             # _set_tokens will set an error if needed.
194 0         0 return $self->_set_tokens(tokens => $tokens);
195             }
196              
197             sub can_refresh_tokens {
198 0     0 1 0 my $self = shift;
199 0 0       0 if (not $self->{access_token}) {
200 0         0 return 0;
201             }
202             else {
203 0         0 my %opts = ($self->{access_token}->for_refresh(), @_);
204 0         0 return $self->{service_provider}->can_refresh_tokens($self, %opts);
205             }
206             }
207              
208             sub refresh_access_token {
209 0     0 0 0 my $self = shift;
210 0 0       0 if (not $self->{access_token}) {
211 0         0 croak("Cannot try to refresh access token without tokens");
212             }
213 0         0 my %opts = ($self->{access_token}->for_refresh(), @_);
214              
215             # Give a chance for the hook to do it.
216 0 0       0 if ($self->{prerefresh}) {
217 0         0 my $tokens = $self->{prerefresh}->($self, %opts);
218 0 0       0 if ($tokens) {
219 0 0       0 if (not (ref($tokens))) {
220             # Did I get JSON?
221 0         0 my $data = eval {decode_json($tokens)};
  0         0  
222              
223 0 0 0     0 if ($data and not $@) {
224 0 0       0 my $class = $data->{_class} or croak("No _class in token_string '$tokens'");
225 0         0 eval {load($class)};
  0         0  
226 0 0       0 if ($@) { croak("Can't load access token class '$class': $@"); }
  0         0  
227 0         0 $tokens = $class->from_ref($data);
228             }
229             }
230 0         0 return $self->_set_tokens(tokens => $tokens, skip_save => 1);
231             }
232             }
233              
234 0         0 my $tokens = $self->{service_provider}->refreshed_tokens($self, %opts);
235             # _set_tokens will set an error if needed.
236 0         0 return $self->_set_tokens(tokens => $tokens);
237             }
238              
239             sub access_token {
240 0     0 0 0 my $self = shift;
241              
242 0         0 return $self->{access_token};
243             }
244              
245             sub should_refresh {
246 0     0 1 0 my $self = shift;
247              
248 0         0 return $self->access_token->should_refresh($self->{early_refresh_time});
249             }
250              
251             sub expires_time {
252 0     0 1 0 my $self = shift;
253 0 0       0 return 0 if ! $self->{access_token};
254 0         0 return $self->access_token->expires_time;
255             }
256              
257             sub set_early_refresh_time {
258 0     0 1 0 my ($self, $early_refresh_time) = @_;
259 0         0 $self->{early_refresh_time} = $early_refresh_time;
260             }
261              
262             sub set_is_strict {
263 0     0 1 0 my ($self, $strict) = @_;
264 0         0 $self->{is_strict} = $strict;
265             }
266              
267             sub is_strict {
268 1     1 1 2 my $self = shift;
269 1         4 return $self->{is_strict};
270             }
271              
272             sub set_error_handler {
273 0     0 1 0 my ($self, $handler) = @_;
274 0         0 $self->{error_handler} = @_;
275             }
276              
277             sub error {
278 0     0 0 0 my $self = shift;
279 0 0       0 if ($self->{error_handler}) {
280 0         0 return $self->{error_handler}->(@_);
281             }
282             else {
283 0         0 croak(@_);
284             }
285             }
286              
287             sub for_service_provider {
288 1     1 0 3 my $self = shift;
289 1   50     5 return $self->{for_service_provider} ||= {};
290             }
291              
292             sub set_prerefresh {
293 0     0 1   my ($self, $prerefresh) = @_;
294 0           $self->{prerefresh} = $prerefresh;
295             }
296              
297             sub set_save_tokens {
298 0     0 1   my ($self, $save_tokens) = @_;
299 0           $self->{save_tokens} = $save_tokens;
300             }
301              
302             sub set_user_agent {
303 0     0 1   my ($self, $agent) = @_;
304 0           $self->{user_agent} = $agent;
305             }
306              
307             sub load_token_string {
308 0     0 0   my ($self, $token_string) = @_;
309 0   0       $token_string ||= $self->{token_string};
310              
311             # Probably not the object that I need in access_token.
312 0           my $tokens = eval{ decode_json($token_string) };
  0            
313 0 0         if ($@) {
314 0           croak("While decoding token_string: $@");
315             }
316              
317             my $class = $tokens->{_class}
318 0 0         or croak("No _class in token_string '$token_string'");
319              
320 0           eval {load($class)};
  0            
321 0 0         if ($@) {
322 0           croak("Can't load access token class '$class': $@");
323             }
324              
325             # I will assume this works.
326 0           $self->{access_token} = $class->from_ref($tokens);
327             }
328              
329             sub user_agent {
330 0     0 1   my $self = shift;
331 0   0       return $self->{user_agent} ||= LWP::UserAgent->new();
332             }
333              
334              
335             1; # End of LWP::Authen::OAuth2
336              
337             __END__