[Jifty-commit] r4258 - in jifty/trunk: . lib/Jifty/Plugin/OAuth lib/Jifty/Plugin/OAuth/Model

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Thu Oct 18 21:52:05 EDT 2007


Author: sartak
Date: Thu Oct 18 21:52:03 2007
New Revision: 4258

Added:
   jifty/trunk/t/TestApp-Plugin-OAuth/t/id_rsa
   jifty/trunk/t/TestApp-Plugin-OAuth/t/id_rsa.pub
Removed:
   jifty/trunk/t/TestApp-Plugin-OAuth/t/02-request-token.t-config.yml
Modified:
   jifty/trunk/   (props changed)
   jifty/trunk/Makefile.PL
   jifty/trunk/lib/Jifty/Plugin/OAuth/Dispatcher.pm
   jifty/trunk/lib/Jifty/Plugin/OAuth/Model/Consumer.pm
   jifty/trunk/lib/Jifty/Plugin/OAuth/Token.pm
   jifty/trunk/lib/Jifty/Plugin/OAuth/View.pm
   jifty/trunk/t/TestApp-Plugin-OAuth/t/02-request-token.t

Log:
 r43849 at onn:  sartak | 2007-10-18 21:51:56 -0400
 Expand the tests, now they actually test something real!
 So right now the OAuth plugin is rejecting a few kinds of bad requests (e.g. unknown consumer) while accepting a good request. Now to make more requests of each type :)


Modified: jifty/trunk/Makefile.PL
==============================================================================
--- jifty/trunk/Makefile.PL	(original)
+++ jifty/trunk/Makefile.PL	Thu Oct 18 21:52:03 2007
@@ -170,6 +170,7 @@
         recommends('Net::OAuth::Request'),
         recommends('Crypt::OpenSSL::RSA'),
         recommends('Digest::HMAC_SHA1'),
+        recommends('MIME::Base64'),
     ],
 );
 

Modified: jifty/trunk/lib/Jifty/Plugin/OAuth/Dispatcher.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/OAuth/Dispatcher.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/Dispatcher.pm	Thu Oct 18 21:52:03 2007
@@ -15,7 +15,7 @@
 # helper function to abort with a debug message
 sub abortmsg {
     my ($code, $msg) = @_;
-    Jifty->log->error($msg) if defined($msg);
+    Jifty->log->debug($msg) if defined($msg);
     abort($code || 400);
 }
 
@@ -24,15 +24,16 @@
     my @params = qw/consumer_key signature_method signature
                     timestamp nonce version/;
 
-    my %oauth_params = get_parameters(@params);
-    validate_signature_method($oauth_params{signature_method});
-    my $consumer = get_consumer($oauth_params{consumer_key});
+    my %oauth_params  = get_parameters(@params);
+    my $consumer      = get_consumer($oauth_params{consumer_key});
+    my $signature_key = get_signature_key($oauth_params{signature_method}, $consumer);
 
     # Net::OAuth::Request will die hard if it doesn't get everything it wants
     my $request = eval { Net::OAuth::RequestTokenRequest->new(
         request_url     => Jifty->web->url(path => '/oauth/request_token'),
         request_method  => Jifty->handler->apache->method(),
         consumer_secret => $consumer->secret,
+        signature_key   => $signature_key,
 
         map { $_ => $oauth_params{$_} } @params
     ) };
@@ -56,9 +57,10 @@
     };
 
     abortmsg(401, "Unable to create a Request Token: " . $@ || $msg)
-        if $@ || !defined($token) || !$ok;
+        if $@ || !$ok;
 
     # XXX: actually send the token
+    abort(200);
 }
 
 # the user is authorizing (or denying) a consumer's request token
@@ -87,9 +89,9 @@
     my @params = qw/consumer_key signature_method signature
                     timestamp nonce token version/;
 
-    my %oauth_params = get_parameters(@params);
-    validate_signature_method($oauth_params{signature_method});
-    my $consumer = get_consumer($oauth_params{consumer_key});
+    my %oauth_params  = get_parameters(@params);
+    my $consumer      = get_consumer($oauth_params{consumer_key});
+    my $signature_key = get_signature_key($oauth_params{signature_method}, $consumer);
 
     # is the request token they're using still valid?
     my $request_token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
