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',
166 'statuses/followers',
167 'favorites/favorites',
169 'groups/list_groups');
171 $fullname = "$this->api_action/$this->api_method";
173 // If the site is "private", all API methods except statusnet/config
174 // need authentication
176 if (common_config('site', 'private')) {
177 return $fullname != 'statusnet/config' || false;
180 // bareauth: only needs auth if without an argument or query param specifying user
182 if (in_array($fullname, $bareauth)) {
184 // Special case: friendships/show only needs auth if source_id or
185 // source_screen_name is not specified as a param
187 if ($fullname == 'friendships/show') {
189 $source_id = $this->arg('source_id');
190 $source_screen_name = $this->arg('source_screen_name');
192 if (empty($source_id) && empty($source_screen_name)) {
199 // if all of these are empty, auth is required
201 $id = $this->arg('id');
202 $user_id = $this->arg('user_id');
203 $screen_name = $this->arg('screen_name');
205 if (empty($this->api_arg)
208 && empty($screen_name)
215 } else if (in_array($fullname, $noauth)) {
217 // noauth: never needs auth
222 // everybody else needs auth
228 function basic_auth_process_header()
230 if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) {
231 $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
234 if (isset($_SERVER['PHP_AUTH_USER'])) {
235 $this->auth_user = $_SERVER['PHP_AUTH_USER'];
236 $this->auth_pw = $_SERVER['PHP_AUTH_PW'];
237 } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) {
238 // decode the HTTP_AUTHORIZATION header on php-cgi server self
239 // on fcgid server the header name is AUTHORIZATION
241 $auth_hash = base64_decode(substr($authorization_header, 6));
242 list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
244 // set all to null on a empty basic auth request
245 if ($this->auth_user == "") {
246 $this->auth_user = null;
247 $this->auth_pw = null;
250 $this->auth_user = null;
251 $this->auth_pw = null;
255 function show_basic_auth_error()
257 header('HTTP/1.1 401 Unauthorized');
258 $msg = 'Could not authenticate you.';
260 if ($this->content_type == 'xml') {
261 header('Content-Type: application/xml; charset=utf-8');
263 $this->elementStart('hash');
264 $this->element('error', null, $msg);
265 $this->element('request', null, $_SERVER['REQUEST_URI']);
266 $this->elementEnd('hash');
268 } else if ($this->content_type == 'json') {
269 header('Content-Type: application/json; charset=utf-8');
270 $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
271 print(json_encode($error_array));
273 header('Content-type: text/plain');
278 function isReadOnly($args)
280 $apiaction = $args['apiaction'];
281 $method = $args['method'];
283 list($cmdtext, $fmt) = explode('.', $method);
285 static $write_methods = array(
286 'account' => array('update_location', 'update_delivery_device', 'end_session'),
287 'blocks' => array('create', 'destroy'),
288 'direct_messages' => array('create', 'destroy'),
289 'favorites' => array('create', 'destroy'),
290 'friendships' => array('create', 'destroy'),
292 'notifications' => array('follow', 'leave'),
293 'statuses' => array('update', 'destroy'),
297 if (array_key_exists($apiaction, $write_methods)) {
298 if (!in_array($cmdtext, $write_methods[$apiaction])) {