]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/userauthorization.php
29a0c9d6acdaa149462941e2442e238f194fcede
[quix0rs-gnu-social.git] / actions / userauthorization.php
1 <?php
2 /*
3  * Laconica - a distributed open-source microblogging tool
4  * Copyright (C) 2008, Controlez-Vous, 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
20 if (!defined('LACONICA')) { exit(1); }
21
22 require_once(INSTALLDIR.'/lib/omb.php');
23 define('TIMESTAMP_THRESHOLD', 300);
24
25 class UserauthorizationAction extends Action {
26         function handle($args) {
27                 parent::handle($args);
28                 
29                 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
30                         # We've shown the form, now post user's choice
31                         $this->send_authorization();
32                 } else {
33                         try {
34                                 common_debug('userauthorization.php - fetching request');
35                                 # We get called after login if we have a stored request
36                                 $req = $this->get_stored_request();
37                                 if (!$req) {
38                                         # this must be a new request
39                                         common_debug('userauthorization.php - getting new request');
40                                         $req = $this->get_new_request();
41                                         if (!$req) {
42                                                 common_server_error(_t('No request found!'));
43                                         }
44                                         common_debug('userauthorization.php - validating request');
45                                         # XXX: only validate new requests, since nonce is one-time use
46                                         $this->validate_request($req);
47                                 }
48                         } catch (OAuthException $e) {
49                                 $this->clear_request();
50                                 common_server_error($e->getMessage());
51                                 return;
52                         }
53                         
54                         if (common_logged_in()) {
55                                 common_debug('userauthorization.php - showing form');
56                                 $this->show_form($req);
57                         } else {
58                                 common_debug('userauthorization.php - storing request in session');
59                                 # Go log in, and then come back
60                                 $this->store_request($req);
61                                 common_debug('userauthorization.php - saving URL for returnto');
62                                 common_set_returnto(common_local_url('userauthorization'));
63                                 common_debug('userauthorization.php - redirecting to login');                           
64                                 common_redirect(common_local_url('login'));
65                         }
66                 }
67         }
68
69         function show_form($req) {
70
71                 $nickname = $req->get_parameter('omb_listenee_nickname');
72                 $profile = $req->get_parameter('omb_listenee_profile');
73                 $license = $req->get_parameter('omb_listenee_license');
74                 $fullname = $req->get_parameter('omb_listenee_fullname');
75                 $homepage = $req->get_parameter('omb_listenee_homepage');
76                 $bio = $req->get_parameter('omb_listenee_bio');
77                 $location = $req->get_parameter('omb_listenee_location');
78                 $avatar = $req->get_parameter('omb_listenee_avatar');
79                 
80                 common_show_header(_t('Authorize subscription'));
81                 common_element('p', _t('Please check these details to make sure '.
82                                                            'that you want to subscribe to this user\'s notices. '.
83                                                            'If you didn\'t just ask to subscribe to someone\'s notices, '.
84                                                            'click "Cancel".'));
85                 common_element_start('div', 'profile');
86                 if ($avatar) {
87                         common_element('img', array('src' => $avatar,
88                                                                                 'class' => 'avatar profile',
89                                                                                 'width' => AVATAR_PROFILE_SIZE,
90                                                                                 'height' => AVATAR_PROFILE_SIZE,
91                                                                                 'alt' => $nickname));
92                 }
93                 common_element('a', array('href' => $profile,
94                                                                   'class' => 'external profile nickname'),
95                                            $nickname);
96                 if ($fullname) {
97                         common_element_start('div', 'fullname');
98                         if ($homepage) {
99                                 common_element('a', array('href' => $homepage),
100                                                            $fullname);
101                         } else {
102                                 common_text($fullname);
103                         }
104                         common_element_end('div');
105                 }
106                 if ($location) {
107                         common_element('div', 'location', $location);
108                 }
109                 if ($bio) {
110                         common_element('div', 'bio', $bio);
111                 }
112                 common_element_start('div', 'license');
113                 common_element('a', array('href' => $license,
114                                                                   'class' => 'license'),
115                                            $license);
116                 common_element_end('div');
117                 common_element_end('div');
118                 common_element_start('form', array('method' => 'POST',
119                                                                                    'id' => 'userauthorization',
120                                                                                    'name' => 'userauthorization',
121                                                                                    'action' => common_local_url('userauthorization')));
122                 common_submit('accept', _t('Accept'));
123                 common_submit('reject', _t('Reject'));          
124                 common_element_end('form');
125                 common_show_footer();
126         }
127         
128         function send_authorization() {
129                 $req = $this->get_stored_request();
130                 
131                 if (!$req) {
132                         common_user_error(_t('No authorization request!'));
133                         return;
134                 }
135
136                 $callback = $req->get_parameter('oauth_callback');
137
138                 if ($this->arg('accept')) {
139                         $this->authorize_token($req);
140                         $this->save_remote_profile($req);
141                         if (!$callback) {
142                                 $this->show_accept_message($req->get_parameter('oauth_token'));
143                         } else {
144                                 $params = array();
145                                 $params['oauth_token'] = $req->get_parameter('oauth_token');
146                                 $params['omb_version'] = OMB_VERSION_01;
147                                 $user = User::staticGet('uri', $req->get_parameter('omb_listener'));
148                                 $profile = $user->getProfile();
149                                 $params['omb_listener_nickname'] = $user->nickname;
150                                 $params['omb_listener_profile'] = common_local_url('showstream',
151                                                                                                                                    array('nickname' => $user->nickname));
152                                 if ($profile->fullname) {
153                                         $params['omb_listener_fullname'] = $profile->fullname;
154                                 }
155                                 if ($profile->homepage) {
156                                         $params['omb_listener_homepage'] = $profile->homepage;
157                                 }
158                                 if ($profile->bio) {
159                                         $params['omb_listener_bio'] = $profile->bio;
160                                 }
161                                 if ($profile->location) {
162                                         $params['omb_listener_location'] = $profile->location;
163                                 }
164                                 $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
165                                 if ($avatar) {
166                                         $params['omb_listener_avatar'] = $avatar->url;
167                                 }
168                                 $parts = array();
169                                 foreach ($params as $k => $v) {
170                                         $parts[] = $k . '=' . OAuthUtil::urlencodeRFC3986($v);
171                                 }
172                                 $query_string = implode('&', $parts);
173                                 $parsed = parse_url($callback);
174                                 $url = $callback . (($parsed['query']) ? '&' : '?') . $query_string;
175                                 common_redirect($url, 303);
176                         }
177                 } else {
178                         if (!$callback) {
179                                 $this->show_reject_message();
180                         } else {
181                                 # XXX: not 100% sure how to signal failure... just redirect without token?
182                                 common_redirect($callback, 303);
183                         }
184                 }
185         }
186
187         function authorize_token(&$req) {
188                 $consumer_key = @$req->get_parameter('oauth_consumer_key');
189                 $token_field = @$req->get_parameter('oauth_token');
190                 $rt = new Token();
191                 $rt->consumer_key = $consumer_key;
192                 $rt->tok = $token_field;
193                 if ($rt->find(TRUE)) {
194                         $orig_rt = clone($rt);
195                         $rt->state = 1; # Authorized but not used
196                         if ($rt->update($orig_rt)) {
197                                 return true;
198                         }
199                 }
200                 return FALSE;
201         }
202
203         # XXX: refactor with similar code in finishremotesubscribe.php
204         
205         function save_remote_profile(&$req) {
206                 # FIXME: we should really do this when the consumer comes
207                 # back for an access token. If they never do, we've got stuff in a 
208                 # weird state.
209                 
210                 $fullname = $req->get_parameter('omb_listenee_fullname');
211                 $profile_url = $req->get_parameter('omb_listenee_profile');             
212                 $homepage = $req->get_parameter('omb_listenee_homepage');
213                 $bio = $req->get_parameter('omb_listenee_bio');
214                 $location = $req->get_parameter('omb_listenee_location');
215                 $avatar_url = $req->get_parameter('omb_listenee_avatar');
216                 
217                 $listenee = $req->get_parameter('omb_listenee');
218                 $remote = Remote_profile::staticGet('uri', $listenee);
219                 
220                 if ($remote) {
221                         $exists = true;
222                         $profile = Profile::staticGet($remote->id);
223                         $orig_remote = clone($remote);
224                         $orig_profile = clone($profile);
225                 } else {
226                         $exists = false;
227                         $remote = new Remote_profile();
228                         $remote->uri = $omb['listener'];
229                         $profile = new Profile();
230                 }
231
232                 $profile->nickname = $nickname;
233                 $profile->profileurl = $profile_url;
234                 
235                 if ($fullname) {
236                         $profile->fullname = $fullname;
237                 }
238                 if ($homepage) {
239                         $profile->homepage = $homepage;
240                 }
241                 if ($bio) {
242                         $profile->bio = $bio;
243                 }
244                 if ($location) {
245                         $profile->location = $location;
246                 }
247                 
248                 if ($exists) {
249                         $profile->update($orig_profile);
250                 } else {
251                         $profile->created = DB_DataObject_Cast::dateTime(); # current time
252                         $id = $profile->insert();
253                         $remote->id = $id;
254                 }
255
256                 if ($avatar_url) {
257                         $this->add_avatar($avatar_url);
258                 }
259
260                 if ($exists) {
261                         $remote->update($orig_remote);
262                 } else {
263                         $remote->created = DB_DataObject_Cast::dateTime(); # current time
264                         $remote->insert();
265                 }
266
267                 $user = common_current_user();
268                 $datastore = omb_oauth_datastore();
269                 $consumer = $this->get_consumer($datastore, $req);
270                 $token = $this->get_token($datastore, $req, $consumer);
271
272                 $sub = new Subscription();
273                 $sub->subscriber = $user->id;
274                 $sub->subscribed = $remote->id;
275                 $sub->token = $token->key; # NOTE: request token, not valid for use!
276                 $sub->created = DB_DataObject_Cast::dateTime(); # current time
277                 
278                 if (!$sub->insert()) {
279                         common_user_error(_t('Couldn\'t insert new subscription.'));
280                         return;
281                 }
282         }
283         
284         function show_accept_message($tok) {
285                 common_show_header(_t('Subscription authorized'));
286                 common_element('p', NULL, 
287                                            _t('The subscription has been authorized, but no '.
288                                                   'callback URL was passed. Check with the site\'s instructions for '.
289                                                   'details on how to authorize the subscription. Your subscription token is:'));
290                 common_element('blockquote', 'token', $tok);
291                 common_show_footer();
292         }
293
294         function show_reject_message($tok) {
295                 common_show_header(_t('Subscription rejected'));
296                 common_element('p', NULL, 
297                                            _t('The subscription has been rejected, but no '.
298                                                   'callback URL was passed. Check with the site\'s instructions for '.
299                                                   'details on how to fully reject the subscription.'));
300                 common_show_footer();
301         }
302         
303         function store_request($req) {
304                 common_ensure_session();
305                 $_SESSION['userauthorizationrequest'] = $req;
306         }
307         
308         function clear_request($req) {
309                 common_ensure_session();
310                 unset($_SESSION['userauthorizationrequest']);
311         }
312         
313         function get_stored_request() {
314                 common_ensure_session();                
315                 $req = $_SESSION['userauthorizationrequest'];
316                 return $req;
317         }
318
319         function get_new_request() {
320                 $req = OAuthRequest::from_request();
321                 return $req;
322         }
323         
324         # Throws an OAuthException if anything goes wrong
325         
326         function validate_request(&$req) {
327                 # OAuth stuff -- have to copy from OAuth.php since they're
328                 # all private methods, and there's no user-authentication method
329                 $this->check_version($req);
330                 $datastore = omb_oauth_datastore();
331                 $consumer = $this->get_consumer($datastore, $req);
332                 $token = $this->get_token($datastore, $req, $consumer);
333                 $this->check_timestamp($req);
334                 $this->check_nonce($datastore, $req, $consumer, $token);
335                 $this->check_signature($req, $consumer, $token);
336                 $this->validate_omb($req);
337                 return true;
338         }
339
340         function validate_omb(&$req) {
341                 foreach (array('omb_version', 'omb_listener', 'omb_listenee',
342                                            'omb_listenee_profile', 'omb_listenee_nickname',
343                                            'omb_listenee_license') as $param)
344                 {
345                         if (!$req->get_parameter($param)) {
346                                 throw new OAuthException("Required parameter '$param' not found");
347                         }
348                 }
349                 # Now, OMB stuff
350                 $version = $req->get_parameter('omb_version');
351                 if ($version != OMB_VERSION_01) {
352                         throw new OAuthException("OpenMicroBlogging version '$version' not supported");
353                 }
354                 $user = User::staticGet('uri', $req->get_parameter('omb_listener'));
355                 if (!$user) {
356                         throw new OAuthException("Listener URI '$listener' not found here");
357                 }
358                 $listenee = $req->get_parameter('omb_listenee');
359                 if (!Validate::uri($listenee)) {
360                         throw new OAuthException("Listenee URI '$listenee' not a valid URI");
361                 } else if (strlen($listenee) > 255) {
362                         throw new OAuthException("Listenee URI '$listenee' too long");
363                 }
364                 $nickname = $req->get_parameter('omb_listenee_nickname');
365                 if (!Validate::string($nickname, array('min_length' => 1,
366                                                                                            'max_length' => 64,
367                                                                                            'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
368                         throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
369                 }
370                 $profile = $req->get_parameter('omb_listenee_profile');
371                 if (!common_valid_http_url($profile)) {
372                         throw new OAuthException("Invalid profile URL '$profile'.");
373                 }
374                 $license = $req->get_parameter('omb_listenee_license');
375                 if (!common_valid_http_url($license)) {
376                         throw new OAuthException("Invalid license URL '$license'.");
377                 }
378                 # optional stuff
379                 $fullname = $req->get_parameter('omb_listenee_fullname');
380                 if ($fullname && strlen($fullname) > 255) {
381                         throw new OAuthException("Full name '$fullname' too long.");
382                 }
383                 $homepage = $req->get_parameter('omb_listenee_homepage');
384                 if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) {
385                         throw new OAuthException("Invalid homepage '$homepage'");
386                 }
387                 $bio = $req->get_parameter('omb_listenee_bio');
388                 if ($bio && strlen($bio) > 140) {
389                         throw new OAuthException("Bio too long '$bio'");
390                 }
391                 $location = $req->get_parameter('omb_listenee_location');
392                 if ($location && strlen($location) > 255) {
393                         throw new OAuthException("Location too long '$location'");
394                 }
395                 $avatar = $req->get_parameter('omb_listenee_avatar');
396                 if ($avatar && (!common_valid_http_url($avatar) || strlen($avatar) > 255)) {
397                         throw new OAuthException("Invalid avatar '$avatar'");
398                 }
399                 $callback = $req->get_parameter('oauth_callback');
400                 if ($avatar && common_valid_http_url($callback)) {
401                         throw new OAuthException("Invalid callback URL '$callback'");
402                 }
403         }
404         
405         # Snagged from OAuthServer
406         
407         function check_version(&$req) {
408                 $version = $req->get_parameter("oauth_version");
409                 if (!$version) {
410                         $version = 1.0;
411                 }
412                 if ($version != 1.0) {
413                         throw new OAuthException("OAuth version '$version' not supported");
414                 }
415                 return $version;
416         }
417
418         # Snagged from OAuthServer
419         
420         function get_consumer($datastore, $req) {
421                 $consumer_key = @$req->get_parameter("oauth_consumer_key");
422                 if (!$consumer_key) {
423                         throw new OAuthException("Invalid consumer key");
424                 }
425                 
426                 $consumer = $datastore->lookup_consumer($consumer_key);
427                 if (!$consumer) {
428                         throw new OAuthException("Invalid consumer");
429                 }
430                 return $consumer;
431         }
432
433         # Mostly cadged from OAuthServer
434         
435         function get_token(&$req, $consumer, $datastore) {/*{{{*/
436                 $token_field = @$req->get_parameter('oauth_token');
437                 $token = $datastore->lookup_token($consumer, 'request', $token_field);
438                 if (!$token) {
439                         throw new OAuthException("Invalid $token_type token: $token_field");
440                 }
441                 return $token;
442         }
443         
444         function check_timestamp(&$req) {
445                 $timestamp = @$req->get_parameter('oauth_timestamp');
446                 $now = time();
447                 if ($now - $timestamp > TIMESTAMP_THRESHOLD) {
448                         throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
449                 }
450         }
451
452         # NOTE: don't call twice on the same request; will fail!
453         function check_nonce(&$datastore, &$req, $consumer, $token) {
454                 $timestamp = @$req->get_parameter('oauth_timestamp');
455                 $nonce = @$req->get_parameter('oauth_nonce');
456                 $found = $datastore->lookup_nonce($consumer, $token, $nonce, $timestamp);
457                 if ($found) {
458                         throw new OAuthException("Nonce already used");
459                 }
460                 return true;
461         }
462         
463         function check_signature(&$req, $consumer, $token) {
464                 $signature_method = $this->get_signature_method($req);
465                 $signature = $req->get_parameter('oauth_signature');    
466                 $valid_sig = $signature_method->check_signature($req, 
467                                                                                                                 $consumer, 
468                                                                                                                 $token, 
469                                                                                                                 $signature);
470                 if (!$valid_sig) {
471                         throw new OAuthException("Invalid signature");
472                 }
473         }
474         
475         function get_signature_method(&$req) {
476                 $signature_method = @$req->get_parameter("oauth_signature_method");
477                 if (!$signature_method) {
478                         $signature_method = "PLAINTEXT";
479                 }
480                 if ($signature_method != 'HMAC-SHA1') {
481                         throw new OAuthException("Signature method '$signature_method' not supported.");
482                 }
483                 return omb_hmac_sha1();
484         }
485 }