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