File Coverage

blib/lib/LWP/Authen/OAuth.pm
Criterion Covered Total %
statement 29 121 23.9
branch 0 32 0.0
condition 0 6 0.0
subroutine 8 18 44.4
pod 7 11 63.6
total 44 188 23.4


line stmt bran cond sub pod time code
1             package LWP::Authen::OAuth;
2              
3             =head1 NAME
4              
5             LWP::Authen::OAuth - generate signed OAuth requests
6              
7             =head1 SYNOPSIS
8              
9             require LWP::Authen::OAuth;
10              
11             =head2 Google
12              
13             # Google uses 'anonymous' for unregistered Web/offline applications or the
14             # domain name for registered Web applications
15             my $ua = LWP::Authen::OAuth->new(
16             oauth_consumer_secret => "anonymous",
17             );
18            
19             # request a 'request' token
20             my $r = $ua->post( "https://www.google.com/accounts/OAuthGetRequestToken",
21             [
22             oauth_consumer_key => 'anonymous',
23             oauth_callback => 'http://example.net/oauth',
24             xoauth_displayname => 'Example Application',
25             scope => 'https://docs.google.com/feeds/',
26             ]
27             );
28             die $r->as_string if $r->is_error;
29            
30             # update the token secret from the HTTP response
31             $ua->oauth_update_from_response( $r );
32            
33             # open a browser for the user
34            
35             # data are returned as form-encoded
36             my $uri = URI->new( 'http:' );
37             $uri->query( $r->content );
38             my %oauth_data = $uri->query_form;
39            
40             # Direct the user to here to grant you access:
41             # https://www.google.com/accounts/OAuthAuthorizeToken?
42             # oauth_token=$oauth_data{oauth_token}\n";
43            
44             # turn the 'request' token into an 'access' token with the verifier
45             # returned by google
46             $r = $ua->post( "https://www.google.com/accounts/OAuthGetAccessToken", [
47             oauth_consumer_key => 'anonymous',
48             oauth_token => $oauth_data{oauth_token},
49             oauth_verifier => $oauth_verifier,
50             ]);
51            
52             # update the token secret from the HTTP response
53             $ua->oauth_update_from_response( $r );
54            
55             # now use the $ua to perform whatever actions you want
56              
57             =head2 Twitter
58              
59             Sending status updates to a single account is quite easy if you create an application. The C and C come from the 'Application Details' page and the C and C from the 'My Access Token' page.
60              
61             my $ua = LWP::Authen::OAuth->new(
62             oauth_consumer_key => 'xxx1',
63             oauth_consumer_secret => 'xxx2',
64             oauth_token => 'yyy1',
65             oauth_token_secret => 'yyy2',
66             );
67            
68             $ua->post( 'http://api.twitter.com/1/statuses/update.json', [
69             status => 'Posted this using LWP::Authen::OAuth!'
70             ]);
71              
72             =head1 DESCRIPTION
73              
74             This module provides a sub-class of L that generates OAuth 1.0 signed requests. You should familiarise yourself with OAuth at L.
75              
76             This module only supports HMAC_SHA1 signing.
77              
78             OAuth nonces are generated using the Perl random number generator. To set a nonce manually define 'oauth_nonce' in your requests via a CGI parameter or the Authorization header - see the OAuth documentation.
79              
80             =head1 METHODS
81              
82             =over 4
83              
84             =item $ua = LWP::Authen::OAuth->new( ... )
85              
86             Takes the same options as L plus optionally:
87              
88             oauth_consumer_key
89             oauth_consumer_secret
90             oauth_token
91             oauth_token_secret
92              
93             Most services will require some or all of these to be set even if it's just 'anonymous'.
94              
95             =item $ua->oauth_update_from_response( $r )
96              
97             Update the C and C from an L object returned by a previous request e.g. when converting a request token into an access token.
98              
99             =item $key = $ua->oauth_consumer_key( [ KEY ] )
100              
101             Get and optionally set the consumer key.
102              
103             =item $secret = $ua->oauth_consumer_secret( [ SECRET ] )
104              
105             Get and optionally set the consumer secret.
106              
107             =item $token = $ua->oauth_token( [ TOKEN ] )
108              
109             Get and optionally set the oauth token.
110              
111             =item $secret = $ua->oauth_token_secret( [ SECRET ] )
112              
113             Get and optionally set the oauth token secret.
114              
115             =back
116              
117             =head1 SEE ALSO
118              
119             L, L, L, L, L
120              
121             =head2 Rationale
122              
123             I think the complexity in OAuth is in the parameter normalisation and message signing. What this module does is to hide that complexity without replicating the higher-level protocol chatter.
124              
125             In Net::OAuth:
126              
127             $r = Net::OAuth->request('request token')->new(
128             consumer_key => 'xxx',
129             request_url => 'https://photos.example.net/request_token',
130             callback => 'http://printer.example.com/request_token_ready',
131             ...
132             extra_params {
133             scope => 'global',
134             }
135             );
136             $r->sign;
137             $res = $ua->request(POST $r->to_url);
138             $res = Net::OAuth->response('request token')
139             ->from_post_body($res->content);
140             ... etc
141              
142             In LWP::Authen::OAuth:
143              
144             $ua = LWP::Authen::OAuth->new(
145             oauth_consumer_key => 'xxx'
146             );
147             $res = $ua->post( 'https://photos.example.net/request_token', [
148             oauth_callback => 'http://printer.example.com/request_token_ready',
149             ...
150             scope => 'global',
151             ]);
152             $ua->oauth_update_from_response( $res );
153             ... etc
154              
155             L, L.
156              
157             =head1 AUTHOR
158              
159             Timothy D Brody
160              
161             Copyright 2011 University of Southampton, UK
162              
163             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself
164              
165             =cut
166              
167 1     1   33139 use LWP::UserAgent;
  1         120411  
  1         36  
