summaryrefslogtreecommitdiff
path: root/actions
diff options
context:
space:
mode:
Diffstat (limited to 'actions')
-rw-r--r--actions/apioauthaccesstoken.php56
-rw-r--r--actions/apioauthauthorize.php241
-rw-r--r--actions/apioauthpin.php67
-rw-r--r--actions/apioauthrequesttoken.php91
4 files changed, 377 insertions, 78 deletions
diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php
index 887df4c20..663a7a2bb 100644
--- a/actions/apioauthaccesstoken.php
+++ b/actions/apioauthaccesstoken.php
@@ -2,7 +2,8 @@
/**
* StatusNet, the distributed open-source microblogging tool
*
- * Exchange an authorized OAuth request token for an access token
+ * Action for getting OAuth token credentials (exchange an authorized
+ * request token for an access token)
*
* PHP version 5
*
@@ -34,7 +35,8 @@ if (!defined('STATUSNET')) {
require_once INSTALLDIR . '/lib/apioauth.php';
/**
- * Exchange an authorized OAuth request token for an access token
+ * Action for getting OAuth token credentials (exchange an authorized
+ * request token for an access token)
*
* @category API
* @package StatusNet
@@ -45,6 +47,8 @@ require_once INSTALLDIR . '/lib/apioauth.php';
class ApiOauthAccessTokenAction extends ApiOauthAction
{
+ protected $reqToken = null;
+ protected $verifier = null;
/**
* Class handler.
@@ -65,30 +69,58 @@ class ApiOauthAccessTokenAction extends ApiOauthAction
$atok = null;
+ // XXX: Insist that oauth_token and oauth_verifier be populated?
+ // Spec doesn't say they MUST be.
+
try {
+
$req = OAuthRequest::from_request();
+
+ $this->reqToken = $req->get_parameter('oauth_token');
+ $this->verifier = $req->get_parameter('oauth_verifier');
+
$atok = $server->fetch_access_token($req);
} catch (OAuthException $e) {
common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage());
common_debug(var_export($req, true));
- $this->outputError($e->getMessage());
- return;
+ $code = $e->getCode();
+ $this->clientError($e->getMessage(), empty($code) ? 401 : $code, 'text');
}
if (empty($atok)) {
- common_debug('couldn\'t get access token.');
- print "Token exchange failed. Has the request token been authorized?\n";
+
+ // Token exchange failed -- log it
+
+ list($proxy, $ip) = common_client_ip();
+
+ $msg = sprintf(
+ 'API OAuth - Failure exchanging request token for access token, '
+ . 'request token = %s, verifier = %s, IP = %s, proxy = %s',
+ $this->reqToken,
+ $this->verifier,
+ $ip,
+ $proxy
+ );
+
+ common_log(LOG_WARNING, $msg);
+
+ $this->clientError(_("Invalid request token or verifier.", 400, 'text'));
+
} else {
- print $atok;
+ $this->showAccessToken($atok);
}
}
- function outputError($msg)
+ /*
+ * Display OAuth token credentials
+ *
+ * @param OAuthToken token the access token
+ */
+
+ function showAccessToken($token)
{
- header('HTTP/1.1 401 Unauthorized');
- header('Content-Type: text/html; charset=utf-8');
- print $msg . "\n";
+ header('Content-Type: application/x-www-form-urlencoded');
+ print $token;
}
}
-
diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php
index c2fbbcdd8..ea5c30c2a 100644
--- a/actions/apioauthauthorize.php
+++ b/actions/apioauthauthorize.php
@@ -32,6 +32,7 @@ if (!defined('STATUSNET')) {
}
require_once INSTALLDIR . '/lib/apioauth.php';
+require_once INSTALLDIR . '/lib/info.php';
/**
* Authorize an OAuth request token
@@ -43,9 +44,10 @@ require_once INSTALLDIR . '/lib/apioauth.php';
* @link http://status.net/
*/
-class ApiOauthAuthorizeAction extends ApiOauthAction
+class ApiOauthAuthorizeAction extends Action
{
- var $oauth_token;
+ var $oauthTokenParam;
+ var $reqToken;
var $callback;
var $app;
var $nickname;
@@ -67,12 +69,17 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
{
parent::prepare($args);
- $this->nickname = $this->trimmed('nickname');
- $this->password = $this->arg('password');
- $this->oauth_token = $this->arg('oauth_token');
- $this->callback = $this->arg('oauth_callback');
- $this->store = new ApiStatusNetOAuthDataStore();
- $this->app = $this->store->getAppByRequestToken($this->oauth_token);
+ $this->nickname = $this->trimmed('nickname');
+ $this->password = $this->arg('password');
+ $this->oauthTokenParam = $this->arg('oauth_token');
+ $this->callback = $this->arg('oauth_callback');
+ $this->store = new ApiStatusNetOAuthDataStore();
+
+ try {
+ $this->app = $this->store->getAppByRequestToken($this->oauthTokenParam);
+ } catch (Exception $e) {
+ $this->clientError($e->getMessage());
+ }
return true;
}
@@ -97,14 +104,30 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
} else {
- if (empty($this->oauth_token)) {
+ // Make sure a oauth_token parameter was provided
+ if (empty($this->oauthTokenParam)) {
$this->clientError(_('No oauth_token parameter provided.'));
- return;
+ } else {
+
+ // Check to make sure the token exists
+ $this->reqToken = $this->store->getTokenByKey($this->oauthTokenParam);
+
+ if (empty($this->reqToken)) {
+ $this->serverError(
+ _('Invalid request token.')
+ );
+ } else {
+
+ // Check to make sure we haven't already authorized the token
+ if ($this->reqToken->state != 0) {
+ $this->clientError("Invalid request token.");
+ }
+ }
}
+ // make sure there's an app associated with this token
if (empty($this->app)) {
- $this->clientError(_('Invalid token.'));
- return;
+ $this->clientError(_('Invalid request token.'));
}
$name = $this->app->name;
@@ -120,8 +143,8 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
- $this->showForm(_('There was a problem with your session token. '.
- 'Try again, please.'));
+ $this->showForm(
+ _('There was a problem with your session token. Try again, please.'));
return;
}
@@ -130,6 +153,11 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
$user = null;
if (!common_logged_in()) {
+
+ // XXX Force credentials check?
+
+ // XXX OpenID
+
$user = common_check_user($this->nickname, $this->password);
if (empty($user)) {
$this->showForm(_("Invalid nickname / password!"));
@@ -141,9 +169,15 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
if ($this->arg('allow')) {
- // mark the req token as authorized
+ // fetch the token
+ $this->reqToken = $this->store->getTokenByKey($this->oauthTokenParam);
- $this->store->authorize_token($this->oauth_token);
+ // mark the req token as authorized
+ try {
+ $this->store->authorize_token($this->oauthTokenParam);
+ } catch (Exception $e) {
+ $this->serverError($e->getMessage());
+ }
// Check to see if there was a previous token associated
// with this user/app and kill it. If the user is doing this she
@@ -156,8 +190,7 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
if (!$result) {
common_log_db_error($appUser, 'DELETE', __FILE__);
- throw new ServerException(_('Database error deleting OAuth application user.'));
- return;
+ $this->serverError(_('Database error deleting OAuth application user.'));
}
}
@@ -175,20 +208,19 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
// granted. The OAuth app user record then gets updated
// with the new access token and access type.
- $appUser->token = $this->oauth_token;
+ $appUser->token = $this->oauthTokenParam;
$appUser->created = common_sql_now();
$result = $appUser->insert();
if (!$result) {
common_log_db_error($appUser, 'INSERT', __FILE__);
- throw new ServerException(_('Database error inserting OAuth application user.'));
- return;
+ $this->serverError(_('Database error inserting OAuth application user.'));
}
- // if we have a callback redirect and provide the token
+ // If we have a callback redirect and provide the token
- // A callback specified in the app setup overrides whatever
+ // Note: A callback specified in the app setup overrides whatever
// is passed in with the request.
if (!empty($this->app->callback_url)) {
@@ -197,40 +229,40 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
if (!empty($this->callback)) {
- $target_url = $this->getCallback($this->callback,
- array('oauth_token' => $this->oauth_token));
+ $targetUrl = $this->getCallback(
+ $this->callback,
+ array(
+ 'oauth_token' => $this->oauthTokenParam,
+ 'oauth_verifier' => $this->reqToken->verifier // 1.0a
+ )
+ );
+
+ // Redirect the user to the provided OAuth callback
+ common_redirect($targetUrl, 303);
- common_redirect($target_url, 303);
} else {
- common_debug("callback was empty!");
+ common_log(
+ LOG_INFO,
+ "No oauth_callback parameter provided for application ID "
+ . $this->app->id
+ . " when authorizing request token."
+ );
}
- // otherwise inform the user that the rt was authorized
-
- $this->elementStart('p');
-
- // XXX: Do OAuth 1.0a verifier code
-
- $this->raw(sprintf(_("The request token %s has been authorized. " .
- 'Please exchange it for an access token.'),
- $this->oauth_token));
-
- $this->elementEnd('p');
-
- } else if ($this->arg('deny')) {
-
- $datastore = new ApiStatusNetOAuthDataStore();
- $datastore->revoke_token($this->oauth_token, 0);
+ // Otherwise, inform the user that the rt was authorized
+ $this->showAuthorized();
- $this->elementStart('p');
+ } else if ($this->arg('cancel')) {
- $this->raw(sprintf(_("The request token %s has been denied and revoked."),
- $this->oauth_token));
+ try {
+ $this->store->revoke_token($this->oauthTokenParam, 0);
+ $this->showCanceled();
+ } catch (Exception $e) {
+ $this->ServerError($e->getMessage());
+ }
- $this->elementEnd('p');
} else {
$this->clientError(_('Unexpected form submission.'));
- return;
}
}
@@ -276,7 +308,7 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
_('Allow or deny access'));
$this->hidden('token', common_session_token());
- $this->hidden('oauth_token', $this->oauth_token);
+ $this->hidden('oauth_token', $this->oauthTokenParam);
$this->hidden('oauth_callback', $this->callback);
$this->elementStart('ul', 'form_data');
@@ -321,11 +353,11 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
}
- $this->element('input', array('id' => 'deny_submit',
+ $this->element('input', array('id' => 'cancel_submit',
'class' => 'submit submit form_action-primary',
- 'name' => 'deny',
+ 'name' => 'cancel',
'type' => 'submit',
- 'value' => _('Deny')));
+ 'value' => _('Cancel')));
$this->element('input', array('id' => 'allow_submit',
'class' => 'submit submit form_action-secondary',
@@ -348,7 +380,7 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
function getInstructions()
{
- return _('Allow or deny access to your account information.');
+ return _('Authorize access to your account information.');
}
/**
@@ -388,4 +420,107 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
// NOP
}
+ /*
+ * Show a nice message confirming the authorization
+ * operation was canceled.
+ *
+ * @return nothing
+ */
+
+ function showCanceled()
+ {
+ $info = new InfoAction(
+ _('Authorization canceled.'),
+ sprintf(
+ _('The request token %s has been revoked.'),
+ $this->oauthTokenParm
+ )
+ );
+
+ $info->showPage();
+ }
+
+ /*
+ * Show a nice message that the authorization was successful.
+ * If the operation is out-of-band, show a pin.
+ *
+ * @return nothing
+ */
+
+ function showAuthorized()
+ {
+ $title = sprintf(
+ _("You have successfully authorized %s."),
+ $this->app->name
+ );
+
+ $msg = sprintf(
+ _('Please return to %s and enter the following security code to complete the process.'),
+ $this->app->name
+ );
+
+ if ($this->reqToken->verified_callback == 'oob') {
+ $pin = new ApiOauthPinAction($title, $msg, $this->reqToken->verifier);
+ $pin->showPage();
+ } else {
+
+ // NOTE: This would only happen if an application registered as
+ // a web application but sent in 'oob' for the oauth_callback
+ // parameter. Usually web apps will send in a callback and
+ // not use the pin-based workflow.
+
+ $info = new InfoAction(
+ $title,
+ $msg,
+ $this->oauthTokenParam,
+ $this->reqToken->verifier
+ );
+
+ $info->showPage();
+ }
+ }
+
+ /*
+ * Properly format the callback URL and parameters so it's
+ * suitable for a redirect in the OAuth dance
+ *
+ * @param string $url the URL
+ * @param array $params an array of parameters
+ *
+ * @return string $url a URL to use for redirecting to
+ */
+
+ function getCallback($url, $params)
+ {
+ foreach ($params as $k => $v) {
+ $url = $this->appendQueryVar(
+ $url,
+ OAuthUtil::urlencode_rfc3986($k),
+ OAuthUtil::urlencode_rfc3986($v)
+ );
+ }
+
+ return $url;
+ }
+
+ /*
+ * Append a new query parameter after any existing query
+ * parameters.
+ *
+ * @param string $url the URL
+ * @prarm string $k the parameter name
+ * @param string $v value of the paramter
+ *
+ * @return string $url the new URL with added parameter
+ */
+
+ function appendQueryVar($url, $k, $v) {
+ $url = preg_replace('/(.*)(\?|&)' . $k . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
+ $url = substr($url, 0, -1);
+ if (strpos($url, '?') === false) {
+ return ($url . '?' . $k . '=' . $v);
+ } else {
+ return ($url . '&' . $k . '=' . $v);
+ }
+ }
}
diff --git a/actions/apioauthpin.php b/actions/apioauthpin.php
new file mode 100644
index 000000000..5e6713a54
--- /dev/null
+++ b/actions/apioauthpin.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Action for displaying an OAuth verifier pin
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/lib/info.php';
+
+/**
+ * Class for displaying an OAuth verifier pin
+ *
+ * XXX: I'm pretty sure we don't need to check the logged in state here. -- Zach
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class ApiOauthPinAction extends InfoAction
+{
+ function __construct($title, $message, $verifier)
+ {
+ $this->verifier = $verifier;
+ $this->title = $title;
+ parent::__construct($title, $message);
+ }
+
+ /**
+ * Display content.
+ *
+ * @return nothing
+ */
+ function showContent()
+ {
+ $this->element('div', array('class' => 'info'), $this->message);
+ $this->element('div', array('id' => 'oauth_pin'), $this->verifier);
+ }
+}
diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php
index 4fa626d86..478d2dbfc 100644
--- a/actions/apioauthrequesttoken.php
+++ b/actions/apioauthrequesttoken.php
@@ -2,7 +2,7 @@
/**
* StatusNet, the distributed open-source microblogging tool
*
- * Get an OAuth request token
+ * Issue temporary OAuth credentials (a request token)
*
* PHP version 5
*
@@ -34,7 +34,7 @@ if (!defined('STATUSNET')) {
require_once INSTALLDIR . '/lib/apioauth.php';
/**
- * Get an OAuth request token
+ * Issue temporary OAuth credentials (a request token)
*
* @category API
* @package StatusNet
@@ -58,22 +58,23 @@ class ApiOauthRequestTokenAction extends ApiOauthAction
{
parent::prepare($args);
- $this->callback = $this->arg('oauth_callback');
-
- if (!empty($this->callback)) {
- common_debug("callback: $this->callback");
- }
+ // XXX: support "force_login" parameter like Twitter? (Forces the user to enter
+ // their credentials to ensure the correct users account is authorized.)
return true;
}
/**
- * Class handler.
+ * Handle a request for temporary OAuth credentials
+ *
+ * Make sure the request is kosher, then emit a set of temporary
+ * credentials -- AKA an unauthorized request token.
*
* @param array $args array of arguments
*
* @return void
*/
+
function handle($args)
{
parent::handle($args);
@@ -85,14 +86,78 @@ class ApiOauthRequestTokenAction extends ApiOauthAction
$server->add_signature_method($hmac_method);
try {
- $req = OAuthRequest::from_request();
+
+ $req = OAuthRequest::from_request();
+
+ // verify callback
+ if (!$this->verifyCallback($req->get_parameter('oauth_callback'))) {
+ throw new OAuthException(
+ "You must provide a valid URL or 'oob' in oauth_callback.",
+ 400
+ );
+ }
+
+ // check signature and issue a new request token
$token = $server->fetch_request_token($req);
- print $token;
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ "API OAuth - Issued request token %s for consumer %s with oauth_callback %s",
+ $token->key,
+ $req->get_parameter('oauth_consumer_key'),
+ "'" . $req->get_parameter('oauth_callback') ."'"
+ )
+ );
+
+ // return token to the client
+ $this->showRequestToken($token);
+
} catch (OAuthException $e) {
common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage());
- header('HTTP/1.1 401 Unauthorized');
- header('Content-Type: text/html; charset=utf-8');
- print $e->getMessage() . "\n";
+
+ // Return 401 for for bad credentials or signature problems,
+ // and 400 for missing or unsupported parameters
+
+ $code = $e->getCode();
+ $this->clientError($e->getMessage(), empty($code) ? 401 : $code, 'text');
+ }
+ }
+
+ /*
+ * Display temporary OAuth credentials
+ */
+
+ function showRequestToken($token)
+ {
+ header('Content-Type: application/x-www-form-urlencoded');
+ print $token;
+ print '&oauth_callback_confirmed=true';
+ }
+
+ /* Make sure the callback parameter contains either a real URL
+ * or the string 'oob'.
+ *
+ * @todo Check for evil/banned URLs here
+ *
+ * @return boolean true or false
+ */
+
+ function verifyCallback($callback)
+ {
+ if ($callback == "oob") {
+ common_debug("OAuth request token requested for out of bounds client.");
+
+ // XXX: Should we throw an error if a client is registered as a
+ // web application but requests the pin based workflow? For now I'm
+ // allowing the workflow to proceed and issuing a pin. --Zach
+
+ return true;
+ } else {
+ return Validate::uri(
+ $callback,
+ array('allowed_schemes' => array('http', 'https'))
+ );
}
}