diff options
author | franck cuny <franck@lumberjaph.net> | 2011-07-22 13:51:15 +0200 |
---|---|---|
committer | franck cuny <franck@lumberjaph.net> | 2011-07-26 13:21:02 +0200 |
commit | 597df56b91a2f68a7304f8662af354f95f9eda33 (patch) | |
tree | dc6303830ab6def532c421bf3f44fc5c1408884f | |
parent | fix bug with trace set to 0 (diff) | |
download | net-http-spore-597df56b91a2f68a7304f8662af354f95f9eda33.tar.gz |
working OAuth middleware
Signed-off-by: franck cuny <franck@lumberjaph.net>
-rw-r--r-- | lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm | 235 |
1 files changed, 203 insertions, 32 deletions
diff --git a/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm b/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm index 524205d..ffff80b 100644 --- a/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm +++ b/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm @@ -3,61 +3,232 @@ package Net::HTTP::Spore::Middleware::Auth::OAuth; # ABSTRACT: middleware for OAuth authentication use Moose; -extends 'Net::HTTP::Spore::Middleware::Auth'; - -use Net::OAuth; +use URI::Escape; +use Digest::SHA; use MIME::Base64; -has [qw/consumer_key consumer_secret token token_secret/] => ( +extends 'Net::HTTP::Spore::Middleware::Auth'; + +has [qw/oauth_consumer_key oauth_consumer_secret/] => ( is => 'ro', isa => 'Str', required => 1, ); +has oauth_callback => ( + is => 'ro', + isa => 'Str', + lazy => 1, + default => 'oob', +); + +has oauth_signature_method => ( + is => 'ro', + isa => 'Str', + lazy => 1, + default => 'HMAC-SHA1', +); + +has [qw/oauth_token oauth_token_secret oauth_verifier realm/] => ( + is => 'ro', + isa => 'Str', +); + sub call { my ( $self, $req ) = @_; return unless $self->should_authenticate($req); - my $uri = $req->uri; - my $request = Net::OAuth->request('protected resource')->new( - version => '1.0', - consumer_key => $self->consumer_key, - consumer_secret => $self->consumer_secret, - token => $self->token, - token_secret => $self->token_secret, - request_method => $req->method, - signature_method => 'HMAC-SHA1', - timestamp => time, - nonce => MIME::Base64::encode( time . $$ . rand, '' ), - request_url => $req->uri, - # extra_params => \%post_args, + my $oauth_params = { + oauth_signature_method => $self->oauth_signature_method, + oauth_consumer_key => $self->oauth_consumer_key, + oauth_token => $self->oauth_token, + oauth_verifier => $self->oauth_verifier, + oauth_version => '1.0', + }; + + if ( !defined $oauth_params->{oauth_token} ) { + $oauth_params->{oauth_callback} = $self->oauth_callback; + } + + foreach my $k ( keys %$oauth_params ) { + $oauth_params->{$k} = uri_escape( $oauth_params->{$k} ); + } + + $req->finalize; + + my $oauth_sig = $self->_oauth_sig( $req, $oauth_params ); + $req->header( 'Authorization' => + $self->_build_auth_string( $oauth_params, $oauth_sig ) ); +} + +sub _base_string { + my ($self, $req, $oparams) = @_; + + my $query_keys = []; + my $query_vals = {}; + + if ( defined $req->env->{QUERY_STRING} ) { + while ($req->env->{QUERY_STRING} =~ /([^=]+)=([^&]*)&?/g){ + my ($k,$v) = ($1,$2); + push @$query_keys, $k; + $query_vals->{$k} = $v; + } + } + + my $payload = $req->body; + if ( defined $payload ) { + my $ct = $req->header('content-type'); + if ( !defined $ct or $ct eq 'application/x-www-form-urlencoded' ) { + while ($payload =~ /([^=]+)=([^&]*)&?/g){ + my ($k,$v) = ($1,$2); + $v =~ s/\+/\%\%20/; + push @$query_keys, $k; + $query_vals->{$k} = $v; + } + } + } + + my $scheme = $req->scheme; + my $port = $req->port; + + if ($port == 80 and $scheme eq 'http'){ + $port = undef; + } + if (defined $scheme && $port == 443 and $scheme eq 'https'){ + $port = undef; + } + + my $uri = + ( $scheme || 'https' ) . "://" + . $req->env->{SERVER_NAME} + . $req->env->{SCRIPT_NAME} + . $req->env->{PATH_INFO}; + + foreach my $k (keys %$oparams){ + push @$query_keys, $k; + $query_vals->{$k} = $oparams->{$k}; + } + + my @sort = sort {$a cmp $b} @$query_keys; + my $params = []; + + foreach my $k (@sort){ + my $v = $query_vals->{$k}; + push @$params, $k . '=' . $v if defined $v; + } + my $normalized = join('&', @$params); + my $str = uc($req->method) . '&' . uri_escape($uri) . '&' . uri_escape($normalized); + return $str; +} + +sub _build_auth_string { + my ( $self, $oauth_params, $oauth_sig ) = @_; + + my $auth = 'OAuth'; + + if ( $self->realm ) { + $auth = $auth . ' realm="' . $self->realm . '",'; + } + + $auth = + $auth + . ' oauth_consumer_key="' + . $oauth_params->{oauth_consumer_key} . '"' + . ', oauth_signature_method="' + . $oauth_params->{oauth_signature_method} . '"' + . ', oauth_signature="' + . $oauth_sig . '"'; + + if ( $oauth_params->{oauth_signature_method} ne 'PLAINTEXT' ) { + $auth = + $auth + . ', oauth_timestamp="' + . $oauth_params->{oauth_timestamp} . '"' + . ', oauth_nonce="' + . $oauth_params->{oauth_nonce} . '"'; + } + + if ( !$oauth_params->{oauth_token} ) { + $auth = + $auth . ', oauth_callback="' . $oauth_params->{oauth_callback} . '"'; + } + else { + if ( $oauth_params->{oauth_verifier} ) { + $auth = + $auth + . ', oauth_token="' + . $oauth_params->{oauth_token} . '"' + . ', oauth_verifier="' + . $oauth_params->{oauth_verifier} . '"'; + } + else { + $auth = + $auth . ', oauth_token="' . $oauth_params->{oauth_token} . '"'; + } + } + + $auth = $auth . ', oauth_version="' . $oauth_params->{oauth_version} . '"'; + return $auth; +} + +sub _oauth_sig { + my ( $self, $req, $oauth_params ) = @_; + + die $oauth_params->{oauth_signature_method} . " is not supported" + unless ( $oauth_params->{oauth_signature_method} eq 'PLAINTEXT' + || $oauth_params->{oauth_signature_method} eq 'HMAC-SHA1' ); + + if ( $oauth_params->{oauth_signature_method} eq 'PLAINTEXT' ) { + return uri_escape( $self->_signature_key ); + } + + $oauth_params->{oauth_timestamp} = time; + $oauth_params->{oauth_nonce} = $self->_oauth_nonce; + + my $oauth_signature_base_string = $self->_base_string( $req, $oauth_params ); + + return uri_escape( + MIME::Base64::encode_base64( + Digest::SHA::hmac_sha1( + $oauth_signature_base_string, $self->_signature_key + ) + ) ); +} - $request->sign; - my $auth = $request->to_authorization_header; - $req->header( 'Authorization' => $auth ); +sub _oauth_nonce { + Digest::SHA::sha1_hex( rand() . 'random' . time() . 'keyyy' ); +} + +sub _signature_key { + my $self = shift; + my $signature_key = + uri_escape( $self->oauth_consumer_secret ) . '&' + . uri_escape( $self->oauth_token_secret || '' ); + return $signature_key; } 1; =head1 SYNOPSIS - my $client = Net::HTTP::Spore->new_from_spec('twitter.json'); - + my $client = Net::HTTP::Spore->new_from_spec( 'google-url-shortener.json' ); $client->enable('Format::JSON'); - - $client->enable( - 'Auth::OAuth', - consumer_key => 'xxx', - consumer_secret => 'yyy', - token => '123', - token_secret => '456' + $client->enable('Auth::OAuth', + oauth_consumer_key => '00000000.apps.googleusercontent.com', + oauth_consumer_secret => 'xxxxxxxxx', + oauth_token => 'yyyyyyyyy', + oauth_token_secret => 'zzzzzzzzz', ); - print $client->friends_timeline( - format => 'json' - )->body->[0]->{text}; + my $r = $client->insert( payload => { longUrl => 'http://f.lumberjaph.net/' } ); + say( $r->body->{id} . ' is ' . $r->body->{longUrl} ); + say "list >"; + $r = $client->list(); + foreach my $short (@{$r->body->{items}}){ + say $short->{id} . ' ' . $short->{longUrl}; + } =head1 DESCRIPTION |