| 1 | package OAuth::Lite::Consumer; |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use warnings; |
|---|
| 5 | |
|---|
| 6 | use base qw( |
|---|
| 7 | LWP::UserAgent |
|---|
| 8 | Class::ErrorHandler |
|---|
| 9 | Class::Accessor::Fast |
|---|
| 10 | ); |
|---|
| 11 | |
|---|
| 12 | __PACKAGE__->mk_accessors(qw( |
|---|
| 13 | consumer_key |
|---|
| 14 | consumer_secret |
|---|
| 15 | oauth_request |
|---|
| 16 | oauth_response |
|---|
| 17 | request_token |
|---|
| 18 | access_token |
|---|
| 19 | )); |
|---|
| 20 | |
|---|
| 21 | *oauth_req = \&oauth_request; |
|---|
| 22 | *oauth_res = \&oauth_response; |
|---|
| 23 | |
|---|
| 24 | use Carp (); |
|---|
| 25 | use bytes (); |
|---|
| 26 | use URI; |
|---|
| 27 | use HTTP::Request; |
|---|
| 28 | use HTTP::Headers; |
|---|
| 29 | use UNIVERSAL::require; |
|---|
| 30 | use List::MoreUtils qw(any); |
|---|
| 31 | |
|---|
| 32 | use OAuth::Lite; |
|---|
| 33 | |
|---|
| 34 | use OAuth::Lite::Token; |
|---|
| 35 | use OAuth::Lite::Util qw(:all); |
|---|
| 36 | use OAuth::Lite::AuthMethod qw(:all); |
|---|
| 37 | |
|---|
| 38 | =head1 NAME |
|---|
| 39 | |
|---|
| 40 | OAuth::Lite::Consumer - consumer agent |
|---|
| 41 | |
|---|
| 42 | =head1 SYNOPSIS |
|---|
| 43 | |
|---|
| 44 | my $consumer = OAuth::Lite::Consumer->new( |
|---|
| 45 | consumer_key => $consumer_key, |
|---|
| 46 | consumer_secret => $consumer_secret, |
|---|
| 47 | site => q{http://api.example.org}, |
|---|
| 48 | request_token_path => q{/request_token}, |
|---|
| 49 | access_token_path => q{/access_token}, |
|---|
| 50 | authorize_path => q{http://example.org/authorize}, |
|---|
| 51 | ); |
|---|
| 52 | |
|---|
| 53 | # At first you have to publish request-token, and |
|---|
| 54 | # with it, redirect end-user to authorization-url that Service Provider tell you beforehand. |
|---|
| 55 | |
|---|
| 56 | my $request_token = $consumer->get_request_token(); |
|---|
| 57 | |
|---|
| 58 | $your_app->session->set( request_token => $request_token ); |
|---|
| 59 | |
|---|
| 60 | $your_app->redirect( $consumer->url_to_authorize( |
|---|
| 61 | token => $request_token, |
|---|
| 62 | callback_url => q{http://yourservice/callback}, |
|---|
| 63 | ) ); |
|---|
| 64 | |
|---|
| 65 | # After user authorize the request on a Service Provider side web application. |
|---|
| 66 | |
|---|
| 67 | my $request_token = $your_app->session->get('request_token'); |
|---|
| 68 | |
|---|
| 69 | my $access_token = $consumer->get_access_token( token => $request_token ); |
|---|
| 70 | |
|---|
| 71 | $your_app->session->set( access_token => $access_token ); |
|---|
| 72 | $your_app->session->remove('request_token'); |
|---|
| 73 | |
|---|
| 74 | # After all, you can request protected-resource with access token |
|---|
| 75 | |
|---|
| 76 | my $access_token = $your_app->session->get('access_token'); |
|---|
| 77 | |
|---|
| 78 | my $res = $consumer->request( |
|---|
| 79 | method => 'GET', |
|---|
| 80 | url => q{http://api.example.org/picture}, |
|---|
| 81 | token => $access_token, |
|---|
| 82 | params => { file => 'mypic.jpg', size => 'small' }, |
|---|
| 83 | ); |
|---|
| 84 | |
|---|
| 85 | unless ($res->is_success) { |
|---|
| 86 | if ($res->status == 400 || $res->status == 401) { |
|---|
| 87 | my $auth_header = $res->header('WWW-Authenticate'); |
|---|
| 88 | if ($auth_header && $auth_header =~ /^OAuth/) { |
|---|
| 89 | # access token may be expired, |
|---|
| 90 | # get request-token and authorize again |
|---|
| 91 | } else { |
|---|
| 92 | # another auth error. |
|---|
| 93 | } |
|---|
| 94 | } |
|---|
| 95 | # another error. |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | my $resource = $res->content; |
|---|
| 99 | |
|---|
| 100 | $your_app->handle_resource($resource); |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | =head1 DESCRIPTION |
|---|
| 104 | |
|---|
| 105 | This module helps you to build OAuth Consumer application. |
|---|
| 106 | |
|---|
| 107 | =head1 METHODS |
|---|
| 108 | |
|---|
| 109 | =head2 new(%args) |
|---|
| 110 | |
|---|
| 111 | =head3 parameters |
|---|
| 112 | |
|---|
| 113 | =over 4 |
|---|
| 114 | |
|---|
| 115 | =item consumer_key |
|---|
| 116 | |
|---|
| 117 | consumer_key value |
|---|
| 118 | |
|---|
| 119 | =item consumer_secret |
|---|
| 120 | |
|---|
| 121 | consumer_secret value |
|---|
| 122 | |
|---|
| 123 | =item signature_method |
|---|
| 124 | |
|---|
| 125 | Signature method you can choose from 'HMAC-SHA1', 'PLAINTEXT', and 'RSA-SHA1' (optional, 'HMAC-SHA1' is set by default) |
|---|
| 126 | |
|---|
| 127 | =item http_method |
|---|
| 128 | |
|---|
| 129 | HTTP method (GET or POST) when the request is for request token or access token. (optional, 'POST' is set by default) |
|---|
| 130 | |
|---|
| 131 | =item auth_method |
|---|
| 132 | |
|---|
| 133 | L<OAuth::Lite::AuthMethod>'s value you can choose from AUTH_HEADER, POST_BODY and URL_QUERY (optional, AUTH_HEADER is set by default) |
|---|
| 134 | |
|---|
| 135 | =item realm |
|---|
| 136 | |
|---|
| 137 | The OAuth realm value for a protected-resource you wanto to access to. (optional. empty-string is set by default) |
|---|
| 138 | |
|---|
| 139 | =item site |
|---|
| 140 | |
|---|
| 141 | The base site url of Service Provider |
|---|
| 142 | |
|---|
| 143 | =item request_token_path |
|---|
| 144 | |
|---|
| 145 | =item access_token_path |
|---|
| 146 | |
|---|
| 147 | =item authorize_path |
|---|
| 148 | |
|---|
| 149 | =item callback_url |
|---|
| 150 | |
|---|
| 151 | =back |
|---|
| 152 | |
|---|
| 153 | Site and other paths, simple usage. |
|---|
| 154 | |
|---|
| 155 | my $consumer = OAuth::Lite::Consumer->new( |
|---|
| 156 | ... |
|---|
| 157 | site => q{http://example.org}, |
|---|
| 158 | request_token_path => q{/request_token}, |
|---|
| 159 | access_token_path => q{/access_token}, |
|---|
| 160 | authorize_path => q{/authorize}, |
|---|
| 161 | ); |
|---|
| 162 | |
|---|
| 163 | say $consumer->request_token_url; # http://example.org/request_token |
|---|
| 164 | say $consumer->access_token_url; # http://example.org/access_token |
|---|
| 165 | say $consumer->authorization_url; # http://example.org/authorize |
|---|
| 166 | |
|---|
| 167 | If the authorization_url is run under another domain, for example. |
|---|
| 168 | |
|---|
| 169 | my $consumer = OAuth::Lite::Consumer->new( |
|---|
| 170 | ... |
|---|
| 171 | site => q{http://api.example.org}, |
|---|
| 172 | request_token_path => q{/request_token}, |
|---|
| 173 | access_token_path => q{/access_token}, |
|---|
| 174 | authorize_path => q{http://www.example.org/authorize}, |
|---|
| 175 | ); |
|---|
| 176 | say $consumer->request_token_url; # http://api.example.org/request_token |
|---|
| 177 | say $consumer->access_token_url; # http://api.example.org/access_token |
|---|
| 178 | say $consumer->authorization_url; # http://www.example.org/authorize |
|---|
| 179 | |
|---|
| 180 | Like this, if you pass absolute url, consumer uses them as it is. |
|---|
| 181 | |
|---|
| 182 | You can omit site param, if you pass all paths as absolute url. |
|---|
| 183 | |
|---|
| 184 | my $consumer = OAuth::Lite::Consumer->new( |
|---|
| 185 | ... |
|---|
| 186 | request_token_path => q{http://api.example.org/request_token}, |
|---|
| 187 | access_token_path => q{http://api.example.org/access_token}, |
|---|
| 188 | authorize_path => q{http://www.example.org/authorize}, |
|---|
| 189 | ); |
|---|
| 190 | |
|---|
| 191 | |
|---|
| 192 | And there is a flexible way. |
|---|
| 193 | |
|---|
| 194 | # don't set each paths here. |
|---|
| 195 | my $consumer = OAuth::Lite::Consumer->new( |
|---|
| 196 | consumer_key => $consumer_key, |
|---|
| 197 | consumer_secret => $consumer_secret, |
|---|
| 198 | ); |
|---|
| 199 | |
|---|
| 200 | # set request token url here directly |
|---|
| 201 | my $rtoken = $consumer->get_request_token( url => q{http://api.example.org/request_token} ); |
|---|
| 202 | |
|---|
| 203 | # set authorize path here directly |
|---|
| 204 | my $url = $consumer->url_to_authorize( |
|---|
| 205 | token => $rtoken, |
|---|
| 206 | url => q{http://www.example.org/authorize}, |
|---|
| 207 | callback_url => q{http://www.yourservice/callback}, |
|---|
| 208 | ); |
|---|
| 209 | |
|---|
| 210 | # set access token url here directly |
|---|
| 211 | my $atoken = $consumer->get_access_token( url => q{http://api.example.org/access_token} ); |
|---|
| 212 | |
|---|
| 213 | So does callback_url. You can set it on consutructor or url_to_authorize method directly. |
|---|
| 214 | |
|---|
| 215 | my $consumer = OAuth::Lite::Consumer->new( |
|---|
| 216 | ... |
|---|
| 217 | callback_url => q{http://www.yourservice/callback}, |
|---|
| 218 | ); |
|---|
| 219 | ... |
|---|
| 220 | my $url = $consumer->url_to_authorize( token => $request_token ); |
|---|
| 221 | |
|---|
| 222 | Or |
|---|
| 223 | |
|---|
| 224 | my $consumer = OAuth::Lite::Consumer->new( |
|---|
| 225 | ... |
|---|
| 226 | ); |
|---|
| 227 | ... |
|---|
| 228 | my $url = $consumer->url_to_authorize( |
|---|
| 229 | token => $request_token, |
|---|
| 230 | callback_url => q{http://www.yourservice/callback}, |
|---|
| 231 | ); |
|---|
| 232 | |
|---|
| 233 | =cut |
|---|
| 234 | |
|---|
| 235 | sub new { |
|---|
| 236 | my ($class, %args) = @_; |
|---|
| 237 | my %args_for_parent = %args; |
|---|
| 238 | delete $args_for_parent{$_} |
|---|
| 239 | for qw/consumer_key consumer_secret signature_method http_method auth_method realm |
|---|
| 240 | site request_token_path access_token_path authorize_path |
|---|
| 241 | callback_url/; |
|---|
| 242 | my $self = $class->SUPER::new(%args_for_parent); |
|---|
| 243 | $self = bless $self, $class; |
|---|
| 244 | $self->_init(%args); |
|---|
| 245 | $self; |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | sub _init { |
|---|
| 249 | my ($self, %args) = @_; |
|---|
| 250 | |
|---|
| 251 | my $signature_method_class = exists $args{signature_method} |
|---|
| 252 | ? $args{signature_method} |
|---|
| 253 | : 'HMAC_SHA1'; |
|---|
| 254 | $signature_method_class =~ s/-/_/g; |
|---|
| 255 | $signature_method_class = join('::', |
|---|
| 256 | 'OAuth::Lite::SignatureMethod', |
|---|
| 257 | $signature_method_class |
|---|
| 258 | ); |
|---|
| 259 | $signature_method_class->require |
|---|
| 260 | or Carp::croak( |
|---|
| 261 | sprintf |
|---|
| 262 | qq/Could't find signature method class, %s/, |
|---|
| 263 | $signature_method_class |
|---|
| 264 | ); |
|---|
| 265 | |
|---|
| 266 | $self->{consumer_key} = $args{consumer_key} || ''; |
|---|
| 267 | $self->{consumer_secret} = $args{consumer_secret} || ''; |
|---|
| 268 | $self->{http_method} = $args{http_method} || 'POST'; |
|---|
| 269 | $self->{auth_method} = $args{auth_method} || AUTH_HEADER; |
|---|
| 270 | unless ( OAuth::Lite::AuthMethod->validate_method( $self->{auth_method} ) ) { |
|---|
| 271 | Carp::croak( sprintf |
|---|
| 272 | qq/Invalid auth method "%s"./, $self->{auth_method} ); |
|---|
| 273 | } |
|---|
| 274 | $self->{signature_method} = $signature_method_class; |
|---|
| 275 | $self->{realm} = $args{realm}; |
|---|
| 276 | $self->{site} = $args{site}; |
|---|
| 277 | $self->{request_token_path} = $args{request_token_path}; |
|---|
| 278 | $self->{access_token_path} = $args{access_token_path}; |
|---|
| 279 | $self->{authorize_path} = $args{authorize_path}; |
|---|
| 280 | $self->{callback_url} = $args{callback_url}; |
|---|
| 281 | $self->{oauth_request} = undef; |
|---|
| 282 | $self->{oauth_response} = undef; |
|---|
| 283 | $self->agent($args{agent} || join('/', __PACKAGE__, $OAuth::Lite::VERSION)); |
|---|
| 284 | } |
|---|
| 285 | |
|---|
| 286 | =head2 request_token_url |
|---|
| 287 | |
|---|
| 288 | =cut |
|---|
| 289 | |
|---|
| 290 | sub request_token_url { |
|---|
| 291 | my $self = shift; |
|---|
| 292 | $self->{request_token_path} =~ m!^http(?:s)?\://! |
|---|
| 293 | ? $self->{request_token_path} |
|---|
| 294 | : sprintf q{%s%s}, $self->{site}, $self->{request_token_path}; |
|---|
| 295 | } |
|---|
| 296 | |
|---|
| 297 | |
|---|
| 298 | =head2 access_token_url |
|---|
| 299 | |
|---|
| 300 | =cut |
|---|
| 301 | |
|---|
| 302 | sub access_token_url { |
|---|
| 303 | my $self = shift; |
|---|
| 304 | $self->{access_token_path} =~ m!^http(?:s)?\://! |
|---|
| 305 | ? $self->{access_token_path} |
|---|
| 306 | : sprintf q{%s%s}, $self->{site}, $self->{access_token_path}; |
|---|
| 307 | } |
|---|
| 308 | |
|---|
| 309 | =head2 authorization_url |
|---|
| 310 | |
|---|
| 311 | =cut |
|---|
| 312 | |
|---|
| 313 | sub authorization_url { |
|---|
| 314 | my $self = shift; |
|---|
| 315 | $self->{authorize_path} =~ m!^http(?:s)?\://! |
|---|
| 316 | ? $self->{authorize_path} |
|---|
| 317 | : sprintf q{%s%s}, $self->{site}, $self->{authorize_path}; |
|---|
| 318 | } |
|---|
| 319 | |
|---|
| 320 | |
|---|
| 321 | =head2 url_to_authorize(%params) |
|---|
| 322 | |
|---|
| 323 | =head3 parameters |
|---|
| 324 | |
|---|
| 325 | =over 4 |
|---|
| 326 | |
|---|
| 327 | =item url |
|---|
| 328 | |
|---|
| 329 | authorization url, you can omit this if you set authorization_path on constructor. |
|---|
| 330 | |
|---|
| 331 | =item callback_url |
|---|
| 332 | |
|---|
| 333 | Url which service provider redirect end-user to after authorization. |
|---|
| 334 | You can omit this if you set callback_url on constructor. |
|---|
| 335 | |
|---|
| 336 | =item token |
|---|
| 337 | |
|---|
| 338 | request token value |
|---|
| 339 | |
|---|
| 340 | =back |
|---|
| 341 | |
|---|
| 342 | my $url = $consumer->url_to_authorize( |
|---|
| 343 | url => q{http://example.org/authorize}, |
|---|
| 344 | token => $request_token, |
|---|
| 345 | callback_url => q{http://www.yousrservice/callback}, |
|---|
| 346 | ); |
|---|
| 347 | |
|---|
| 348 | =cut |
|---|
| 349 | |
|---|
| 350 | sub url_to_authorize { |
|---|
| 351 | my ($self, %args) = @_; |
|---|
| 352 | $args{url} ||= $self->authorization_url; |
|---|
| 353 | $args{callback_url} ||= $self->{callback_url}; |
|---|
| 354 | my $url = $args{url} |
|---|
| 355 | or Carp::croak qq/url_to_authorize needs url./; |
|---|
| 356 | my %params = (); |
|---|
| 357 | $params{oauth_callback} = $args{callback_url} if $args{callback_url}; |
|---|
| 358 | if (my $token = $args{token}) { |
|---|
| 359 | $params{oauth_token} = ( eval { $token->isa('OAuth::Lite::Token') } ) |
|---|
| 360 | ? $token->token |
|---|
| 361 | : $token; |
|---|
| 362 | } |
|---|
| 363 | $url = URI->new($url); |
|---|
| 364 | $url->query_form(%params); |
|---|
| 365 | $url->as_string; |
|---|
| 366 | } |
|---|
| 367 | |
|---|
| 368 | =head2 get_request_token(%params) |
|---|
| 369 | |
|---|
| 370 | Returns a request token as an L<OAuth::Lite::Token> object. |
|---|
| 371 | |
|---|
| 372 | =head3 parameters |
|---|
| 373 | |
|---|
| 374 | =over 4 |
|---|
| 375 | |
|---|
| 376 | =item url |
|---|
| 377 | |
|---|
| 378 | Request token url. You can omit this if you set request_token_path on constructor |
|---|
| 379 | |
|---|
| 380 | =item realm |
|---|
| 381 | |
|---|
| 382 | Realm for the resource you want to access to. |
|---|
| 383 | You can omit this if you set realm on constructor. |
|---|
| 384 | |
|---|
| 385 | =back |
|---|
| 386 | |
|---|
| 387 | my $token = $consumer->get_request_token( |
|---|
| 388 | url => q{http://api.example.org/request_token}, |
|---|
| 389 | realm => q{http://api.example.org/picture}, |
|---|
| 390 | ) or die $consumer->errstr; |
|---|
| 391 | |
|---|
| 392 | say $token->token; |
|---|
| 393 | say $token->secret; |
|---|
| 394 | |
|---|
| 395 | =cut |
|---|
| 396 | |
|---|
| 397 | sub get_request_token { |
|---|
| 398 | my ($self, %args) = @_; |
|---|
| 399 | $args{url} ||= $self->request_token_url; |
|---|
| 400 | my $request_token_url = $args{url} |
|---|
| 401 | or Carp::croak qq/get_request_token needs url in hash params |
|---|
| 402 | or set request_token_path on constructor./; |
|---|
| 403 | my $realm = $args{realm} || $self->{realm} || ''; |
|---|
| 404 | my $res = $self->__request( |
|---|
| 405 | realm => $realm, |
|---|
| 406 | url => $request_token_url, |
|---|
| 407 | ); |
|---|
| 408 | unless ($res->is_success) { |
|---|
| 409 | return $self->error($res->status_line); |
|---|
| 410 | } |
|---|
| 411 | my $token = OAuth::Lite::Token->from_encoded($res->content); |
|---|
| 412 | $self->request_token($token); |
|---|
| 413 | $token; |
|---|
| 414 | } |
|---|
| 415 | |
|---|
| 416 | =head2 get_access_token(%params) |
|---|
| 417 | |
|---|
| 418 | Returns a access token as an L<OAuth::Lite::Token> object. |
|---|
| 419 | |
|---|
| 420 | =head3 parameters |
|---|
| 421 | |
|---|
| 422 | =over 4 |
|---|
| 423 | |
|---|
| 424 | =item url |
|---|
| 425 | |
|---|
| 426 | Request token url. You can omit this if you set request_token_path on constructor |
|---|
| 427 | |
|---|
| 428 | =item realm |
|---|
| 429 | |
|---|
| 430 | Realm for the resource you want to access to. |
|---|
| 431 | You can omit this if you set realm on constructor. |
|---|
| 432 | |
|---|
| 433 | =item token |
|---|
| 434 | |
|---|
| 435 | Request token object. |
|---|
| 436 | |
|---|
| 437 | =back |
|---|
| 438 | |
|---|
| 439 | my $token = $consumer->get_access_token( |
|---|
| 440 | url => q{http://api.example.org/request_token}, |
|---|
| 441 | realm => q{http://api.example.org/picture}, |
|---|
| 442 | token => $request_token, |
|---|
| 443 | ) or die $consumer->errstr; |
|---|
| 444 | |
|---|
| 445 | say $token->token; |
|---|
| 446 | say $token->secret; |
|---|
| 447 | |
|---|
| 448 | |
|---|
| 449 | =cut |
|---|
| 450 | |
|---|
| 451 | sub get_access_token { |
|---|
| 452 | my ($self, %args) = @_; |
|---|
| 453 | $args{url} ||= $self->access_token_url; |
|---|
| 454 | $args{token} ||= $self->request_token; |
|---|
| 455 | my $access_token_url = $args{url} |
|---|
| 456 | or Carp::croak qq/get_access_token needs access_token_url./; |
|---|
| 457 | my $token = $args{token} |
|---|
| 458 | or Carp::croak qq/get_access_token needs token./; |
|---|
| 459 | my $realm = $args{realm} || $self->{realm} || ''; |
|---|
| 460 | my $res = $self->__request( |
|---|
| 461 | realm => $realm, |
|---|
| 462 | url => $access_token_url, |
|---|
| 463 | token => $token, |
|---|
| 464 | ); |
|---|
| 465 | unless ($res->is_success) { |
|---|
| 466 | return $self->error($res->status_line); |
|---|
| 467 | } |
|---|
| 468 | my $access_token = OAuth::Lite::Token->from_encoded($res->content); |
|---|
| 469 | $self->access_token($access_token); |
|---|
| 470 | $access_token; |
|---|
| 471 | } |
|---|
| 472 | |
|---|
| 473 | =head2 gen_oauth_request(%args) |
|---|
| 474 | |
|---|
| 475 | Returns L<HTTP::Request> object. |
|---|
| 476 | |
|---|
| 477 | my $req = $consumer->gen_oauth_request( |
|---|
| 478 | method => 'GET', |
|---|
| 479 | url => 'http://example.com/', |
|---|
| 480 | headers => [ Accept => q{...}, 'Content-Type' => q{...}, ... ], |
|---|
| 481 | content => $content, |
|---|
| 482 | realm => $realm, |
|---|
| 483 | token => $token, |
|---|
| 484 | params => { file => 'mypic.jpg', size => 'small' }, |
|---|
| 485 | ); |
|---|
| 486 | |
|---|
| 487 | =cut |
|---|
| 488 | |
|---|
| 489 | sub gen_oauth_request { |
|---|
| 490 | |
|---|
| 491 | my ($self, %args) = @_; |
|---|
| 492 | |
|---|
| 493 | my $method = $args{method} || $self->{http_method}; |
|---|
| 494 | my $url = $args{url}; |
|---|
| 495 | my $content = $args{content}; |
|---|
| 496 | my $token = $args{token}; |
|---|
| 497 | my $extra = $args{params} || {}; |
|---|
| 498 | my $realm = $args{realm} |
|---|
| 499 | || $self->{realm} |
|---|
| 500 | || $self->find_realm_from_last_response |
|---|
| 501 | || ''; |
|---|
| 502 | |
|---|
| 503 | my $headers = $args{headers}; |
|---|
| 504 | if (defined $headers) { |
|---|
| 505 | if (ref($headers) eq 'ARRAY') { |
|---|
| 506 | $headers = HTTP::Headers->new(@$headers); |
|---|
| 507 | } else { |
|---|
| 508 | $headers = $headers->clone; |
|---|
| 509 | } |
|---|
| 510 | } else { |
|---|
| 511 | $headers = HTTP::Headers->new; |
|---|
| 512 | } |
|---|
| 513 | |
|---|
| 514 | my @send_data_methods = qw/POST PUT/; |
|---|
| 515 | my @non_send_data_methods = qw/GET HEAD DELETE/; |
|---|
| 516 | |
|---|
| 517 | my $auth_method = $self->{auth_method}; |
|---|
| 518 | if (any { $method eq $_ } @non_send_data_methods) { |
|---|
| 519 | $auth_method = AUTH_HEADER |
|---|
| 520 | unless $auth_method eq URL_QUERY; |
|---|
| 521 | } else { # POST or PUT |
|---|
| 522 | $auth_method = AUTH_HEADER |
|---|
| 523 | unless $auth_method eq POST_BODY; |
|---|
| 524 | } |
|---|
| 525 | |
|---|
| 526 | if ($auth_method eq URL_QUERY) { |
|---|
| 527 | my $query = $self->gen_auth_query($method, $url, $token, $extra); |
|---|
| 528 | $url = sprintf q{%s?%s}, $url, $query; |
|---|
| 529 | } elsif ($auth_method eq POST_BODY) { |
|---|
| 530 | my $query = $self->gen_auth_query($method, $url, $token, $extra); |
|---|
| 531 | $content = $query; |
|---|
| 532 | } else { |
|---|
| 533 | my $header = $self->gen_auth_header($method, $url, |
|---|
| 534 | { realm => $realm, token => $token }); |
|---|
| 535 | $headers->header( Authorization => $header ); |
|---|
| 536 | if (keys %$extra > 0) { |
|---|
| 537 | my $data = join('&', map(sprintf(q{%s=%s}, |
|---|
| 538 | encode_param($_), encode_param($extra->{$_}) ), keys %$extra)); |
|---|
| 539 | if (any { $method eq $_ } @send_data_methods) { |
|---|
| 540 | $content = $data; |
|---|
| 541 | } else { |
|---|
| 542 | $url = sprintf q{%s?%s}, $url, $data; |
|---|
| 543 | } |
|---|
| 544 | } |
|---|
| 545 | } |
|---|
| 546 | if (any { $method eq $_ } @send_data_methods) { |
|---|
| 547 | $headers->header('Content-Type', q{application/x-www-form-urlencoded}) |
|---|
| 548 | unless $headers->header('Content-Type'); |
|---|
| 549 | $headers->header('Content-Length', bytes::length($content) ); |
|---|
| 550 | } |
|---|
| 551 | my $req = HTTP::Request->new( $method, $url, $headers, $content ); |
|---|
| 552 | $req; |
|---|
| 553 | } |
|---|
| 554 | |
|---|
| 555 | =head2 request(%params) |
|---|
| 556 | |
|---|
| 557 | Returns L<HTTP::Response> object. |
|---|
| 558 | |
|---|
| 559 | =head3 parameters |
|---|
| 560 | |
|---|
| 561 | =over 4 |
|---|
| 562 | |
|---|
| 563 | =item realm |
|---|
| 564 | |
|---|
| 565 | Realm for a resource you want to access |
|---|
| 566 | |
|---|
| 567 | =item token |
|---|
| 568 | |
|---|
| 569 | Access token L<OAuth::Lite::Token> object |
|---|
| 570 | |
|---|
| 571 | =item method |
|---|
| 572 | |
|---|
| 573 | HTTP method. |
|---|
| 574 | |
|---|
| 575 | =item url |
|---|
| 576 | |
|---|
| 577 | Request URL |
|---|
| 578 | |
|---|
| 579 | =item parmas |
|---|
| 580 | |
|---|
| 581 | Extra params. |
|---|
| 582 | |
|---|
| 583 | =item content |
|---|
| 584 | |
|---|
| 585 | body data sent when method is POST or PUT. |
|---|
| 586 | |
|---|
| 587 | =back |
|---|
| 588 | |
|---|
| 589 | my $response = $consumer->request( |
|---|
| 590 | method => 'POST', |
|---|
| 591 | url => 'http://api.example.com/picture', |
|---|
| 592 | headers => [ Accept => q{...}, 'Content-Type' => q{...}, ... ], |
|---|
| 593 | content => $content, |
|---|
| 594 | realm => $realm, |
|---|
| 595 | token => $access_token, |
|---|
| 596 | params => { file => 'mypic.jpg', size => 'small' }, |
|---|
| 597 | ); |
|---|
| 598 | |
|---|
| 599 | unless ($response->is_success) { |
|---|
| 600 | ... |
|---|
| 601 | } |
|---|
| 602 | |
|---|
| 603 | =cut |
|---|
| 604 | |
|---|
| 605 | sub request { |
|---|
| 606 | my ($self, %args) = @_; |
|---|
| 607 | $args{token} ||= $self->access_token; |
|---|
| 608 | $self->__request(%args); |
|---|
| 609 | } |
|---|
| 610 | |
|---|
| 611 | sub __request { |
|---|
| 612 | my ($self, %args) = @_; |
|---|
| 613 | my $req = $self->gen_oauth_request(%args); |
|---|
| 614 | $self->oauth_clear(); |
|---|
| 615 | $self->oauth_request($req); |
|---|
| 616 | my $res = $self->SUPER::request($req); |
|---|
| 617 | $self->oauth_response($res); |
|---|
| 618 | $res; |
|---|
| 619 | } |
|---|
| 620 | |
|---|
| 621 | =head2 find_realm_from_last_response |
|---|
| 622 | |
|---|
| 623 | =cut |
|---|
| 624 | |
|---|
| 625 | sub find_realm_from_last_response { |
|---|
| 626 | my $self = shift; |
|---|
| 627 | return unless $self->oauth_response; |
|---|
| 628 | my $authenticate = $self->oauth_response->header('WWW-Authenticate'); |
|---|
| 629 | return unless ($authenticate && $authenticate =~ /^\s*OAuth/); |
|---|
| 630 | my $realm = parse_auth_header($authenticate); |
|---|
| 631 | $realm; |
|---|
| 632 | } |
|---|
| 633 | |
|---|
| 634 | =head2 gen_auth_header($http_method, $request_url, $params); |
|---|
| 635 | |
|---|
| 636 | =head3 parameters |
|---|
| 637 | |
|---|
| 638 | =over 4 |
|---|
| 639 | |
|---|
| 640 | =item realm |
|---|
| 641 | |
|---|
| 642 | realm for a resource you want to access |
|---|
| 643 | |
|---|
| 644 | =item token |
|---|
| 645 | |
|---|
| 646 | OAuth::Lite::Token object(optional) |
|---|
| 647 | |
|---|
| 648 | =back |
|---|
| 649 | |
|---|
| 650 | my $header = $consumer->gen_auth_header($method, $url, { |
|---|
| 651 | realm => $realm, |
|---|
| 652 | token => $token, |
|---|
| 653 | }); |
|---|
| 654 | |
|---|
| 655 | =cut |
|---|
| 656 | |
|---|
| 657 | sub gen_auth_header { |
|---|
| 658 | my ($self, $method, $url, $args) = @_; |
|---|
| 659 | my $params = $self->gen_auth_params($method, $url, $args->{token}); |
|---|
| 660 | my $realm = $args->{realm} || ''; |
|---|
| 661 | my $authorization_header = build_auth_header($realm, $params); |
|---|
| 662 | $authorization_header; |
|---|
| 663 | } |
|---|
| 664 | |
|---|
| 665 | =head2 gen_auth_query($http_method, $ruqest_url, $token, $extra) |
|---|
| 666 | |
|---|
| 667 | =cut |
|---|
| 668 | |
|---|
| 669 | sub gen_auth_query { |
|---|
| 670 | my ($self, $method, $url, $token, $extra) = @_; |
|---|
| 671 | $extra ||= {}; |
|---|
| 672 | my $params = $self->gen_auth_params($method, $url, $token); |
|---|
| 673 | my %all = (%$extra, %$params); |
|---|
| 674 | my $query = join('&', sort { $a cmp $b } |
|---|
| 675 | map(sprintf(q{%s=%s}, encode_param($_), encode_param($all{$_})), |
|---|
| 676 | keys %all)); |
|---|
| 677 | $query; |
|---|
| 678 | } |
|---|
| 679 | |
|---|
| 680 | =head2 gen_auth_params($http_method, $request_url, [$token]) |
|---|
| 681 | |
|---|
| 682 | Generates and returns all oauth params. |
|---|
| 683 | |
|---|
| 684 | my $params = $consumer->gen_auth_params($http_method, $request_url); |
|---|
| 685 | say $params->{oauth_consumer_key}; |
|---|
| 686 | say $params->{oauth_timestamp}; |
|---|
| 687 | say $params->{oauth_nonce}; |
|---|
| 688 | say $params->{oauth_signature_method}; |
|---|
| 689 | say $params->{oauth_signature}; |
|---|
| 690 | say $params->{oauth_version}; |
|---|
| 691 | |
|---|
| 692 | If you pass token as third argument, the result includes oauth_token value. |
|---|
| 693 | |
|---|
| 694 | my $params = $consumer->gen_auth_params($http_method, $request_url, $token); |
|---|
| 695 | say $params->{oauth_consumer_key}; |
|---|
| 696 | say $params->{oauth_timestamp}; |
|---|
| 697 | say $params->{oauth_nonce}; |
|---|
| 698 | say $params->{oauth_signature_method}; |
|---|
| 699 | say $params->{oauth_signature}; |
|---|
| 700 | say $params->{oauth_token}; |
|---|
| 701 | say $params->{oauth_version}; |
|---|
| 702 | |
|---|
| 703 | =cut |
|---|
| 704 | |
|---|
| 705 | sub gen_auth_params { |
|---|
| 706 | my ($self, $method, $url, $token) = @_; |
|---|
| 707 | my $params = {}; |
|---|
| 708 | $params->{oauth_consumer_key} = $self->consumer_key || ''; |
|---|
| 709 | $params->{oauth_timestamp} = time(); |
|---|
| 710 | $params->{oauth_nonce} = gen_random_key(); |
|---|
| 711 | $params->{oauth_version} = $OAuth::Lite::OAUTH_DEFAULT_VERSION; |
|---|
| 712 | if (defined $token) { |
|---|
| 713 | $params->{oauth_token} = ( eval { $token->isa('OAuth::Lite::Token') } ) |
|---|
| 714 | ? $token->token |
|---|
| 715 | : $token; |
|---|
| 716 | } |
|---|
| 717 | my $consumer_secret = $self->consumer_secret || ''; |
|---|
| 718 | my $token_secret = defined $token ? $token->secret : ''; |
|---|
| 719 | $params->{oauth_signature_method} = $self->{signature_method}->method_name; |
|---|
| 720 | if ($params->{oauth_signature_method} eq 'PLAINTEXT' && lc($url) !~ /^https/) { |
|---|
| 721 | warn qq(PLAINTEXT signature method should be used on SSL/TSL.); |
|---|
| 722 | } |
|---|
| 723 | my $base = create_signature_base_string($method, $url, $params); |
|---|
| 724 | $params->{oauth_signature} = $self->{signature_method}->new( |
|---|
| 725 | consumer_secret => $consumer_secret, |
|---|
| 726 | token_secret => $token_secret, |
|---|
| 727 | )->sign($base); |
|---|
| 728 | $params; |
|---|
| 729 | } |
|---|
| 730 | |
|---|
| 731 | =head2 oauth_request |
|---|
| 732 | |
|---|
| 733 | =head2 oauth_req |
|---|
| 734 | |
|---|
| 735 | Returns last oauth request. |
|---|
| 736 | |
|---|
| 737 | my $req_token = $consumer->get_request_token(...); |
|---|
| 738 | say $consumer->oauth_request->uri; |
|---|
| 739 | |
|---|
| 740 | my $req_token = $consumer->get_access_token(...); |
|---|
| 741 | say $consumer->oauth_request->uri; |
|---|
| 742 | |
|---|
| 743 | =head2 oauth_response |
|---|
| 744 | |
|---|
| 745 | =head2 oauth_res |
|---|
| 746 | |
|---|
| 747 | Returns last oauth response. |
|---|
| 748 | |
|---|
| 749 | my $req_token = $consumer->get_request_token(...); |
|---|
| 750 | say $consumer->oauth_response->status; |
|---|
| 751 | |
|---|
| 752 | my $req_token = $consumer->get_access_token(...); |
|---|
| 753 | say $consumer->oauth_response->status; |
|---|
| 754 | |
|---|
| 755 | =head2 oauth_clear |
|---|
| 756 | |
|---|
| 757 | remove last oauth-request and oauth-response. |
|---|
| 758 | |
|---|
| 759 | =cut |
|---|
| 760 | |
|---|
| 761 | sub oauth_clear { |
|---|
| 762 | my $self = shift; |
|---|
| 763 | $self->{oauth_request} = undef; |
|---|
| 764 | $self->{oauth_response} = undef; |
|---|
| 765 | } |
|---|
| 766 | |
|---|
| 767 | =head1 AUTHOR |
|---|
| 768 | |
|---|
| 769 | Lyo Kato, C<lyo.kato _at_ gmail.com> |
|---|
| 770 | |
|---|
| 771 | =head1 COPYRIGHT AND LICENSE |
|---|
| 772 | |
|---|
| 773 | This library is free software; you can redistribute it and/or modify |
|---|
| 774 | it under the same terms as Perl itself, either Perl version 5.8.6 or, |
|---|
| 775 | at your option, any later version of Perl 5 you may have available. |
|---|
| 776 | |
|---|
| 777 | =cut |
|---|
| 778 | |
|---|
| 779 | 1; |
|---|