]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/api.php
1bc90de1108cb2f385eb97c8ffbb145dc5e0d946
[quix0rs-gnu-social.git] / actions / api.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, StatusNet, Inc.
5  *
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.
10  *
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.
15  *
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/>.
18  *
19  * @category Actions
20  * @package  Actions
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
34  */
35
36 if (!defined('STATUSNET') && !defined('LACONICA')) {
37     exit(1);
38 }
39
40 class ApiAction extends Action
41 {
42
43     var $user;
44     var $content_type;
45     var $api_arg;
46     var $api_method;
47     var $api_action;
48     var $auth_user;
49     var $auth_pw;
50
51     function handle($args)
52     {
53         parent::handle($args);
54
55         $this->api_action = $this->arg('apiaction');
56         $method = $this->arg('method');
57         $argument = $this->arg('argument');
58         $this->basic_auth_process_header();
59
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]);
65         } else {
66
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]);
71         }
72
73         if ($this->requires_auth()) {
74             if (!isset($this->auth_user)) {
75
76                 //This header makes basic auth go
77                 header('WWW-Authenticate: Basic realm="StatusNet API"');
78
79                 //If the user hits cancel -- bam!
80                 $this->show_basic_auth_error();
81             } else {
82                 $nickname = $this->auth_user;
83                 $password = $this->auth_pw;
84                 $user = common_check_user($nickname, $password);
85
86                 if ($user) {
87                     $this->user = $user;
88                     $this->process_command();
89                 } else {
90                     //basic authentication failed
91                     list($proxy, $ip) = common_client_ip();
92
93                     common_log(LOG_WARNING, "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
94                     $this->show_basic_auth_error();
95                 }
96             }
97         } else {
98
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);
102                 if ($user) {
103                     $this->user = $user;
104                 }
105                 //Twitter doesn't throw an error if the user isn't found
106             }
107
108             $this->process_command();
109         }
110     }
111
112     function process_command()
113     {
114         $action = "twitapi$this->api_action";
115         $actionfile = INSTALLDIR."/actions/$action.php";
116
117         if (file_exists($actionfile)) {
118             include_once $actionfile;
119             $action_class = ucfirst($action)."Action";
120             $action_obj = new $action_class();
121
122             if (!$action_obj->prepare($this->args)) {
123                 return;
124             }
125
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);
131
132                 call_user_func(array($action_obj, $this->api_method), $_REQUEST, $apidata);
133             } else {
134                 $this->clientError("API method not found!", $code = 404);
135             }
136         } else {
137             $this->clientError("API method not found!", $code = 404);
138         }
139     }
140
141     // Whitelist of API methods that don't need authentication
142     function requires_auth()
143     {
144         static $noauth = array( 'statuses/public_timeline',
145                                 'statuses/show',
146                                 'users/show',
147                                 'help/test',
148                                 'help/downtime_schedule',
149                                 'statusnet/version',
150                                 'statusnet/config',
151                                 'statusnet/wadl',
152                                 'tags/timeline',
153                                 'oembed/oembed',
154                                 'groups/show',
155                                 'groups/timeline',
156                                 'groups/list_all',
157                                 'groups/membership',
158                                 'groups/is_member',
159                                 'groups/timeline');
160
161         static $bareauth = array('statuses/user_timeline',
162                                  'statuses/friends_timeline',
163                                  'statuses/home_timeline',
164                                  'statuses/friends',
165                                  'statuses/replies',
166                                  'statuses/mentions',
167                                  'statuses/followers',
168                                  'favorites/favorites',
169                                  'friendships/show',
170                                  'groups/list_groups');
171
172         $fullname = "$this->api_action/$this->api_method";
173
174         // If the site is "private", all API methods except statusnet/config
175         // need authentication
176
177         if (common_config('site', 'private')) {
178             return $fullname != 'statusnet/config' || false;
179         }
180
181         // bareauth: only needs auth if without an argument or query param specifying user
182
183         if (in_array($fullname, $bareauth)) {
184
185             // Special case: friendships/show only needs auth if source_id or
186             // source_screen_name is not specified as a param
187
188             if ($fullname == 'friendships/show') {
189
190                 $source_id          = $this->arg('source_id');
191                 $source_screen_name = $this->arg('source_screen_name');
192
193                 if (empty($source_id) && empty($source_screen_name)) {
194                     return true;
195                 }
196
197                 return false;
198             }
199
200             // if all of these are empty, auth is required
201
202             $id          = $this->arg('id');
203             $user_id     = $this->arg('user_id');
204             $screen_name = $this->arg('screen_name');
205
206             if (empty($this->api_arg)
207                 && empty($id)
208                 && empty($user_id)
209                 && empty($screen_name)
210             ) {
211                 return true;
212             } else {
213                 return false;
214             }
215
216         } else if (in_array($fullname, $noauth)) {
217
218             // noauth: never needs auth
219
220             return false;
221         } else {
222
223             // everybody else needs auth
224
225             return true;
226         }
227     }
228
229     function basic_auth_process_header()
230     {
231         if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) {
232             $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
233         }
234
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
241
242             $auth_hash = base64_decode(substr($authorization_header, 6));
243             list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
244
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;
249             }
250         } else {
251             $this->auth_user = null;
252             $this->auth_pw = null;
253         }
254     }
255
256     function show_basic_auth_error()
257     {
258         header('HTTP/1.1 401 Unauthorized');
259         $msg = 'Could not authenticate you.';
260
261         if ($this->content_type == 'xml') {
262             header('Content-Type: application/xml; charset=utf-8');
263             $this->startXML();
264             $this->elementStart('hash');
265             $this->element('error', null, $msg);
266             $this->element('request', null, $_SERVER['REQUEST_URI']);
267             $this->elementEnd('hash');
268             $this->endXML();
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));
273         } else {
274             header('Content-type: text/plain');
275             print "$msg\n";
276         }
277     }
278
279     function isReadOnly($args)
280     {
281         $apiaction = $args['apiaction'];
282         $method = $args['method'];
283
284         list($cmdtext, $fmt) = explode('.', $method);
285
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'),
292             'help' => array(),
293             'notifications' => array('follow', 'leave'),
294             'statuses' => array('update', 'destroy'),
295             'users' => array()
296         );
297
298         if (array_key_exists($apiaction, $write_methods)) {
299             if (!in_array($cmdtext, $write_methods[$apiaction])) {
300                 return true;
301             }
302         }
303
304         return false;
305     }
306 }