]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/userauthorization.php
a6dc2a5b07780b326d810b2530dc466a05df529b
[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() {
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 (strlen($listenee) > 255) {
369                         throw new OAuthException("Listenee URI '$listenee' too long");
370                 }
371                 $nickname = $req->get_parameter('omb_listenee_nickname');
372                 if (!Validate::string($nickname, array('min_length' => 1,
373                                                                                            'max_length' => 64,
374                                                                                            'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
375                         throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
376                 }
377                 $profile = $req->get_parameter('omb_listenee_profile');
378                 if (!common_valid_http_url($profile)) {
379                         throw new OAuthException("Invalid profile URL '$profile'.");
380                 }
381                 $license = $req->get_parameter('omb_listenee_license');
382                 if (!common_valid_http_url($license)) {
383                         throw new OAuthException("Invalid license URL '$license'.");
384                 }
385                 # optional stuff
386                 $fullname = $req->get_parameter('omb_listenee_fullname');
387                 if ($fullname && strlen($fullname) > 255) {
388                         throw new OAuthException("Full name '$fullname' too long.");
389                 }
390                 $homepage = $req->get_parameter('omb_listenee_homepage');
391                 if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) {
392                         throw new OAuthException("Invalid homepage '$homepage'");
393                 }
394                 $bio = $req->get_parameter('omb_listenee_bio');
395                 if ($bio && strlen($bio) > 140) {
396                         throw new OAuthException("Bio too long '$bio'");
397                 }
398                 $location = $req->get_parameter('omb_listenee_location');
399                 if ($location && strlen($location) > 255) {
400                         throw new OAuthException("Location too long '$location'");
401                 }
402                 $avatar = $req->get_parameter('omb_listenee_avatar');
403                 if ($avatar && (!common_valid_http_url($avatar) || strlen($avatar) > 255)) {
404                         throw new OAuthException("Invalid avatar '$avatar'");
405                 }
406                 $callback = $req->get_parameter('oauth_callback');
407                 if ($avatar && common_valid_http_url($callback)) {
408                         throw new OAuthException("Invalid callback URL '$callback'");
409                 }
410         }
411         
412         # Snagged from OAuthServer
413         
414         function check_version(&$req) {
415                 $version = $req->get_parameter("oauth_version");
416                 if (!$version) {
417                         $version = 1.0;
418                 }
419                 if ($version != 1.0) {
420                         throw new OAuthException("OAuth version '$version' not supported");
421                 }
422                 return $version;
423         }
424
425         # Snagged from OAuthServer
426         
427         function get_consumer($datastore, $req) {
428                 $consumer_key = @$req->get_parameter("oauth_consumer_key");
429                 if (!$consumer_key) {
430                         throw new OAuthException("Invalid consumer key");
431                 }
432                 
433                 $consumer = $datastore->lookup_consumer($consumer_key);
434                 if (!$consumer) {
435                         throw new OAuthException("Invalid consumer");
436                 }
437                 return $consumer;
438         }
439
440         # Mostly cadged from OAuthServer
441         
442         function get_token($datastore, &$req, $consumer) {/*{{{*/
443                 $token_field = @$req->get_parameter('oauth_token');
444                 $token = $datastore->lookup_token($consumer, 'request', $token_field);
445                 if (!$token) {
446                         throw new OAuthException("Invalid $token_type token: $token_field");
447                 }
448                 return $token;
449         }
450         
451         function check_timestamp(&$req) {
452                 $timestamp = @$req->get_parameter('oauth_timestamp');
453                 $now = time();
454                 if ($now - $timestamp > TIMESTAMP_THRESHOLD) {
455                         throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
456                 }
457         }
458
459         # NOTE: don't call twice on the same request; will fail!
460         function check_nonce(&$datastore, &$req, $consumer, $token) {
461                 $timestamp = @$req->get_parameter('oauth_timestamp');
462                 $nonce = @$req->get_parameter('oauth_nonce');
463                 $found = $datastore->lookup_nonce($consumer, $token, $nonce, $timestamp);
464                 if ($found) {
465                         throw new OAuthException("Nonce already used");
466                 }
467                 return true;
468         }
469         
470         function check_signature(&$req, $consumer, $token) {
471                 $signature_method = $this->get_signature_method($req);
472                 $signature = $req->get_parameter('oauth_signature');    
473                 $valid_sig = $signature_method->check_signature($req, 
474                                                                                                                 $consumer, 
475                                                                                                                 $token, 
476                                                                                                                 $signature);
477                 if (!$valid_sig) {
478                         throw new OAuthException("Invalid signature");
479                 }
480         }
481         
482         function get_signature_method(&$req) {
483                 $signature_method = @$req->get_parameter("oauth_signature_method");
484                 if (!$signature_method) {
485                         $signature_method = "PLAINTEXT";
486                 }
487                 if ($signature_method != 'HMAC-SHA1') {
488                         throw new OAuthException("Signature method '$signature_method' not supported.");
489                 }
490                 return omb_hmac_sha1();
491         }
492 }