File Coverage

blib/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm
Criterion Covered Total %
statement 77 89 86.5
branch 17 30 56.6
condition 7 22 31.8
subroutine 10 10 100.0
pod 0 1 0.0
total 111 152 73.0


line stmt bran cond sub pod time code
1             package Net::HTTP::Spore::Middleware::Auth::OAuth;
2             $Net::HTTP::Spore::Middleware::Auth::OAuth::VERSION = '0.09';
3             # ABSTRACT: middleware for OAuth authentication
4              
5 1     1   626 use Moose;
  1         2  
  1         10  
6 1     1   6299 use URI::Escape;
  1         2  
  1         72  
7 1     1   368 use Digest::SHA;
  1         2246  
  1         37  
8 1     1   7 use MIME::Base64;
  1         2  
  1         1024  
9              
10             extends 'Net::HTTP::Spore::Middleware::Auth';
11              
12             has [qw/oauth_consumer_key oauth_consumer_secret/] => (
13             is => 'ro',
14             isa => 'Str',
15             required => 1,
16             );
17              
18             has oauth_callback => (
19             is => 'ro',
20             isa => 'Str',
21             lazy => 1,
22             default => 'oob',
23             );
24              
25             has oauth_signature_method => (
26             is => 'ro',
27             isa => 'Str',
28             lazy => 1,
29             default => 'HMAC-SHA1',
30             );
31              
32             has [qw/oauth_token oauth_token_secret oauth_verifier realm/] => (
33             is => 'ro',
34             isa => 'Str',
35             );
36              
37             sub call {
38 3     3 0 7 my ( $self, $req ) = @_;
39              
40 3 50       18 return unless $self->should_authenticate($req);
41              
42 3         96 my $oauth_params = {
43             oauth_signature_method => $self->oauth_signature_method,
44             oauth_consumer_key => $self->oauth_consumer_key,
45             oauth_token => $self->oauth_token,
46             oauth_verifier => $self->oauth_verifier,
47             oauth_version => '1.0',
48             };
49              
50 3 100       9 if ( !defined $oauth_params->{oauth_token} ) {
51 2         56 $oauth_params->{oauth_callback} = $self->oauth_callback;
52             }
53              
54 3         25 foreach my $k ( keys %$oauth_params ) {
55 17         145 $oauth_params->{$k} = uri_escape( $oauth_params->{$k} );
56             }
57             # save the environment so the request will no be finalized twice
58 3         90 my $env = $req->env;
59 3         13 $req->finalize;
60              
61 3         12 my $oauth_sig = $self->_oauth_sig( $req, $oauth_params );
62             # put back the environment the signature is now computed
63 3         120 $req->env($env);
64              
65 3         9 $req->header( 'Authorization' =>
66             $self->_build_auth_string( $oauth_params, $oauth_sig ) );
67             }
68              
69             sub _base_string {
70 3     3   8 my ($self, $req, $oparams) = @_;
71              
72 3         7 my $query_keys = [];
73 3         6 my $query_vals = {};
74              
75 3 100       76 if ( defined $req->env->{QUERY_STRING} ) {
76 1         23 while ($req->env->{QUERY_STRING} =~ /([^=]+)=([^&]*)&?/g){
77 1         4 my ($k,$v) = ($1,$2);
78 1         3 push @$query_keys, $k;
79 1         23 $query_vals->{$k} = $v;
80             }
81             }
82              
83 3         9 my $payload = $req->body;
84 3 50       33 if ( defined $payload ) {
85 0         0 my $ct = $req->header('content-type');
86 0 0 0     0 if ( !defined $ct or $ct eq 'application/x-www-form-urlencoded' ) {
87 0         0 while ($payload =~ /([^=]+)=([^&]*)&?/g){
88 0         0 my ($k,$v) = ($1,$2);
89 0         0 $v =~ s/\+/\%\%20/;
90 0         0 push @$query_keys, $k;
91 0         0 $query_vals->{$k} = $v;
92             }
93             }
94             }
95              
96 3         12 my $scheme = $req->scheme;
97 3         11 my $port = $req->port;
98              
99 3 50 33     15 if ( $port == 80 && $scheme eq 'http' ) {
100 3         6 $port = undef;
101             }
102 3 0 33     7 if ( defined $port
      33        
      0        
103             && defined $scheme
104             && $port == 443
105             && $scheme eq 'https' )
106             {
107 0         0 $port = undef;
108             }
109              
110              
111             my $uri =
112             ( $scheme || 'https' ) . "://"
113 3   50     70 . $req->env->{SERVER_NAME};
114 3 50       8 if ( $port ) { $uri .= ":$port"; }
  0         0  
115             $uri .= $req->env->{SCRIPT_NAME}
116 3         63 . $req->env->{PATH_INFO};
117              
118              
119 3         13 foreach my $k (keys %$oparams){
120 23         33 push @$query_keys, $k;
121 23         37 $query_vals->{$k} = $oparams->{$k};
122             }
123              
124 3         22 my @sort = sort {$a cmp $b} @$query_keys;
  47         65  
125 3         6 my $params = [];
126              
127 3         7 foreach my $k (@sort){
128 24         37 my $v = $query_vals->{$k};
129 24 100       50 push @$params, $k . '=' . $v if defined $v;
130             }
131 3         10 my $normalized = join('&', @$params);
132 3         8 my $str = uc($req->method) . '&' . uri_escape($uri) . '&' . uri_escape($normalized);
133 3         192 return $str;
134             }
135              
136             sub _build_auth_string {
137 3     3   7 my ( $self, $oauth_params, $oauth_sig ) = @_;
138              
139 3         6 my $auth = 'OAuth';
140              
141 3 50       79 if ( $self->realm ) {
142 0         0 $auth = $auth . ' realm="' . $self->realm . '",';
143             }
144              
145             $auth =
146             $auth
147             . ' oauth_consumer_key="'
148             . $oauth_params->{oauth_consumer_key} . '"'
149             . ', oauth_signature_method="'
150 3         12 . $oauth_params->{oauth_signature_method} . '"'
151             . ', oauth_signature="'
152             . $oauth_sig . '"';
153              
154 3 50       9 if ( $oauth_params->{oauth_signature_method} ne 'PLAINTEXT' ) {
155             $auth =
156             $auth
157             . ', oauth_timestamp="'
158             . $oauth_params->{oauth_timestamp} . '"'
159             . ', oauth_nonce="'
160 3         11 . $oauth_params->{oauth_nonce} . '"';
161             }
162              
163 3 100       9 if ( !$oauth_params->{oauth_token} ) {
164             $auth =
165 2         6 $auth . ', oauth_callback="' . $oauth_params->{oauth_callback} . '"';
166             }
167             else {
168 1 50       5 if ( $oauth_params->{oauth_verifier} ) {
169             $auth =
170             $auth
171             . ', oauth_token="'
172             . $oauth_params->{oauth_token} . '"'
173             . ', oauth_verifier="'
174 1         6 . $oauth_params->{oauth_verifier} . '"';
175             }
176             else {
177             $auth =
178 0         0 $auth . ', oauth_token="' . $oauth_params->{oauth_token} . '"';
179             }
180             }
181              
182 3         9 $auth = $auth . ', oauth_version="' . $oauth_params->{oauth_version} . '"';
183 3         15 return $auth;
184             }
185              
186             sub _oauth_sig {
187 3     3   8 my ( $self, $req, $oauth_params ) = @_;
188              
189             die $oauth_params->{oauth_signature_method} . " is not supported"
190             unless ( $oauth_params->{oauth_signature_method} eq 'PLAINTEXT'
191 3 50 33     19 || $oauth_params->{oauth_signature_method} eq 'HMAC-SHA1' );
192              
193 3 50       8 if ( $oauth_params->{oauth_signature_method} eq 'PLAINTEXT' ) {
194 0         0 return uri_escape( $self->_signature_key );
195             }
196              
197 3         9 $oauth_params->{oauth_timestamp} = time;
198 3         13 $oauth_params->{oauth_nonce} = $self->_oauth_nonce;
199              
200 3         9 my $oauth_signature_base_string = $self->_base_string( $req, $oauth_params );
201              
202 3         11 return uri_escape(
203             MIME::Base64::encode_base64(
204             Digest::SHA::hmac_sha1(
205             $oauth_signature_base_string, $self->_signature_key
206             )
207             )
208             );
209             }
210              
211             sub _oauth_nonce {
212 3     3   79 Digest::SHA::sha1_hex( rand() . 'random' . time() . 'keyyy' );
213             }
214              
215             sub _signature_key {
216 3     3   5 my $self = shift;
217 3   100     90 my $signature_key =
218             uri_escape( $self->oauth_consumer_secret ) . '&'
219             . uri_escape( $self->oauth_token_secret || '' );
220 3         55 return $signature_key;
221             }
222              
223             1;
224              
225             __END__
226              
227             =pod
228              
229             =encoding UTF-8
230              
231             =head1 NAME
232              
233             Net::HTTP::Spore::Middleware::Auth::OAuth - middleware for OAuth authentication
234              
235             =head1 VERSION
236              
237             version 0.09
238              
239             =head1 SYNOPSIS
240              
241             my $client = Net::HTTP::Spore->new_from_spec( 'google-url-shortener.json' );
242             $client->enable('Format::JSON');
243             $client->enable('Auth::OAuth',
244             oauth_consumer_key => '00000000.apps.googleusercontent.com',
245             oauth_consumer_secret => 'xxxxxxxxx',
246             oauth_token => 'yyyyyyyyy',
247             oauth_token_secret => 'zzzzzzzzz',
248             );
249              
250             my $r = $client->insert( payload => { longUrl => 'http://f.lumberjaph.net/' } );
251             say( $r->body->{id} . ' is ' . $r->body->{longUrl} );
252             say "list >";
253             $r = $client->list();
254             foreach my $short (@{$r->body->{items}}){
255             say $short->{id} . ' ' . $short->{longUrl};
256             }
257              
258             =head1 DESCRIPTION
259              
260             Net::HTTP::Spore::Middleware::Auth::OAuth is a middleware to handle OAuth mechanism. This middleware should be loaded as the last middleware, because it requires all parameters to be setted to calculate the signature.
261              
262             =head1 AUTHORS
263              
264             =over 4
265              
266             =item *
267              
268             Franck Cuny <franck.cuny@gmail.com>
269              
270             =item *
271              
272             Ash Berlin <ash@cpan.org>
273              
274             =item *
275              
276             Ahmad Fatoum <athreef@cpan.org>
277              
278             =back
279              
280             =head1 COPYRIGHT AND LICENSE
281              
282             This software is copyright (c) 2012 by Linkfluence.
283              
284             This is free software; you can redistribute it and/or modify it under
285             the same terms as the Perl 5 programming language system itself.
286              
287             =cut