From 8da5e98cba12c32f0b75a90d1ff0007b73f0fc8d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 05:06:35 +0000 Subject: OAuth 1.0 working now --- lib/apiauth.php | 138 ++++++++++++++++++++++---------------------------- lib/apioauth.php | 122 ++++++++++++++++++++++++++++++++++++++++++++ lib/apioauthstore.php | 69 ++++++++++++++----------- lib/router.php | 11 ++-- 4 files changed, 229 insertions(+), 111 deletions(-) create mode 100644 lib/apioauth.php (limited to 'lib') diff --git a/lib/apiauth.php b/lib/apiauth.php index 3229ab19f..431f3ac4f 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -39,7 +39,7 @@ if (!defined('STATUSNET')) { } require_once INSTALLDIR . '/lib/api.php'; -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Actions extending this class will require auth @@ -71,14 +71,14 @@ class ApiAuthAction extends ApiAction if ($this->requiresAuth()) { - $this->consumer_key = $this->arg('oauth_consumer_key'); - $this->access_token = $this->arg('oauth_token'); + $this->consumer_key = $this->arg('oauth_consumer_key'); + $this->access_token = $this->arg('oauth_token'); - if (!empty($this->access_token)) { - $this->checkOAuthRequest(); - } else { - $this->checkBasicAuthUser(); - } + if (!empty($this->access_token)) { + $this->checkOAuthRequest(); + } else { + $this->checkBasicAuthUser(); + } } return true; @@ -86,101 +86,83 @@ class ApiAuthAction extends ApiAction function checkOAuthRequest() { - common_debug("We have an OAuth request."); + common_debug("We have an OAuth request."); - $datastore = new ApiStatusNetOAuthDataStore(); - $server = new OAuthServer($datastore); - $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); - $server->add_signature_method($hmac_method); + $server->add_signature_method($hmac_method); - $this->cleanRequest(); + ApiOauthAction::cleanRequest(); - try { + try { - $req = OAuthRequest::from_request(); - $server->verify_request($req); + $req = OAuthRequest::from_request(); + $server->verify_request($req); - common_debug("Good OAuth request!"); + common_debug("Good OAuth request!"); - $app = Oauth_application::getByConsumerKey($this->consumer_key); + $app = Oauth_application::getByConsumerKey($this->consumer_key); - if (empty($app)) { + if (empty($app)) { - // this should really not happen - common_log(LOG_WARN, - "Couldn't find the OAuth app for consumer key: $this->consumer_key"); + // this should really not happen + common_log(LOG_WARN, + "Couldn't find the OAuth app for consumer key: $this->consumer_key"); - throw new OAuthException('No application for that consumer key.'); - } + throw new OAuthException('No application for that consumer key.'); + } - $appUser = Oauth_application_user::staticGet('token', - $this->access_token); + $appUser = Oauth_application_user::staticGet('token', + $this->access_token); - // XXX: check that app->id and appUser->application_id and consumer all - // match? + // XXX: check that app->id and appUser->application_id and consumer all + // match? - if (!empty($appUser)) { + if (!empty($appUser)) { - // read or read-write - $this->oauth_access_type = $appUser->access_type; + // read or read-write + $this->oauth_access_type = $appUser->access_type; - // If access_type == 0 we have either a request token - // or a bad / revoked access token + // If access_type == 0 we have either a request token + // or a bad / revoked access token - if ($this->oauth_access_type != 0) { + if ($this->oauth_access_type != 0) { - $this->auth_user = User::staticGet('id', $appUser->profile_id); + $this->auth_user = User::staticGet('id', $appUser->profile_id); - $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . - "application '%s' (id: %d)."; + $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . + "application '%s' (id: %d)."; - common_log(LOG_INFO, sprintf($msg, - $this->auth_user->nickname, - $this->auth_user->id, - $app->name, - $app->id)); - return true; - } else { - throw new OAuthException('Bad access token.'); - } - } else { + common_log(LOG_INFO, sprintf($msg, + $this->auth_user->nickname, + $this->auth_user->id, + $app->name, + $app->id)); + return true; + } else { + throw new OAuthException('Bad access token.'); + } + } else { - // also should not happen - throw new OAuthException('No user for that token.'); - } + // also should not happen + throw new OAuthException('No user for that token.'); + } - } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); - common_debug(var_export($req, true)); - $this->showOAuthError($e->getMessage()); - exit(); - } + } catch (OAuthException $e) { + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_debug(var_export($req, true)); + $this->showOAuthError($e->getMessage()); + exit(); + } } function showOAuthError($msg) { - header('HTTP/1.1 401 Unauthorized'); - header('Content-Type: text/html; charset=utf-8'); - print $msg . "\n"; - } - - function cleanRequest() - { - // kill evil effects of magical slashing - - if(get_magic_quotes_gpc() == 1) { - $_POST = array_map('stripslashes', $_POST); - $_GET = array_map('stripslashes', $_GET); - } - - // strip out the p param added in index.php - - // XXX: should we strip anything else? Or alternatively - // only allow a known list of params? - - unset($_GET['p']); - unset($_POST['p']); + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; } /** diff --git a/lib/apioauth.php b/lib/apioauth.php new file mode 100644 index 000000000..4cb8a6775 --- /dev/null +++ b/lib/apioauth.php @@ -0,0 +1,122 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @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')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apioauthstore.php'; + +/** + * Base action for API OAuth enpoints. Clean up the + * the request, and possibly some other common things + * here. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthAction extends Action +{ + /** + * Is this a read-only action? + * + * @return boolean false + */ + + function isReadOnly($args) + { + return false; + } + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle input, produce output + * + * Switches on request method; either shows the form or handles its input. + * + * @param array $args $_REQUEST data + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + self::cleanRequest(); + } + + static function cleanRequest() + { + // kill evil effects of magical slashing + + if (get_magic_quotes_gpc() == 1) { + $_POST = array_map('stripslashes', $_POST); + $_GET = array_map('stripslashes', $_GET); + } + + // strip out the p param added in index.php + + // XXX: should we strip anything else? Or alternatively + // only allow a known list of params? + + unset($_GET['p']); + unset($_POST['p']); + } + + function getCallback($url, $params) + { + foreach ($params as $k => $v) { + $url = $this->appendQueryVar($url, + OAuthUtil::urlencode_rfc3986($k), + OAuthUtil::urlencode_rfc3986($v)); + } + + return $url; + } + + 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/lib/apioauthstore.php b/lib/apioauthstore.php index 290ce8973..c39ddbb0f 100644 --- a/lib/apioauthstore.php +++ b/lib/apioauthstore.php @@ -40,44 +40,44 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore { common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); - $rt = new Token(); + $rt = new Token(); $rt->consumer_key = $consumer->key; $rt->tok = $token->key; $rt->type = 0; // request $app = Oauth_application::getByConsumerKey($consumer->key); - if (empty($app)) { - common_debug("empty app!"); - } + if (empty($app)) { + common_debug("empty app!"); + } - if ($rt->find(true) && $rt->state == 1) { // authorized + if ($rt->find(true) && $rt->state == 1) { // authorized common_debug('request token found.', __FILE__); - // find the associated user of the app + // find the associated user of the app - $appUser = new Oauth_application_user(); - $appUser->application_id = $app->id; - $appUser->token = $rt->tok; - $result = $appUser->find(true); + $appUser = new Oauth_application_user(); + $appUser->application_id = $app->id; + $appUser->token = $rt->tok; + $result = $appUser->find(true); - if (!empty($result)) { - common_debug("Oath app user found."); - } else { - common_debug("Oauth app user not found."); - return null; - } + if (!empty($result)) { + common_debug("Oath app user found."); + } else { + common_debug("Oauth app user not found."); + return null; + } - // go ahead and make the access token + // go ahead and make the access token - $at = new Token(); + $at = new Token(); $at->consumer_key = $consumer->key; $at->tok = common_good_rand(16); $at->secret = common_good_rand(16); $at->type = 1; // access $at->created = DB_DataObject_Cast::dateTime(); - if (!$at->insert()) { + if (!$at->insert()) { $e = $at->_lastError; common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__); return null; @@ -91,21 +91,30 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore } common_debug('request token "'.$rt->tok.'" updated', __FILE__); - // update the token from req to access for the user + // update the token from req to access for the user + + $orig = clone($appUser); + $appUser->token = $at->tok; - $orig = clone($appUser); - $appUser->token = $at->tok; - $result = $appUser->update($orig); + // It's at this point that we change the access type + // to whatever the application's access is. Request + // tokens should always have an access type of 0, and + // therefore be unuseable for making requests for + // protected resources. - if (empty($result)) { - common_debug('couldn\'t update OAuth app user.'); - return null; - } + $appUser->access_type = $app->access_type; + + $result = $appUser->update($orig); + + if (empty($result)) { + common_debug('couldn\'t update OAuth app user.'); + return null; + } - // Okay, good + // Okay, good - return new OAuthToken($at->tok, $at->secret); - } + return new OAuthToken($at->tok, $at->secret); + } } else { return null; diff --git a/lib/router.php b/lib/router.php index 420f5a0a1..d6e448c2f 100644 --- a/lib/router.php +++ b/lib/router.php @@ -50,8 +50,7 @@ class Router var $m = null; static $inst = null; static $bare = array('requesttoken', 'accesstoken', 'userauthorization', - 'postnotice', 'updateprofile', 'finishremotesubscribe', - 'apioauthrequesttoken', 'apioauthaccesstoken'); + 'postnotice', 'updateprofile', 'finishremotesubscribe'); static function get() { @@ -659,7 +658,13 @@ class Router 'id' => '[0-9]+') ); - $m->connect('oauth/authorize', + $m->connect('api/oauth/request_token', + array('action' => 'apioauthrequesttoken')); + + $m->connect('api/oauth/access_token', + array('action' => 'apioauthaccesstoken')); + + $m->connect('api/oauth/authorize', array('action' => 'apioauthauthorize')); foreach (array('subscriptions', 'subscribers') as $a) { -- cgit v1.2.3-54-g00ecf