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

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Wed Oct 17 18:08:31 EDT 2007


Author: sartak
Date: Wed Oct 17 18:08:30 2007
New Revision: 4253

Added:
   jifty/trunk/lib/Jifty/Plugin/OAuth/Model/Consumer.pm
   jifty/trunk/lib/Jifty/Plugin/OAuth/Token.pm
Modified:
   jifty/trunk/   (props changed)
   jifty/trunk/Makefile.PL
   jifty/trunk/lib/Jifty/Plugin/OAuth/Dispatcher.pm
   jifty/trunk/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm
   jifty/trunk/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm

Log:
 r43829 at onn:  sartak | 2007-10-17 18:08:12 -0400
 Some more fleshing out of the OAuth plugin


Modified: jifty/trunk/Makefile.PL
==============================================================================
--- jifty/trunk/Makefile.PL	(original)
+++ jifty/trunk/Makefile.PL	Wed Oct 17 18:08:30 2007
@@ -165,6 +165,12 @@
         recommends('Devel::Events::Generator::Objects'),
         recommends('Devel::Size'),
     ],
+    'OAuth Plugin' => [
+        -default => 0,
+        recommends('Net::OAuth::Request'),
+        recommends('Crypt::OpenSSL::RSA'),
+        recommends('Digest::HMAC_SHA1'),
+    ],
 );
 
 

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	Wed Oct 17 18:08:30 2007
@@ -4,6 +4,10 @@
 
 use Jifty::Dispatcher -base;
 
+use Net::OAuth::RequestTokenRequest;
+use Net::OAuth::AccessTokenRequest;
+use Net::OAuth::ProtectedResourceRequest;
+
 my $request_token_url = '/oauth/request_token';
 my $authorize_url     = '/oauth/authorize';
 my $access_token_url  = '/oauth/access_token';
@@ -14,60 +18,121 @@
 
 # a consumer wants a request token
 sub request_token {
-    set oauth_url => $request_token_url;
-    my $headers = Jifty->web->handler->apache->headers_in();
+    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});
+
+    # Net::OAuth::Request will die hard if it doesn't get everything it wants
+    my $request = eval { Net::OAuth::RequestTokenRequest->new(
+        request_url     => $request_token_url,
+        request_method  => Jifty->handler->apache->method(),
+        consumer_secret => $consumer->secret,
+
+        map { $_ => $oauth_params{$_} } @params
+    ) };
+
+    abort(400) if $@ || !defined($request);
+
+    # make sure the signature matches the rest of what the consumer gave us
+    abort(401) unless $request->verify;
 
-    for my $necessary_header (map {"oauth_$_"}
-                                  qw/consumer_key signature_method signature
-                                     timestamp nonce/) {
-        abort(400) if !defined $headers->{$necessary_header};
-    }
+    # ok, everything checks out. send them back a request token
+    # at this point, the only things that could go wrong are:
+    # 1) we've already seen this nonce and timestamp. possibly a replay attack,
+    #    so we abort
+    # 2) we tried a bunch of times to create a unique token but failed. abort
+    #    because we don't have any other option
 
