]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/TwitterBridge/actions/twitterauthorization.php
Merge branch 'master' of git.gnu.io:Quix0r/gnu-social
[quix0rs-gnu-social.git] / plugins / TwitterBridge / actions / 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  Plugin
23  * @package   StatusNet
24  * @author    Zach Copley <zach@status.net>
25  * @author    Julien C <chaumond@gmail.com>
26  * @copyright 2009-2010 StatusNet, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28  * @link      http://status.net/
29  */
30
31 if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); }
32
33 require_once dirname(__DIR__) . '/twitter.php';
34
35 /**
36  * Class for doing OAuth authentication against Twitter
37  *
38  * Peforms the OAuth "dance" between StatusNet and Twitter -- requests a token,
39  * authorizes it, and exchanges it for an access token.  It also creates a link
40  * (Foreign_link) between the StatusNet user and Twitter user and stores the
41  * access token and secret in the link.
42  *
43  * @category Plugin
44  * @package  StatusNet
45  * @author   Zach Copley <zach@status.net>
46  * @author   Julien C <chaumond@gmail.com>
47  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
48  * @link     http://status.net/
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     var $verifier     = null;
58
59     /**
60      * Initialize class members. Looks for 'oauth_token' parameter.
61      *
62      * @param array $args misc. arguments
63      *
64      * @return boolean true
65      */
66     function prepare(array $args=array())
67     {
68         parent::prepare($args);
69
70         $this->signin      = $this->boolean('signin');
71         $this->oauth_token = $this->arg('oauth_token');
72         $this->verifier    = $this->arg('oauth_verifier');
73
74         return true;
75     }
76
77     /**
78      * Handler method
79      *
80      * @param array $args is ignored since it's now passed in in prepare()
81      *
82      * @return nothing
83      */
84     function handle(array $args=array())
85     {
86         parent::handle($args);
87
88         if (common_logged_in()) {
89             $user  = common_current_user();
90             $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
91
92             // If there's already a foreign link record and a foreign user
93             // it means the accounts are already linked, and this is unecessary.
94             // So go back.
95
96             if (isset($flink)) {
97                 $fuser = $flink->getForeignUser();
98                 if (!empty($fuser)) {
99                     common_redirect(common_local_url('twittersettings'));
100                 }
101             }
102         }
103
104         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
105
106             // User was not logged in to StatusNet before
107
108             $this->twuid = $this->trimmed('twuid');
109
110             $this->tw_fields = array('screen_name' => $this->trimmed('tw_fields_screen_name'),
111                                      'fullname' => $this->trimmed('tw_fields_fullname'));
112
113             $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret'));
114
115             $token = $this->trimmed('token');
116
117             if (!$token || $token != common_session_token()) {
118                 // TRANS: Client error displayed when the session token does not match or is not given.
119                 $this->showForm(_m('There was a problem with your session token. Try again, please.'));
120                 return;
121             }
122
123             if ($this->arg('create')) {
124                 if (!$this->boolean('license')) {
125                     // TRANS: Form validation error displayed when the checkbox to agree to the license has not been checked.
126                     $this->showForm(_m('You cannot register if you do not agree to the license.'),
127                                     $this->trimmed('newname'));
128                     return;
129                 }
130                 $this->createNewUser();
131             } else if ($this->arg('connect')) {
132                 $this->connectNewUser();
133             } else {
134                 common_debug('Twitter bridge - ' . print_r($this->args, true));
135                 // TRANS: Form validation error displayed when an unhandled error occurs.
136                 $this->showForm(_m('Something weird happened.'),
137                                 $this->trimmed('newname'));
138             }
139         } else {
140             // $this->oauth_token is only populated once Twitter authorizes our
141             // request token. If it's empty we're at the beginning of the auth
142             // process
143
144             if (empty($this->oauth_token)) {
145                 $this->authorizeRequestToken();
146             } else {
147                 $this->saveAccessToken();
148             }
149         }
150     }
151
152     /**
153      * Asks Twitter for a request token, and then redirects to Twitter
154      * to authorize it.
155      *
156      * @return nothing
157      */
158     function authorizeRequestToken()
159     {
160         try {
161             // Get a new request token and authorize it
162
163             $client  = new TwitterOAuthClient();
164             $req_tok = $client->getRequestToken();
165
166             // Sock the request token away in the session temporarily
167
168             $_SESSION['twitter_request_token']        = $req_tok->key;
169             $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
170
171             $auth_link = $client->getAuthorizeLink($req_tok, $this->signin);
172         } catch (OAuthClientException $e) {
173             $msg = sprintf(
174                 'OAuth client error - code: %1s, msg: %2s',
175                 $e->getCode(),
176                 $e->getMessage()
177             );
178             common_log(LOG_INFO, 'Twitter bridge - ' . $msg);
179             $this->serverError(
180                 // TRANS: Server error displayed when linking to a Twitter account fails.
181                 _m('Could not link your Twitter account.')
182             );
183         }
184
185         common_redirect($auth_link);
186     }
187
188     /**
189      * Called when Twitter returns an authorized request token. Exchanges
190      * it for an access token and stores it.
191      *
192      * @return nothing
193      */
194     function saveAccessToken()
195     {
196         // Check to make sure Twitter returned the same request
197         // token we sent them
198
199         if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
200             $this->serverError(
201                 // TRANS: Server error displayed when linking to a Twitter account fails because of an incorrect oauth_token.
202                 _m('Could not link your Twitter account: oauth_token mismatch.')
203             );
204         }
205
206         $twitter_user = null;
207
208         try {
209
210             $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
211                 $_SESSION['twitter_request_token_secret']);
212
213             // Exchange the request token for an access token
214
215             $atok = $client->getAccessToken($this->verifier);
216
217             // Test the access token and get the user's Twitter info
218
219             $client       = new TwitterOAuthClient($atok->key, $atok->secret);
220             $twitter_user = $client->verifyCredentials();
221
222         } catch (OAuthClientException $e) {
223             $msg = sprintf(
224                 'OAuth client error - code: %1$s, msg: %2$s',
225                 $e->getCode(),
226                 $e->getMessage()
227             );
228             common_log(LOG_INFO, 'Twitter bridge - ' . $msg);
229             $this->serverError(
230                 // TRANS: Server error displayed when linking to a Twitter account fails.
231                 _m('Could not link your Twitter account.')
232             );
233         }
234
235         if (common_logged_in()) {
236             // Save the access token and Twitter user info
237
238             $user = common_current_user();
239             $this->saveForeignLink($user->id, $twitter_user->id, $atok);
240             save_twitter_user($twitter_user->id, $twitter_user->screen_name);
241
242         } else {
243
244             $this->twuid = $twitter_user->id;
245             $this->tw_fields = array("screen_name" => $twitter_user->screen_name,
246                                      "fullname" => $twitter_user->name);
247             $this->access_token = $atok;
248             $this->tryLogin();
249         }
250
251         // Clean up the the mess we made in the session
252
253         unset($_SESSION['twitter_request_token']);
254         unset($_SESSION['twitter_request_token_secret']);
255
256         if (common_logged_in()) {
257             common_redirect(common_local_url('twittersettings'));
258         }
259     }
260
261     /**
262      * Saves a Foreign_link between Twitter user and local user,
263      * which includes the access token and secret.
264      *
265      * @param int        $user_id StatusNet user ID
266      * @param int        $twuid   Twitter user ID
267      * @param OAuthToken $token   the access token to save
268      *
269      * @return nothing
270      */
271     function saveForeignLink($user_id, $twuid, $access_token)
272     {
273         $flink = new Foreign_link();
274
275         $flink->user_id = $user_id;
276         $flink->service = TWITTER_SERVICE;
277
278         // delete stale flink, if any
279         $result = $flink->find(true);
280
281         if (!empty($result)) {
282             $flink->safeDelete();
283         }
284
285         $flink->user_id     = $user_id;
286         $flink->foreign_id  = $twuid;
287         $flink->service     = TWITTER_SERVICE;
288
289         $creds = TwitterOAuthClient::packToken($access_token);
290
291         $flink->credentials = $creds;
292         $flink->created     = common_sql_now();
293
294         // Defaults: noticesync on, everything else off
295
296         $flink->set_flags(true, false, false, false);
297
298         $flink_id = $flink->insert();
299
300         if (empty($flink_id)) {
301             common_log_db_error($flink, 'INSERT', __FILE__);
302             // TRANS: Server error displayed when linking to a Twitter account fails.
303             $this->serverError(_m('Could not link your Twitter account.'));
304         }
305
306         return $flink_id;
307     }
308
309     function showPageNotice()
310     {
311         if ($this->error) {
312             $this->element('div', array('class' => 'error'), $this->error);
313         } else {
314             $this->element('div', 'instructions',
315                            // TRANS: Page instruction. %s is the StatusNet sitename.
316                            sprintf(_m('This is the first time you have 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')));
317         }
318     }
319
320     function title()
321     {
322         // TRANS: Page title.
323         return _m('Twitter Account Setup');
324     }
325
326     function showForm($error=null, $username=null)
327     {
328         $this->error = $error;
329         $this->username = $username;
330
331         $this->showPage();
332     }
333
334     function showPage()
335     {
336         parent::showPage();
337     }
338
339     /**
340      * @fixme much of this duplicates core code, which is very fragile.
341      * Should probably be replaced with an extensible mini version of
342      * the core registration form.
343      */
344     function showContent()
345     {
346         if (!empty($this->message_text)) {
347             $this->element('p', null, $this->message);
348             return;
349         }
350
351         $this->elementStart('form', array('method' => 'post',
352                                           'id' => 'form_settings_twitter_connect',
353                                           'class' => 'form_settings',
354                                           'action' => common_local_url('twitterauthorization')));
355         $this->elementStart('fieldset', array('id' => 'settings_twitter_connect_options'));
356         // TRANS: Fieldset legend.
357         $this->element('legend', null, _m('Connection options'));
358
359         $this->hidden('access_token_key', $this->access_token->key);
360         $this->hidden('access_token_secret', $this->access_token->secret);
361         $this->hidden('twuid', $this->twuid);
362         $this->hidden('tw_fields_screen_name', $this->tw_fields['screen_name']);
363         $this->hidden('tw_fields_name', $this->tw_fields['fullname']);
364         $this->hidden('token', common_session_token());
365
366         // Don't allow new account creation if site is flagged as invite only
367         if (common_config('site', 'inviteonly') == false) {
368             $this->elementStart('fieldset');
369             $this->element('legend', null,
370                            // TRANS: Fieldset legend.
371                            _m('Create new account'));
372             $this->element('p', null,
373                            // TRANS: Sub form introduction text.
374                           _m('Create a new user with this nickname.'));
375             $this->elementStart('ul', 'form_data');
376
377             // Hook point for captcha etc
378             Event::handle('StartRegistrationFormData', array($this));
379
380             $this->elementStart('li');
381             // TRANS: Field label.
382             $this->input('newname', _m('New nickname'),
383                          ($this->username) ? $this->username : '',
384                          // TRANS: Field title for nickname field.
385                          _m('1-64 lowercase letters or numbers, no punctuation or spaces.'));
386             $this->elementEnd('li');
387             $this->elementStart('li');
388             // TRANS: Field label.
389             $this->input('email', _m('LABEL','Email'), $this->getEmail(),
390                          // TRANS: Field title for e-mail address field.
391                          _m('Used only for updates, announcements, '.
392                            'and password recovery'));
393             $this->elementEnd('li');
394
395             // Hook point for captcha etc
396             Event::handle('EndRegistrationFormData', array($this));
397
398             $this->elementEnd('ul');
399             // TRANS: Button text for creating a new StatusNet account in the Twitter connect page.
400             $this->submit('create', _m('BUTTON','Create'));
401             $this->elementEnd('fieldset');
402         }
403
404         $this->elementStart('fieldset');
405         $this->element('legend', null,
406                        // TRANS: Fieldset legend.
407                        _m('Connect existing account'));
408         $this->element('p', null,
409                        // TRANS: Sub form introduction text.
410                        _m('If you already have an account, login with your username and password to connect it to your Twitter account.'));
411         $this->elementStart('ul', 'form_data');
412         $this->elementStart('li');
413         // TRANS: Field label.
414         $this->input('nickname', _m('Existing nickname'));
415         $this->elementEnd('li');
416         $this->elementStart('li');
417         // TRANS: Field label.
418         $this->password('password', _m('Password'));
419         $this->elementEnd('li');
420         $this->elementEnd('ul');
421         $this->elementEnd('fieldset');
422
423         $this->elementStart('fieldset');
424         $this->element('legend', null,
425                        // TRANS: Fieldset legend.
426                        _m('License'));
427         $this->elementStart('ul', 'form_data');
428         $this->elementStart('li');
429         $this->element('input', array('type' => 'checkbox',
430                                       'id' => 'license',
431                                       'class' => 'checkbox',
432                                       'name' => 'license',
433                                       'value' => 'true'));
434         $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
435         // TRANS: Text for license agreement checkbox.
436         // TRANS: %s is the license as configured for the StatusNet site.
437         $message = _m('My text and files are available under %s ' .
438                      'except this private data: password, ' .
439                      'email address, IM address, and phone number.');
440         $link = '<a href="' .
441                 htmlspecialchars(common_config('license', 'url')) .
442                 '">' .
443                 htmlspecialchars(common_config('license', 'title')) .
444                 '</a>';
445         $this->raw(sprintf(htmlspecialchars($message), $link));
446         $this->elementEnd('label');
447         $this->elementEnd('li');
448         $this->elementEnd('ul');
449         $this->elementEnd('fieldset');
450         // TRANS: Button text for connecting an existing StatusNet account in the Twitter connect page..
451         $this->submit('connect', _m('BUTTON','Connect'));
452         $this->elementEnd('fieldset');
453         $this->elementEnd('form');
454     }
455
456     /**
457      * Get specified e-mail from the form, or the invite code.
458      *
459      * @return string
460      */
461     function getEmail()
462     {
463         $email = $this->trimmed('email');
464         if (!empty($email)) {
465             return $email;
466         }
467
468         // Terrible hack for invites...
469         if (common_config('site', 'inviteonly')) {
470             $code = $_SESSION['invitecode'];
471             if ($code) {
472                 $invite = Invitation::getKV($code);
473
474                 if ($invite && $invite->address_type == 'email') {
475                     return $invite->address;
476                 }
477             }
478         }
479         return '';
480     }
481
482     function message($msg)
483     {
484         $this->message_text = $msg;
485         $this->showPage();
486     }
487
488     function createNewUser()
489     {
490         if (!Event::handle('StartRegistrationTry', array($this))) {
491             return;
492         }
493
494         if (common_config('site', 'closed')) {
495             // TRANS: Client error displayed when trying to create a new user while creating new users is not allowed.
496             $this->clientError(_m('Registration not allowed.'));
497         }
498
499         $invite = null;
500
501         if (common_config('site', 'inviteonly')) {
502             $code = $_SESSION['invitecode'];
503             if (empty($code)) {
504                 // TRANS: Client error displayed when trying to create a new user while creating new users is not allowed.
505                 $this->clientError(_m('Registration not allowed.'));
506             }
507
508             $invite = Invitation::getKV($code);
509
510             if (empty($invite)) {
511                 // TRANS: Client error displayed when trying to create a new user with an invalid invitation code.
512                 $this->clientError(_m('Not a valid invitation code.'));
513             }
514         }
515
516         try {
517             $nickname = Nickname::normalize($this->trimmed('newname'), true);
518         } catch (NicknameException $e) {
519             $this->showForm($e->getMessage());
520             return;
521         }
522
523         $fullname = trim($this->tw_fields['fullname']);
524
525         $args = array('nickname' => $nickname, 'fullname' => $fullname);
526
527         if (!empty($invite)) {
528             $args['code'] = $invite->code;
529         }
530
531         $email = $this->getEmail();
532         if (!empty($email)) {
533             $args['email'] = $email;
534         }
535
536         try {
537             $user = User::register($args);
538         } catch (Exception $e) {
539             $this->serverError($e->getMessage());
540         }
541
542         $result = $this->saveForeignLink($user->id,
543                                          $this->twuid,
544                                          $this->access_token);
545
546         save_twitter_user($this->twuid, $this->tw_fields['screen_name']);
547
548         if (!$result) {
549             // TRANS: Server error displayed when connecting a user to a Twitter user has failed.
550             $this->serverError(_m('Error connecting user to Twitter.'));
551         }
552
553         common_set_user($user);
554         common_real_login(true);
555
556         common_debug('TwitterBridge Plugin - ' .
557                      "Registered new user $user->id from Twitter user $this->twuid");
558
559         Event::handle('EndRegistrationTry', array($this));
560
561         common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), 303);
562     }
563
564     function connectNewUser()
565     {
566         $nickname = $this->trimmed('nickname');
567         $password = $this->trimmed('password');
568
569         if (!common_check_user($nickname, $password)) {
570             // TRANS: Form validation error displayed when connecting an existing user to a Twitter user fails because
571             // TRANS: the provided username and/or password are incorrect.
572             $this->showForm(_m('Invalid username or password.'));
573             return;
574         }
575
576         $user = User::getKV('nickname', $nickname);
577
578         if (!empty($user)) {
579             common_debug('TwitterBridge Plugin - ' .
580                          "Legit user to connect to Twitter: $nickname");
581         }
582
583         $result = $this->saveForeignLink($user->id,
584                                          $this->twuid,
585                                          $this->access_token);
586
587         save_twitter_user($this->twuid, $this->tw_fields['screen_name']);
588
589         if (!$result) {
590             // TRANS: Server error displayed connecting a user to a Twitter user has failed.
591             $this->serverError(_m('Error connecting user to Twitter.'));
592         }
593
594         common_debug('TwitterBridge Plugin - ' .
595                      "Connected Twitter user $this->twuid to local user $user->id");
596
597         common_set_user($user);
598         common_real_login(true);
599
600         $this->goHome($user->nickname);
601     }
602
603     function connectUser()
604     {
605         $user = common_current_user();
606
607         $result = $this->flinkUser($user->id, $this->twuid);
608
609         if (empty($result)) {
610             // TRANS: Server error displayed connecting a user to a Twitter user has failed.
611             $this->serverError(_m('Error connecting user to Twitter.'));
612         }
613
614         common_debug('TwitterBridge Plugin - ' .
615                      "Connected Twitter user $this->twuid to local user $user->id");
616
617         // Return to Twitter connection settings tab
618         common_redirect(common_local_url('twittersettings'), 303);
619     }
620
621     function tryLogin()
622     {
623         common_debug('TwitterBridge Plugin - ' .
624                      "Trying login for Twitter user $this->twuid.");
625
626         $flink = Foreign_link::getByForeignID($this->twuid,
627                                               TWITTER_SERVICE);
628
629         if (!empty($flink)) {
630             $user = $flink->getUser();
631
632             if (!empty($user)) {
633
634                 common_debug('TwitterBridge Plugin - ' .
635                              "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)");
636
637                 common_set_user($user);
638                 common_real_login(true);
639                 $this->goHome($user->nickname);
640             }
641
642         } else {
643
644             common_debug('TwitterBridge Plugin - ' .
645                          "No flink found for twuid: $this->twuid - new user");
646
647             $this->showForm(null, $this->bestNewNickname());
648         }
649     }
650
651     function goHome($nickname)
652     {
653         $url = common_get_returnto();
654         if ($url) {
655             // We don't have to return to it again
656             common_set_returnto(null);
657         } else {
658             $url = common_local_url('all',
659                                     array('nickname' =>
660                                           $nickname));
661         }
662
663         common_redirect($url, 303);
664     }
665
666     function bestNewNickname()
667     {
668         try {
669             return Nickname::normalize($this->tw_fields['fullname'], true);
670         } catch (NicknameException $e) {
671             return null;
672         }
673     }
674 }