]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/api.php
Merge branch '0.8.x' of git@gitorious.org:statusnet/mainline into 0.9.x
[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/friends',
164                                  'statuses/replies',
165                                  'statuses/mentions',
166                                  'statuses/followers',
167                                  'favorites/favorites',
168                                  'friendships/show',
169                                  'groups/list_groups');
170
171         $fullname = "$this->api_action/$this->api_method";
172
173         // If the site is "private", all API methods except statusnet/config
174         // need authentication
175
176         if (common_config('site', 'private')) {
177             return $fullname != 'statusnet/config' || false;
178         }
179
180         // bareauth: only needs auth if without an argument or query param specifying user
181
182         if (in_array($fullname, $bareauth)) {
183
184             // Special case: friendships/show only needs auth if source_id or
185             // source_screen_name is not specified as a param
186
187             if ($fullname == 'friendships/show') {
188
189                 $source_id          = $this->arg('source_id');
190                 $source_screen_name = $this->arg('source_screen_name');
191
192                 if (empty($source_id) && empty($source_screen_name)) {
193                     return true;
194                 }
195
196                 return false;
197             }
198
199             // if all of these are empty, auth is required
200
201             $id          = $this->arg('id');
202             $user_id     = $this->arg('user_id');
203             $screen_name = $this->arg('screen_name');
204
205             if (empty($this->api_arg)
206                 && empty($id)
207                 && empty($user_id)
208                 && empty($screen_name)
209             ) {
210                 return true;
211             } else {
212                 return false;
213             }
214
215         } else if (in_array($fullname, $noauth)) {
216
217             // noauth: never needs auth
218
219             return false;
220         } else {
221
222             // everybody else needs auth
223
224             return true;
225         }
226     }
227
228     function basic_auth_process_header()
229     {
230         if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) {
231             $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
232         }
233
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
240
241             $auth_hash = base64_decode(substr($authorization_header, 6));
242             list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
243
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;
248             }
249         } else {
250             $this->auth_user = null;
251             $this->auth_pw = null;
252         }
253     }
254
255     function show_basic_auth_error()
256     {
257         header('HTTP/1.1 401 Unauthorized');
258         $msg = 'Could not authenticate you.';
259
260         if ($this->content_type == 'xml') {
261             header('Content-Type: application/xml; charset=utf-8');
262             $this->startXML();
263             $this->elementStart('hash');
264             $this->element('error', null, $msg);
265             $this->element('request', null, $_SERVER['REQUEST_URI']);
266             $this->elementEnd('hash');
267             $this->endXML();
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));
272         } else {
273             header('Content-type: text/plain');
274             print "$msg\n";
275         }
276     }
277
278     function isReadOnly($args)
279     {
280         $apiaction = $args['apiaction'];
281         $method = $args['method'];
282
283         list($cmdtext, $fmt) = explode('.', $method);
284
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'),
291             'help' => array(),
292             'notifications' => array('follow', 'leave'),
293             'statuses' => array('update', 'destroy'),
294             'users' => array()
295         );
296
297         if (array_key_exists($apiaction, $write_methods)) {
298             if (!in_array($cmdtext, $write_methods[$apiaction])) {
299                 return true;
300             }
301         }
302
303         return false;
304     }
305 }