+    my $token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+
+    my ($ok) = eval {
+        $token->create(map { $_ => $oauth_params{$_} } qw/timestamp nonce/);
+    };
+
+    abort(401) if $@ || !defined($token) || !$ok;
+
+    # XXX: actually send the token
 }
 
 # the user is authorizing (or denying) a consumer's request token
 sub authorize {
-    set oauth_url => $authorize_url;
 
 }
 
 # the consumer is trying to trade a request token for an access token
 sub access_token {
-    set oauth_url => $access_token_url;
+    my @params = qw/consumer_key signature_method signature
+                    timestamp nonce token token_secret version/;
 
-}
+    my %oauth_params = get_parameters(@params);
+    validate_signature_method($oauth_params{signature_method});
+    my $consumer = get_consumer($oauth_params{consumer_key});
+
+    # Net::OAuth::Request will die hard if it doesn't get everything it wants
+    my $request = eval { Net::OAuth::AccessTokenRequest->new(
+        request_url     => $request_token_url,
+        request_method  => Jifty->handler->apache->method(),
+        consumer_secret => $consumer->secret,
 
-# 9.1.1
-sub get_normalized_parameters {
-    my $parameters = Jifty->handler->apache->headers_in();
-    my @parameters;
+        map { $_ => $oauth_params{$_} } @params
+    ) };
 
-    # we can't just use a hash because parameters may be repeated
-    $parameters->do(sub {
-        my ($key, $value) = @_;
-        push @parameters, [$key, defined($value) ? $value : ''];
-        return 1;
-    });
+    abort(400) if $@ || !defined($request);
 
-    # XXX: include query parameters (http://x.com/path?THIS=THAT)
+    # make sure the signature matches the rest of what the consumer gave us
+    abort(401) unless $request->verify;
 
-    for (@parameters) {
-        @$_ = map { Jifty->web->escape_uri($_) } @$_;
-    }
+    # is the request token they're using still valid?
+    my $request_token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+    $request_token->load_by_cols(consumer => $consumer, token => $oauth_params{token}, token_secret => $oauth_params{token_secret});
+    abort(401) unless $request_token->id;
+    abort(401) unless $request_token->can_trade_for_access_token;
 
-    return join '&',
-           map  { "$_->[0]=$_->[1]" }
-           grep { $_->[0] ne 'oauth_signature' }
-           sort { $a->[0] cmp $b->[0] || $a->[1] cmp $b->[1] } @parameters;
+    my $token = Jifty::Plugin::OAuth::Model::AccessToken->new(current_user => Jifty::CurrentUser->superuser);
+
+    my ($ok) = eval {
+        $token->create(consumer => $consumer,
+                       user => $request_token->authorized_by,
+                       map { $_ => $oauth_params{$_} } qw/timestamp nonce/);
+    };
+
+    abort(401) if $@ || !defined($token) || !$ok;
+
+    # XXX: actually send the token
 }
 
-# 9.1.2
-sub get_request_elements {
-    my $method          = uc Jifty->handler->apache->method();
-    my $url             = Jifty->web->url(get 'oauth_url');
-    my $parameters      = get_normalized_parameters();
-    my $consumer_secret = 'todo';
-    my $token_secret    = 'todo' || '';
+sub get_consumer {
+    my $key = shift;
+    my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+    $consumer->load_by_cols(key => $key);
+    abort(401) if !$consumer->id;
+    return $consumer;
+}
+
+my %valid_signature_methods = map { $_ => 1 } qw/PLAINTEXT HMAC-SHA1 RSA-SHA1/;
+sub validate_signature_method {
+    my $method = shift;
+    abort(400) unless $valid_signature_methods{$method};
+}
+
+sub get_parameters {
+    my %p;
+
+    # XXX: Check Authorization header
+
+    %p = ((map {
+        my $v = Jifty->handler->apache->header_in("oauth_$_");
+        defined $v ? ($_ => $v) : ()
+    } @_), %p);
+
+    # XXX: Check query string
+
+    $p{version} ||= '1.0';
+    abort(400) if grep { !defined($p{$_}) } @_;
+    return %p;
 }
 
 1;

Modified: jifty/trunk/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm	Wed Oct 17 18:08:30 2007
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use base qw( Jifty::Record );
+use base qw( Jifty::Plugin::OAuth::Token Jifty::Record );
 
 # kludge 1: you cannot call Jifty->app_class within schema {}
 my $app_user;
@@ -21,12 +21,19 @@
         type is 'timestamp',
         filters are 'Jifty::DBI::Filter::DateTime';
 
-};
+    column token_secret =>
+        type is 'varchar';
+
+    column consumer =>
+        refers_to Jifty::Plugin::OAuth::Model::Consumer;
 
-sub before_create {
-    my ($self, $attr) = @_;
-    $attr{valid_until} ||= DateTime->now->add(hours => 1);
-}
+    # we use these to make sure we aren't being hit with a replay attack
+    column timestamp =>
+        type is 'integer';
+
+    column nonce =>
+        type is 'varchar';
+};
 
 1;
 