@@ -106,6 +108,7 @@
         request_method  => Jifty->handler->apache->method(),
         consumer_secret => $consumer->secret,
         token_secret    => $request_token->secret,
+        signature_key   => $signature_key,
 
         map { $_ => $oauth_params{$_} } @params
     ) };
@@ -138,11 +141,29 @@
     return $consumer;
 }
 
-my %valid_signature_methods = map { $_ => 1 } qw/PLAINTEXT HMAC-SHA1 RSA-SHA1/;
-sub validate_signature_method {
-    my $method = shift;
-    return if $valid_signature_methods{$method};
-    abortmsg(400, "Unsupported signature method requested: $method");
+{
+    my %valid_signature_methods = map { $_ => 1 }
+                                  qw/PLAINTEXT HMAC-SHA1 RSA-SHA1/;
+    my %key_field = ('RSA-SHA1' => 'rsa_key');
+
+    sub get_signature_key {
+        my ($method, $consumer) = @_;
+        if (!$valid_signature_methods{$method}) {
+            abortmsg(400, "Unsupported signature method requested: $method");
+        }
+
+        my $field = $key_field{$method};
+
+        # this MUST return undef if the signature method requires no prior key
+        return undef if !defined($field);
+
+        my $key = $consumer->$field;
+
+        abortmsg(400, "Consumer does not have necessary field $field required for signature method $method")
+            unless defined $key;
+
+        return $key;
+    }
 }
 
 sub get_parameters {
@@ -152,8 +173,10 @@
     # XXX: Check WWW-Authenticate header
 
     my %params = Jifty->handler->apache->params();
-    use Data::Dumper; warn Dumper \%params;
-    @p{@_} = @params{map {"oauth_$_"} @_};
+    for (@_) {
+        $p{$_} = $params{"oauth_$_"}
+            if !defined $p{$_};
+    }
 
     $p{version} ||= '1.0';
 

Modified: jifty/trunk/lib/Jifty/Plugin/OAuth/Model/Consumer.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/OAuth/Model/Consumer.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/Model/Consumer.pm	Thu Oct 18 21:52:03 2007
@@ -29,6 +29,10 @@
     column url =>
         type is 'varchar';
 
+    column rsa_key =>
+        type is 'varchar',
+        hints are 'This is only necessary if you want to support RSA-SHA1 signatures';
+
 };
 
 1;

Modified: jifty/trunk/lib/Jifty/Plugin/OAuth/Token.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/OAuth/Token.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/Token.pm	Thu Oct 18 21:52:03 2007
@@ -2,6 +2,7 @@
 package Jifty::Plugin::OAuth::Token;
 use strict;
 use warnings;
+use Scalar::Util 'blessed';
 
 # this just provides some helper methods for both token classes to use
 
@@ -15,7 +16,13 @@
     # check if we're seeing a replay attack
     my $token = $self->new(current_user => Jifty::CurrentUser->superuser);
     $token->load_by_cols(nonce => $attr->{nonce}, time_stamp => $attr->{time_stamp});
-    return if $token->id;
+    if ($token->id) {
+        use Data::Dumper;
+        Jifty->log->warn(Dumper $token);
+        Jifty->log->warn(Dumper $self);
+        Jifty->log->warn("Duplicate nonce ($attr->{nonce}) and timestamp ($attr->{time_stamp}) from " . $token->id . " (self is " . $self->id . "). Possibly a replay attack.");
+        return;
+    }
 
     # attempt 20 times to create a unique token string
     for (1..20) {
@@ -25,7 +32,10 @@
         last if !$token->id;
         delete $attr->{token};
     }
-    return if !defined($attr->{token});
+    if (!defined $attr->{token}) {
+        Jifty->log->warn("Failed 20 times to create a unique token. Giving up.");
+        return;
+    }
 
     # generate a secret. need not be unique, just hard to guess
     $attr->{secret} = generate_token();

Modified: jifty/trunk/lib/Jifty/Plugin/OAuth/View.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/OAuth/View.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/View.pm	Thu Oct 18 21:52:03 2007
@@ -14,7 +14,7 @@
     }
 
     p {
-        "This application supports OAuth. If you'd like to access the private resources of users of this site, you must first establish a Consumer Key and Consumer Secret with us. You can do so by contacting " . (Jifty->config->framework('AdminEmail')||'us') . ".";
+        "This application supports OAuth. If you'd like to access the private resources of users of this site, you must first establish a Consumer Key, Consumer Secret, and, if applicable, RSA public key with us. You can do so by contacting " . (Jifty->config->framework('AdminEmail')||'us') . ".";
     }
 
     p {

Modified: jifty/trunk/t/TestApp-Plugin-OAuth/t/02-request-token.t
==============================================================================
--- jifty/trunk/t/TestApp-Plugin-OAuth/t/02-request-token.t	(original)
+++ jifty/trunk/t/TestApp-Plugin-OAuth/t/02-request-token.t	Thu Oct 18 21:52:03 2007
@@ -5,86 +5,216 @@
 use lib 't/lib';
 use Jifty::SubTest;
 
-use Jifty::Test tests => 9;
+use Jifty::Test tests => 16;
 use Jifty::Test::WWW::Mechanize;
 
+use MIME::Base64;
+use Crypt::OpenSSL::RSA;
+use Digest::HMAC_SHA1 'hmac_sha1';
+
 my $server  = Jifty::Test->make_server;
 isa_ok($server, 'Jifty::Server');
 my $URL     = $server->started_ok;
 my $mech    = Jifty::Test::WWW::Mechanize->new();
-my $url     = $URL . '/oauth/request_token';
+our $url     = $URL . '/oauth/request_token';
 
 # helper functions {{{
-sub response_is ($%;$) {
-    my ($code, $params, $testname) = @_;
-
-    my $method          = (delete $params->{method})          || 'POST';
-    my $token_secret    = (delete $params->{token_secret})    || '';
-    my $consumer_secret = delete $params->{consumer_secret}
+my $timestamp = 0;
+sub response_is {
+    ++$timestamp;
+
+    my %params = (
+        oauth_timestamp        => $timestamp,
+        oauth_nonce            => scalar(reverse $timestamp),
+        oauth_signature_method => 'HMAC-SHA1',
+        oauth_version          => '1.0',
+
+        code                   => 400,
+        testname               => "",
+        method                 => 'POST',
+        token_secret           => '',
+        @_,
+    );
+
+    my $code            = delete $params{code};
+    my $testname        = delete $params{testname} || "Response was $code";
+    my $method          = delete $params{method};
+    my $token_secret    = delete $params{token_secret};
+    my $consumer_secret = delete $params{consumer_secret}
         or die "consumer_secret not passed to response_is!";
 
-    $params->{oauth_signature} ||= sign($params, $method, $token_secret, $consumer_secret);
+    $params{oauth_signature} ||= sign($method, $token_secret, $consumer_secret, %params);
 
     my $r;
 
     if ($method eq 'POST') {
-        $r = $mech->post($url);
+        $r = $mech->post($url, [%params]);
     }
     else {
-        $r = $mech->get($url);
+        my $query = join '&',
+                    map { "$_=" . Jifty->web->escape_uri($params{$_}||'') }
+                    keys %params;
+        $r = $mech->get("$url?$query");
     }
 
-    is($r->code, $code, $testname || "Request got $code");
+    is($r->code, $code, $testname);
 }
 
 sub sign {
-    my ($params, $method, $token_secret, $consumer_secret) = @_;
+    my ($method, $token_secret, $consumer_secret, %params) = @_;
+
+    local $url = delete $params{url} || $url;
+
+    my $key = delete $params{signature_key};
+
+    if ($params{oauth_signature_method} eq 'PLAINTEXT') {
+        my $signature = join '&',
+                        map { Jifty->web->escape_uri($_||'') }
+                            $consumer_secret,
+                            $token_secret;
+        return $signature;
+    }
 
     my $normalized_request_parameters
         = join '&',
-          map { "$_=" . Jifty->web->escape_uri($params->{$_}||'') }
-          sort keys %$params;
+          map { "$_=" . Jifty->web->escape_uri($params{$_}||'') }
+          sort keys %params;
 
     my $signature_base_string
         = join '&',
-          map { Jifty->web->escape_uri($params->{$_}||'') }
+          map { Jifty->web->escape_uri($_||'') }
               uc($method),
               $url,
               $normalized_request_parameters,
               $consumer_secret,
               $token_secret;
 
-    # XXX: do some signing based on $params->{signature_method}!
-    return '!!';
+    my $signature;
+
+    if ($params{oauth_signature_method} eq 'RSA-SHA1') {
+        my $pubkey = Crypt::OpenSSL::RSA->new_private_key($key);
+        $signature = encode_base64($pubkey->sign($signature_base_string));
+    }
+    elsif ($params{oauth_signature_method} eq 'HMAC-SHA1') {
+        my $key = join '&',
+          map { Jifty->web->escape_uri($_||'') }
+              $consumer_secret,
+              $token_secret;
+        my $hmac = Digest::HMAC_SHA1->new($key);
+        $hmac->add($signature_base_string);
+        $signature = $hmac->b64digest;
+    }
+
+    return ($signature, $signature_base_string, $normalized_request_parameters)
+        if wantarray;
+    return $signature;
+
 }
 # }}}
+# load the RSA keys {{{
+sub slurp {
+    my $file = shift;
+    local $/;
+    local @ARGV = $file;
+    my $contents = scalar <>
+        or die "Unable to slurp $file";
+    return $contents;
+}
 
+my $pubkey = slurp 't/id_rsa.pub';
+my $seckey = slurp 't/id_rsa';
+# }}}
+# testing the local sign function {{{
+# PLAINTEXT {{{
+is(sign('POST', 'jjd999tj88uiths3', 'djr9rjt0jd78jf88',
+        oauth_signature_method => 'PLAINTEXT'),
+    'djr9rjt0jd78jf88&jjd999tj88uiths3', 'PLAINTEXT example 1 works');
+is(sign('POST', 'jjd99$tj88uiths3', 'djr9rjt0jd78jf88',
+        oauth_signature_method => 'PLAINTEXT'),
+    'djr9rjt0jd78jf88&jjd99%24tj88uiths3', 'PLAINTEXT example 2 works');
+is(sign('POST', undef, 'djr9rjt0jd78jf88',
+        oauth_signature_method => 'PLAINTEXT'),
+    'djr9rjt0jd78jf88&', 'PLAINTEXT example 2 works');
+# }}}
+# HMAC-SHA1 {{{
+my ($sig, $sbs, $nrp) = sign(
+    'GET',
+    'pfkkdhi9sl3r4s00',
+    'kd94hf93k423kf44',
+    url => 'http://photos.example.net/photos',
+    oauth_consumer_key => 'dpf43f3p2l4k3l03',
+    oauth_signature_method => 'HMAC-SHA1',
+    oauth_timestamp => '1191242096',
+    oauth_nonce => 'kllo9940pd9333jh',
+    oauth_token => 'nnch734d00sl2jdk',
+    file => 'vacation.jpg',
+    size => 'original',
+    oauth_version => '1.0');
+
+is($nrp, 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original', 'HMAC-SHA1 normalized request paramaters correct');
+is($sbs, 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal&kd94hf93k423kf44&pfkkdhi9sl3r4s00', 'HMAC-SHA1 signature-base-string correct');
+is($sig, 'Gcg/323lvAsQ707p+y41y14qWfY', 'HMAC-SHA1 signature correct');
+# }}}
+# RSA-SHA1 {{{
+($sig, $sbs, $nrp) = sign(
+    'GET',
+    'pfkkdhi9sl3r4s00',
+    'kd94hf93k423kf44',
+    url => 'http://photos.example.net/photos',
+    signature_key => $seckey,
+    oauth_consumer_key => 'dpf43f3p2l4k3l03',
+    oauth_signature_method => 'RSA-SHA1',
+    oauth_timestamp => '1191242096',
+    oauth_nonce => 'kllo9940pd9333jh',
+    oauth_token => 'nnch734d00sl2jdk',
+    file => 'vacation.jpg',
+    size => 'original',
+    oauth_version => '1.0');
+
+is($nrp, 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=RSA-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original', 'RSA-SHA1 normalized request paramaters correct');
+is($sbs, 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal&kd94hf93k423kf44&pfkkdhi9sl3r4s00', 'RSA-SHA1 signature-base-string correct');
+$sig =~ s/\s+//g;
+is($sig, 'oSjbUzMjD4E+LeHMaYzYx1KyULDwuR6V9oeNgTLoO9m90iJh4d01J/8SzvHKT8N0y2vs1o8s72z19Eicj6l+mEmH5Rp0cwWOE9UdvC+JdFSIA1bmlwVPCFL7jDQqRSBJsXEiT44T5j9P+Dh5Z5WUjEgCExQyNP38Z3nMnYYOCRM=', 'RSA-SHA1 signature correct');
+# }}}
+# }}}
 # get a request token as a known consumer {{{
 my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
 my ($ok, $msg) = $consumer->create(
     consumer_key => 'foo',
-    secret => 'bar',
-    name => 'FooBar industries',
-    url => 'http://foo.bar.example.com',
+    secret       => 'bar',
+    name         => 'FooBar industries',
+    url          => 'http://foo.bar.example.com',
+    rsa_key      => $pubkey,
 );
 ok($ok, $msg);
 
-
+response_is(
+    code                   => 200,
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
 # }}}
-
 # unknown consumer {{{
-response_is 401, {
+response_is(
+    code                   => 401,
     consumer_secret        => 'zzz',
     oauth_consumer_key     => 'whoami',
-    oauth_signature_method => 'RSA-SHA1',
-    oauth_timestamp        => 100,
-    oauth_nonce            => 'haa haa',
-    oauth_version          => '1.0',
-};
+);
 # }}}
 # wrong secret {{{
+response_is (
+    code                   => 401,
+    consumer_secret        => 'not bar!',
+    oauth_consumer_key     => 'foo',
+);
 # }}}
 # wrong signature {{{
+response_is(
+    code                   => 401,
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+);
 # }}}
 # duplicate timestamp and nonce {{{
 # }}}

Added: jifty/trunk/t/TestApp-Plugin-OAuth/t/id_rsa
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-OAuth/t/id_rsa	Thu Oct 18 21:52:03 2007
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC1ekM402pEiZ6MyaG0RzDNrw0digCV0e45mCgaQs2F0q4v2O8C
+xjl9pbsuf2qz1jHKGdJIXuhaW1XRqCOE2ZHc/n/+s2s8TUIcBve3B2glKxJhgyV8
+nDpZkjOEctef8uFPU3Alfm382kj0THcXdgsQ+jreLJ1VCS5xNcU6VpXa4QIDAQAB
+AoGAOHsl4tDB2TTvuKekgURK5ykdLt1dk0N0Hk7B5HJ4HrdUaSXeNYHWMMnc+PrF
+DdWTR3BD5yxKqpyUmBz5eQZyA8vVKzEVmYCkA+EO6TQeo6xveH/9xaFbTtXpwtvS
+N9m3kwEfmfudJvQRFb3q79I+17/g8rWbZlDYK7CKyfVs17UCQQDxdMOz/Q7xpP+f
+sXTHxvhtw4FFvAZEOEQA1a+uHGSmz+Vq0SIOpwZwri4aFG1YVUS2FUGHuhSpsuUJ
+Pg3kY1N3AkEAwGiiObgemFQLvCVigP8YcZyt98a+vE2Joq3iJyd/4DnEqvN98WNm
+5zaSDEXAJzC1ZuqnMUFVbiYBt2W4InBqZwJAEpgyZg8L8pIJWYv5+VSaVyGiN/OV
+6/UFT6clI1xuZ+ZEvagjXkuAlHbld/6wuQfABeG3LTOoWbU8LC0KNtdrWwJAF0gR
+6R4IRbJVwSxc4PL9CDJHMqYPykUvlEmqBcbXyE/1JiJUaPL4Lp4Byg5ek99m888M
+7/7R0YQzzPc38qLbnQJBAMbs/L0td6AponlpHCLmhHd7dka6GNIdyaALLNSVefD+
++MLQ7dATQne1y5n08vswMX9QnNTxFnlK59gWk/0gow4=
+-----END RSA PRIVATE KEY-----

Added: jifty/trunk/t/TestApp-Plugin-OAuth/t/id_rsa.pub
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-OAuth/t/id_rsa.pub	Thu Oct 18 21:52:03 2007
@@ -0,0 +1,5 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBALV6QzjTakSJnozJobRHMM2vDR2KAJXR7jmYKBpCzYXSri/Y7wLGOX2l
+uy5/arPWMcoZ0khe6FpbVdGoI4TZkdz+f/6zazxNQhwG97cHaCUrEmGDJXycOlmS
+M4Ry15/y4U9TcCV+bfzaSPRMdxd2CxD6Ot4snVUJLnE1xTpWldrhAgMBAAE=
+-----END RSA PUBLIC KEY-----


More information about the Jifty-commit mailing list