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