Added: jifty/trunk/lib/Jifty/Plugin/OAuth/Model/Consumer.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/Model/Consumer.pm	Wed Oct 17 18:08:30 2007
@@ -0,0 +1,35 @@
+#!/usr/bin/env perl
+package Jifty::Plugin::OAuth::Model::Consumer;
+use strict;
+use warnings;
+
+use base qw( Jifty::Record );
+
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+
+    # the unique key that identifies a consumer
+    column key =>
+        type is 'varchar',
+        is distinct,
+        is required;
+
+    # a secret used in signing to verify that we have the real consumer (and
+    # not just someone who got ahold of the key)
+    column secret =>
+        type is 'varchar',
+        is required;
+
+    # the name of the consumer, e.g. Bob's Social Network
+    column name =>
+        type is 'varchar',
+        is required;
+
+    # the url of the consumer, e.g. http://social.bob/
+    column url =>
+        type is 'varchar';
+
+};
+
+1;
+

Modified: jifty/trunk/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm	Wed Oct 17 18:08:30 2007
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use base qw( Jifty::Record );
+use base qw( Jifty::Plugin::OAuth::Token Jifty::Record );
 
 # kludge 1: you cannot call Jifty->app_class within schema {}
 my $app_user;
@@ -25,34 +25,39 @@
         type is 'integer';
         #refers_to $app_user;
 
+    column consumer =>
+        refers_to Jifty::Plugin::OAuth::Model::Consumer;
+
     column used =>
         type is 'boolean',
         default is 'f';
 
-};
+    column token =>
+        type is 'varchar';
 
-sub before_create {
-    my ($self, $attr) = @_;
-    $attr{valid_until} ||= DateTime->now->add(hours => 1);
-}
+    column token_secret =>
+        type is 'varchar';
+
+    # we use these to make sure we aren't being hit with a replay attack
+    column timestamp =>
+        type is 'integer';
+
+    column nonce =>
+        type is 'varchar';
+};
 
 sub set_authorized {
     my $self = shift;
     $self->set_authorized_by(Jifty->web->current_user->id);
 }
 
-sub trade_for_access_token {
+sub can_trade_for_access_token {
     my $self = shift;
-    return undef if !$self->authorized;
-    return undef if !$self->authorized_by;
-    return undef if $self->used;
-    return undef if $self->valid_until < DateTime->now;
-
-    my $access_token = Jifty::Plugin::OAuth::Model::AccessToken->new(current_user => Jifty::CurrentUser->superuser);
-    my ($ok, $msg) = $access_token->create(user => $self->authorized_by);
-
-    return undef if !$ok;
-    return $access_token;
+    return 0 if !$self->authorized;
+    return 0 if !$self->authorized_by;
+    return 0 if $self->used;
+    return 0 if $self->valid_until < DateTime->now;
+    return 1;
 }
 
 1;

Added: jifty/trunk/lib/Jifty/Plugin/OAuth/Token.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/Token.pm	Wed Oct 17 18:08:30 2007
@@ -0,0 +1,40 @@
+#!/usr/bin/env perl
+package Jifty::Plugin::OAuth::Token;
+use strict;
+use warnings;
+
+# this just provides some helper methods for both token classes to use
+
+sub generate_token {
+    return join '', map { unpack('H2', chr(int rand 256)) } 1..10;
+}
+
+sub before_create {
+    my ($self, $attr) = @_;
+
+    # check if we're seeing a replay attack
+    my $token = $self->new(current_user => Jifty::CurrentUser->superuser);
+    $token->load_by_cols(nonce => $attr->{nonce}, timestamp => $attr->{nonce});
+    return if $token->id;
+
+    # attempt 20 times to create a unique token string
+    for (1..20) {
+        $attr->{token} = generate_token();
+        my $token = $self->new(current_user => Jifty::CurrentUser->superuser);
+        $token->load_by_cols(token => $attr->{token});
+        last if !$token->id;
+        delete $attr->{token};
+    }
+    return if !defined($attr->{token});
+
+    # generate a secret. need not be unique, just hard to guess
+    $attr->{token_secret} = generate_token();
+
+    # default the lifetime of this token to 1 hour
+    $attr->{valid_until} ||= DateTime->now->add(hours => 1);
+
+    return 1;
+}
+
+1;
+


More information about the Jifty-commit mailing list