root/lang/perl/OAuth-Lite/trunk/lib/OAuth/Lite/Consumer.pm @ 7759

Revision 7759, 20.2 kB (checked in by lyokato, 5 years ago)

lang/perl/OAuth-Lite: Checking in changes prior to tagging of version 1.08. Changelog diff is:

Index: Changes
===================================================================
--- Changes (リビジョン 7754)
+++ Changes (作業コピー)
@@ -1,5 +1,11 @@

Revision history for Perl extension OAuth::Lite.


+1.08 Mon Mar 10 16:03:00 2008
+ - rt #33943
+ added Content-Length header in case request method is POST or PUT,
+ at the end of Consumer::gen_oauth_request.
+ Thanks, TOMI.
+

1.07 Sun Jan 20 15:12:00 2008

  • put sorting process into building query-string process.
    The hash keys order is deferrent between perl environments.
Line 
1package OAuth::Lite::Consumer;
2
3use strict;
4use warnings;
5
6use 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
24use Carp ();
25use bytes ();
26use URI;
27use HTTP::Request;
28use HTTP::Headers;
29use UNIVERSAL::require;
30use List::MoreUtils qw(any);
31
32use OAuth::Lite;
33
34use OAuth::Lite::Token;
35use OAuth::Lite::Util qw(:all);
36use OAuth::Lite::AuthMethod qw(:all);
37
38=head1 NAME
39
40OAuth::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
105This 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
117consumer_key value
118
119=item consumer_secret
120
121consumer_secret value
122
123=item signature_method
124
125Signature method you can choose from 'HMAC-SHA1', 'PLAINTEXT', and 'RSA-SHA1' (optional, 'HMAC-SHA1' is set by default)
126
127=item http_method
128
129HTTP 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
133L<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
137The OAuth realm value for a protected-resource you wanto to access to. (optional. empty-string is set by default)
138
139=item site
140
141The 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
153Site 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
167If 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
180Like this, if you pass absolute url, consumer uses them as it is.
181
182You 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
192And 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
213So 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
222Or
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
235sub 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
248sub _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
290sub 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
302sub 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
313sub 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
329authorization url, you can omit this if you set authorization_path on constructor.
330
331=item callback_url
332
333Url which service provider redirect end-user to after authorization.
334You can omit this if you set callback_url on constructor.
335
336=item token
337
338request 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
350sub 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
370Returns a request token as an L<OAuth::Lite::Token> object.
371
372=head3 parameters
373
374=over 4
375
376=item url
377
378Request token url. You can omit this if you set request_token_path on constructor
379
380=item realm
381
382Realm for the resource you want to access to.
383You 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
397sub 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
418Returns a access token as an L<OAuth::Lite::Token> object.
419
420=head3 parameters
421
422=over 4
423
424=item url
425
426Request token url. You can omit this if you set request_token_path on constructor
427
428=item realm
429
430Realm for the resource you want to access to.
431You can omit this if you set realm on constructor.
432
433=item token
434
435Request 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
451sub 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
475Returns 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
489sub 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
557Returns L<HTTP::Response> object.
558
559=head3 parameters
560
561=over 4
562
563=item realm
564
565Realm for a resource you want to access
566
567=item token
568
569Access token  L<OAuth::Lite::Token> object
570
571=item method
572
573HTTP method.
574
575=item url
576
577Request URL
578
579=item parmas
580
581Extra params.
582
583=item content
584
585body 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
605sub request {
606    my ($self, %args) = @_;
607    $args{token} ||= $self->access_token;
608    $self->__request(%args);
609}
610
611sub __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
625sub 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
642realm for a resource you want to access
643
644=item token
645
646OAuth::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
657sub 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
669sub 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
682Generates 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
692If 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
705sub 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
735Returns 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
747Returns 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
757remove last oauth-request and oauth-response.
758
759=cut
760
761sub oauth_clear {
762    my $self = shift;
763    $self->{oauth_request}  = undef;
764    $self->{oauth_response} = undef;
765}
766
767=head1 AUTHOR
768
769Lyo Kato, C<lyo.kato _at_ gmail.com>
770
771=head1 COPYRIGHT AND LICENSE
772
773This library is free software; you can redistribute it and/or modify
774it under the same terms as Perl itself, either Perl version 5.8.6 or,
775at your option, any later version of Perl 5 you may have available.
776
777=cut
778
7791;
Note: See TracBrowser for help on using the browser.