File Coverage

blib/lib/Net/Twitter/Role/OAuth.pm
Criterion Covered Total %
statement 34 85 40.0
branch 1 8 12.5
condition 2 5 40.0
subroutine 11 21 52.3
pod 9 9 100.0
total 57 128 44.5


line stmt bran cond sub pod time code
1             package Net::Twitter::Role::OAuth;
2             $Net::Twitter::Role::OAuth::VERSION = '4.01042';
3 7     7   4955 use Moose::Role;
  7         10  
  7         54  
4 7     7   24931 use HTTP::Request::Common;
  7         14  
  7         460  
5 7     7   76 use Carp::Clan qw/^(?:Net::Twitter|Moose|Class::MOP)/;
  7         8  
  7         66  
6 7     7   1239 use URI;
  7         12  
  7         157  
7 7     7   523 use Digest::SHA;
  7         2102  
  7         257  
8 7     7   28 use List::Util qw/first/;
  7         10  
  7         351  
9 7     7   2645 use Net::Twitter::Types;
  7         14  
  7         274  
10              
11             requires qw/_add_authorization_header ua/;
12              
13 7     7   36 use namespace::autoclean;
  7         9  
  7         60  
14              
15 7     7   3143 use Net::OAuth;
  7         3049  
  7         6761  
