[Jifty-commit] r5154 - in jifty/trunk: . lib/Jifty/Plugin lib/Jifty/Plugin/OAuth lib/Jifty/Plugin/OAuth/Action lib/Jifty/Plugin/OAuth/Model t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth t/TestApp-Plugin-OAuth/t

Jifty commits jifty-commit at lists.jifty.org
Wed Feb 20 23:59:14 EST 2008


Author: sartak
Date: Wed Feb 20 23:59:13 2008
New Revision: 5154

Added:
   jifty/trunk/t/TestApp-Plugin-OAuth/t/06-read-only.t
Modified:
   jifty/trunk/   (props changed)
   jifty/trunk/lib/Jifty/Plugin/OAuth.pm
   jifty/trunk/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm
   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
   jifty/trunk/lib/Jifty/Plugin/OAuth/View.pm
   jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm
   jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm
   jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm

Log:
 r51986 at onn:  sartak | 2008-02-20 23:58:57 -0500
 OAuth Checkpoint:
 * User has some choice how long to grant access (instead of only 1 hour)
 * User may now choose to deny the consumer write access (the current_user_can check isn't working just yet)
 * Add CurrentUser->oauth_token
 * Test fixes so that http methods other than GET and POST work
 * TestApp::OAuth::User->current_user_can


Modified: jifty/trunk/lib/Jifty/Plugin/OAuth.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/OAuth.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth.pm	Wed Feb 20 23:59:13 2008
@@ -7,7 +7,29 @@
 our $VERSION = 0.01;
 
 sub init {
-    Jifty::CurrentUser->mk_accessors(qw(is_oauthed));
+    Jifty::CurrentUser->mk_accessors(qw(is_oauthed oauth_token));
+
+    Jifty::Record->add_trigger(before_access => sub {
+        my $record = shift;
+        my $right  = shift;
+
+        # not oauthed? usual rules
+        warn $record->current_user->id;
+        $record->current_user->is_oauthed
+            or return 'ignore';
+
+        my $token = $record->current_user->oauth_token;
+
+        # read access? usual rules
+        $right eq 'read'
+            or return 'ignore';
+
+        # if the token does not give write access, then WE DO NOT HAVE IT
+        return 'deny' unless $token->__value('can_write');
+
+        # we have not been forbidden from updating, so: usual rules
+        return 'ignore';
+    });
 }
 
 =head1 NAME
@@ -20,9 +42,9 @@
 and limited access to your users' data.
 
 This plugin adds an C</oauth> set of URLs to your application, listed below. It
-also adds C<is_oauthed> to L<Jifty::CurrentUser>, so you may have additional
-restrictions on OAuth access (such as forbidding OAuthed users to change users'
-passwords).
+also adds C<is_oauthed> and C<oauth_token> to L<Jifty::CurrentUser>, so you may
+have additional restrictions on OAuth access (such as forbidding OAuthed users
+to change users' passwords).
 
 =head2 /oauth
 
@@ -185,7 +207,9 @@
 
 =head2 init
 
-This adds an is_oauthed accessor to L<Jifty::CurrentUser>.
+This adds an is_oauthed accessor to L<Jifty::CurrentUser>. It also establishes
+a trigger in L<Jifty::Record> so that only OAuthed consumers with write access
+can do anything other than read.
 
 =head1 SEE ALSO
 

Modified: jifty/trunk/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm	Wed Feb 20 23:59:13 2008
@@ -25,6 +25,23 @@
     param 'callback',
         render as 'hidden';
 
+    param 'use_limit',
+        label is 'Use limit',
+        hints are 'How long should the site have access?',
+        render as 'select',
+        default is '1 hour',
+        valid_values are (
+            '5 minutes',
+            '1 hour',
+            '1 day',
+            '1 week',
+        );
+
+    param 'can_write',
+        label is 'Write access?',
+        hints are 'Should the site be allowed to update your data? (unchecking restricts to read-only)',
+        render as 'checkbox',
+        default is 0;
 };
 
 =head2 validate_token
@@ -69,10 +86,18 @@
 
     $self->result->content(token_obj => $token);
     $self->result->content(token     => $token->token);
-    $self->result->content(callback  => $self->argument_value('callback'));
+
+    for (qw/callback use_limit can_write/) {
+        $self->result->content($_ => $self->argument_value($_));
+    }
 
     if ($self->argument_value('authorize') eq 'allow') {
         $token->set_authorized(1);
+        $token->set_access_token_restrictions({
+            can_write => $self->argument_value('can_write'),
+            use_limit => $self->inflate_use_limit,
+        });
+
         $self->result->message("Allowing " . $token->consumer->name . " to access your stuff.");
     }
     else {
@@ -83,5 +108,26 @@
     return 1;
 }
 
+=head2 inflate_use_limit -> DateTime
+
+Takes the use_limit argument and inflates it to a DateTime object representing
+when the access token will expire. It expects the input to be of the form
+"number_of_periods period_length", so "5 minutes", "1 hour", etc.
+
+=cut
+
+sub inflate_use_limit {
+    my $self      = shift;
+    my $use_limit = $self->argument_value('use_limit');
+
+    my ($periods, $length) = $use_limit =~ m{^(\d+)\s+(\w+)$}
+        or die "AuthorizeRequestToken->inflate_use_limit failed to parse input $use_limit";
+
+    # DateTime::Duration accepts only plurals
+    $length .= 's' if $periods == 1;
+
+    return DateTime->now->add($length => $periods);
+}
+
 1;
 

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 Feb 20 23:59:13 2008
@@ -182,12 +182,7 @@
     # make sure the signature matches the rest of what the consumer gave us
     abortmsg(401, "Invalid signature (type: $oauth_params{signature_method}).") unless $request->verify;
 
-    my $token = Jifty::Plugin::OAuth::Model::AccessToken->new(current_user => Jifty::CurrentUser->superuser);
-
-    ($ok, $msg) = eval {
-        $token->create(consumer => $consumer,
-                       auth_as  => $request_token->authorized_by);
-    };
+    my $token = Jifty::Plugin::OAuth::Model::AccessToken->create_from_request_token($request_token);
 
     abortmsg(401, "Unable to create an Access Token: " . $@ || $msg)
         if $@ || !defined($token) || !$ok;
@@ -262,8 +257,13 @@
 
     $consumer->made_request(@oauth_params{qw/timestamp nonce/});
 
-    Jifty->web->temporary_current_user(Jifty->app_class('CurrentUser')->new(id => $access_token->auth_as));
-    Jifty->web->current_user->is_oauthed($access_token);
+    my $new_current_user = Jifty->app_class('CurrentUser')->new(
+        id => $access_token->auth_as,
+    );
+    $new_current_user->is_oauthed(1);
+    $new_current_user->oauth_token($access_token);
+
+    Jifty->web->temporary_current_user($new_current_user);
 
     Jifty->log->info("Consumer " . $consumer->name . " successfully OAuthed as user ". $access_token->auth_as);
 }

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 Feb 20 23:59:13 2008
@@ -35,6 +35,9 @@
     column consumer =>
         refers_to Jifty::Plugin::OAuth::Model::Consumer;
 
