3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 * @author Evan Prodromou <evan@status.net>
22 * @author Brenda Wallace <shiny@cpan.org>
23 * @author Jeffery To <jeffery.to@gmail.com>
24 * @author Robin Millette <millette@controlyourself.ca>
25 * @author Tom Adams <tom@holizz.com>
26 * @author Christopher Vollick <psycotica0@gmail.com>
27 * @author CiaranG <ciaran@ciarang.com>
28 * @author Craig Andrews <candrews@integralblue.com>
29 * @author Gina Haeussge <osd@foosel.net>
30 * @author Mike Cochrane <mikec@mikenz.geek.nz>
31 * @author Sarven Capadisli <csarven@status.net>
32 * @license GNU Affero General Public License http://www.gnu.org/licenses/
33 * @link http://status.net
36 if (!defined('STATUSNET') && !defined('LACONICA')) {
40 class ApiAction extends Action
51 function handle($args)
53 parent::handle($args);
55 $this->api_action = $this->arg('apiaction');
56 $method = $this->arg('method');
57 $argument = $this->arg('argument');
58 $this->basic_auth_process_header();
60 if (isset($argument)) {
61 $cmdext = explode('.', $argument);
62 $this->api_arg = $cmdext[0];
63 $this->api_method = $method;
64 $this->content_type = strtolower($cmdext[1]);
67 //Requested format / content-type will be an extension on the method
68 $cmdext = explode('.', $method);
69 $this->api_method = $cmdext[0];
70 $this->content_type = strtolower($cmdext[1]);
73 if ($this->requires_auth()) {
74 if (!isset($this->auth_user)) {
76 //This header makes basic auth go
77 header('WWW-Authenticate: Basic realm="StatusNet API"');
79 //If the user hits cancel -- bam!
80 $this->show_basic_auth_error();
82 $nickname = $this->auth_user;
83 $password = $this->auth_pw;
84 $user = common_check_user($nickname, $password);
88 $this->process_command();
90 //basic authentication failed
91 list($proxy, $ip) = common_client_ip();
93 common_log(LOG_WARNING, "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
94 $this->show_basic_auth_error();
99 // Caller might give us a username even if not required
100 if (isset($this->auth_user)) {
101 $user = User::staticGet('nickname', $this->auth_user);
105 //Twitter doesn't throw an error if the user isn't found
108 $this->process_command();
112 function process_command()
114 $action = "twitapi$this->api_action";
115 $actionfile = INSTALLDIR."/actions/$action.php";
117 if (file_exists($actionfile)) {
118 include_once $actionfile;
119 $action_class = ucfirst($action)."Action";
120 $action_obj = new $action_class();
122 if (!$action_obj->prepare($this->args)) {
126 if (method_exists($action_obj, $this->api_method)) {
127 $apidata = array( 'content-type' => $this->content_type,
128 'api_method' => $this->api_method,
129 'api_arg' => $this->api_arg,
130 'user' => $this->user);
132 call_user_func(array($action_obj, $this->api_method), $_REQUEST, $apidata);
134 $this->clientError("API method not found!", $code = 404);
137 $this->clientError("API method not found!", $code = 404);
141 // Whitelist of API methods that don't need authentication
142 function requires_auth()
144 static $noauth = array( 'statuses/public_timeline',
148 'help/downtime_schedule',
161 static $bareauth = array('statuses/user_timeline',
162 'statuses/friends_timeline',
163 'statuses/home_timeline',
167 'statuses/followers',
168 'favorites/favorites',
170 'groups/list_groups');
172 $fullname = "$this->api_action/$this->api_method";
174 // If the site is "private", all API methods except statusnet/config
175 // need authentication
177 if (common_config('site', 'private')) {
178 return $fullname != 'statusnet/config' || false;
181 // bareauth: only needs auth if without an argument or query param specifying user
183 if (in_array($fullname, $bareauth)) {
185 // Special case: friendships/show only needs auth if source_id or
186 // source_screen_name is not specified as a param
188 if ($fullname == 'friendships/show') {
190 $source_id = $this->arg('source_id');
191 $source_screen_name = $this->arg('source_screen_name');
193 if (empty($source_id) && empty($source_screen_name)) {
200 // if all of these are empty, auth is required
202 $id = $this->arg('id');
203 $user_id = $this->arg('user_id');
204 $screen_name = $this->arg('screen_name');
206 if (empty($this->api_arg)
209 && empty($screen_name)
216 } else if (in_array($fullname, $noauth)) {
218 // noauth: never needs auth
223 // everybody else needs auth
229 function basic_auth_process_header()
231 if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) {
232 $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
235 if (isset($_SERVER['PHP_AUTH_USER'])) {
236 $this->auth_user = $_SERVER['PHP_AUTH_USER'];
237 $this->auth_pw = $_SERVER['PHP_AUTH_PW'];
238 } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) {
239 // decode the HTTP_AUTHORIZATION header on php-cgi server self
240 // on fcgid server the header name is AUTHORIZATION
242 $auth_hash = base64_decode(substr($authorization_header, 6));
243 list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
245 // set all to null on a empty basic auth request
246 if ($this->auth_user == "") {
247 $this->auth_user = null;
248 $this->auth_pw = null;
251 $this->auth_user = null;
252 $this->auth_pw = null;
256 function show_basic_auth_error()
258 header('HTTP/1.1 401 Unauthorized');
259 $msg = 'Could not authenticate you.';
261 if ($this->content_type == 'xml') {
262 header('Content-Type: application/xml; charset=utf-8');
264 $this->elementStart('hash');
265 $this->element('error', null, $msg);
266 $this->element('request', null, $_SERVER['REQUEST_URI']);
267 $this->elementEnd('hash');
269 } else if ($this->content_type == 'json') {
270 header('Content-Type: application/json; charset=utf-8');
271 $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
272 print(json_encode($error_array));
274 header('Content-type: text/plain');
279 function isReadOnly($args)
281 $apiaction = $args['apiaction'];
282 $method = $args['method'];
284 list($cmdtext, $fmt) = explode('.', $method);
286 static $write_methods = array(
287 'account' => array('update_location', 'update_delivery_device', 'end_session'),
288 'blocks' => array('create', 'destroy'),
289 'direct_messages' => array('create', 'destroy'),
290 'favorites' => array('create', 'destroy'),
291 'friendships' => array('create', 'destroy'),
293 'notifications' => array('follow', 'leave'),
294 'statuses' => array('update', 'destroy'),
298 if (array_key_exists($apiaction, $write_methods)) {
299 if (!in_array($cmdtext, $write_methods[$apiaction])) {