]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/userauthorization.php
f1f2d8305615ca49ee472f55fcb5b0258c22b3f5
[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                 common_debug('checking version', __FILE__);
330                 $this->check_version($req);
331                 common_debug('getting datastore', __FILE__);            
332                 $datastore = omb_oauth_datastore();
333                 common_debug('getting consumer', __FILE__);
334                 $consumer = $this->get_consumer($datastore, $req);
335                 common_debug('getting token', __FILE__);                
336                 $token = $this->get_token($datastore, $req, $consumer);
337                 common_debug('checking timestamp', __FILE__);
338                 $this->check_timestamp($req);
339                 common_debug('checking nonce', __FILE__);               
340                 $this->check_nonce($datastore, $req, $consumer, $token);
341                 common_debug('checking signature', __FILE__);
342                 $this->check_signature($req, $consumer, $token);
343                 common_debug('validating omb stuff', __FILE__);         
344                 $this->validate_omb($req);
345                 common_debug('done validating', __FILE__);                              
346                 return true;
347         }
348
349         function validate_omb(&$req) {
350                 foreach (array('omb_version', 'omb_listener', 'omb_listenee',
351                                            'omb_listenee_profile', 'omb_listenee_nickname',
352                                            'omb_listenee_license') as $param)
353                 {
354                         if (!$req->get_parameter($param)) {
355                                 throw new OAuthException("Required parameter '$param' not found");
356                         }
357                 }
358                 # Now, OMB stuff
359                 $version = $req->get_parameter('omb_version');
360                 if ($version != OMB_VERSION_01) {
361                         throw new OAuthException("OpenMicroBlogging version '$version' not supported");
362                 }
363                 $user = User::staticGet('uri', $req->get_parameter('omb_listener'));
364                 if (!$user) {
365                         throw new OAuthException("Listener URI '$listener' not found here");
366                 }
367                 $listenee = $req->get_parameter('omb_listenee');
368                 if (!Validate::uri($listenee)) {
369                         throw new OAuthException("Listenee URI '$listenee' not a valid URI");
370                 } else if (strlen($listenee) > 255) {
371                         throw new OAuthException("Listenee URI '$listenee' too long");
372                 }
373                 $nickname = $req->get_parameter('omb_listenee_nickname');
374                 if (!Validate::string($nickname, array('min_length' => 1,
375                                                                                            'max_length' => 64,
376                                                                                            'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
377                         throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
378                 }
379                 $profile = $req->get_parameter('omb_listenee_profile');
380                 if (!common_valid_http_url($profile)) {
381                         throw new OAuthException("Invalid profile URL '$profile'.");
382                 }
383                 $license = $req->get_parameter('omb_listenee_license');
384                 if (!common_valid_http_url($license)) {
385                         throw new OAuthException("Invalid license URL '$license'.");
386                 }
387                 # optional stuff
388                 $fullname = $req->get_parameter('omb_listenee_fullname');
389                 if ($fullname && strlen($fullname) > 255) {
390                         throw new OAuthException("Full name '$fullname' too long.");
391                 }
392                 $homepage = $req->get_parameter('omb_listenee_homepage');
393                 if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) {
394                         throw new OAuthException("Invalid homepage '$homepage'");
395                 }
396                 $bio = $req->get_parameter('omb_listenee_bio');
397                 if ($bio && strlen($bio) > 140) {
398                         throw new OAuthException("Bio too long '$bio'");
399                 }
400                 $location = $req->get_parameter('omb_listenee_location');
401                 if ($location && strlen($location) > 255) {
402                         throw new OAuthException("Location too long '$location'");
403                 }
404                 $avatar = $req->get_parameter('omb_listenee_avatar');
405                 if ($avatar && (!common_valid_http_url($avatar) || strlen($avatar) > 255)) {
406                         throw new OAuthException("Invalid avatar '$avatar'");
407                 }
408                 $callback = $req->get_parameter('oauth_callback');
409                 if ($avatar && common_valid_http_url($callback)) {
410                         throw new OAuthException("Invalid callback URL '$callback'");
411                 }
412         }
413         
414         # Snagged from OAuthServer
415         
416         function check_version(&$req) {
417                 $version = $req->get_parameter("oauth_version");
418                 if (!$version) {
419                         $version = 1.0;
420                 }
421                 if ($version != 1.0) {
422                         throw new OAuthException("OAuth version '$version' not supported");
423                 }
424                 return $version;
425         }
426
427         # Snagged from OAuthServer
428         
429         function get_consumer($datastore, $req) {
430                 $consumer_key = @$req->get_parameter("oauth_consumer_key");
431                 if (!$consumer_key) {
432                         throw new OAuthException("Invalid consumer key");
433                 }
434                 
435                 $consumer = $datastore->lookup_consumer($consumer_key);
436                 if (!$consumer) {
437                         throw new OAuthException("Invalid consumer");
438                 }
439                 return $consumer;
440         }
441
442         # Mostly cadged from OAuthServer
443         
444         function get_token($datastore, &$req, $consumer) {/*{{{*/
445                 $token_field = @$req->get_parameter('oauth_token');
446                 $token = $datastore->lookup_token($consumer, 'request', $token_field);
447                 if (!$token) {
448                         throw new OAuthException("Invalid $token_type token: $token_field");
449                 }
450                 return $token;
451         }
452         
453         function check_timestamp(&$req) {
454                 $timestamp = @$req->get_parameter('oauth_timestamp');
455                 $now = time();
456                 if ($now - $timestamp > TIMESTAMP_THRESHOLD) {
457                         throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
458                 }
459         }
460
461         # NOTE: don't call twice on the same request; will fail!
462         function check_nonce(&$datastore, &$req, $consumer, $token) {
463                 $timestamp = @$req->get_parameter('oauth_timestamp');
464                 $nonce = @$req->get_parameter('oauth_nonce');
465                 $found = $datastore->lookup_nonce($consumer, $token, $nonce, $timestamp);
466                 if ($found) {
467                         throw new OAuthException("Nonce already used");
468                 }
469                 return true;
470         }
471         
472         function check_signature(&$req, $consumer, $token) {
473                 $signature_method = $this->get_signature_method($req);
474                 $signature = $req->get_parameter('oauth_signature');    
475                 $valid_sig = $signature_method->check_signature($req, 
476                                                                                                                 $consumer, 
477                                                                                                                 $token, 
478                                                                                                                 $signature);
479                 if (!$valid_sig) {
480                         throw new OAuthException("Invalid signature");
481                 }
482         }
483         
484         function get_signature_method(&$req) {
485                 $signature_method = @$req->get_parameter("oauth_signature_method");
486                 if (!$signature_method) {
487                         $signature_method = "PLAINTEXT";
488                 }
489                 if ($signature_method != 'HMAC-SHA1') {
490                         throw new OAuthException("Signature method '$signature_method' not supported.");
491                 }
492                 return omb_hmac_sha1();
493         }
494 }