]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Twitter OAuth server dance working
authorZach Copley <zach@controlyourself.ca>
Sat, 1 Aug 2009 08:20:44 +0000 (08:20 +0000)
committerEvan Prodromou <evan@controlyourself.ca>
Mon, 24 Aug 2009 15:52:15 +0000 (11:52 -0400)
actions/twitterauthorization.php [new file with mode: 0644]
actions/twittersettings.php
lib/common.php
lib/router.php
lib/twitteroauthclient.php [new file with mode: 0644]

diff --git a/actions/twitterauthorization.php b/actions/twitterauthorization.php
new file mode 100644 (file)
index 0000000..f19cd7f
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Class for doing OAuth authentication against Twitter
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  TwitterauthorizationAction
+ * @package   Laconica
+ * @author    Zach Copely <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+class TwitterauthorizationAction extends Action
+{
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->oauth_token = $this->arg('oauth_token');
+
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!common_logged_in()) {
+            $this->clientError(_('Not logged in.'), 403);
+        }
+
+        $user = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        // If there's already a foreign link record, it means we already
+        // have an access token, and this is unecessary. So go back.
+
+        if (isset($flink)) {
+            common_redirect(common_local_url('twittersettings'));
+        }
+
+        // $this->oauth_token is only populated once Twitter authorizes our
+        // request token. If it's empty we're at the beginning of the auth
+        // process
+
+        if (empty($this->oauth_token)) {
+
+            // Get a new request token and authorize it
+
+            $client = new TwitterOAuthClient();
+            $req_tok = $client->getRequestToken();
+
+            // Sock the request token away in the session temporarily
+
+            $_SESSION['twitter_request_token'] = $req_tok->key;
+            $_SESSION['twitter_request_token_secret'] = $req_tok->key;
+
+            $auth_link = $client->getAuthorizeLink($req_tok);
+            common_redirect($auth_link);
+
+        } else {
+
+            // Check to make sure Twitter sent us the same request token we sent
+
+            if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
+                $this->serverError(_('Couldn\'t link your Twitter account.'));
+            }
+
+            $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
+                $_SESSION['twitter_request_token_secret']);
+
+            // Exchange the request token for an access token
+
+            $atok = $client->getAccessToken();
+
+            // Save the access token and Twitter user info
+
+            $client = new TwitterOAuthClient($atok->key, $atok->secret);
+
+            $twitter_user = $client->verify_credentials();
+
+            $user = common_current_user();
+
+            $flink = new Foreign_link();
+
+            $flink->user_id     = $user->id;
+            $flink->foreign_id  = $twitter_user->id;
+            $flink->service     = TWITTER_SERVICE;
+            $flink->token       = $atok->key;
+            $flink->credentials = $atok->secret;
+            $flink->created     = common_sql_now();
+
+            $flink->set_flags(true, false, false, false);
+
+            $flink_id = $flink->insert();
+
+            if (empty($flink_id)) {
+                common_log_db_error($flink, 'INSERT', __FILE__);
+                $this->serverError(_('Couldn\'t link your Twitter account.'));
+            }
+
+            save_twitter_user($twitter_user->id, $twitter_user->screen_name);
+
+            // clean up the the mess we made in the session
+
+            unset($_SESSION['twitter_request_token']);
+            unset($_SESSION['twitter_request_token_secret']);
+
+            common_redirect(common_local_url('twittersettings'));
+        }
+    }
+
+}
+
index 2b742788eee55419112a718d271f6e4285960163..acc9fb935f01404636835a133063eb39e9715c68 100644 (file)
@@ -69,9 +69,8 @@ class TwittersettingsAction extends ConnectSettingsAction
 
     function getInstructions()
     {
-        return _('Add your Twitter account to automatically send '.
-                 ' your notices to Twitter, ' .
-                 'and subscribe to Twitter friends already here.');
+        return _('Connect your Twitter account to share your updates ' .
+         'with your Twitter friends and vice-versa.');
     }
 
     /**
@@ -93,7 +92,7 @@ class TwittersettingsAction extends ConnectSettingsAction
 
         $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
 
-        if ($flink) {
+        if (!empty($flink)) {
             $fuser = $flink->getForeignUser();
         }
 
@@ -102,73 +101,61 @@ class TwittersettingsAction extends ConnectSettingsAction
                                           'class' => 'form_settings',
                                           'action' =>
                                           common_local_url('twittersettings')));
-        $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
-        $this->element('legend', null, _('Twitter Account'));
+
         $this->hidden('token', common_session_token());
-        if ($fuser) {
+
+
+        if (empty($fuser)) {
+
+            $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
             $this->elementStart('ul', 'form_data');
-            $this->elementStart('li', array('id' => 'settings_twitter_remove'));
-            $this->element('span', 'twitter_user', $fuser->nickname);
-            $this->element('a', array('href' => $fuser->uri), $fuser->uri);
-            $this->element('p', 'form_note',
-                           _('Current verified Twitter account.'));
-            $this->hidden('flink_foreign_id', $flink->foreign_id);
-            $this->elementEnd('li');
+            $this->elementStart('li', array('id' => 'settings_twitter_login_button'));
+            $this->element('a', array('href' => common_local_url('twitterauthorization')),
+                'Connect my Twitter account');
+                $this->elementEnd('li');
             $this->elementEnd('ul');
-            $this->submit('remove', _('Remove'));
+            $this->elementEnd('fieldset');
+
         } else {
+
+            $this->elementStart('fieldset',
+                                array('id' => 'settings_twitter_preferences'));
+            $this->element('legend', null, _('Preferences'));
+
             $this->elementStart('ul', 'form_data');
-            $this->elementStart('li', array('id' => 'settings_twitter_login'));
-            $this->input('twitter_username', _('Twitter user name'),
-                         ($this->arg('twitter_username')) ?
-                         $this->arg('twitter_username') :
-                         $profile->nickname,
-                         _('No spaces, please.')); // hey, it's what Twitter says
+            $this->elementStart('li');
+            $this->checkbox('noticesend',
+                    _('Automatically send my notices to Twitter.'),
+                    ($flink) ?
+                    ($flink->noticesync & FOREIGN_NOTICE_SEND) :
+                    true);
             $this->elementEnd('li');
             $this->elementStart('li');
-            $this->password('twitter_password', _('Twitter password'));
-            $this->elementend('li');
-            $this->elementEnd('ul');
-        }
-        $this->elementEnd('fieldset');
+            $this->checkbox('replysync',
+                    _('Send local "@" replies to Twitter.'),
+                    ($flink) ?
+                    ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
+                    true);
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->checkbox('friendsync',
+                    _('Subscribe to my Twitter friends here.'),
+                    ($flink) ?
+                    ($flink->friendsync & FOREIGN_FRIEND_RECV) :
+                    false);
+            $this->elementEnd('li');
 
-        $this->elementStart('fieldset',
-                            array('id' => 'settings_twitter_preferences'));
-        $this->element('legend', null, _('Preferences'));
-
-        $this->elementStart('ul', 'form_data');
-        $this->elementStart('li');
-        $this->checkbox('noticesend',
-                        _('Automatically send my notices to Twitter.'),
-                        ($flink) ?
-                        ($flink->noticesync & FOREIGN_NOTICE_SEND) :
-                        true);
-        $this->elementEnd('li');
-        $this->elementStart('li');
-        $this->checkbox('replysync',
-                        _('Send local "@" replies to Twitter.'),
-                        ($flink) ?
-                        ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
-                        true);
-        $this->elementEnd('li');
-        $this->elementStart('li');
-        $this->checkbox('friendsync',
-                        _('Subscribe to my Twitter friends here.'),
-                        ($flink) ?
-                        ($flink->friendsync & FOREIGN_FRIEND_RECV) :
-                        false);
-        $this->elementEnd('li');
-
-        if (common_config('twitterbridge','enabled')) {
+            if (common_config('twitterbridge','enabled')) {
             $this->elementStart('li');
             $this->checkbox('noticerecv',
-                            _('Import my Friends Timeline.'),
-                            ($flink) ?
-                            ($flink->noticesync & FOREIGN_NOTICE_RECV) :
-                            false);
+                    _('Import my Friends Timeline.'),
+                    ($flink) ?
+                    ($flink->noticesync & FOREIGN_NOTICE_RECV) :
+                    false);
             $this->elementEnd('li');
-        } else {
+
             // preserve setting even if bidrection bridge toggled off
+
             if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
                 $this->hidden('noticerecv', true, 'noticerecv');
             }
@@ -181,11 +168,13 @@ class TwittersettingsAction extends ConnectSettingsAction
         } else {
             $this->submit('add', _('Add'));
         }
+
         $this->elementEnd('fieldset');
+    }
 
-        $this->showTwitterSubscriptions();
+    $this->showTwitterSubscriptions();
 
-        $this->elementEnd('form');
+    $this->elementEnd('form');
     }
 
     /**
index 8cd3ae2fc0e5b273b411c1b36beedd8ef620f756..5d6956d9b232c982a5b33856af51449a75e2d038 100644 (file)
@@ -188,6 +188,9 @@ $config =
         'integration' =>
         array('source' => 'Laconica', # source attribute for Twitter
               'taguri' => $_server.',2009'), # base for tag URIs
+       'twitter' =>
+       array('consumer_key'    => null,
+             'consumer_secret' => null),
         'memcached' =>
         array('enabled' => false,
               'server' => 'localhost',
index 19839b99722a0682b8f4c253479579d67e969744..6651773c0d8e3c93355027e8ead89e1d8461a783 100644 (file)
@@ -88,6 +88,10 @@ class Router
 
         $m->connect('doc/:title', array('action' => 'doc'));
 
+        // Twitter
+
+        $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
+
         // facebook
 
         $m->connect('facebook', array('action' => 'facebookhome'));
diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php
new file mode 100644 (file)
index 0000000..616fbc2
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+
+require_once('OAuth.php');
+
+class TwitterOAuthClient
+{
+    public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
+    public static $authorizeURL    = 'https://twitter.com/oauth/authorize';
+    public static $accessTokenURL  = 'https://twitter.com/oauth/access_token';
+
+    function __construct($oauth_token = null, $oauth_token_secret = null)
+    {
+        $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
+        $consumer_key    = common_config('twitter', 'consumer_key');
+        $consumer_secret = common_config('twitter', 'consumer_secret');
+        $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
+        $this->token = null;
+
+        if (isset($oauth_token) && isset($oauth_token_secret)) {
+            $this->token = new OAuthToken($oauth_token, $oauth_token_secret);
+        }
+    }
+
+    function getRequestToken()
+    {
+        $response = $this->oAuthGet(TwitterOAuthClient::$requestTokenURL);
+        parse_str($response);
+        $token = new OAuthToken($oauth_token, $oauth_token_secret);
+        return $token;
+    }
+
+    function getAuthorizeLink($request_token)
+    {
+        // Not sure Twitter actually looks at oauth_callback
+
+        return TwitterOAuthClient::$authorizeURL .
+        '?oauth_token=' . $request_token->key . '&oauth_callback=' .
+        urlencode(common_local_url('twitterauthorization'));
+    }
+
+    function getAccessToken()
+    {
+        $response = $this->oAuthPost(TwitterOAuthClient::$accessTokenURL);
+        parse_str($response);
+        $token = new OAuthToken($oauth_token, $oauth_token_secret);
+        return $token;
+    }
+
+    function verify_credentials()
+    {
+        $url = 'https://twitter.com/account/verify_credentials.json';
+        $response = $this->oAuthGet($url);
+        $twitter_user = json_decode($response);
+        return $twitter_user;
+    }
+
+    function oAuthGet($url)
+    {
+        $request = OAuthRequest::from_consumer_and_token($this->consumer,
+            $this->token, 'GET', $url, null);
+        $request->sign_request($this->sha1_method,
+            $this->consumer, $this->token);
+
+        return $this->httpRequest($request->to_url());
+    }
+
+    function oAuthPost($url, $params = null)
+    {
+        $request = OAuthRequest::from_consumer_and_token($this->consumer,
+            $this->token, 'POST', $url, $params);
+        $request->sign_request($this->sha1_method,
+            $this->consumer, $this->token);
+
+        return $this->httpRequest($request->get_normalized_http_url(),
+            $request->to_postdata());
+    }
+
+    function httpRequest($url, $params = null)
+    {
+        $options = array(
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_FAILONERROR    => true,
+            CURLOPT_HEADER         => false,
+            CURLOPT_FOLLOWLOCATION => true,
+            CURLOPT_USERAGENT      => 'Laconica',
+            CURLOPT_CONNECTTIMEOUT => 120,
+            CURLOPT_TIMEOUT        => 120,
+            CURLOPT_HTTPAUTH       => CURLAUTH_ANY,
+            CURLOPT_SSL_VERIFYPEER => false,
+
+            // Twitter is strict about accepting invalid "Expect" headers
+
+            CURLOPT_HTTPHEADER => array('Expect:')
+            );
+
+            if (isset($params)) {
+                $options[CURLOPT_POST] = true;
+                $options[CURLOPT_POSTFIELDS] = $params;
+            }
+
+            $ch = curl_init($url);
+            curl_setopt_array($ch, $options);
+            $response = curl_exec($ch);
+            curl_close($ch);
+
+            return $response;
+    }
+
+}