+    column can_write =>
+        type is 'boolean',
+        default is '0';
 };
 
 =head2 table
@@ -45,6 +48,34 @@
 
 sub table {'oauth_access_tokens'}
 
+=head2 create_from_request_token
+
+This creates a new access token (as the superuser) and populates its values
+from the given request token.
+
+=cut
+
+sub create_from_request_token {
+    my $self = shift;
+    my $request_token = shift;
+
+    if (!ref($self)) {
+        $self = $self->new(current_user => Jifty::CurrentUser->superuser);
+    }
+
+    my $restrictions = $request_token->access_token_restrictions
+        or die "No access-token restrictions given in the request token.";
+
+    $self->create(
+        consumer    => $request_token->consumer,
+        auth_as     => $request_token->authorized_by,
+        valid_until => $restrictions->{use_limit},
+        can_write   => $restrictions->{can_write} ? 1 : 0,
+    );
+
+    return $self;
+}
+
 =head2 is_valid
 
 This neatly encapsulates the "is this access token perfect?" check.

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 Feb 20 23:59:13 2008
@@ -45,6 +45,9 @@
         type is 'varchar',
         is required;
 
+    column access_token_restrictions =>
+        type is 'blob',
+        filters are 'Jifty::DBI::Filter::Storable';
 };
 
 =head2 table
@@ -65,7 +68,6 @@
 sub after_set_authorized {
     my $self = shift;
     $self->set_authorized_by(Jifty->web->current_user->id);
-    $self->set_valid_until(DateTime->now->add(hours => 1));
 }
 
 =head2 can_trade_for_access_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	Wed Feb 20 23:59:13 2008
@@ -143,6 +143,9 @@
         $authorize->form_field('token')->render;
     }
 
