| Jifty-Plugin-OAuth documentation | Contained in the Jifty-Plugin-OAuth distribution. |
Helper function to abort with a debug message. Maybe should be factored into
the abort procedure?
The consumer wants a request token
The consumer is trying to trade a request token for an access token
If this is a protected resource request, see if we can authorize the request with an access token.
This is dissimilar to the other OAuth requests because if anything fails, you just don't set a current_user, and then the rest of the dispatcher rules will take care of it. Thus, failure is handled quite differently in this rule. We try to abort as early as possible to make OAuth less of a hit on all requests.
This aborts the request with an "invalid HTTP method" response code.
Helper function to load a consumer by consumer key. Will abort if the key is unknown.
Figures out the signature key for this consumer. Will abort if the signature method is unsupported, or if the consumer lacks the prerequisites for this signature method.
Will return undef if the signature key is consumer independent, as is the
case for PLAINTEXT and HMAC-SHA1. RSA-SHA1 depends on the consumer
having the rsa_key field.
If the signature method is invalid and no_abort is set, it will return a special value of a reference to undef. Yes this sucks but undef already has an important meaning.
This will retrieve all the request paremeters. This gets parameters besides the ones in the OAuth spec, because the signature is based on all such request parameters.
Pass in by name all the OAuth-required parameters. Do not include the oauth_
prefix.
The precedence of parameters, from highest priority to lowest priority, is:
| Jifty-Plugin-OAuth documentation | Contained in the Jifty-Plugin-OAuth distribution. |
package Jifty::Plugin::OAuth::Dispatcher; use warnings; use strict; use Jifty::Dispatcher -base; use Net::OAuth::RequestTokenRequest; use Net::OAuth::AccessTokenRequest; use Net::OAuth::ProtectedResourceRequest; use URI::Escape 'uri_unescape'; # Let the app do auth, etc, before we let these rules run after app, before '/oauth/authorized' => redirect '/oauth/authorize'; after app, before GET '/oauth/authorize' => \&authorize; on POST '/oauth/authorize' => \&authorize_post; on GET '/oauth/access_token' => \&invalid_method; on POST '/oauth/access_token' => \&access_token; on GET '/oauth/request_token' => \&invalid_method; on POST '/oauth/request_token' => \&request_token; before '*' => \&try_oauth;
sub abortmsg { my ($code, $msg) = @_; if ($code) { Jifty->log->debug("$code for ".Jifty->web->request->path.": $msg") if defined($msg); abort($code); } elsif (defined $msg) { Jifty->log->debug("OAuth denied for ".Jifty->web->request->path.": $msg"); } }
sub request_token { my @params = qw/consumer_key signature_method signature timestamp nonce version/; set no_abort => 0; 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); my ($ok, $msg) = $consumer->is_valid_request(@oauth_params{qw/timestamp nonce/}); abortmsg(401, $msg) if !$ok; # 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 ) }; abortmsg(400, "Unable to create RequestTokenRequest: $@") if $@ || !defined($request); # 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; # 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); ($ok, $msg) = eval { $token->create(consumer => $consumer); }; abortmsg(401, "Unable to create a Request Token: " . $@ || $msg) if $@ || !$ok; $consumer->made_request(@oauth_params{qw/timestamp nonce/}); set oauth_response => { oauth_token => $token->token, oauth_token_secret => $token->secret }; show 'oauth/response'; }
sub authorize { my @params = qw/token callback/; abortmsg(403, "Cannot authorize tokens as an OAuthed user") if Jifty->web->current_user->is_oauthed; abortmsg(401, "Must be authenticated to authorize tokens") if !Jifty->web->current_user->id; set no_abort => 1; my %oauth_params = get_parameters(@params); set callback => $oauth_params{callback}; set consumer => 'Some application'; del 'token'; if ($oauth_params{token}) { my $request_token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser); $request_token->load_by_cols(token => $oauth_params{token}, authorized => 0); if ($request_token->id) { set consumer => $request_token->consumer; set token => $oauth_params{token}; } } }
sub authorize_post { abortmsg(403, "Cannot authorize tokens as an OAuthed user") if Jifty->web->current_user->is_oauthed; my $result = Jifty->web->response->result("authorize_request_token"); unless ($result && $result->success) { redirect '/oauth/authorize'; } set result => $result; show '/oauth/authorized'; }
sub access_token { my @params = qw/consumer_key signature_method signature timestamp nonce token version/; set no_abort => 0; 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); my ($ok, $msg) = $consumer->is_valid_request(@oauth_params{qw/timestamp nonce/}); abortmsg(401, $msg) if !$ok; # 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}); abortmsg(401, "No token found for consumer ".$consumer->name." with key $oauth_params{token}") unless $request_token->id; ($ok, $msg) = $request_token->can_trade_for_access_token; abortmsg(401, "Cannot trade request token for access token: $msg") if !$ok; # Net::OAuth::Request will die hard if it doesn't get everything it wants my $request = eval { Net::OAuth::AccessTokenRequest->new( request_url => Jifty->web->url(path => '/oauth/access_token'), request_method => Jifty->handler->apache->method(), consumer_secret => $consumer->secret, token_secret => $request_token->secret, signature_key => $signature_key, map { $_ => $oauth_params{$_} } @params ) }; abortmsg(400, "Unable to create AccessTokenRequest: $@") if $@ || !defined($request); # 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->create_from_request_token($request_token); abortmsg(401, "Unable to create an Access Token: " . $@ || $msg) if $@ || !defined($token) || !$ok; $consumer->made_request(@oauth_params{qw/timestamp nonce/}); $request_token->set_used(1); set oauth_response => { oauth_token => $token->token, oauth_token_secret => $token->secret }; show 'oauth/response'; }
sub try_oauth { my @params = qw/consumer_key signature_method signature timestamp nonce token version/; set no_abort => 1; my %oauth_params = get_parameters(@params); for (@params) { abortmsg(undef, "Undefined required parameter: $_"), return if !defined($oauth_params{$_}); } my $consumer = get_consumer($oauth_params{consumer_key}); return if !$consumer->id; abortmsg(undef, "No known consumer with key $oauth_params{consumer_key}"), return unless $consumer->id; my $signature_key = get_signature_key($oauth_params{signature_method}, $consumer); if ($signature_key && ref($signature_key) && !defined($$signature_key)) { abortmsg(undef, "Failed to get signature key."); return; } my ($ok, $msg) = $consumer->is_valid_request(@oauth_params{qw/timestamp nonce/}); abortmsg(undef, $msg), return if !$ok; my $access_token = Jifty::Plugin::OAuth::Model::AccessToken->new(current_user => Jifty::CurrentUser->superuser); $access_token->load_by_cols(consumer => $consumer, token => $oauth_params{token}); abortmsg(undef, "No token found for consumer ".$consumer->name." with key $oauth_params{token}"), return unless $access_token->id; ($ok, $msg) = $access_token->is_valid; abortmsg(undef, "Cannot access protected resources with this access token: $msg"), return if !$ok; # Net::OAuth::Request will die hard if it doesn't get everything it wants my $request = eval { Net::OAuth::ProtectedResourceRequest->new( request_url => Jifty->web->url(path => Jifty->web->request->path), request_method => Jifty->handler->apache->method(), consumer_secret => $consumer->secret, token_secret => $access_token->secret, signature_key => $signature_key, map { $_ => $oauth_params{$_} } @params ) }; abortmsg(undef, "Unable to create ProtectedResourceRequest: $@"), return if $@ || !defined($request); abortmsg(undef, "Invalid signature (type: $oauth_params{signature_method})."), return unless $request->verify; $consumer->made_request(@oauth_params{qw/timestamp nonce/}); 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); }
sub invalid_method { Jifty->web->response->add_header(Allow => 'POST'); abort(405); }
sub get_consumer { my $key = shift; my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser); $consumer->load_by_cols(consumer_key => $key); abortmsg(401, "No known consumer with key $key") unless $consumer->id || get 'no_abort'; return $consumer; }
{ my %valid_signature_methods = map { $_ => 1 } qw/PLAINTEXT HMAC-SHA1 /; if (eval { require Crypt::OpenSSL::RSA; 1 }) { $valid_signature_methods{"RSA-SHA1"} = 1; } else { Jifty->log->debug("RSA-SHA1 support for OAuth unavailable: Crypt::OpenSSL::RSA is not installed."); } 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") unless get 'no_abort'; return \undef; } 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; if (!defined $key) { abortmsg(400, "Consumer does not have necessary field $field required for signature method $method") unless get 'no_abort'; return; } if ($method eq 'RSA-SHA1') { $key = Crypt::OpenSSL::RSA->new_public_key($key); } return $key; } }
sub get_parameters { my %p; my %params = Jifty->handler->apache->params(); # Check Authorization header my $authz = Jifty->handler->apache->header_in("Authorization"); if ($authz && $authz =~ s/^\s*OAuth\s*//i) { while ($authz =~ m{\s*([%a-zA-Z0-9._~-]+)="([%a-zA-Z0-9._~-]*)"\s*}g) { $params{uri_unescape($1)} = uri_unescape($2); } } for (@_) { $p{$_} = delete $params{"oauth_$_"} if !defined $p{$_}; } $p{version} ||= '1.0'; unless (get 'no_abort') { # check to see if there are any unsupported parameters while (my ($key, undef) = each %params) { abortmsg(400, "Unsupported parameter: $key") if $key =~ /^oauth_/; } # check to see if we're missing anything for (@_) { abortmsg(400, "Undefined required parameter: $_") if !defined($p{$_}); } if ($p{timestamp} && $p{timestamp} !~ /^\d+$/) { abortmsg(400, "Malformed timestamp. Expected positive integer, got $p{timestamp}"); } } return %p; } 1;