diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/apiauth.php | 174 | ||||
-rw-r--r-- | lib/apibareauth.php | 79 | ||||
-rw-r--r-- | lib/router.php | 102 | ||||
-rw-r--r-- | lib/twitterapi.php | 45 |
4 files changed, 372 insertions, 28 deletions
diff --git a/lib/apiauth.php b/lib/apiauth.php new file mode 100644 index 000000000..f0b4b6bf7 --- /dev/null +++ b/lib/apiauth.php @@ -0,0 +1,174 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Base class for API actions that require authentication + * + * 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 API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @copyright 2009 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/twitterapi.php'; + +/** + * Actions extending this class will require auth + * + * @category API + * @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 ApiAuthAction extends TwitterapiAction +{ + + var $auth_user = null; + + /** + * Does this API resource require authentication? + * + * @return boolean true + */ + + function requiresAuth() + { + return true; + } + + /** + * Check for a user specified via HTTP basic auth. If there isn't + * one, try to get one by outputting the basic auth header. + * + * @return boolean true or false + */ + + function checkBasicAuthUser() + { + $this->basicAuthProcessHeader(); + + if (!isset($this->auth_user)) { + header('WWW-Authenticate: Basic realm="StatusNet API"'); + + // show error if the user clicks 'cancel' + + $this->showBasicAuthError(); + return false; + + } else { + $nickname = $this->auth_user; + $password = $this->auth_pw; + $this->auth_user = common_check_user($nickname, $password); + + if (empty($this->auth_user)) { + + // basic authentication failed + + list($proxy, $ip) = common_client_ip(); + common_log( + LOG_WARNING, + 'Failed API auth attempt, nickname = ' . + "$nickname, proxy = $proxy, ip = $ip." + ); + $this->showBasicAuthError(); + return false; + } + } + return true; + } + + /** + * Read the HTTP headers and set the auth user. Decodes HTTP_AUTHORIZATION + * param to support basic auth when PHP is running in CGI mode. + * + * @return void + */ + + function basicAuthProcessHeader() + { + if (isset($_SERVER['AUTHORIZATION']) + || isset($_SERVER['HTTP_AUTHORIZATION']) + ) { + $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION']) + ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; + } + + if (isset($_SERVER['PHP_AUTH_USER'])) { + $this->auth_user = $_SERVER['PHP_AUTH_USER']; + $this->auth_pw = $_SERVER['PHP_AUTH_PW']; + } elseif (isset($authorization_header) + && strstr(substr($authorization_header, 0, 5), 'Basic')) { + + // decode the HTTP_AUTHORIZATION header on php-cgi server self + // on fcgid server the header name is AUTHORIZATION + + $auth_hash = base64_decode(substr($authorization_header, 6)); + list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash); + + // set all to null on a empty basic auth request + + if ($this->auth_user == "") { + $this->auth_user = null; + $this->auth_pw = null; + } + } else { + $this->auth_user = null; + $this->auth_pw = null; + } + } + + /** + * Output an authentication error message. Use XML or JSON if one + * of those formats is specified, otherwise output plain text + * + * @return void + */ + + function showBasicAuthError() + { + header('HTTP/1.1 401 Unauthorized'); + $msg = 'Could not authenticate you.'; + + if ($this->arg('format') == 'xml') { + header('Content-Type: application/xml; charset=utf-8'); + $this->startXML(); + $this->elementStart('hash'); + $this->element('error', null, $msg); + $this->element('request', null, $_SERVER['REQUEST_URI']); + $this->elementEnd('hash'); + $this->endXML(); + } elseif ($this->arg('format') == 'json') { + header('Content-Type: application/json; charset=utf-8'); + $error_array = array('error' => $msg, + 'request' => $_SERVER['REQUEST_URI']); + print(json_encode($error_array)); + } else { + header('Content-type: text/plain'); + print "$msg\n"; + } + } + +} diff --git a/lib/apibareauth.php b/lib/apibareauth.php new file mode 100644 index 000000000..a99d450ec --- /dev/null +++ b/lib/apibareauth.php @@ -0,0 +1,79 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Base class for API actions that require "bare auth". Bare auth means + * authentication is required only if the action is called without an argument + * or query param specifying user id. + * + * 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 API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @copyright 2009 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/apiauth.php'; + +/** + * Actions extending this class will require auth unless a target + * user ID has been specified + * + * @category API + * @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 ApiBareAuthAction extends ApiAuthAction +{ + /** + * Does this API resource require authentication? + * + * @return boolean true or false + */ + + function requiresAuth() + { + // If the site is "private", all API methods except statusnet/config + // need authentication + + if (common_config('site', 'private')) { + return true; + } + + // check whether a user has been specified somehow + + $id = $this->arg('id'); + $user_id = $this->arg('user_id'); + $screen_name = $this->arg('screen_name'); + + if (empty($id) && empty($user_id) && empty($screen_name)) { + return true; + } + + return false; + } + +}
\ No newline at end of file diff --git a/lib/router.php b/lib/router.php index c18f273ed..b3bb240d9 100644 --- a/lib/router.php +++ b/lib/router.php @@ -266,15 +266,81 @@ class Router // statuses API + $m->connect('api/statuses/public_timeline.:format', + array('action' => 'ApiPublicTimeline', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends_timeline.:format', + array('action' => 'ApiFriendsTimeline', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends_timeline/:id.:format', + array('action' => 'ApiFriendsTimeline', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + $m->connect('api/statuses/home_timeline.:format', + array('action' => 'ApiFriendsTimeline', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/home_timeline/:id.:format', + array('action' => 'ApiFriendsTimeline', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/user_timeline.:format', + array('action' => 'ApiUserTimeline', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/user_timeline/:id.:format', + array('action' => 'ApiUserTimeline', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/mentions.:format', + array('action' => 'ApiMentions', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/mentions/:id.:format', + array('action' => 'ApiMentions', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/replies.:format', + array('action' => 'ApiMentions', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/replies/:id.:format', + array('action' => 'ApiMentions', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends.:format', + array('action' => 'ApiFriends', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/friends/:id.:format', + array('action' => 'ApiFriends', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/followers.:format', + array('action' => 'ApiFollowers', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/followers/:id.:format', + array('action' => 'ApiFollowers', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + $m->connect('api/statuses/:method', array('action' => 'api', 'apiaction' => 'statuses'), - array('method' => '(public_timeline|home_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?')); + array('method' => '(update|show|featured)(\.(atom|rss|xml|json))?')); $m->connect('api/statuses/:method/:argument', array('action' => 'api', 'apiaction' => 'statuses'), - array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); + array('method' => '(show|destroy)')); // users @@ -329,29 +395,21 @@ class Router // Social graph - $m->connect('api/friends/ids/:argument', - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'friendsIDs')); + $m->connect('api/friends/ids/:id.:format', + array('action' => 'apiFriends', + 'ids_only' => true)); - foreach (array('xml', 'json') as $e) { - $m->connect('api/friends/ids.'.$e, - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'friendsIDs.'.$e)); - } + $m->connect('api/followers/ids/:id.:format', + array('action' => 'apiFollowers', + 'ids_only' => true)); - $m->connect('api/followers/ids/:argument', - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'followersIDs')); + $m->connect('api/friends/ids.:format', + array('action' => 'apiFriends', + 'ids_only' => true)); - foreach (array('xml', 'json') as $e) { - $m->connect('api/followers/ids.'.$e, - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'followersIDs.'.$e)); - } + $m->connect('api/followers/ids.:format', + array('action' => 'apiFollowers', + 'ids_only' => true)); // account diff --git a/lib/twitterapi.php b/lib/twitterapi.php index 4a5de6ab3..708738832 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -24,8 +24,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { class TwitterapiAction extends Action { - var $auth_user; - /** * Initialization. * @@ -934,7 +932,7 @@ class TwitterapiAction extends Action return; } - function clientError($msg, $code = 400, $content_type = 'json') + function clientError($msg, $code = 400, $format = 'xml') { $action = $this->trimmed('action'); @@ -948,20 +946,23 @@ class TwitterapiAction extends Action header('HTTP/1.1 '.$code.' '.$status_string); - if ($content_type == 'xml') { + if ($format == 'xml') { $this->init_document('xml'); $this->elementStart('hash'); $this->element('error', null, $msg); $this->element('request', null, $_SERVER['REQUEST_URI']); $this->elementEnd('hash'); $this->end_document('xml'); - } else { + } elseif ($format == 'json'){ $this->init_document('json'); $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); print(json_encode($error_array)); $this->end_document('json'); - } + } else { + // If user didn't request a useful format, throw a regular client error + throw new ClientException($msg, $code); + } } function serverError($msg, $code = 500, $content_type = 'json') @@ -1073,6 +1074,38 @@ class TwitterapiAction extends Action } } + function getTargetUser($id) + { + if (empty($id)) { + + // Twitter supports these other ways of passing the user ID + if (is_numeric($this->arg('id'))) { + return User::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return User::staticGet('nickname', $nickname); + } else if ($this->arg('user_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('user_id'))) { + return User::staticGet('id', $this->arg('user_id')); + } + } else if ($this->arg('screen_name')) { + $nickname = common_canonical_nickname($this->arg('screen_name')); + return User::staticGet('nickname', $nickname); + } else { + // Fall back to trying the currently authenticated user + return $this->auth_user; + } + + } else if (is_numeric($id)) { + return User::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + return User::staticGet('nickname', $nickname); + } + } + function get_group($id, $apidata=null) { if (empty($id)) { |