+    $authorize->form_field('use_limit')->render;
+    $authorize->form_field('can_write')->render;
+
     outs_raw $authorize->hidden(callback => get 'callback');
 
     outs_raw($authorize->button(
@@ -209,9 +212,7 @@
     div {
         p {
             show '/oauth/consumer';
-            outs ' is trying to access your data on this site. If you trust this application, you may grant it access. Note that ';
-            strong { "access is unrestricted" };
-            outs ' and will expire in one hour after you click "Allow".';
+            outs ' is trying to access your data on this site. If you trust this application, you may grant it access.';
         }
         p {
             "If you're at all uncomfortable with the idea of someone rifling through your things, or don't know what this is, click Deny."

Modified: jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm
==============================================================================
--- jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm	(original)
+++ jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm	Wed Feb 20 23:59:13 2008
@@ -6,6 +6,7 @@
 my @login_required = qw{
     oauth/authorize
     nuke/?
+    =
 };
 
 my $login_required = join '|', map {"^$_"} @login_required;

Modified: jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm
==============================================================================
--- jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm	(original)
+++ jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm	Wed Feb 20 23:59:13 2008
@@ -14,5 +14,16 @@
 use Jifty::Plugin::User::Mixin::Model::User;
 use Jifty::Plugin::Authentication::Password::Mixin::Model::User;
 
+sub current_user_can {
+    my $self = shift;
+
+    return 1 if $_[0] eq 'create';
+
+    my $id = $self->__value('id');
+    return 1 if $id == $self->current_user->id;
+
+    $self->SUPER::current_user_can(@_);
+}
+
 1;
 

Modified: jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm
==============================================================================
--- jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm	(original)
+++ jifty/trunk/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm	Wed Feb 20 23:59:13 2008
@@ -82,16 +82,26 @@
         $cmech->default_header("Authorization" => authz(%params));
     }
 
-    if ($method eq 'POST') {
-        $r = $cmech->post($url, $params_in eq 'method' ? [%params] : ());
-    }
-    else {
+    if ($method eq 'GET') {
         my $query = join '&',
                     map { "$_=" . Jifty->web->escape_uri($params{$_}||'') }
                     keys %params;
         my $params = $params_in eq 'method' ? "?$query" : '';
         $r = $cmech->get("$url$params");
     }
+    else {
+        my $req = HTTP::Request->new(
+            uc($method) => $url,
+        );
+
+        if ($params_in eq 'method') {
+            my $content = Jifty->web->query_string(%params);
+            $req->header('Content-type' => 'application/x-www-form-urlencoded');
+            $req->content($content);
+        }
+
+        $r = $cmech->request($req);
+    }
 
     $cmech->default_headers->remove_header("Authorization");
 

Added: jifty/trunk/t/TestApp-Plugin-OAuth/t/06-read-only.t
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-OAuth/t/06-read-only.t	Wed Feb 20 23:59:13 2008
@@ -0,0 +1,99 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+        plan tests => 58;
+    }
+    else {
+        plan skip_all => "Net::OAuth or Crypt::OpenSSL::RSA isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use TestApp::Plugin::OAuth::Test;
+
+use Jifty::Test::WWW::Mechanize;
+
+# setup {{{
+# create two consumers {{{
+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',
+    rsa_key      => $pubkey,
+);
+ok($ok, $msg);
+
+my $rsaless = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+($ok, $msg) = $rsaless->create(
+    consumer_key => 'foo2',
+    secret       => 'bar2',
+    name         => 'Backwater.org',
+    url          => 'http://backwater.org',
+);
+ok($ok, $msg);
+# }}}
+# create user and log in {{{
+my $u = TestApp::Plugin::OAuth::Model::User->new(current_user => TestApp::Plugin::OAuth::CurrentUser->superuser);
+$u->create( name => 'You Zer', email => 'youzer at example.com', password => 'secret', email_confirmed => 1);
+my $uid = $u->id;
+ok($uid, "New user has valid id set");
+
+$umech->get_ok($URL . '/login');
+$umech->fill_in_action_ok($umech->moniker_for('TestApp::Plugin::OAuth::Action::Login'), email => 'youzer at example.com', password => 'secret');
+$umech->submit;
+$umech->content_contains('Logout');
+# }}}
+# }}}
+# make sure we're not logged in {{{
+response_is(
+    url                    => '/nuke/the/whales',
+    code                   => 200,
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => 'please',
+    token_secret           => 'letmein',
+);
+$cmech->content_contains("Login with a password", "redirected to login");
+$cmech->content_lacks("Press the shiny red button", "did NOT get to a protected page");
+# }}}}
+# REST GET {{{
+get_access_token();
+
+response_is(
+    url                    => "/=/model/User/id/$uid.yml",
+    code                   => 200,
+    method                 => 'GET',
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => $token_obj->token,
+    token_secret           => $token_obj->secret,
+);
+$cmech->content_contains("You Zer", "REST GET works while OAuthed");
+# }}}
+# REST PUT {{{
+response_is(
+    url                    => "/=/model/User/id/$uid.yml",
+    code                   => 200,
+    method                 => 'DELETE',
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => $token_obj->token,
+    token_secret           => $token_obj->secret,
+);
+$cmech->content_contains("You Zer", "REST DELETE doesn't work while the consumer has no write access");
+# }}}
+


More information about the Jifty-commit mailing list