File Coverage

blib/lib/Net/Twitter/Role/AppAuth.pm
Criterion Covered Total %
statement 25 36 69.4
branch 2 8 25.0
condition n/a
subroutine 7 8 87.5
pod 2 2 100.0
total 36 54 66.6


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