]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/TwitterBridge/twitterauthorization.php
15408668fc7d4a8350b219e0288e1db60a63c484
[quix0rs-gnu-social.git] / plugins / TwitterBridge / twitterauthorization.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Class for doing OAuth authentication against Twitter
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  TwitterauthorizationAction
23  * @package   StatusNet
24  * @author    Zach Copley <zach@status.net>
25  * @copyright 2009 StatusNet, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET') && !defined('LACONICA')) {
31     exit(1);
32 }
33
34 require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
35
36 /**
37  * Class for doing OAuth authentication against Twitter
38  *
39  * Peforms the OAuth "dance" between StatusNet and Twitter -- requests a token,
40  * authorizes it, and exchanges it for an access token.  It also creates a link
41  * (Foreign_link) between the StatusNet user and Twitter user and stores the
42  * access token and secret in the link.
43  *
44  * @category Twitter
45  * @package  StatusNet
46  * @author   Zach Copley <zach@status.net>
47  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
48  * @link     http://laconi.ca/
49  *
50  */
51 class TwitterauthorizationAction extends Action
52 {
53     var $twuid        = null;
54     var $tw_fields    = null;
55     var $access_token = null;
56     var $signin       = null;
57
58     /**
59      * Initialize class members. Looks for 'oauth_token' parameter.
60      *
61      * @param array $args misc. arguments
62      *
63      * @return boolean true
64      */
65     function prepare($args)
66     {
67         parent::prepare($args);
68
69         $this->signin      = $this->boolean('signin');
70         $this->oauth_token = $this->arg('oauth_token');
71
72         return true;
73     }
74
75     /**
76      * Handler method
77      *
78      * @param array $args is ignored since it's now passed in in prepare()
79      *
80      * @return nothing
81      */
82     function handle($args)
83     {
84         parent::handle($args);
85
86         if (common_logged_in()) {
87             $user  = common_current_user();
88             $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
89
90             // If there's already a foreign link record, it means we already
91             // have an access token, and this is unecessary. So go back.
92
93             if (isset($flink)) {
94                 common_redirect(common_local_url('twittersettings'));
95             }
96         }
97
98         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
99
100             // User was not logged in to StatusNet before
101
102             $this->twuid = $this->trimmed('twuid');
103
104             $this->tw_fields = array('name' => $this->trimmed('tw_fields_name'),
105                                      'fullname' => $this->trimmed('tw_fields_fullname'));
106
107             $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret'));
108
109             $token = $this->trimmed('token');
110
111             if (!$token || $token != common_session_token()) {
112                 $this->showForm(_('There was a problem with your session token. Try again, please.'));
113                 return;
114             }
115
116             if ($this->arg('create')) {
117                 if (!$this->boolean('license')) {
118                     $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
119                                     $this->trimmed('newname'));
120                     return;
121                 }
122                 $this->createNewUser();
123             } else if ($this->arg('connect')) {
124                 $this->connectNewUser();
125             } else {
126                 common_debug('Twitter Connect Plugin - ' .
127                              print_r($this->args, true));
128                 $this->showForm(_('Something weird happened.'),
129                                 $this->trimmed('newname'));
130             }
131         } else {
132             // $this->oauth_token is only populated once Twitter authorizes our
133             // request token. If it's empty we're at the beginning of the auth
134             // process
135
136             if (empty($this->oauth_token)) {
137                 $this->authorizeRequestToken();
138             } else {
139                 $this->saveAccessToken();
140             }
141         }
142     }
143
144     /**
145      * Asks Twitter for a request token, and then redirects to Twitter
146      * to authorize it.
147      *
148      * @return nothing
149      */
150     function authorizeRequestToken()
151     {
152         try {
153
154             // Get a new request token and authorize it
155
156             $client  = new TwitterOAuthClient();
157             $req_tok =
158               $client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
159
160             // Sock the request token away in the session temporarily
161
162             $_SESSION['twitter_request_token']        = $req_tok->key;
163             $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
164
165             $auth_link = $client->getAuthorizeLink($req_tok, $this->signin);
166
167         } catch (OAuthClientException $e) {
168             $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
169                            $e->getCode(), $e->getMessage());
170             $this->serverError(_m('Couldn\'t link your Twitter account.'));
171         }
172
173         common_redirect($auth_link);
174     }
175
176     /**
177      * Called when Twitter returns an authorized request token. Exchanges
178      * it for an access token and stores it.
179      *
180      * @return nothing
181      */
182     function saveAccessToken()
183     {
184
185         // Check to make sure Twitter returned the same request
186         // token we sent them
187
188         if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
189             $this->serverError(_m('Couldn\'t link your Twitter account.'));
190         }
191
192         $twitter_user = null;
193
194         try {
195
196             $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
197                 $_SESSION['twitter_request_token_secret']);
198
199             // Exchange the request token for an access token
200
201             $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
202
203             // Test the access token and get the user's Twitter info
204
205             $client       = new TwitterOAuthClient($atok->key, $atok->secret);
206             $twitter_user = $client->verifyCredentials();
207
208         } catch (OAuthClientException $e) {
209             $msg = sprintf('OAuth client error - code: %1$s, msg: %2$s',
210                            $e->getCode(), $e->getMessage());
211             $this->serverError(_m('Couldn\'t link your Twitter account.'));
212         }
213
214         if (common_logged_in()) {
215
216             // Save the access token and Twitter user info
217
218             $this->saveForeignLink($atok, $twitter_user);
219
220         } else {
221
222             $this->twuid = $twitter_user->id;
223             $this->tw_fields = array("name" => $twitter_user->screen_name, "fullname" => $twitter_user->name);
224             $this->access_token = $atok;
225             $this->tryLogin();
226         }
227
228         // Clean up the the mess we made in the session
229
230         unset($_SESSION['twitter_request_token']);
231         unset($_SESSION['twitter_request_token_secret']);
232
233         if (common_logged_in()) {
234             common_redirect(common_local_url('twittersettings'));
235         }
236     }
237
238     /**
239      * Saves a Foreign_link between Twitter user and local user,
240      * which includes the access token and secret.
241      *
242      * @param OAuthToken $access_token the access token to save
243      * @param mixed      $twitter_user twitter API user object
244      *
245      * @return nothing
246      */
247     function saveForeignLink($access_token, $twitter_user)
248     {
249         $user = common_current_user();
250
251         $flink = new Foreign_link();
252
253         $flink->user_id     = $user->id;
254         $flink->foreign_id  = $twitter_user->id;
255         $flink->service     = TWITTER_SERVICE;
256
257         $creds = TwitterOAuthClient::packToken($access_token);
258
259         $flink->credentials = $creds;
260         $flink->created     = common_sql_now();
261
262         // Defaults: noticesync on, everything else off
263
264         $flink->set_flags(true, false, false, false);
265
266         $flink_id = $flink->insert();
267
268         if (empty($flink_id)) {
269             common_log_db_error($flink, 'INSERT', __FILE__);
270                 $this->serverError(_m('Couldn\'t link your Twitter account.'));
271         }
272
273         save_twitter_user($twitter_user->id, $twitter_user->screen_name);
274     }
275
276     function flinkUser($user_id, $twuid)
277     {
278         $flink = new Foreign_link();
279
280         $flink->user_id     = $user_id;
281         $flink->foreign_id  = $twuid;
282         $flink->service     = TWITTER_SERVICE;
283
284         $creds = TwitterOAuthClient::packToken($this->access_token);
285
286         $flink->credentials = $creds;
287         $flink->created     = common_sql_now();
288
289         // Defaults: noticesync on, everything else off
290
291         $flink->set_flags(true, false, false, false);
292
293         $flink_id = $flink->insert();
294
295         if (empty($flink_id)) {
296             common_log_db_error($flink, 'INSERT', __FILE__);
297                 $this->serverError(_('Couldn\'t link your Twitter account.'));
298         }
299
300         save_twitter_user($twuid, $this->tw_fields['name']);
301
302         return $flink_id;
303     }
304
305     function showPageNotice()
306     {
307         if ($this->error) {
308             $this->element('div', array('class' => 'error'), $this->error);
309         } else {
310             $this->element('div', 'instructions',
311                            sprintf(_('This is the first time you\'ve logged into %s so we must connect your Twitter account to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
312         }
313     }
314
315     function title()
316     {
317         return _('Twitter Account Setup');
318     }
319
320     function showForm($error=null, $username=null)
321     {
322         $this->error = $error;
323         $this->username = $username;
324
325         $this->showPage();
326     }
327
328     function showPage()
329     {
330         parent::showPage();
331     }
332
333     function showContent()
334     {
335         if (!empty($this->message_text)) {
336             $this->element('p', null, $this->message);
337             return;
338         }
339
340         $this->elementStart('form', array('method' => 'post',
341                                           'id' => 'form_settings_twitter_connect',
342                                           'class' => 'form_settings',
343                                           'action' => common_local_url('twitterauthorization')));
344         $this->elementStart('fieldset', array('id' => 'settings_twitter_connect_options'));
345         $this->element('legend', null, _('Connection options'));
346         $this->elementStart('ul', 'form_data');
347         $this->elementStart('li');
348         $this->element('input', array('type' => 'checkbox',
349                                       'id' => 'license',
350                                       'class' => 'checkbox',
351                                       'name' => 'license',
352                                       'value' => 'true'));
353         $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
354         $this->text(_('My text and files are available under '));
355         $this->element('a', array('href' => common_config('license', 'url')),
356                        common_config('license', 'title'));
357         $this->text(_(' except this private data: password, email address, IM address, phone number.'));
358         $this->elementEnd('label');
359         $this->elementEnd('li');
360         $this->elementEnd('ul');
361         $this->hidden('access_token_key', $this->access_token->key);
362         $this->hidden('access_token_secret', $this->access_token->secret);
363         $this->hidden('twuid', $this->twuid);
364         $this->hidden('tw_fields_name', $this->tw_fields['name']);
365         $this->hidden('tw_fields_fullname', $this->tw_fields['fullname']);
366
367         $this->elementStart('fieldset');
368         $this->hidden('token', common_session_token());
369         $this->element('legend', null,
370                        _('Create new account'));
371         $this->element('p', null,
372                        _('Create a new user with this nickname.'));
373         $this->elementStart('ul', 'form_data');
374         $this->elementStart('li');
375         $this->input('newname', _('New nickname'),
376                      ($this->username) ? $this->username : '',
377                      _('1-64 lowercase letters or numbers, no punctuation or spaces'));
378         $this->elementEnd('li');
379         $this->elementEnd('ul');
380         $this->submit('create', _('Create'));
381         $this->elementEnd('fieldset');
382
383         $this->elementStart('fieldset');
384         $this->element('legend', null,
385                        _('Connect existing account'));
386         $this->element('p', null,
387                        _('If you already have an account, login with your username and password to connect it to your Twitter account.'));
388         $this->elementStart('ul', 'form_data');
389         $this->elementStart('li');
390         $this->input('nickname', _('Existing nickname'));
391         $this->elementEnd('li');
392         $this->elementStart('li');
393         $this->password('password', _('Password'));
394         $this->elementEnd('li');
395         $this->elementEnd('ul');
396         $this->submit('connect', _('Connect'));
397         $this->elementEnd('fieldset');
398
399         $this->elementEnd('fieldset');
400         $this->elementEnd('form');
401     }
402
403     function message($msg)
404     {
405         $this->message_text = $msg;
406         $this->showPage();
407     }
408
409     function createNewUser()
410     {
411         if (common_config('site', 'closed')) {
412             $this->clientError(_('Registration not allowed.'));
413             return;
414         }
415
416         $invite = null;
417
418         if (common_config('site', 'inviteonly')) {
419             $code = $_SESSION['invitecode'];
420             if (empty($code)) {
421                 $this->clientError(_('Registration not allowed.'));
422                 return;
423             }
424
425             $invite = Invitation::staticGet($code);
426
427             if (empty($invite)) {
428                 $this->clientError(_('Not a valid invitation code.'));
429                 return;
430             }
431         }
432
433         $nickname = $this->trimmed('newname');
434
435         if (!Validate::string($nickname, array('min_length' => 1,
436                                                'max_length' => 64,
437                                                'format' => NICKNAME_FMT))) {
438             $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
439             return;
440         }
441
442         if (!User::allowed_nickname($nickname)) {
443             $this->showForm(_('Nickname not allowed.'));
444             return;
445         }
446
447         if (User::staticGet('nickname', $nickname)) {
448             $this->showForm(_('Nickname already in use. Try another one.'));
449             return;
450         }
451
452         $fullname = trim($this->tw_fields['fullname']);
453
454         $args = array('nickname' => $nickname, 'fullname' => $fullname);
455
456         if (!empty($invite)) {
457             $args['code'] = $invite->code;
458         }
459
460         $user = User::register($args);
461
462         $result = $this->flinkUser($user->id, $this->twuid);
463
464         if (!$result) {
465             $this->serverError(_('Error connecting user to Twitter.'));
466             return;
467         }
468
469         common_set_user($user);
470         common_real_login(true);
471
472         common_debug('TwitterBridge Plugin - ' .
473                      "Registered new user $user->id from Twitter user $this->fbuid");
474
475         common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
476                         303);
477     }
478
479     function connectNewUser()
480     {
481         $nickname = $this->trimmed('nickname');
482         $password = $this->trimmed('password');
483
484         if (!common_check_user($nickname, $password)) {
485             $this->showForm(_('Invalid username or password.'));
486             return;
487         }
488
489         $user = User::staticGet('nickname', $nickname);
490
491         if (!empty($user)) {
492             common_debug('TwitterBridge Plugin - ' .
493                          "Legit user to connect to Twitter: $nickname");
494         }
495
496         $result = $this->flinkUser($user->id, $this->twuid);
497
498         if (!$result) {
499             $this->serverError(_('Error connecting user to Twitter.'));
500             return;
501         }
502
503         common_debug('TwitterBridge Plugin - ' .
504                      "Connected Twitter user $this->fbuid to local user $user->id");
505
506         common_set_user($user);
507         common_real_login(true);
508
509         $this->goHome($user->nickname);
510     }
511
512     function connectUser()
513     {
514         $user = common_current_user();
515
516         $result = $this->flinkUser($user->id, $this->twuid);
517
518         if (empty($result)) {
519             $this->serverError(_('Error connecting user to Twitter.'));
520             return;
521         }
522
523         common_debug('TwitterBridge Plugin - ' .
524                      "Connected Twitter user $this->fbuid to local user $user->id");
525
526         // Return to Twitter connection settings tab
527         common_redirect(common_local_url('twittersettings'), 303);
528     }
529
530     function tryLogin()
531     {
532         common_debug('TwitterBridge Plugin - ' .
533                      "Trying login for Twitter user $this->twuid.");
534
535         $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE);
536
537         if (!empty($flink)) {
538             $user = $flink->getUser();
539
540             if (!empty($user)) {
541
542                 common_debug('TwitterBridge Plugin - ' .
543                              "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)");
544
545                 common_set_user($user);
546                 common_real_login(true);
547                 $this->goHome($user->nickname);
548             }
549
550         } else {
551
552             common_debug('TwitterBridge Plugin - ' .
553                          "No flink found for twuid: $this->twuid - new user");
554
555             $this->showForm(null, $this->bestNewNickname());
556         }
557     }
558
559     function goHome($nickname)
560     {
561         $url = common_get_returnto();
562         if ($url) {
563             // We don't have to return to it again
564             common_set_returnto(null);
565         } else {
566             $url = common_local_url('all',
567                                     array('nickname' =>
568                                           $nickname));
569         }
570
571         common_redirect($url, 303);
572     }
573
574     function bestNewNickname()
575     {
576         if (!empty($this->tw_fields['name'])) {
577             $nickname = $this->nicknamize($this->tw_fields['name']);
578             if ($this->isNewNickname($nickname)) {
579                 return $nickname;
580             }
581         }
582
583         return null;
584     }
585
586      // Given a string, try to make it work as a nickname
587
588      function nicknamize($str)
589      {
590          $str = preg_replace('/\W/', '', $str);
591          $str = str_replace(array('-', '_'), '', $str);
592          return strtolower($str);
593      }
594
595     function isNewNickname($str)
596     {
597         if (!Validate::string($str, array('min_length' => 1,
598                                           'max_length' => 64,
599                                           'format' => NICKNAME_FMT))) {
600             return false;
601         }
602         if (!User::allowed_nickname($str)) {
603             return false;
604         }
605         if (User::staticGet('nickname', $str)) {
606             return false;
607         }
608         return true;
609     }
610
611 }
612