168 1     1   11 use URI;
  1         2  
  1         24  
169 1     1   6 use URI::Escape;
  1         7  
  1         87  
170 1     1   1892 use Digest::SHA;
  1         10685  
  1         149  
171 1     1   1208 use MIME::Base64;
  1         5130  
  1         182  
172              
173             $VERSION = '1.02';
174             @ISA = qw( LWP::UserAgent );
175              
176 1     1   13 use strict;
  1         2  
  1         1119  
177              
178             sub new
179             {
180 1     1 1 14 my( $class, %self ) = @_;
181              
182 1         3 my %opts;
183 1         2 for(qw( oauth_consumer_key oauth_consumer_secret oauth_token oauth_token_secret ))
184             {
185 4         12 $opts{$_} = delete $self{$_};
186             }
187              
188 1         14 my $self = $class->SUPER::new( %self );
189              
190 1         3723 for(keys %opts)
191             {
192 4         13 $self->{$_} = $opts{$_};
193             }
194              
195 1         6 return $self;
196             }
197              
198             sub request
199             {
200 0     0 1   my( $self, $request, @args ) = @_;
201              
202 0           $self->sign_hmac_sha1( $request );
203              
204 0           return $self->SUPER::request( $request, @args );
205             }
206              
207             sub oauth_encode_parameter
208             {
209 0     0 0   my( $str ) = @_;
210 0           return URI::Escape::uri_escape_utf8( $str, '^\w.~-' ); # 5.1
211             }
212              
213             sub oauth_nonce
214             {
215 0     0 0   my $nonce = '';
216 0           $nonce .= sprintf("%02x", int(rand(255))) for 1..16;
217 0           return $nonce;
218             }
219              
220             sub oauth_authorization_param
221             {
222 0     0 0   my( $request, @args ) = @_;
223              
224 0 0         if( @args )
225             {
226 0           my @parts;
227 0           for(my $i = 0; $i < @args; $i+=2)
228             {
229             # header values are in quotes
230 0           push @parts, sprintf('%s="%s"',
231 0           map { oauth_encode_parameter( $_ ) }
232             @args[$i,$i+1]
233             );
234             }
235 0           $request->header( 'Authorization', sprintf('OAuth %s',
236             join ',', @parts ) );
237             }
238              
239 0           my $authorization = $request->header( 'Authorization' );
240 0 0         return if !$authorization;
241 0 0         return if $authorization !~ s/^\s*OAuth\s+//i;
242              
243             return
244 0           map { URI::Escape::uri_unescape( $_ ) }
  0            
245 0           map { $_ =~ /([^=]+)="(.*)"/; ($1, $2) }
  0            
246             split /\s*,\s*/,
247             $authorization;
248             }
249              
250             sub sign_hmac_sha1
251             {
252 0     0 0   my( $self, $request ) = @_;
253              
254 0           my $method = $request->method;
255 0           my $uri = URI->new( $request->uri )->canonical;
256 0           my $content_type = $request->header( 'Content-Type' );
257 0 0         $content_type = '' if !defined $content_type;
258 0           my $oauth_header = $request->header( "Authorization" );
259              
260             # build the parts of the string to sign
261 0           my @parts;
262              
263 0           push @parts, $method;
264              
265 0           my $request_uri = $uri->clone;
266 0           $request_uri->query( undef );
267 0           push @parts, "$request_uri";
268              
269             # build up a list of parameters
270 0           my @params;
271              
272             # CGI parameters (OAuth only supports urlencoded)
273 0 0 0       if(
274             $method eq "POST" &&
275             $content_type eq 'application/x-www-form-urlencoded'
276             )
277             {
278 0           $uri->query( $request->content );
279             }
280            
281 0           push @params, $uri->query_form;
282              
283             # HTTP OAuth Authorization parameters
284 0           my @auth_params = oauth_authorization_param( $request );
285 0           my %auth_params = @auth_params;
286 0 0         if( !exists($auth_params{oauth_nonce}) )
287             {
288 0           push @auth_params, oauth_nonce => oauth_nonce();
289             }
290 0 0         if( !exists($auth_params{oauth_timestamp}) )
291             {
292 0           push @auth_params, oauth_timestamp => time();
293             }
294 0 0         if( !exists($auth_params{oauth_version}) )
295             {
296 0           push @auth_params, oauth_version => '1.0';
297             }
298 0           for(qw( oauth_consumer_key oauth_token ))
299             {
300 0 0 0       if( !exists($auth_params{$_}) && defined($self->{$_}) )
301             {
302 0           push @auth_params, $_ => $self->{$_};
303             }
304             }
305 0           push @auth_params, oauth_signature_method => "HMAC-SHA1";
306              
307 0           push @params, @auth_params;
308              
309             # lexically order the parameters as bytes (sorry for obscure code)
310             {
311 1     1   1530 use bytes;
  1         12  
  1         5  
  0            
312 0           my @pairs;
313 0           push @pairs, [splice(@params,0,2)] while @params;
314             # order by key name then value
315 0 0         @pairs = sort {
316 0           $a->[0] cmp $b->[0] || $a->[1] cmp $b->[0]
317             } @pairs;
318 0           @params = map { @$_ } @pairs;
  0            
319             }
320              
321             # re-encode the parameters according to OAuth spec.
322 0           my @query;
323 0           for(my $i = 0; $i < @params; $i+=2)
324             {
325 0 0         next if $params[$i] eq "oauth_signature"; # 9.1.1
326 0           push @query, sprintf('%s=%s',
327 0           map { oauth_encode_parameter( $_ ) }
328             @params[$i,$i+1]
329             );
330             }
331 0           push @parts, join '&', @query;
332              
333             # calculate the data to sign and the secret to use (encoded again)
334 0           my $data = join '&',
335 0           map { oauth_encode_parameter( $_ ) }
336             @parts;
337 0 0         my $secret = join '&',
338 0           map { defined($_) ? oauth_encode_parameter( $_ ) : '' }
339             $self->{oauth_consumer_secret},
340             $self->{oauth_token_secret};
341              
342             # 9.2
343 0           my $digest = Digest::SHA::hmac_sha1( $data, $secret );
344              
345 0           push @auth_params,
346             oauth_signature => MIME::Base64::encode_base64( $digest, '' );
347              
348 0           oauth_authorization_param( $request, @auth_params );
349             }
350              
351             sub oauth_update_from_response
352             {
353 0     0 1   my( $self, $r ) = @_;
354              
355 0           my $uri = URI->new( 'http:' );
356 0           $uri->query( $r->content );
357 0           my %oauth_data = $uri->query_form;
358              
359 0           for(qw( oauth_token oauth_token_secret ))
360             {
361 0           $self->{$_} = $oauth_data{$_};
362             }
363             }
364              
365             sub oauth_consumer_key
366             {
367 0     0 1   my $self = shift;
368 0 0         if( @_ )
369             {
370 0           $self->{oauth_consumer_key} = shift;
371             }
372 0           return $self->{oauth_consumer_key};
373             }
374              
375             sub oauth_consumer_secret
376             {
377 0     0 1   my $self = shift;
378 0 0         if( @_ )
379             {
380 0           $self->{oauth_consumer_secret} = shift;
381             }
382 0           return $self->{oauth_consumer_secret};
383             }
384              
385             sub oauth_token
386             {
387 0     0 1   my $self = shift;
388 0 0         if( @_ )
389             {
390 0           $self->{oauth_token} = shift;
391             }
392 0           return $self->{oauth_token};
393             }
394              
395             sub oauth_token_secret
396             {
397 0     0 1   my $self = shift;
398 0 0         if( @_ )
399             {
400 0           $self->{oauth_token_secret} = shift;
401             }
402 0           return $self->{oauth_token_secret};
403             }
404              
405             1;