16             $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
17              
18             # flatten oauth_urls with defaults
19             around BUILDARGS => sub {
20                 my $orig = shift;
21                 my $class = shift;
22              
23                 my $args = $class->$orig(@_);
24                 my $oauth_urls = delete $args->{oauth_urls} || {
25                     request_token_url => "https://api.twitter.com/oauth/request_token",
26                     authentication_url => "https://api.twitter.com/oauth/authenticate",
27                     authorization_url => "https://api.twitter.com/oauth/authorize",
28                     access_token_url => "https://api.twitter.com/oauth/access_token",
29                     xauth_url => "https://api.twitter.com/oauth/access_token",
30                 };
31              
32                 return { %$oauth_urls, %$args };
33             };
34              
35             has consumer_key => ( isa => 'Str', is => 'ro', required => 1 );
36             has consumer_secret => ( isa => 'Str', is => 'ro', required => 1 );
37              
38             # URI attributes
39             has [ qw/authentication_url authorization_url request_token_url
40             access_token_url xauth_url/ ] => (
41                 isa => 'Net::Twitter::Types::URI',
42                 is => 'ro',
43                 required => 1,
44                 coerce => 1,
45             );
46              
47             # token attributes
48             for my $attribute ( qw/access_token access_token_secret request_token request_token_secret/ ) {
49                 has $attribute => ( isa => 'Str', is => 'rw',
50                                     clearer => "clear_$attribute",
51                                     predicate => "has_$attribute",
52                 );
53             }
54              
55             # simple check to see if we have access tokens; does not check to see if they are valid
56             sub authorized {
57 7     7 1 13     my $self = shift;
58              
59 7   66     259     return defined $self->has_access_token && $self->has_access_token_secret;
60             }
61              
62             # get the authorization or authentication url
63             sub _get_auth_url {
64 0     0   0     my ($self, $which_url, %params ) = @_;
65              
66 0   0     0     my $callback = delete $params{callback} || 'oob';
67 0         0     $self->_request_request_token(callback => $callback);
68              
69 0         0     my $uri = $self->$which_url;
70 0         0     $uri->query_form(oauth_token => $self->request_token, %params);
71 0         0     return $uri;
72             }
73              
74             # get the authentication URL from Twitter
75 0     0 1 0 sub get_authentication_url { return shift->_get_auth_url(authentication_url => @_) }
76              
77             # get the authorization URL from Twitter
78 0     0 1 0 sub get_authorization_url { return shift->_get_auth_url(authorization_url => @_) }
79              
80             # common portion of all oauth requests
81             sub _make_oauth_request {
82 6     6   503     my ($self, $type, %params) = @_;
83              
84 6 50       58     my $class = $type =~ s/^\+// ? $type : Net::OAuth->request($type);
85                 my $request = $class->new(
86                     version => '1.0',
87                     consumer_key => $self->{consumer_key},
88                     consumer_secret => $self->{consumer_secret},
89 6         31798         request_method => 'GET',
90                     signature_method => 'HMAC-SHA1',
91                     timestamp => time,
92                     nonce => Digest::SHA::sha1_base64(time . $$ . rand),
93                     %params,
94                 );
95              
96 6         1628     $request->sign;
97              
98 6         18659     return $request;
99             }
100              
101             # called by get_authorization_url to obtain request tokens
102             sub _request_request_token {
103 0     0         my ($self, %params) = @_;
104              
105 0               my $uri = $self->request_token_url;
106 0               my $request = $self->_make_oauth_request(
107                     'request token',
108                     request_url => $uri,
109                     %params,
110                 );
111              
112 0               my $msg = HTTP::Request->new(GET => $uri);
113 0               $msg->header(authorization => $request->to_authorization_header);
114              
115 0               my $res = $self->_send_request($msg);
116 0 0             croak "GET $uri failed: ".$res->status_line
117                     unless $res->is_success;
118              
119             # reuse $uri to extract parameters from the response content
120 0               $uri->query($res->content);
121 0               my %res_param = $uri->query_form;
122              
123 0               $self->request_token($res_param{oauth_token});
124 0               $self->request_token_secret($res_param{oauth_token_secret});
125             }
126              
127             # exchange request tokens for access tokens; call with (verifier => $verifier)
128             sub request_access_token {
129 0     0 1       my ($self, %params ) = @_;
130              
131 0               my $uri = $self->access_token_url;
132 0               my $request = $self->_make_oauth_request(
133                     'access token',
134                     request_url => $uri,
135                     token => $self->request_token,
136                     token_secret => $self->request_token_secret,
137                     %params, # verifier => $verifier
138                 );
139              
140 0               my $msg = HTTP::Request->new(GET => $uri);
141 0               $msg->header(authorization => $request->to_authorization_header);
142              
143 0               my $res = $self->_send_request($msg);
144 0 0             croak "GET $uri failed: ".$res->status_line
145                     unless $res->is_success;
146              
147             # discard request tokens, they're no longer valid
148 0               $self->clear_request_token;
149 0               $self->clear_request_token_secret;
150              
151             # reuse $uri to extract parameters from content
152 0               $uri->query($res->content);
153 0               my %res_param = $uri->query_form;
154              
155                 return (
156                     $self->access_token($res_param{oauth_token}),
157                     $self->access_token_secret($res_param{oauth_token_secret}),
158                     $res_param{user_id},
159                     $res_param{screen_name},
160 0               );
161             }
162              
163             around _prepare_request => sub {
164                 my $orig = shift;
165                 my ($self, $http_method, $uri, $args, $authenticate) = @_;
166              
167                 delete $args->{source};
168                 $orig->(@_);
169             };
170              
171             override _add_authorization_header => sub {
172                 my ( $self, $msg, $args ) = @_;
173              
174                 return unless $self->authorized;
175              
176                 my $is_multipart = grep { ref } %$args;
177              
178                 local $Net::OAuth::SKIP_UTF8_DOUBLE_ENCODE_CHECK = 1;
179              
180                 my $uri = $msg->uri->clone;
181                 $uri->query(undef);
182              
183                 my $request = $self->_make_oauth_request(
184                     'protected resource',
185                     request_url => $uri,
186                     request_method => $msg->method,
187                     token => $self->access_token,
188                     token_secret => $self->access_token_secret,
189                     extra_params => $is_multipart ? {} : $args,
190                 );
191              
192                 $msg->header(authorization => $request->to_authorization_header);
193             };
194              
195             sub xauth {
196 0     0 1       my ( $self, $username, $password ) = @_;
197              
198 0               my @args = (
199                     x_auth_username => $username,
200                     x_auth_password => $password,
201                     x_auth_mode => 'client_auth',
202                 );
203              
204 0               my $uri = $self->xauth_url;
205 0               my $request = $self->_make_oauth_request(
206                     'XauthAccessToken',
207                     request_url => $uri,
208                     request_method => 'POST',
209                     @args,
210                 );
211              
212 0               my $res = $self->ua->request(
213                     POST $uri, \@args, Authorization => $request->to_authorization_header);
214 0 0             die "POST $uri failed: ".$res->status_line
215                     unless $res->is_success;
216              
217             # reuse $uri to extract parameters from content
218 0               $uri->query($res->content);
219 0               my %res_param = $uri->query_form;
220              
221                 return (
222                     $self->access_token($res_param{oauth_token}),
223                     $self->access_token_secret($res_param{oauth_token_secret}),
224                     $res_param{user_id},
225                     $res_param{screen_name},
226 0               );
227             }
228              
229             # shortcuts defined in early releases
230             # DEPRECATED
231              
232             sub oauth_token {
233 0     0 1       my($self, @tokens) = @_;
234              
235 0               carp "DEPRECATED: use access_token and access_token_secret instead";
236 0               $self->access_token($tokens[0]);
237 0               $self->access_token_secret($tokens[1]);
238 0               return @tokens;
239             }
240              
241             sub is_authorized {
242 0     0 1       carp "DEPRECATED: use authorized instead";
243 0               shift->authorized(@_)
244             }
245              
246             sub oauth_authorization_url {
247 0     0 1       carp "DEPRECATED: use get_authorization_url instead";
248 0               shift->get_authorization_url(@_)
249             }
250              
251             sub oauth {
252 0     0 1       carp "DEPRECATED: call this method on Net::Twitter itself, rather than through the oauth accessor";
253                 shift
254 0           }
255              
256             1;
257              
258             __END__
259            
260             =encoding utf-8
261            
262             =for stopwords
263            
264             =head1 NAME
265            
266             Net::Twitter::Role::OAuth - Net::Twitter role that provides OAuth instead of Basic Authentication
267            
268             =head1 VERSION
269            
270             version 4.01042
271            
272             =head1 SYNOPSIS
273            
274             use Net::Twitter;
275            
276             my $nt = Net::Twitter->new(
277             traits => ['API::RESTv1_1', 'OAuth'],
278             consumer_key => "YOUR-CONSUMER-KEY",
279             consumer_secret => "YOUR-CONSUMER-SECRET",
280             );
281            
282             # Do some Authentication work. See EXAMPLES
283            
284             my $tweets = $nt->friends_timeline;
285             my $res = $nt->update({ status => "I CAN HAZ OAUTH!" });
286            
287             =head1 DESCRIPTION
288            
289             Net::Twitter::Role::OAuth is a Net::Twitter role that provides OAuth
290             authentication instead of the default Basic Authentication.
291            
292             Note that this client only works with APIs that are compatible to OAuth authentication.
293            
294             =head1 IMPORTANT
295            
296             Beginning with version 3.02, it is necessary for web applications to pass the
297             C<callback> parameter to C<get_authorization_url>. In the absence of a
298             callback parameter, when the user authorizes the application a PIN number is
299             displayed rather than redirecting the user back to your site.
300            
301             =head1 EXAMPLES
302            
303             See the C<examples> directory in this distribution for working examples of both
304             desktop and web applications.
305            
306             Here's how to authorize users as a desktop app mode:
307            
308             use Net::Twitter;
309            
310             my $nt = Net::Twitter->new(
311             traits => ['API::RESTv1_1', 'OAuth'],
312             consumer_key => "YOUR-CONSUMER-KEY",
313             consumer_secret => "YOUR-CONSUMER-SECRET",
314             );
315            
316             # You'll save the token and secret in cookie, config file or session database
317             my($access_token, $access_token_secret) = restore_tokens();
318             if ($access_token && $access_token_secret) {
319             $nt->access_token($access_token);
320             $nt->access_token_secret($access_token_secret);
321             }
322            
323             unless ( $nt->authorized ) {
324             # The client is not yet authorized: Do it now
325             print "Authorize this app at ", $nt->get_authorization_url, " and enter the PIN#\n";
326            
327             my $pin = <STDIN>; # wait for input
328             chomp $pin;
329            
330             my($access_token, $access_token_secret, $user_id, $screen_name) = $nt->request_access_token(verifier => $pin);
331             save_tokens($access_token, $access_token_secret); # if necessary
332             }
333            
334             # Everything's ready
335            
336             In a web application mode, you need to save the oauth_token and
337             oauth_token_secret somewhere when you redirect the user to the OAuth
338             authorization URL.
339            
340             sub twitter_authorize : Local {
341             my($self, $c) = @_;
342            
343             my $nt = Net::Twitter->new(traits => [qw/API::RESTv1_1 OAuth/], %param);
344             my $url = $nt->get_authorization_url(callback => $callbackurl);
345            
346             $c->response->cookies->{oauth} = {
347             value => {
348             token => $nt->request_token,
349             token_secret => $nt->request_token_secret,
350             },
351             };
352            
353             $c->response->redirect($url);
354             }
355            
356             And when the user returns back, you'll reset those request token and
357             secret to upgrade the request token to access token.
358            
359             sub twitter_auth_callback : Local {
360             my($self, $c) = @_;
361            
362             my %cookie = $c->request->cookies->{oauth}->value;
363             my $verifier = $c->req->params->{oauth_verifier};
364            
365             my $nt = Net::Twitter->new(traits => [qw/API::RESTv1_1 OAuth/], %param);
366             $nt->request_token($cookie{token});
367             $nt->request_token_secret($cookie{token_secret});
368            
369             my($access_token, $access_token_secret, $user_id, $screen_name)
370             = $nt->request_access_token(verifier => $verifier);
371            
372             # Save $access_token and $access_token_secret in the database associated with $c->user
373             }
374            
375             Later on, you can retrieve and reset those access token and secret
376             before calling any Twitter API methods.
377            
378             sub make_tweet : Local {
379             my($self, $c) = @_;
380            
381             my($access_token, $access_token_secret) = ...;
382            
383             my $nt = Net::Twitter->new(traits => [qw/API::RESTv1_1 OAuth/], %param);
384             $nt->access_token($access_token);
385             $nt->access_token_secret($access_token_secret);
386            
387             # Now you can call any Net::Twitter API methods on $nt
388             my $status = $c->req->param('status');
389             my $res = $nt->update({ status => $status });
390             }
391            
392             =head1 METHODS
393            
394             =over 4
395            
396             =item authorized
397            
398             Whether the client has the necessary credentials to be authorized.
399            
400             Note that the credentials may be wrong and so the request may fail.
401            
402             =item request_access_token(verifier => $verifier)
403            
404             Request the access token, access token secret, user id and screen name for
405             this user. You must pass the PIN# (for desktop applications) or the
406             C<oauth_verifier> value, provided as a parameter to the oauth callback
407             (for web applications) as C<$verifier>.
408            
409             The user must have authorized this app at the url given by C<get_authorization_url> first.
410            
411             Returns the access_token, access_token_secret, user_id, and screen_name in a
412             list. Also sets them internally so that after calling this method, you can
413             immediately call API methods requiring authentication.
414            
415             =item xauth($username, $password)
416            
417             Exchanges the C<$username> and C<$password> for access tokens. This method has
418             the same return value as C<request_access_token>: access_token, access_token_secret,
419             user_id, and screen_name in a list. Also, like C<request_access_token>, it sets
420             the access_token and access_secret, internally, so you can immediately call API
421             methods requiring authentication.
422            
423             =item get_authorization_url(callback => $callback_url)
424            
425             Get the URL used to authorize the user. Returns a C<URI> object. For web
426             applications, pass your applications callback URL as the C<callback> parameter.
427             No arguments are required for desktop applications (C<callback> defaults to
428             C<oob>, out-of-band).
429            
430             =item get_authentication_url(callback => $callback_url)
431            
432             Get the URL used to authenticate the user with "Sign in with Twitter"
433             authentication flow. Returns a C<URI> object. For web applications, pass your
434             applications callback URL as the C<callback> parameter. No arguments are
435             required for desktop applications (C<callback> defaults to C<oob>, out-of-band).
436            
437             =item access_token
438            
439             Get or set the access token.
440            
441             =item access_token_secret
442            
443             Get or set the access token secret.
444            
445             =item request_token
446            
447             Get or set the request token.
448            
449             =item request_token_secret
450            
451             Get or set the request token secret.
452            
453             =back
454            
455             =head1 DEPRECATED METHODS
456            
457             =over 4
458            
459             =item oauth
460            
461             Prior versions used Net::OAuth::Simple. This method provided access to the
462             contained Net::OAuth::Simple object. Beginning with Net::Twitter 3.00, the
463             OAuth methods were delegated to Net::OAuth::Simple. They have since made first
464             class methods. Net::Simple::OAuth is no longer used. A warning will be
465             displayed when accessing OAuth methods via the <oauth> method. The C<oauth>
466             method will be removed in a future release.
467            
468             =item is_authorized
469            
470             Use C<authorized> instead.
471            
472             =item oauth_authorization_url
473            
474             Use C<get_authorization_url> instead.
475            
476             =item oauth_token
477            
478             $nt->oauth_token($access_token, $access_token_secret);
479            
480             Use C<access_token> and C<access_token_seccret> instead:
481            
482             $nt->access_token($access_token);
483             $nt->access_token_secret($access_token_secret);
484            
485             =back
486            
487             =head1 ACKNOWLEDGEMENTS
488            
489             This module was originally authored by Tatsuhiko Miyagawa as
490             C<Net::Twitter::OAuth>, a subclass of the C<Net::Twitter> 2.x. It was
491             refactored into a Moose Role for use in C<Net::Twitter> 3.0 and above by Marc
492             Mims. Many thanks to Tatsuhiko for the original work on both code and
493             documentation.
494            
495             =head1 AUTHORS
496            
497             Marc Mims E<lt>marc@questright.comE<gt>
498            
499             Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
500            
501             =head1 LICENSE
502            
503             This library is free software; you can redistribute it and/or modify
504             it under the same terms as Perl itself.
505            
506             =head1 SEE ALSO
507            
508             L<Net::Twitter>, L<Net::Twitter::OAuth::Simple>, L<Net::OAuth::Simple>
509            
510             =cut
511