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