1package Net::OAuth::Simple; 2 3use warnings; 4use strict; 5our $VERSION = "1.7"; 6 7use URI; 8use LWP; 9use CGI; 10use HTTP::Request::Common (); 11use Carp; 12use Net::OAuth; 13use Scalar::Util qw(blessed); 14use Digest::SHA; 15use File::Basename; 16require Net::OAuth::Request; 17require Net::OAuth::RequestTokenRequest; 18require Net::OAuth::AccessTokenRequest; 19require Net::OAuth::ProtectedResourceRequest; 20require Net::OAuth::XauthAccessTokenRequest; 21require Net::OAuth::UserAuthRequest; 22 23BEGIN { 24 eval { require Math::Random::MT }; 25 unless ($@) { 26 Math::Random::MT->import(qw(srand rand)); 27 } 28} 29 30our @required_constructor_params = qw(consumer_key consumer_secret); 31our @access_token_params = qw(access_token access_token_secret); 32our @general_token_params = qw(general_token general_token_secret); 33 34=head1 NAME 35 36Net::OAuth::Simple - a simple wrapper round the OAuth protocol 37 38=head1 SYNOPSIS 39 40First create a sub class of C<Net::OAuth::Simple> that will do you requests 41for you. 42 43 package Net::AppThatUsesOAuth; 44 45 use strict; 46 use base qw(Net::OAuth::Simple); 47 48 49 sub new { 50 my $class = shift; 51 my %tokens = @_; 52 return $class->SUPER::new( tokens => \%tokens, 53 protocol_version => '1.0a', 54 urls => { 55 authorization_url => ..., 56 request_token_url => ..., 57 access_token_url => ..., 58 }); 59 } 60 61 sub view_restricted_resource { 62 my $self = shift; 63 my $url = shift; 64 return $self->make_restricted_request($url, 'GET'); 65 } 66 67 sub update_restricted_resource { 68 my $self = shift; 69 my $url = shift; 70 my %extra_params = @_; 71 return $self->make_restricted_request($url, 'POST', %extra_params); 72 } 73 1; 74 75 76Then in your main app you need to do 77 78 # Get the tokens from the command line, a config file or wherever 79 my %tokens = get_tokens(); 80 my $app = Net::AppThatUsesOAuth->new(%tokens); 81 82 # Check to see we have a consumer key and secret 83 unless ($app->consumer_key && $app->consumer_secret) { 84 die "You must go get a consumer key and secret from App\n"; 85 } 86 87 # If the app is authorized (i.e has an access token and secret) 88 # Then look at a restricted resourse 89 if ($app->authorized) { 90 my $response = $app->view_restricted_resource; 91 print $response->content."\n"; 92 exit; 93 } 94 95 96 # Otherwise the user needs to go get an access token and secret 97 print "Go to ".$app->get_authorization_url."\n"; 98 print "Then hit return after\n"; 99 <STDIN>; 100 101 my ($access_token, $access_token_secret) = $app->request_access_token; 102 103 # Now save those values 104 105 106Note the flow will be somewhat different for web apps since the request token 107and secret will need to be saved whilst the user visits the authorization url. 108 109For examples go look at the C<Net::FireEagle> module and the C<fireeagle> command 110line script that ships with it. Also in the same distribution in the C<examples/> 111directory is a sample web app. 112 113=head1 METHODS 114 115=cut 116 117=head2 new [params] 118 119Create a new OAuth enabled app - takes a hash of params. 120 121One of the keys of the hash must be C<tokens>, the value of which 122must be a hash ref with the keys: 123 124=over 4 125 126=item consumer_key 127 128=item consumer_secret 129 130=back 131 132Then, when you have your per-use access token and secret you 133can supply 134 135=over 4 136 137=item access_token 138 139=item access_secret 140 141=back 142 143Another key of the hash must be C<urls>, the value of which must 144be a hash ref with the keys 145 146=over 4 147 148=item authorization_url 149 150=item request_token_url 151 152=item access_token_url 153 154=back 155 156If you pass in a key C<protocol_version> with a value equal to B<1.0a> then 157the newest version of the OAuth protocol will be used. A value equal to B<1.0> will 158mean the old version will be used. Defaults to B<1.0a> 159 160You can pass in your own User Agent by using the key C<browser>. 161 162If you pass in C<return_undef_on_error> then instead of C<die>-ing on error 163methods will return undef instead and the error can be retrieved using the 164C<last_error()> method. See the section on B<ERROR HANDLING>. 165 166=cut 167 168sub new { 169 my $class = shift; 170 my %params = @_; 171 $params{protocol_version} ||= '1.0a'; 172 my $client = bless \%params, $class; 173 174 # Set up LibWWWPerl for HTTP requests 175 $client->{browser} ||= LWP::UserAgent->new; 176 177 # Verify arguments 178 $client->_check; 179 180 # Client Object 181 return $client; 182} 183 184# Validate required constructor params 185sub _check { 186 my $self = shift; 187 188 foreach my $param ( @required_constructor_params ) { 189 unless ( defined $self->{tokens}->{$param} ) { 190 return $self->_error("Missing required parameter '$param'"); 191 } 192 } 193 194 return $self->_error("browser must be a LWP::UserAgent") 195 unless blessed $self->{browser} && $self->{browser}->isa('LWP::UserAgent'); 196} 197 198=head2 oauth_1_0a 199 200Whether or not we're using 1.0a version of OAuth (necessary for, 201amongst others, FireEagle) 202 203=cut 204sub oauth_1_0a { 205 my $self = shift; 206 return $self->{protocol_version } eq '1.0a'; 207} 208 209=head2 authorized 210 211Whether the client has the necessary credentials to be authorized. 212 213Note that the credentials may be wrong and so the request may still fail. 214 215=cut 216 217sub authorized { 218 my $self = shift; 219 foreach my $param ( @access_token_params ) { 220 return 0 unless defined $self->{tokens}->{$param} && length $self->{tokens}->{$param}; 221 } 222 return 1; 223} 224 225=head2 signature_method [method] 226 227The signature method to use. 228 229Defaults to HMAC-SHA1 230 231=cut 232sub signature_method { 233 my $self = shift; 234 $self->{signature_method} = shift if @_; 235 return $self->{signature_method} || 'HMAC-SHA1'; 236} 237 238=head2 tokens 239 240Get all the tokens. 241 242=cut 243sub tokens { 244 my $self = shift; 245 if (@_) { 246 my %tokens = @_; 247 $self->{tokens} = \%tokens; 248 } 249 return %{$self->{tokens}||{}}; 250} 251 252=head2 consumer_key [consumer key] 253 254Returns the current consumer key. 255 256Can optionally set the consumer key. 257 258=cut 259 260sub consumer_key { 261 my $self = shift; 262 $self->_token('consumer_key', @_); 263} 264 265=head2 consumer_secret [consumer secret] 266 267Returns the current consumer secret. 268 269Can optionally set the consumer secret. 270 271=cut 272 273sub consumer_secret { 274 my $self = shift; 275 $self->_token('consumer_secret', @_); 276} 277 278 279=head2 access_token [access_token] 280 281Returns the current access token. 282 283Can optionally set a new token. 284 285=cut 286 287sub access_token { 288 my $self = shift; 289 $self->_token('access_token', @_); 290} 291 292=head2 access_token_secret [access_token_secret] 293 294Returns the current access token secret. 295 296Can optionally set a new secret. 297 298=cut 299 300sub access_token_secret { 301 my $self = shift; 302 return $self->_token('access_token_secret', @_); 303} 304 305=head2 general_token [token] 306 307Get or set the general token. 308 309See documentation in C<new()> 310 311=cut 312 313sub general_token { 314 my $self = shift; 315 $self->_token('general_token', @_); 316} 317 318=head2 general_token_secret [secret] 319 320Get or set the general token secret. 321 322See documentation in C<new()> 323 324=cut 325 326sub general_token_secret { 327 my $self = shift; 328 $self->_token('general_token_secret', @_); 329} 330 331=head2 authorized_general_token 332 333Is the app currently authorized for general token requests. 334 335See documentation in C<new()> 336 337=cut 338 339sub authorized_general_token { 340 my $self = shift; 341 foreach my $param ( @general_token_params ) { 342 return 0 unless defined $self->$param(); 343 } 344 return 1; 345} 346 347 348=head2 request_token [request_token] 349 350Returns the current request token. 351 352Can optionally set a new token. 353 354=cut 355 356sub request_token { 357 my $self = shift; 358 $self->_token('request_token', @_); 359} 360 361 362=head2 request_token_secret [request_token_secret] 363 364Returns the current request token secret. 365 366Can optionally set a new secret. 367 368=cut 369 370sub request_token_secret { 371 my $self = shift; 372 return $self->_token('request_token_secret', @_); 373} 374 375=head2 verifier [verifier] 376 377Returns the current oauth_verifier. 378 379Can optionally set a new verifier. 380 381=cut 382 383sub verifier { 384 my $self = shift; 385 return $self->_param('verifier', @_); 386} 387 388=head2 callback [callback] 389 390Returns the oauth callback. 391 392Can optionally set the oauth callback. 393 394=cut 395 396sub callback { 397 my $self = shift; 398 $self->_param('callback', @_); 399} 400 401=head2 callback_confirmed [callback_confirmed] 402 403Returns the oauth callback confirmed. 404 405Can optionally set the oauth callback confirmed. 406 407=cut 408 409sub callback_confirmed { 410 my $self = shift; 411 $self->_param('callback_confirmed', @_); 412} 413 414 415sub _token { 416 my $self = shift; 417 $self->_store('tokens', @_); 418} 419 420sub _param { 421 my $self = shift; 422 $self->_store('params', @_); 423} 424 425sub _store { 426 my $self = shift; 427 my $ns = shift; 428 my $key = shift; 429 $self->{$ns}->{$key} = shift if @_; 430 return $self->{$ns}->{$key}; 431} 432 433=head2 authorization_url 434 435Get the url the user needs to visit to authorize as a URI object. 436 437Note: this is the base url - not the full url with the necessary OAuth params. 438 439=cut 440sub authorization_url { 441 my $self = shift; 442 return $self->_url('authorization_url', @_); 443} 444 445 446=head2 request_token_url 447 448Get the url to obtain a request token as a URI object. 449 450=cut 451sub request_token_url { 452 my $self = shift; 453 return $self->_url('request_token_url', @_); 454} 455 456=head2 access_token_url 457 458Get the url to obtain an access token as a URI object. 459 460=cut 461sub access_token_url { 462 my $self = shift; 463 return $self->_url('access_token_url', @_); 464} 465 466sub _url { 467 my $self = shift; 468 my $key = shift; 469 $self->{urls}->{$key} = shift if @_; 470 my $url = $self->{urls}->{$key} || return;; 471 return URI->new($url); 472} 473 474# generate a random number 475sub _nonce { 476 return int( rand( 2**32 ) ); 477} 478 479=head2 request_access_token [param[s]] 480 481Request the access token and access token secret for this user. 482 483The user must have authorized this app at the url given by 484C<get_authorization_url> first. 485 486Returns the access token and access token secret but also sets 487them internally so that after calling this method you can 488immediately call a restricted method. 489 490If you pass in a hash of params then they will added as parameters to the URL. 491 492=cut 493 494sub request_access_token { 495 my $self = shift; 496 my %params = @_; 497 my $url = $self->access_token_url; 498 499 $params{token} = $self->request_token unless defined $params{token}; 500 $params{token_secret} = $self->request_token_secret unless defined $params{token_secret}; 501 502 if ($self->oauth_1_0a) { 503 $params{verifier} = $self->verifier unless defined $params{verifier}; 504 return $self->_error("You must pass a verified parameter when using OAuth v1.0a") unless defined $params{verifier}; 505 506 } 507 508 509 my $access_token_response = $self->_make_request( 510 'Net::OAuth::AccessTokenRequest', 511 $url, 'POST', 512 %params, 513 ); 514 515 return $self->_decode_tokens($url, $access_token_response); 516} 517 518sub _decode_tokens { 519 my $self = shift; 520 my $url = shift; 521 my $access_token_response = shift; 522 523 # Cast response into CGI query for EZ parameter decoding 524 my $access_token_response_query = 525 new CGI( $access_token_response->content ); 526 527 # Split out token and secret parameters from the access token response 528 $self->access_token($access_token_response_query->param('oauth_token')); 529 $self->access_token_secret($access_token_response_query->param('oauth_token_secret')); 530 531 delete $self->{tokens}->{$_} for qw(request_token request_token_secret verifier); 532 533 return $self->_error("$url did not reply with an access token") 534 unless ( $self->access_token && $self->access_token_secret ); 535 536 return ( $self->access_token, $self->access_token_secret ); 537 538} 539 540=head2 xauth_request_access_token [param[s]] 541 542The same as C<request_access_token> but for xAuth. 543 544For more information on xAuth see 545 546 http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-oauth-access_token-for-xAuth 547 548You must pass in the parameters 549 550 x_auth_username 551 x_auth_password 552 x_auth_mode 553 554You must have HTTPS enabled for LWP::UserAgent. 555 556See C<examples/twitter_xauth> for a sample implementation. 557 558=cut 559sub xauth_request_access_token { 560 my $self = shift; 561 my %params = @_; 562 my $url = $self->access_token_url; 563 $url =~ s !^http:!https:!; # force https 564 565 my %xauth_params = map { $_ => $params{$_} } 566 grep {/^x_auth_/} 567 @{Net::OAuth::XauthAccessTokenRequest->required_message_params}; 568 569 my $access_token_response = $self->_make_request( 570 'Net::OAuth::XauthAccessTokenRequest', 571 $url, 'POST', 572 %xauth_params, 573 ); 574 575 return $self->_decode_tokens($url, $access_token_response); 576} 577 578=head2 request_request_token [param[s]] 579 580Request the request token and request token secret for this user. 581 582This is called automatically by C<get_authorization_url> if necessary. 583 584If you pass in a hash of params then they will added as parameters to the URL. 585 586=cut 587 588 589sub request_request_token { 590 my $self = shift; 591 my %params = @_; 592 my $url = $self->request_token_url; 593 594 if ($self->oauth_1_0a) { 595 $params{callback} = $self->callback unless defined $params{callback}; 596 return $self->_error("You must pass a callback parameter when using OAuth v1.0a") unless defined $params{callback}; 597 } 598 599 my $request_token_response = $self->_make_request( 600 'Net::OAuth::RequestTokenRequest', 601 $url, 'GET', 602 %params); 603 604 return $self->_error("GET for $url failed: ".$request_token_response->status_line) 605 unless ( $request_token_response->is_success ); 606 607 # Cast response into CGI query for EZ parameter decoding 608 my $request_token_response_query = 609 new CGI( $request_token_response->content ); 610 611 # Split out token and secret parameters from the request token response 612 $self->request_token($request_token_response_query->param('oauth_token')); 613 $self->request_token_secret($request_token_response_query->param('oauth_token_secret')); 614 $self->callback_confirmed($request_token_response_query->param('oauth_callback_confirmed')); 615 616 # Hack to deal with bug in older versions of oauth-php (See https://code.google.com/p/oauth-php/issues/detail?id=60) 617 $self->callback_confirmed($request_token_response_query->param('oauth_callback_accepted')) 618 unless $self->callback_confirmed; 619 620 return $self->_error("Response does not confirm to OAuth1.0a. oauth_callback_confirmed not received") 621 if $self->oauth_1_0a && !$self->callback_confirmed; 622 623} 624 625=head2 get_authorization_url [param[s]] 626 627Get the URL to authorize a user as a URI object. 628 629If you pass in a hash of params then they will added as parameters to the URL. 630 631=cut 632 633sub get_authorization_url { 634 my $self = shift; 635 my %params = @_; 636 my $url = $self->authorization_url; 637 if (!defined $self->request_token) { 638 $self->request_request_token(%params); 639 } 640 #$params{oauth_token} = $self->request_token; 641 $url->query_form(%params); 642 my $req = $self->_build_request('Net::OAuth::UserAuthRequest', $url, "GET"); 643 return $req->normalized_request_url; 644} 645 646=head2 make_restricted_request <url> <HTTP method> [extra[s]] 647 648Make a request to C<url> using the given HTTP method. 649 650Any extra parameters can be passed in as a hash. 651 652=cut 653sub make_restricted_request { 654 my $self = shift; 655 656 return $self->_error("This restricted request is not authorized") unless $self->authorized; 657 658 return $self->_restricted_request( $self->access_token, $self->access_token_secret, @_ ); 659} 660 661=head2 make_general_request <url> <HTTP method> [extra[s]] 662 663Make a request to C<url> using the given HTTP method using 664the general purpose tokens. 665 666Any extra parameters can be passed in as a hash. 667 668=cut 669sub make_general_request { 670 my $self = shift; 671 672 return $self->_error("This general request is not authorized") unless $self->authorized_general_token; 673 674 return $self->_restricted_request( $self->general_token, $self->general_token_secret, @_ ); 675} 676 677sub _restricted_request { 678 my $self = shift; 679 my $token = shift; 680 my $secret = shift; 681 my $url = shift; 682 my $method = shift; 683 my %extras = @_; 684 my $response = $self->_make_request( 685 'Net::OAuth::ProtectedResourceRequest', 686 $url, $method, 687 token => $token, 688 token_secret => $secret, 689 extra_params => \%extras 690 ); 691 return $response; 692} 693 694sub _make_request { 695 my $self = shift; 696 my $class = shift; 697 my $url = shift; 698 my $method = uc(shift); 699 my @extra = @_; 700 701 my $request = $self->_build_request($class, $url, $method, @extra); 702 my $response = $self->{browser}->request($request); 703 return $self->_error("$method on ".$request->normalized_request_url." failed: ".$response->status_line." - ".$response->content) 704 unless ( $response->is_success ); 705 706 return $response; 707} 708 709use Data::Dumper; 710sub _build_request { 711 my $self = shift; 712 my $class = shift; 713 my $url = shift; 714 my $method = uc(shift); 715 my @extra = @_; 716 717 my $uri = URI->new($url); 718 my %query = $uri->query_form; 719 $uri->query_form({}); 720 721 722 my $content; 723 my $filename; 724 if ('PUT' eq $method) { 725 # Get the content (goes in the body), and hash the content for inclusion in the message 726 my %params = @extra; 727 $filename = delete $params{extra_params}->{filename}; 728 729 return $self->_error('Missing required parameter $filename') unless $filename; 730 731 # Slurp the file from above 732 my $content = ""; 733 $self->_read_file( $filename, sub { $content .= shift } ) ; 734 ($filename) = fileparse($filename); 735 736 737 # Net::OAuth doesn't seem to handle body hash, so do it ourselves 738 739 # Per http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html#parameter 740 # If the OAuth signature method is HMAC-SHA1 or RSA-SHA1, SHA1 MUST be used as the body hash algorithm 741 # No discussion of other signature methods, but the draft spec for OAuth2 predictably says that SHA256 742 # should be used for HMAC-SHA256 743 744 if ($self->signature_method eq 'HMAC-SHA1' || $self->signature_method eq 'RSA-SHA1') { 745 $params{body_hash} = Digest::SHA::sha1_hex($content); 746 } elsif ($self->signature_method eq 'HMAC-SHA256') { 747 $params{body_hash} = Digest::SHA::sha256_hex($content); 748 } else { 749 return $self->_error("Unknown signature method: ".$self->signature_method); 750 } 751 752 @extra = %params; 753 } 754 755 756 757 my $request = $class->new( 758 consumer_key => $self->consumer_key, 759 consumer_secret => $self->consumer_secret, 760 request_url => $uri, 761 request_method => $method, 762 signature_method => $self->signature_method, 763 protocol_version => $self->oauth_1_0a ? Net::OAuth::PROTOCOL_VERSION_1_0A : Net::OAuth::PROTOCOL_VERSION_1_0, 764 timestamp => time, 765 nonce => $self->_nonce, 766 extra_params => \%query, 767 @extra, 768 ); 769 $request->add_optional_message_params('body_hash') if 'PUT' eq $method; 770 $request->sign; 771 return $self->_error("Couldn't verify request! Check OAuth parameters.") 772 unless $request->verify; 773 774 my $req_url = ('GET' eq $method || 'DELETE' eq $method) ? $request->to_url() : $url; 775 776 my $req = HTTP::Request->new( $method => $req_url); 777 778 if ('PUT' eq $method) { 779 $req->header('Authorization' => $request->to_authorization_header("")); 780 $req->header('Content-disposition' => qq!attachment; filename="$filename"!); 781 $req->content($content); 782 } 783 784 if ('POST' eq $method) { 785 # "@extra" params are the ones that don't start with oath_ in the hash 786 # User passed them, they must want us to actually send them, huh? 787 $request->add_optional_message_params($_) for grep { ! /^oauth_/ } keys %{$request->to_hash}; 788 $req->content_type('application/x-www-form-urlencoded'); 789 $req->content($request->to_post_body); 790 } 791 792 return $req; 793} 794 795 796sub _error { 797 my $self = shift; 798 my $mess = shift; 799 if ($self->{return_undef_on_error}) { 800 $self->{_last_error} = $mess; 801 } else { 802 croak $mess; 803 } 804 return undef; 805} 806 807=head2 last_error 808 809Get the last error message. 810 811Only works if C<return_undef_on_error> was passed in to the constructor. 812 813See the section on B<ERROR HANDLING>. 814 815=cut 816sub last_error { 817 my $self = shift; 818 return $self->{_last_error}; 819} 820 821=head2 load_tokens <file> 822 823A convenience method for loading tokens from a config file. 824 825Returns a hash with the token names suitable for passing to 826C<new()>. 827 828Returns an empty hash if the file doesn't exist. 829 830=cut 831sub load_tokens { 832 my $class = shift; 833 my $file = shift; 834 my %tokens = (); 835 return %tokens unless -f $file; 836 837 $class->_read_file($file, sub { 838 $_ = shift; 839 chomp; 840 next if /^#/; 841 next if /^\s*$/; 842 next unless /=/; 843 s/(^\s*|\s*$)//g; 844 my ($key, $val) = split /\s*=\s*/, $_, 2; 845 $tokens{$key} = $val; 846 }); 847 return %tokens; 848} 849 850=head2 save_tokens <file> [token[s]] 851 852A convenience method to save a hash of tokens out to the given file. 853 854=cut 855sub save_tokens { 856 my $class = shift; 857 my $file = shift; 858 my %tokens = @_; 859 860 my $max = 0; 861 foreach my $key (keys %tokens) { 862 $max = length($key) if length($key)>$max; 863 } 864 865 open(my $fh, ">$file") || die "Couldn't open $file for writing: $!\n"; 866 foreach my $key (sort keys %tokens) { 867 my $pad = " "x($max-length($key)); 868 print $fh "$key ${pad}= ".$tokens{$key}."\n"; 869 } 870 close($fh); 871} 872 873sub _read_file { 874 my $self = shift; 875 my $file = shift; 876 my $sub = shift; 877 878 open(my $fh, $file) || die "Couldn't open $file: $!\n"; 879 while (<$fh>) { 880 $sub->($_) if $sub; 881 } 882 close($fh); 883} 884 885=head1 ERROR HANDLING 886 887Originally this module would die upon encountering an error (inheriting behaviour 888from the original Yahoo! code). 889 890This is still the default behaviour however if you now pass 891 892 return_undef_on_error => 1 893 894into the constructor then all methods will return undef on error instead. 895 896The error message is accessible via the C<last_error()> method. 897 898=head1 GOOGLE'S SCOPE PARAMETER 899 900Google's OAuth API requires the non-standard C<scope> parameter to be set 901in C<request_token_url>, and you also explicitly need to pass an C<oauth_callback> 902to C<get_authorization_url()> method, so that you can direct the user to your site 903if you're authenticating users in Web Application mode. Otherwise Google will let 904user grant acesss as a desktop app mode and doesn't redirect users back. 905 906Here's an example class that uses Google's Portable Contacts API via OAuth: 907 908 package Net::AppUsingGoogleOAuth; 909 use strict; 910 use base qw(Net::OAuth::Simple); 911 912 sub new { 913 my $class = shift; 914 my %tokens = @_; 915 return $class->SUPER::new( 916 tokens => \%tokens, 917 urls => { 918 request_token_url => "https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www-opensocial.googleusercontent.com/api/people", 919 authorization_url => "https://www.google.com/accounts/OAuthAuthorizeToken", 920 access_token_url => "https://www.google.com/accounts/OAuthGetAccessToken", 921 }, 922 ); 923 } 924 925 package main; 926 my $oauth = Net::AppUsingGoogleOAuth->new(%tokens); 927 928 # Web application 929 $app->redirect( $oauth->get_authorization_url( callback => "http://you.example.com/oauth/callback") ); 930 931 # Desktop application 932 print "Open the URL and come back once you're authenticated!\n", 933 $oauth->get_authorization_url; 934 935See L<http://code.google.com/apis/accounts/docs/OAuth.html> and other 936services API documentation for the possible list of I<scope> parameter value. 937 938=head1 RANDOMNESS 939 940If C<Math::Random::MT> is installed then any nonces generated will use a 941Mersenne Twiser instead of Perl's built in randomness function. 942 943=head1 EXAMPLES 944 945There are example Twitter and Twitter xAuth 'desktop' apps and a FireEagle OAuth 1.0a web app 946in the examples directory of the distribution. 947 948=head1 BUGS 949 950Non known 951 952=head1 DEVELOPERS 953 954The latest code for this module can be found at 955 956 https://svn.unixbeard.net/simon/Net-OAuth-Simple 957 958=head1 AUTHOR 959 960Simon Wistow, C<<simon@thegestalt.org>> 961 962=head1 BUGS 963 964Please report any bugs or feature requests to C<bug-net-oauth-simple at rt.cpan.org>, or through 965the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-OAuth-Simple>. I will be notified, and then you'll 966automatically be notified of progress on your bug as I make changes. 967 968=head1 SUPPORT 969 970You can find documentation for this module with the perldoc command. 971 972 perldoc Net::OAuth::Simple 973 974 975You can also look for information at: 976 977=over 4 978 979=item * RT: CPAN's request tracker 980 981L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-OAuth-Simple> 982 983=item * AnnoCPAN: Annotated CPAN documentation 984 985L<http://annocpan.org/dist/Net-OAuth-Simple> 986 987=item * CPAN Ratings 988 989L<http://cpanratings.perl.org/d/Net-OAuth-Simple> 990 991=item * Search CPAN 992 993L<http://search.cpan.org/dist/Net-OAuth-Simple/> 994 995=back 996 997=head1 COPYRIGHT & LICENSE 998 999Copyright 2009 Simon Wistow, all rights reserved. 1000 1001This program is free software; you can redistribute it and/or modify it 1002under the same terms as Perl itself. 1003 1004 1005=cut 1006 10071; # End of Net::OAuth::Simple 1008