File Coverage

lib/LWP/Authen/OAuth2.pm
Criterion Covered Total %
statement 57 188 30.3
branch 3 54 5.5
condition 0 21 0.0
subroutine 12 41 29.2
pod 23 30 76.6
total 95 334 28.4


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.19'; # VERSION
5              
6 7     7   403021 use 5.006;
  7         86  
7 7     7   49 use strict;
  7         11  
  7         164  
8 7     7   32 use warnings;
  7         13  
  7         221  
9              
10 7     7   35 use Carp qw(croak confess);
  7         22  
  7         382  
11             # LWP::UserAgent lazyloads these, but we always need it.
12 7     7   2726 use HTTP::Request::Common;
  7         126869  
  7         521  
13 7     7   3697 use JSON qw(encode_json decode_json);
  7         57818  
  7         37  
14 7     7   4701 use LWP::UserAgent;
  7         163240  
  7         263  
15 7     7   3018 use Module::Load qw(load);
  7         6155  
  7         43  
16              
17             our @CARP_NOT = map "LWP::Authen::OAUth2::$_", qw(Args ServiceProvider);
18 7         441 use LWP::Authen::OAuth2::Args qw(
19             extract_option copy_option assert_options_empty
20 7     7   3252 );
  7         18  
21 7     7   3283 use LWP::Authen::OAuth2::ServiceProvider;
  7         17  
  7         11881  
22              
23             sub new {
24 6     6 0 2891 my ($class, %opts) = @_;
25              
26             # Constructing the service provider can consume my options.
27 6         30 my $service_provider = LWP::Authen::OAuth2::ServiceProvider->new(\%opts);
28 6         37 my $self
29             = bless {
30             service_provider => $service_provider
31             }, $service_provider->oauth2_class();
32 6         44 $self->init(%opts, service_provider => $service_provider);
33 6         21 return $self;
34             }
35              
36             sub init {
37 6     6 0 27 my ($self , %opts) = @_;
38              
39             # Collect arguments for the service providers.
40 6         30 my $service_provider = $self->{service_provider};
41 6         33 my $for_service_provider = LWP::Authen::OAuth2::Args->new();
42 6         15 my %is_seen;
43 6         10 for my $opt (@{ $service_provider->{required_init} }) {
  6         17  
44 11         21 $is_seen{$opt}++;
45 11         34 $for_service_provider->copy_option(\%opts, $opt);
46             }
47 6         11 for my $opt (@{ $service_provider->{optional_init} }) {
  6         14  
48 14 100       32 if (not $is_seen{$opt}) {
49 13         24 $is_seen{$opt}++;
50 13         27 $for_service_provider->copy_option(\%opts, $opt, undef);
51             }
52             }
53 6         15 $self->{for_service_provider} = $for_service_provider;
54              
55 6         30 $self->copy_option(\%opts, "early_refresh_time", 300);
56 6         21 $self->copy_option(\%opts, "error_handler", undef);
57 6         26 $self->copy_option(\%opts, "is_strict", 1);
58 6         28 $self->copy_option(\%opts, "prerefresh", undef);
59 6         32 $self->copy_option(\%opts, "save_tokens", undef);
60 6         30 $self->copy_option(\%opts, "save_tokens_args", undef);
61 6         34 $self->copy_option(\%opts, "token_string", undef);
62 6         20 $self->copy_option(\%opts, "user_agent", undef);
63              
64 6 50       24 if ($self->{token_string}) {
65 0           $self->load_token_string();
66             }
67             }
68              
69             # Standard shortcut request methods.
70             sub delete {
71 0     0 1   my ($self, @parameters) = @_;
72 0           my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1);
73 0           my $request = HTTP::Request::Common::DELETE(@parameters);
74 0           return $self->request($request, @rest);
75             }
76              
77             sub get {
78 0     0 1   my ($self, @parameters) = @_;
79 0           my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1);
80 0           my $request = HTTP::Request::Common::GET(@parameters);
81 0           return $self->request($request, @rest);
82             }
83              
84             sub head {
85 0     0 1   my ($self, @parameters) = @_;
86 0           my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1);
87 0           my $request = HTTP::Request::Common::HEAD(@parameters);
88 0           return $self->request($request, @rest);
89             }
90              
91             sub post {
92 0     0 1   my ($self, @parameters) = @_;
93 0 0         my @rest = $self->user_agent->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1));
94 0           my $request = HTTP::Request::Common::POST(@parameters);
95 0           return $self->request($request, @rest);
96             }
97              
98             sub put {
99 0     0 1   my ($self, @parameters) = @_;
100 0 0         my @rest = $self->user_agent->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1));
101 0           my $request = HTTP::Request::Common::PUT(@parameters);
102 0           return $self->request($request, @rest);
103             }
104              
105             sub request {
106 0     0 1   my ($self, $request, @rest) = @_;
107 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   my $self = shift;
113 0 0         if ($self->{access_token}) {
114 0           my $ref = $self->{access_token}->to_ref;
115 0           $ref->{_class} = ref($self->{access_token});
116 0           return encode_json($ref);
117             }
118             else {
119 0           return undef;
120             }
121             }
122              
123             # This does the actual saving.
124             sub _set_tokens {
125 0     0     my ($self, %opts) = @_;
126              
127 0           my $tokens = $self->extract_option(\%opts, "tokens");
128 0           my $skip_save = $self->extract_option(\%opts, "skip_save", 0);
129 0           assert_options_empty(\%opts);
130              
131 0 0         if (ref($tokens)) {
132             # Assume we have tokens.
133 0           $self->{access_token} = $tokens;
134 0 0 0       if ($self->{save_tokens} and not $skip_save) {
135 0           my $as_string = $self->token_string;
136 0           $self->{save_tokens}->($as_string, @{$self->{save_tokens_args}});
  0            
137             }
138 0           return;
139             }
140             else {
141             # Assume we have an error message.
142 0           return $self->error($tokens);
143             }
144             }
145              
146             sub authorization_url {
147 0     0 1   my ($self, %opts) = @_;
148              
149             # If we get here, the service provider does it.
150 0           my $url = $self->{service_provider}->authorization_url($self, %opts);
151 0 0         if ($url =~ / /) {
152             # Assume an error.
153 0           return $self->error($url);
154             }
155             else {
156 0           return $url;
157             }
158             }
159              
160             sub api_url_base {
161 0     0 1   my $self = shift;
162 0   0       return $self->{service_provider}->api_url_base || '';
163             }
164              
165             sub make_api_call {
166 0     0 1   my ($self, $uri, $params, $headers) = @_;
167 0 0         my $url = $uri =~ m|^http| ? $uri : $self->api_url_base.$uri;
168 0 0         if ($self->{service_provider}->can('default_api_headers')) {
169 0           my $service_provider_headers = $self->{service_provider}->default_api_headers;
170 0 0 0       $headers = ref $headers eq 'HASH' ? { %$headers, %$service_provider_headers } : $service_provider_headers || {};
171             }
172              
173 0 0         my $response = $params ? $self->post($url, Content => encode_json($params), %$headers) : $self->get($url, %$headers);
174              
175 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       $self->{'_api_call_error'} = $response->error_as_HTML || $response->status_line;
178 0           return undef;
179             }
180              
181 0           my $content = $response->content;
182 0 0         return 1 if ! $content; # success
183 0           return eval { decode_json($content) }; # return decoded JSON if response has a body
  0            
