]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/userauthorization.php
Update copyright dates in files modified in 2009
[quix0rs-gnu-social.git] / actions / userauthorization.php
1 <?php
2 /*
3  * Laconica - a distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, Control Yourself, 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 {
27     var $error;
28     var $params;
29
30     function handle($args)
31     {
32         parent::handle($args);
33
34         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
35             # CSRF protection
36             $token = $this->trimmed('token');
37             if (!$token || $token != common_session_token()) {
38                 $params = $this->getStoredParams();
39                 $this->showForm($params, _('There was a problem with your session token. '.
40                                         'Try again, please.'));
41                 return;
42             }
43             # We've shown the form, now post user's choice
44             $this->sendAuthorization();
45         } else {
46             if (!common_logged_in()) {
47                 # Go log in, and then come back
48                 common_set_returnto($_SERVER['REQUEST_URI']);
49
50                 common_redirect(common_local_url('login'));
51                 return;
52             }
53
54             try {
55                 $this->validateRequest();
56                 $this->storeParams($_GET);
57                 $this->showForm($_GET);
58             } catch (OAuthException $e) {
59                 $this->clearParams();
60                 $this->clientError($e->getMessage());
61                 return;
62             }
63
64         }
65     }
66
67     function showForm($params, $error=null)
68     {
69         $this->params = $params;
70         $this->error = $error;
71         $this->showPage();
72     }
73
74     function title()
75     {
76         return _('Authorize subscription');
77     }
78
79     function showPageNotice()
80     {
81         $this->element('p', null, _('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 "Reject".'));
85     }
86
87     function showContent()
88     {
89         $params = $this->params;
90
91         $nickname = $params['omb_listenee_nickname'];
92         $profile = $params['omb_listenee_profile'];
93         $license = $params['omb_listenee_license'];
94         $fullname = $params['omb_listenee_fullname'];
95         $homepage = $params['omb_listenee_homepage'];
96         $bio = $params['omb_listenee_bio'];
97         $location = $params['omb_listenee_location'];
98         $avatar = $params['omb_listenee_avatar'];
99
100         $this->elementStart('div', array('class' => 'profile'));
101         $this->elementStart('div', 'entity_profile vcard');
102         $this->elementStart('a', array('href' => $profile,
103                                             'class' => 'url'));
104         if ($avatar) {
105             $this->element('img', array('src' => $avatar,
106                                         'class' => 'photo avatar',
107                                         'width' => AVATAR_PROFILE_SIZE,
108                                         'height' => AVATAR_PROFILE_SIZE,
109                                         'alt' => $nickname));
110         }
111         $hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname';
112         $this->elementStart('span', $hasFN);
113         $this->raw($nickname);
114         $this->elementEnd('span');
115         $this->elementEnd('a');
116
117         if (!is_null($fullname)) {
118             $this->elementStart('dl', 'entity_fn');
119             $this->elementStart('dd');
120             $this->elementStart('span', 'fn');
121             $this->raw($fullname);
122             $this->elementEnd('span');
123             $this->elementEnd('dd');
124             $this->elementEnd('dl');
125         }
126         if (!is_null($location)) {
127             $this->elementStart('dl', 'entity_location');
128             $this->element('dt', null, _('Location'));
129             $this->elementStart('dd', 'label');
130             $this->raw($location);
131             $this->elementEnd('dd');
132             $this->elementEnd('dl');
133         }
134
135         if (!is_null($homepage)) {
136             $this->elementStart('dl', 'entity_url');
137             $this->element('dt', null, _('URL'));
138             $this->elementStart('dd');
139             $this->elementStart('a', array('href' => $homepage,
140                                                 'class' => 'url'));
141             $this->raw($homepage);
142             $this->elementEnd('a');
143             $this->elementEnd('dd');
144             $this->elementEnd('dl');
145         }
146
147         if (!is_null($bio)) {
148             $this->elementStart('dl', 'entity_note');
149             $this->element('dt', null, _('Note'));
150             $this->elementStart('dd', 'note');
151             $this->raw($bio);
152             $this->elementEnd('dd');
153             $this->elementEnd('dl');
154         }
155
156         if (!is_null($license)) {
157             $this->elementStart('dl', 'entity_license');
158             $this->element('dt', null, _('License'));
159             $this->elementStart('dd', 'license');
160             $this->element('a', array('href' => $license,
161                                       'class' => 'license'),
162                            $license);
163             $this->elementEnd('dd');
164             $this->elementEnd('dl');
165         }
166         $this->elementEnd('div');
167
168         $this->elementStart('div', 'entity_actions');
169         $this->elementStart('ul');
170         $this->elementStart('li', 'entity_subscribe');
171         $this->elementStart('form', array('method' => 'post',
172                                           'id' => 'userauthorization',
173                                           'class' => 'form_user_authorization',
174                                           'name' => 'userauthorization',
175                                           'action' => common_local_url('userauthorization')));
176         $this->hidden('token', common_session_token());
177
178         $this->submit('accept', _('Accept'), 'submit accept', null, _('Subscribe to this user'));
179         $this->submit('reject', _('Reject'), 'submit reject', null, _('Reject this subscription'));
180         $this->elementEnd('form');
181         $this->elementEnd('li');
182         $this->elementEnd('ul');
183         $this->elementEnd('div');
184         $this->elementEnd('div');
185     }
186
187     function sendAuthorization()
188     {
189         $params = $this->getStoredParams();
190
191         if (!$params) {
192             $this->clientError(_('No authorization request!'));
193             return;
194         }
195
196         $callback = $params['oauth_callback'];
197
198         if ($this->arg('accept')) {
199             if (!$this->authorizeToken($params)) {
200                 $this->clientError(_('Error authorizing token'));
201             }
202             if (!$this->saveRemoteProfile($params)) {
203                 $this->clientError(_('Error saving remote profile'));
204             }
205             if (!$callback) {
206                 $this->showAcceptMessage($params['oauth_token']);
207             } else {
208                 $newparams = array();
209                 $newparams['oauth_token'] = $params['oauth_token'];
210                 $newparams['omb_version'] = OMB_VERSION_01;
211                 $user = User::staticGet('uri', $params['omb_listener']);
212                 $profile = $user->getProfile();
213                 if (!$profile) {
214                     common_log_db_error($user, 'SELECT', __FILE__);
215                     $this->serverError(_('User without matching profile'));
216                     return;
217                 }
218                 $newparams['omb_listener_nickname'] = $user->nickname;
219                 $newparams['omb_listener_profile'] = common_local_url('showstream',
220                                                                    array('nickname' => $user->nickname));
221                 if (!is_null($profile->fullname)) {
222                     $newparams['omb_listener_fullname'] = $profile->fullname;
223                 }
224                 if (!is_null($profile->homepage)) {
225                     $newparams['omb_listener_homepage'] = $profile->homepage;
226                 }
227                 if (!is_null($profile->bio)) {
228                     $newparams['omb_listener_bio'] = $profile->bio;
229                 }
230                 if (!is_null($profile->location)) {
231                     $newparams['omb_listener_location'] = $profile->location;
232                 }
233                 $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
234                 if ($avatar) {
235                     $newparams['omb_listener_avatar'] = $avatar->url;
236                 }
237                 $parts = array();
238                 foreach ($newparams as $k => $v) {
239                     $parts[] = $k . '=' . OAuthUtil::urlencode_rfc3986($v);
240                 }
241                 $query_string = implode('&', $parts);
242                 $parsed = parse_url($callback);
243                 $url = $callback . (($parsed['query']) ? '&' : '?') . $query_string;
244                 common_redirect($url, 303);
245             }
246         } else {
247             if (!$callback) {
248                 $this->showRejectMessage();
249             } else {
250                 # XXX: not 100% sure how to signal failure... just redirect without token?
251                 common_redirect($callback, 303);
252             }
253         }
254     }
255
256     function authorizeToken(&$params)
257     {
258         $token_field = $params['oauth_token'];
259         $rt = new Token();
260         $rt->tok = $token_field;
261         $rt->type = 0;
262         $rt->state = 0;
263         if ($rt->find(true)) {
264             $orig_rt = clone($rt);
265             $rt->state = 1; # Authorized but not used
266             if ($rt->update($orig_rt)) {
267                 return true;
268             }
269         }
270         return false;
271     }
272
273     # XXX: refactor with similar code in finishremotesubscribe.php
274
275     function saveRemoteProfile(&$params)
276     {
277         # FIXME: we should really do this when the consumer comes
278         # back for an access token. If they never do, we've got stuff in a
279         # weird state.
280
281         $nickname = $params['omb_listenee_nickname'];
282         $fullname = $params['omb_listenee_fullname'];
283         $profile_url = $params['omb_listenee_profile'];
284         $homepage = $params['omb_listenee_homepage'];
285         $bio = $params['omb_listenee_bio'];
286         $location = $params['omb_listenee_location'];
287         $avatar_url = $params['omb_listenee_avatar'];
288
289         $listenee = $params['omb_listenee'];
290         $remote = Remote_profile::staticGet('uri', $listenee);
291
292         if ($remote) {
293             $exists = true;
294             $profile = Profile::staticGet($remote->id);
295             $orig_remote = clone($remote);
296             $orig_profile = clone($profile);
297         } else {
298             $exists = false;
299             $remote = new Remote_profile();
300             $remote->uri = $listenee;
301             $profile = new Profile();
302         }
303
304         $profile->nickname = $nickname;
305         $profile->profileurl = $profile_url;
306
307         if (!is_null($fullname)) {
308             $profile->fullname = $fullname;
309         }
310         if (!is_null($homepage)) {
311             $profile->homepage = $homepage;
312         }
313         if (!is_null($bio)) {
314             $profile->bio = $bio;
315         }
316         if (!is_null($location)) {
317             $profile->location = $location;
318         }
319
320         if ($exists) {
321             $profile->update($orig_profile);
322         } else {
323             $profile->created = DB_DataObject_Cast::dateTime(); # current time
324             $id = $profile->insert();
325             if (!$id) {
326                 return false;
327             }
328             $remote->id = $id;
329         }
330
331         if ($exists) {
332             if (!$remote->update($orig_remote)) {
333                 return false;
334             }
335         } else {
336             $remote->created = DB_DataObject_Cast::dateTime(); # current time
337             if (!$remote->insert()) {
338                 return false;
339             }
340         }
341
342         if ($avatar_url) {
343             if (!$this->addAvatar($profile, $avatar_url)) {
344                 return false;
345             }
346         }
347
348         $user = common_current_user();
349
350         $sub = new Subscription();
351         $sub->subscriber = $user->id;
352         $sub->subscribed = $remote->id;
353         $sub->token = $params['oauth_token']; # NOTE: request token, not valid for use!
354         $sub->created = DB_DataObject_Cast::dateTime(); # current time
355
356         if (!$sub->insert()) {
357             return false;
358         }
359
360         return true;
361     }
362
363     function addAvatar($profile, $url)
364     {
365         $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
366         copy($url, $temp_filename);
367         $imagefile = new ImageFile($profile->id, $temp_filename);
368         $filename = Avatar::filename($profile->id,
369                                      image_type_to_extension($imagefile->type),
370                                      null,
371                                      common_timestamp());
372         rename($temp_filename, Avatar::path($filename));
373         return $profile->setOriginal($filename);
374     }
375
376     function showAcceptMessage($tok)
377     {
378         common_show_header(_('Subscription authorized'));
379         $this->element('p', null,
380                        _('The subscription has been authorized, but no '.
381                          'callback URL was passed. Check with the site\'s instructions for '.
382                          'details on how to authorize the subscription. Your subscription token is:'));
383         $this->element('blockquote', 'token', $tok);
384         common_show_footer();
385     }
386
387     function showRejectMessage($tok)
388     {
389         common_show_header(_('Subscription rejected'));
390         $this->element('p', null,
391                        _('The subscription has been rejected, but no '.
392                          'callback URL was passed. Check with the site\'s instructions for '.
393                          'details on how to fully reject the subscription.'));
394         common_show_footer();
395     }
396
397     function storeParams($params)
398     {
399         common_ensure_session();
400         $_SESSION['userauthorizationparams'] = $params;
401     }
402
403     function clearParams()
404     {
405         common_ensure_session();
406         unset($_SESSION['userauthorizationparams']);
407     }
408
409     function getStoredParams()
410     {
411         common_ensure_session();
412         $params = $_SESSION['userauthorizationparams'];
413         return $params;
414     }
415
416     # Throws an OAuthException if anything goes wrong
417
418     function validateRequest()
419     {
420         /* Find token.
421            TODO: If no token is passed the user should get a prompt to enter it
422                  according to OAuth Core 1.0 */
423         $t = new Token();
424         $t->tok = $_GET['oauth_token'];
425         $t->type = 0;
426         if (!$t->find(true)) {
427             throw new OAuthException("Invalid request token: " . $_GET['oauth_token']);
428         }
429
430         $this->validateOmb();
431         return true;
432     }
433
434     function validateOmb()
435     {
436         foreach (array('omb_version', 'omb_listener', 'omb_listenee',
437                        'omb_listenee_profile', 'omb_listenee_nickname',
438                        'omb_listenee_license') as $param)
439         {
440             if (!isset($_GET[$param]) || is_null($_GET[$param])) {
441                 throw new OAuthException("Required parameter '$param' not found");
442             }
443         }
444         # Now, OMB stuff
445         $version = $_GET['omb_version'];
446         if ($version != OMB_VERSION_01) {
447             throw new OAuthException("OpenMicroBlogging version '$version' not supported");
448         }
449         $listener = $_GET['omb_listener'];
450         $user = User::staticGet('uri', $listener);
451         if (!$user) {
452             throw new OAuthException("Listener URI '$listener' not found here");
453         }
454         $cur = common_current_user();
455         if ($cur->id != $user->id) {
456             throw new OAuthException("Can't add for another user!");
457         }
458         $listenee = $_GET['omb_listenee'];
459         if (!Validate::uri($listenee) &&
460             !common_valid_tag($listenee)) {
461             throw new OAuthException("Listenee URI '$listenee' not a recognizable URI");
462         }
463         if (strlen($listenee) > 255) {
464             throw new OAuthException("Listenee URI '$listenee' too long");
465         }
466
467         $other = User::staticGet('uri', $listenee);
468         if ($other) {
469             throw new OAuthException("Listenee URI '$listenee' is local user");
470         }
471
472         $remote = Remote_profile::staticGet('uri', $listenee);
473         if ($remote) {
474             $sub = new Subscription();
475             $sub->subscriber = $user->id;
476             $sub->subscribed = $remote->id;
477             if ($sub->find(true)) {
478                 throw new OAuthException("Already subscribed to user!");
479             }
480         }
481         $nickname = $_GET['omb_listenee_nickname'];
482         if (!Validate::string($nickname, array('min_length' => 1,
483                                                'max_length' => 64,
484                                                'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
485             throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
486         }
487         $profile = $_GET['omb_listenee_profile'];
488         if (!common_valid_http_url($profile)) {
489             throw new OAuthException("Invalid profile URL '$profile'.");
490         }
491
492         if ($profile == common_local_url('showstream', array('nickname' => $nickname))) {
493             throw new OAuthException("Profile URL '$profile' is for a local user.");
494         }
495
496         $license = $_GET['omb_listenee_license'];
497         if (!common_valid_http_url($license)) {
498             throw new OAuthException("Invalid license URL '$license'.");
499         }
500         $site_license = common_config('license', 'url');
501         if (!common_compatible_license($license, $site_license)) {
502             throw new OAuthException("Listenee stream license '$license' not compatible with site license '$site_license'.");
503         }
504         # optional stuff
505         $fullname = $_GET['omb_listenee_fullname'];
506         if ($fullname && mb_strlen($fullname) > 255) {
507             throw new OAuthException("Full name '$fullname' too long.");
508         }
509         $homepage = $_GET['omb_listenee_homepage'];
510         if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
511             throw new OAuthException("Invalid homepage '$homepage'");
512         }
513         $bio = $_GET['omb_listenee_bio'];
514         if ($bio && mb_strlen($bio) > 140) {
515             throw new OAuthException("Bio too long '$bio'");
516         }
517         $location = $_GET['omb_listenee_location'];
518         if ($location && mb_strlen($location) > 255) {
519             throw new OAuthException("Location too long '$location'");
520         }
521         $avatar = $_GET['omb_listenee_avatar'];
522         if ($avatar) {
523             if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
524                 throw new OAuthException("Invalid avatar URL '$avatar'");
525             }
526             $size = @getimagesize($avatar);
527             if (!$size) {
528                 throw new OAuthException("Can't read avatar URL '$avatar'");
529             }
530             if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
531                 throw new OAuthException("Wrong size image at '$avatar'");
532             }
533             if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
534                                           IMAGETYPE_PNG))) {
535                 throw new OAuthException("Wrong image type for '$avatar'");
536             }
537         }
538         $callback = $_GET['oauth_callback'];
539         if ($callback && !common_valid_http_url($callback)) {
540             throw new OAuthException("Invalid callback URL '$callback'");
541         }
542         if ($callback && $callback == common_local_url('finishremotesubscribe')) {
543             throw new OAuthException("Callback URL '$callback' is for local site.");
544         }
545     }
546 }