File Coverage

blib/lib/Net/Twitter/Role/AppAuth.pm
Criterion Covered Total %
statement 22 33 66.6
branch 2 8 25.0
condition n/a
subroutine 6 7 85.7
pod 2 2 100.0
total 32 50 64.0


line stmt bran cond sub pod time code
1             package Net::Twitter::Role::AppAuth;
2             $Net::Twitter::Role::AppAuth::VERSION = '4.01010';
3 1     1   772 use Moose::Role;
  1         2914  
  1         3  
4 1     1   3654 use Carp::Clan qw/^(?:Net::Twitter|Moose|Class::MOP)/;
  1         1  
  1         7  
5 1     1   147 use HTTP::Request::Common qw/POST/;
  1         1  
  1         58  
6              
7             requires qw/_add_authorization_header ua from_json/;
8              
9 1     1   4 use namespace::autoclean;
  1         1  
  1         7  
10              
11             # flatten oauth_urls with defaults
12             around BUILDARGS => sub {
13             my $orig = shift;
14             my $class = shift;
15              
16             my $args = $class->$orig(@_);
17             my $oauth_urls = delete $args->{oauth_urls} || {
18             request_token_url => "https://api.twitter.com/oauth2/token",
19             invalidate_token_url => "https://api.twitter.com/oauth2/invalidate_token",
20             };
21              
22             return { %$oauth_urls, %$args };
23             };
24              
25             has [ qw/consumer_key consumer_secret/ ] => (
26             isa => 'Str',
27             is => 'ro',
28             required => 1,
29             );
30              
31             # url attributes
32             has [ qw/request_token_url invalidate_token_url/ ] => (
33             isa => 'Str',
34             is => 'rw',
35             required => 1,
36             );
37              
38             has access_token => (
39             isa => 'Str',
40             is => 'rw',
41             clearer => "clear_access_token",
42             predicate => "authorized",
43             );
44              
45             sub _add_consumer_auth_header {
46 1     1   2 my ( $self, $req ) = @_;
47              
48 1         3 $req->headers->authorization_basic(
49             $self->consumer_key, $self->consumer_secret);
50             }
51              
52             sub request_access_token {
53 1     1 1 73 my $self = shift;
54              
55 1         33 my $req = POST($self->request_token_url,
56             'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8',
57             Content => { grant_type => 'client_credentials' },
58             );
59 1         5806 $self->_add_consumer_auth_header($req);
60              
61 1         1190 my $res = $self->ua->request($req);
62 1 50       952 croak "request_token failed: ${ \$res->code }: ${ \$res->message }"
  0         0  
  0         0  
63             unless $res->is_success;
64              
65 1         20 my $r = $self->from_json($res->decoded_content);
66 1 50       30 croak "unexpected token type: $$r{token_type}" unless $$r{token_type} eq 'bearer';
67              
68 1         30 return $self->access_token($$r{access_token});
69             }
70              
71             sub invalidate_token {
72 0     0 1   my $self = shift;
73              
74 0 0         croak "no access_token" unless $self->authorized;
75              
76 0           my $req = POST($self->invalidate_token_url,
77             'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8',
78             Content => join '=', access_token => $self->access_token,
79             );
80 0           $self->_add_consumer_auth_header($req);
81              
82 0           my $res = $self->ua->request($req);
83 0 0         croak "invalidate_token failed: ${ \$res->code }: ${ \$res->message }"
  0            
  0            
84             unless $res->is_success;
85              
86 0           $self->clear_access_token;
87             }
88              
89             around _prepare_request => sub {
90             my $orig = shift;
91             my $self = shift;
92             my ($http_method, $uri, $args, $authenticate) = @_;
93              
94             delete $args->{source};
95             $self->$orig(@_);
96             };
97              
98             override _add_authorization_header => sub {
99             my ( $self, $msg ) = @_;
100              
101             return unless $self->authorized;
102              
103             $msg->header(authorization => join ' ', Bearer => $self->access_token);
104             };
105              
106             1;
107              
108             __END__
109              
110             =encoding utf-8
111              
112             =for stopwords
113              
114             =head1 NAME
115              
116             Net::Twitter::Role::AppAuth - OAuth2 Application Only Authentication
117              
118             =head1 VERSION
119              
120             version 4.01010
121              
122             =head1 SYNOPSIS
123              
124             use Net::Twitter;
125              
126             my $nt = Net::Twitter->new(
127             traits => ['API::RESTv1_1', 'AppAuth'],
128             consumer_key => "YOUR-CONSUMER-KEY",
129             consumer_secret => "YOUR-CONSUMER-SECRET",
130             );
131              
132             $nt->request_token;
133              
134             my $tweets = $nt->user_timeline({ screen_name => 'Twitter' });
135              
136             =head1 DESCRIPTION
137              
138             Net::Twitter::Role::OAuth is a Net::Twitter role that provides OAuth
139             authentication instead of the default Basic Authentication.
140              
141             Note that this client only works with APIs that are compatible to OAuth authentication.
142              
143              
144             =head1 METHODS
145              
146             =over 4
147              
148             =item authorized
149              
150             True if the client has an access_token. This does not check the validity of the
151             access token, so requests may fail if it is invalid.
152              
153             =item request_access_token
154              
155             Request an access token. Returns the token as well as saving it in the object.
156              
157             =item access_token
158              
159             Get or set the access token.
160              
161             =item invalidate_token
162              
163             Invalidates and clears the access_token.
164              
165             Note: There seems to be a Twitter bug preventing this from working---perhaps a
166             documentation bug. E.g., see: L<https://twittercommunity.com/t/revoke-an-access-token-programmatically-always-getting-a-403-forbidden/1902>
167              
168             =back
169              
170             =cut