184             }
185              
186 0     0 1   sub api_call_error { return shift->{'_api_call_error'}; }
187              
188             sub request_tokens {
189 0     0 1   my ($self, %opts) = @_;
190              
191             # If we get here, the service provider does it.
192 0           my $tokens = $self->{service_provider}->request_tokens($self, %opts);
193             # _set_tokens will set an error if needed.
194 0           return $self->_set_tokens(tokens => $tokens);
195             }
196              
197             sub can_refresh_tokens {
198 0     0 1   my $self = shift;
199 0 0         if (not $self->{access_token}) {
200 0           return 0;
201             }
202             else {
203 0           my %opts = ($self->{access_token}->for_refresh(), @_);
204 0           return $self->{service_provider}->can_refresh_tokens($self, %opts);
205             }
206             }
207              
208             sub refresh_access_token {
209 0     0 0   my $self = shift;
210 0 0         if (not $self->{access_token}) {
211 0           croak("Cannot try to refresh access token without tokens");
212             }
213 0           my %opts = ($self->{access_token}->for_refresh(), @_);
214              
215             # Give a chance for the hook to do it.
216 0 0         if ($self->{prerefresh}) {
217 0           my $tokens = $self->{prerefresh}->($self, %opts);
218 0 0         if ($tokens) {
219 0 0         if (not (ref($tokens))) {
220             # Did I get JSON?
221 0           my $data = eval {decode_json($tokens)};
  0            
222              
223 0 0 0       if ($data and not $@) {
224 0 0         my $class = $data->{_class} or croak("No _class in token_string '$tokens'");
225 0           eval {load($class)};
  0            
226 0 0         if ($@) { croak("Can't load access token class '$class': $@"); }
  0            
227 0           $tokens = $class->from_ref($data);
228             }
229             }
230 0           return $self->_set_tokens(tokens => $tokens, skip_save => 1);
231             }
232             }
233              
234 0           my $tokens = $self->{service_provider}->refreshed_tokens($self, %opts);
235             # _set_tokens will set an error if needed.
236 0           return $self->_set_tokens(tokens => $tokens);
237             }
238              
239             sub access_token {
240 0     0 0   my $self = shift;
241              
242 0           return $self->{access_token};
243             }
244              
245             sub should_refresh {
246 0     0 1   my $self = shift;
247              
248 0           return $self->access_token->should_refresh($self->{early_refresh_time});
249             }
250              
251             sub expires_time {
252 0     0 1   my $self = shift;
253 0 0         return 0 if ! $self->{access_token};
254 0           return $self->access_token->expires_time;
255             }
256              
257             sub set_early_refresh_time {
258 0     0 1   my ($self, $early_refresh_time) = @_;
259 0           $self->{early_refresh_time} = $early_refresh_time;
260             }
261              
262             sub set_is_strict {
263 0     0 1   my ($self, $strict) = @_;
264 0           $self->{is_strict} = $strict;
265             }
266              
267             sub is_strict {
268 0     0 1   my $self = shift;
269 0           return $self->{is_strict};
270             }
271              
272             sub set_error_handler {
273 0     0 1   my ($self, $handler) = @_;
274 0           $self->{error_handler} = @_;
275             }
276              
277             sub error {
278 0     0 0   my $self = shift;
279 0 0         if ($self->{error_handler}) {
280 0           return $self->{error_handler}->(@_);
281             }
282             else {
283 0           croak(@_);
284             }
285             }
286              
287             sub for_service_provider {
288 0     0 0   my $self = shift;
289 0   0       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__