File Coverage

blib/lib/Net/OAuth/Easy.pm
Criterion Covered Total %
statement 2 4 50.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 4 6 66.6


line stmt bran cond sub pod time code
1             package Net::OAuth::Easy;
2             BEGIN {
3 1     1   389838 $Net::OAuth::Easy::VERSION = '0.001_07';
4             }
5 1     1   419 use Moose;
  0            
  0            
6             use Digest::MD5 qw{md5_hex};
7             require Net::OAuth;
8             require HTTP::Request;
9              
10             # ABSTRACT: A moose class that abstracts Net::OAuth for you
11              
12              
13             with qw{
14             Net::OAuth::Easy::Roles::Types
15             };
16              
17              
18             has ua => (
19             is => 'rw',
20             isa => 'LWP::UserAgent',
21             lazy => 1,
22             default => sub{
23             require LWP::UserAgent;
24             LWP::UserAgent->new;
25             },
26             );
27              
28              
29             has protocol => (
30             is => 'rw',
31             isa => 'OAuthProtocol',
32             lazy => 1,
33             default => sub{'1.0a'},
34             trigger => \&set_net_oauth_protocol,
35             );
36             sub set_net_oauth_protocol {
37             no warnings;
38             $Net::OAuth::PROTOCOL_VERSION = (shift->protocol eq '1.0a') ? &Net::OAuth::PROTOCOL_VERSION_1_0A : &Net::OAuth::PROTOCOL_VERSION_1_0;
39             }
40              
41             sub BUILD {
42             my $self = shift;
43             $self->set_net_oauth_protocol;
44             }
45              
46              
47             has $_ => (
48             is => 'rw',
49             isa => 'Str',
50             predicate => qq{has_$_},
51             clearer => qq{clear_$_},
52             ) for qw{ consumer_key consumer_secret };
53              
54              
55             has $_ => (
56             is => 'rw',
57             isa => 'ValidURI',
58             predicate => qq{has_$_},
59             clearer => qq{clear_$_},
60             ) for qw{ request_token_url authorize_token_url access_token_url callback };
61              
62              
63             has request_method => (
64             is => 'rw',
65             isa => 'RequestMethod',
66             default => 'GET',
67             );
68              
69              
70             has signature_method => (
71             is => 'rw',
72             isa => 'SignatureMethod',
73             default => 'HMAC-SHA1',
74             );
75              
76              
77             has signature_key => (
78             is => 'rw',
79             isa => 'SignatureKey',
80             coerce => 1,
81             predicate => 'has_signature_key',
82             clearer => 'clear_signature_key',
83             );
84              
85              
86             sub timestamp { time };
87              
88              
89             sub nonce { md5_hex( join '', rand(2**32), time, rand(2**32) ); };
90              
91              
92             has request_parameters => (
93             is => 'rw',
94             isa => 'HashRef[ArrayRef]',
95             default => sub{{ request_token => [qw{consumer_key
96             consumer_secret
97             request_url
98             request_method
99             signature_key
100             signature_method
101             protocol_version
102             timestamp
103             nonce
104             callback
105             token
106             token_secret
107             verifier
108             }],
109             access_token => [qw{consumer_key
110             consumer_secret
111             request_url
112             request_method
113             protocol_version
114             signature_key
115             signature_method
116             timestamp
117             nonce
118             token
119             token_secret
120             verifier
121             }],
122              
123             protected_resource => [qw{
124             consumer_key
125             consumer_secret
126             request_url
127             request_method
128             signature_key
129             signature_method
130             protocol_version
131             timestamp
132             nonce
133             token
134             token_secret
135             }],
136             #verifier
137             }},
138             );
139              
140              
141             has exception_handle => (
142             is => 'rw',
143             isa => 'CodeRef',
144             default => sub{sub{shift;die @_}},
145             );
146              
147              
148             sub build_request {
149             my $self = shift;
150             my $type = shift;
151             my $request = Net::OAuth->request($type)->new($self->gather_request_parts($type => @_));
152              
153             $self->exception_handle->( q{Unable to sign request} )
154             unless $request->sign;
155              
156             $self->exception_handle->( q{Unable to verify request} )
157             unless $request->verify;
158              
159             return $request;
160             }
161              
162              
163             sub gather_request_parts {
164             my $self = shift;
165             my $type = shift;
166             my %opts = @_;
167              
168             # use type to grab the right url
169             my $url_method = sprintf q{%s_url}, $type;
170             $opts{request_url} ||= $self->can($url_method) ? $self->$url_method : undef;
171              
172             # pull any overrides from %opts/@_ everything else is pulled from $self
173             my %req = map{ $_ => ( exists $opts{$_} ) ? delete $opts{$_} : ( $self->can($_) ) ? $self->$_ : undef;
174             } @{$self->request_parameters->{ $type } || [] };
175             # TODO: this is likely not what we really want in cases where you pass Content, NOS builds the URL and then plucks from that, possibly more accurate?
176             $req{extra_params} = \%opts if scalar(keys %opts); # save off anything left from @_ as extra params
177              
178             $req{protocol_version} = ($self->protocol eq '1.0a') ? &Net::OAuth::PROTOCOL_VERSION_1_0A : &Net::OAuth::PROTOCOL_VERSION_1_0 ;
179            
180             return %req;
181             }
182              
183              
184             has response => (
185             is => 'rw',
186             isa => 'Object', # TODO: this is too vague
187             predicate => 'has_response',
188             clearer => 'clear_response',
189             );
190              
191              
192             sub content {
193             my $self = shift;
194             ( $self->has_response ) ? $self->response->content : undef;
195             }
196              
197              
198             sub success {
199             my $self = shift;
200             return ( $self->has_response ) ? $self->response->is_success : 0;
201             }
202              
203              
204             sub failure { ! shift->success };
205              
206              
207             sub error{
208             my $self = shift;
209             return ($self->failure) ? join qq{\n}, map{$self->response->$_} qw{status_line content} : undef;
210             }
211              
212              
213             sub make_request {
214             my $self = shift;
215             my $content;
216             # find content if it was passed
217             for (my $i=0; $i<scalar(@_); $i++ ) {
218             if (defined $_[$i] && $_[$i] =~ m/^Content$/i) {
219             $content = delete $_[$i+1];
220             delete $_[$i];
221             last;
222             }
223             }
224             $self->clear_response if $self->has_response;
225             my $request = ( ref($_[0]) && $_[0]->isa('Net::OAuth::Message') ) ? $_[0] : $self->build_request(grep { defined }@_);
226              
227             my $req = HTTP::Request->new( $request->request_method => ( $request->request_method eq 'GET' && !$self->include_auth_header_for_GET )
228             ? $request->to_url
229             : $request->request_url
230             );
231             $req->content($content) if defined $content;
232             return $self->add_auth_headers($req, $request);
233             }
234              
235              
236             has [qw{oauth_header_realm oauth_header_separator}] => (
237             is => 'rw',
238             isa => 'Maybe[Str]',
239             );
240              
241              
242             has include_auth_header_for_GET => (
243             is => 'rw',
244             isa => 'Bool',
245             default => 0,
246             );
247              
248             sub build_auth_header {
249             my ($self,$oauth_req) = @_;
250             $oauth_req->to_authorization_header(
251             (defined $self->oauth_header_realm) ? $self->oauth_header_realm : undef ,
252             (defined $self->oauth_header_separator) ? $self->oauth_header_separator : undef ,
253             );
254             };
255              
256              
257             sub add_auth_headers {
258             my ($self, $http_req, $oauth_req) = @_;
259             $self->exception_handle( 'HTTP::Request expected as first paramater') unless $http_req->isa('HTTP::Request');
260             $self->exception_handle( 'Net::OAuth::Message expected as second paramater') unless $oauth_req->isa('Net::OAuth::Message');
261             $http_req->authorization( $self->build_auth_header($oauth_req)
262             ) if $http_req->method eq 'POST' || $self->include_auth_header_for_GET;
263             return $http_req;
264             }
265              
266              
267             sub send_request {
268             my $self = shift;
269             my $req = ( ref($_[0]) && $_[0]->isa('HTTP::Request') ) ? $_[0] : $self->make_request(@_);
270             $self->response( $self->ua->request( $req ) );
271             }
272              
273              
274             has $_ => (
275             is => 'rw',
276             isa => 'Str',
277             predicate => qq{has_$_},
278             clearer => qq{clear_$_},
279             ) for qw{request_token request_token_secret access_token access_token_secret};
280              
281              
282             sub get_request_token {
283             my $self = shift;
284             $self->send_request(request_token => @_);
285             if ($self->success) {
286             my $resp = Net::OAuth->response('request token')->from_post_body($self->response->content);
287             $self->request_token( $resp->token );
288             $self->request_token_secret( $resp->token_secret );
289             }
290             return $self->success;
291             }
292              
293            
294             sub get_authorization_url {
295             my $self = shift;
296             my %opts = @_;
297             $opts{oauth_token} ||= $self->request_token;
298             $opts{callback} ||= $self->callback;
299             my $url = URI->new( $self->authorize_token_url );
300             $url->query_form( %opts );
301             return $url;
302             }
303              
304              
305             sub process_authorization_callback {
306             my $self = shift;
307             my $url = (ref($_[0]) eq '') ? URI->new($_[0]) : $_[0]; # if we are handed a string build a uri object of it
308             my %opts = $url->query_form;
309             for ( grep{! m/^oauth_/} keys %opts ) {
310             delete $opts{$_};
311             }
312             return %opts;
313             }
314              
315              
316             has process_access_token_mapping => (
317             is => 'rw',
318             isa => 'HashRef[ArrayRef]',
319             auto_deref => 1,
320             default => sub{{ token => [qw{oauth_token request_token}],
321             token_secret => [qw{request_token_secret}],
322             verifier => [qw{oauth_verifier}],
323             }},
324             );
325              
326              
327             sub process_access_token_input {
328             my $self = shift;
329             my %opts = @_;
330             my %mapp = $self->process_access_token_mapping;
331             while ( my ( $key, $map ) = each %mapp ) {
332             next if exists $opts{$key}; # dont overwrite anything that was passed to us (respect overwrites)
333             for my $lookup ( @$map ) {
334             my $value = ( exists $opts{$lookup} ) ? delete $opts{$lookup}
335             : ( $self->can($lookup) ) ? $self->$lookup
336             : undef;
337             $opts{$key} = $value;
338             next if $value; # stop looking if we found a value
339             }
340             }
341             return %opts;
342             }
343              
344              
345             sub get_access_token {
346             my $self = shift;
347             my %opts = $self->process_access_token_input( (scalar(@_) == 1)
348             ? $self->process_authorization_callback(@_)
349             : @_
350             );
351              
352             $self->send_request(access_token => %opts);
353             if ($self->success) {
354             my $resp = Net::OAuth->response('access token')->from_post_body($self->response->content);
355             $self->access_token( $resp->token );
356             $self->access_token_secret( $resp->token_secret );
357             }
358             return $self->success;
359             }
360              
361              
362             sub get_protected_resource {
363             my $self = shift;
364             my %opts = (scalar(@_) == 1) ? (request_url => $_[0]) : @_ ; # allow just the requested URL to be pased
365             $opts{token} ||= $self->access_token;
366             $opts{token_secret} ||= $self->access_token_secret;
367             $self->send_request(protected_resource => %opts);
368             return $self->success;
369             }
370              
371              
372             1;
373              
374             __END__
375             =pod
376              
377             =head1 NAME
378              
379             Net::OAuth::Easy - A moose class that abstracts Net::OAuth for you
380              
381             =head1 VERSION
382              
383             version 0.001_07
384              
385             =head1 SYNOPSIS
386              
387             use Net::OAuth::Easy;
388             my $oauth = Net::OAuth::Easy->new(
389             consumer_key => $key,
390             consumer_secret => $secret,
391             request_token_url => q{http://someplace.com/request_token},
392             authorize_token_url => q{http://someplace.com/authorize},
393             access_token_url => q{http://someplace.com/access_token},
394             callback => q{http://here.com/user},
395             );
396             $oauth->get_request_token;
397             # save off request token secret somewhere, you need it later
398             $some_session_idea->request_token_secret($oauth->requset_token_secret);
399              
400             my $auth_url = $oauth->get_authorization_url;
401             # redirect user to $auth_url
402              
403             ...
404              
405             #reload the token secret
406             $oauth->request_token_secret( $some_session_idea->request_token_secret );
407             $oauth->get_access_token( $q->url );
408             #safe off the access tokens now
409             $some_storage_idea->access_token($oauth->access_token);
410             $some_storage_idea->access_token_secret($oauth->access_token_secret);
411              
412             ...
413              
414             $oauth->access_token( $some_storage_idea->access_token );
415             $oauth->access_token_secret( $some_storage_idea->access_token_secret );
416             $oauth->get_protected_resource( $restricted_url )
417              
418             get_access_token
419              
420             =head1 DESCRIPTION
421              
422             =head1 OVERVIEW
423              
424             =head1 ATTRIBUTES
425              
426             =head2 ua
427              
428             A LWP::UserAgent object to do the message passing.
429              
430             =head2 protocol
431              
432             What OAuth protocol do you wish your messages to be build in?
433              
434             =over 4
435              
436             =item * '1.0a' B<Default>
437              
438             =item * '1.0'
439              
440             =back
441              
442             =head2 consumer_key
443              
444             =head2 consumer_secret
445              
446             =head2 request_token_url
447              
448             =head2 authorize_token_url
449              
450             =head2 access_token_url
451              
452             =head2 callback
453              
454             =head2 request_method
455              
456             Defines the method of the request.
457              
458             =over 4
459              
460             =item * 'GET' B<Default>
461              
462             =item * 'POST'
463              
464             =back
465              
466             =head2 signature_method
467              
468             Defines the method to sign the request.
469              
470             =over 4
471              
472             =item * 'HMAC-SHA1' B<Default>
473              
474             =item * 'RSA-SHA1'
475              
476             =back
477              
478             =head2 signature_key
479              
480             Where to find the signature key, only used for RSA-SHA1 type signatures.
481              
482             Expected to be passed a Crypt::OpenSSL::RSA object. Though if passed a
483             string, this will be assumped to be a filename and will be passed to
484             the new_private_key method of Crypt::OpenSSL::RSA. The object that
485             results will be stored.
486              
487             =head2 request_parameters
488              
489             This is a HashRef of ArrayRefs that is used to define the required
490             elements of each type of OAuth request. The type (ie request_token)
491             is the key and all items in the ArrayRef value will be collected
492             from $self if not passed at the time that the request is built.
493              
494             =head2 exception_handle
495              
496             Stores a coderef that is called when an exception is hit. Out of
497             the box this does not do anything more then die with a message,
498             though it can be used to leverage diffrent codepaths at the time
499             of an exception.
500              
501             It is used internaly as such:
502              
503             $self->exception_handle->(q{unable to sign request});
504              
505             Thus if you need to define your own you will have $self and a note
506             about why it was called.
507              
508             I'm not completely happy with this so it could change but this should
509             get any one needing this the most basic items currently.
510              
511             =head2 response
512              
513             Stores the response when any of the get_* methods are called.
514              
515             =head2 oauth_header_realm
516              
517             If defined it is expected to be a string(URL) that will be included
518             in to the Authorization headers. If not given it will be ignored.
519              
520             =head2 oauth_header_separator
521              
522             A string that denotes the string that you would like to use to
523             seperate the key=value pairs in the Authuntication header.
524              
525             Defaults to ','.
526              
527             =head2 request_token
528              
529             Stores the request_token when it's collected via L<get_request_token>.
530              
531             =head2 request_token_secret
532              
533             Stores the request_token_secret when it's collected via L<get_request_token>.
534              
535             =head2 access_token
536              
537             Stores the access_token when it's collected via L<get_request_token>.
538              
539             =head2 access_token_secret
540              
541             Stores the access_token_secret when it's collected via L<get_request_token>.
542              
543             =head2 process_access_token_mapping
544              
545             =head1 METHODS
546              
547             =head2 has_consumer_key
548              
549             =head2 clear_consumer_key
550              
551             =head2 has_consumer_secret
552              
553             =head2 clear_consumer_secret
554              
555             =head2 has_request_token_url
556              
557             =head2 clear_request_token_url
558              
559             =head2 has_authorize_token_url
560              
561             =head2 clear_authorize_token_url
562              
563             =head2 has_access_token_url
564              
565             =head2 clear_access_token_url
566              
567             =head2 has_callback
568              
569             =head2 clear_callback
570              
571             =head2 has_signature_key
572              
573             =head2 clear_signature_key
574              
575             =head2 timestamp
576              
577             Currently just an alias to L<time>, it is used to define the timestamp
578             of the OAuth request.
579              
580             =head2 nonce
581              
582             Define a unique id for every OAuth request, curently this is done by
583             taking the md5_hex of two random numbers and the time.
584              
585             =head2 build_request
586              
587             Used to build the Net::OAuth request object based on input and L<gather_request_parts>.
588              
589             =head2 gather_request_parts
590              
591             Uses L<request_parameters> to merge passed items with stored values
592             to complete all items required for L<build_request>.
593              
594             =head2 has_response
595              
596             =head2 clear_response
597              
598             =head2 content
599              
600             Shortcut to get the content of the response, will return undef if in
601             the case of no response yet stored.
602              
603             =head2 success
604              
605             Shortcut to see if a successful response was collected, returns 0
606             in the case of no response yet stored.
607              
608             =head2 failure
609              
610             Returns the inverse of L<success>.
611              
612             =head2 error
613              
614             In the case of a non-successful response, will return a formated
615             string that includes the status_line and content to describe the
616             reason for failure. Will return undef in the case of no response
617             yet stored.
618              
619             =head2 make_request
620              
621             Given a Net::OAuth request, convert it to a HTTP::Request such
622             that it can be sent via L<ua>. One other thing to note is that
623             make_request also calls clear_request thus destroying any
624             previously stored request.
625              
626             =head2 add_auth_headers
627              
628             Add the Authentication header to the HTTP request based on the OAuth
629             request if the request method is POST.
630              
631             =head2 send_request
632              
633             Pass the given HTTP::Request object to L<ua> thus sending out the
634             request to the world.
635              
636             =head2 has_request_token
637              
638             =head2 clear_request_token
639              
640             =head2 has_request_token_secret
641              
642             =head2 clear_request_token_secret
643              
644             =head2 has_access_token
645              
646             =head2 clear_access_token
647              
648             =head2 has_access_token_secret
649              
650             =head2 clear_access_token_secret
651              
652             =head2 get_request_token
653              
654             Builds up an OAuth request to get the request_token pairs.
655              
656             =head2 get_authorization_url
657              
658             Build out the URL that is needed to be called to collect the oauth_verifier.
659              
660             =head2 process_authorization_callback
661              
662             Unpack the return url from the OAuth provider that includes items
663             like oauth_verifier. Returns a hash of unparsed items.
664              
665             =head2 process_access_token_input
666              
667             =head2 get_access_token
668              
669             Collect and store the access_tokens.
670              
671             =head2 get_protected_resource
672              
673             =roles Net::OAuth::Easy::Roles::Types
674              
675             =head1 AUTHOR
676              
677             Ben Hengst <notbenh@cpan.org>
678              
679             =head1 COPYRIGHT AND LICENSE
680              
681             This software is copyright (c) 2010 by Ben Hengst.
682              
683             This is free software; you can redistribute it and/or modify it under
684             the same terms as the Perl 5 programming language system itself.
685              
686             =cut
687