File Coverage

blib/lib/Net/Twitter/Role/OAuth.pm
Criterion Covered Total %
statement 31 83 37.3
branch 1 8 12.5
condition 2 5 40.0
subroutine 10 25 40.0
pod 9 9 100.0
total 53 130 40.7


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