r99355 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r99354‎ | r99355 | r99356 >
Date:16:01, 9 October 2011
Author:kwisatz
Status:deferred (Comments)
Tags:
Comment:
initial import of TwitterLogin extension
Modified paths:
  • /trunk/extensions/TwitterLogin (added) (history)
  • /trunk/extensions/TwitterLogin/SpecialTwitterLogin.php (added) (history)
  • /trunk/extensions/TwitterLogin/TwitterLogin.body.php (added) (history)
  • /trunk/extensions/TwitterLogin/TwitterLogin.i18n.php (added) (history)
  • /trunk/extensions/TwitterLogin/TwitterLogin.php (added) (history)
  • /trunk/extensions/TwitterLogin/images (added) (history)
  • /trunk/extensions/TwitterLogin/images/sign-in-with-twitter-d-sm.png (added) (history)
  • /trunk/extensions/TwitterLogin/images/sign-in-with-twitter-d.png (added) (history)
  • /trunk/extensions/TwitterLogin/images/sign-in-with-twitter-l-sm.png (added) (history)
  • /trunk/extensions/TwitterLogin/images/sign-in-with-twitter-l.png (added) (history)
  • /trunk/extensions/TwitterLogin/twitter_user.sql (added) (history)
  • /trunk/extensions/TwitterLogin/twitteroauth (added) (history)
  • /trunk/extensions/TwitterLogin/twitteroauth/OAuth.php (added) (history)
  • /trunk/extensions/TwitterLogin/twitteroauth/twitteroauth.php (added) (history)

Diff [purge]

Index: trunk/extensions/TwitterLogin/TwitterLogin.i18n.php
@@ -0,0 +1,31 @@
 2+<?php
 3+
 4+/**
 5+ * Internationalization file for the TwitterLogin extension.
 6+ *
 7+ * @file TwitterLogin.i18n.php
 8+ *
 9+ * @author David Raison
 10+ */
 11+
 12+$aliases = array();
 13+$messages = array();
 14+
 15+/** English
 16+ * @author David Raison
 17+ */
 18+$aliases['en'] = array(
 19+ 'TwitterLogin' => array('TwitterLogin')
 20+);
 21+
 22+/** English
 23+ * @author David Raison
 24+ */
 25+$messages['en'] = array(
 26+ 'twitterlogin' => 'Sign in using Twitter',
 27+ 'signuptwitter' => 'You can register to this wiki using your twitter account',
 28+ 'tietoaccount' => 'You are currently logged into this wiki as $1.<br/>You may sign in with Twitter to tie your twitter account to your existing mediawiki account.',
 29+ 'twitterlogin-desc' => 'Register and log in to a mediawiki using your twitter account',
 30+ 'couldnotconnect' => 'Could not connect to Twitter. Refresh the page or try again later.'
 31+);
 32+
Property changes on: trunk/extensions/TwitterLogin/TwitterLogin.i18n.php
___________________________________________________________________
Added: svn:eol-style
133 + native
Index: trunk/extensions/TwitterLogin/TwitterLogin.php
@@ -0,0 +1,59 @@
 2+<?php
 3+/**
 4+ * TwitterLogin.php
 5+ * Written by David Raison, based on the guideline published by Dave Challis at http://blogs.ecs.soton.ac.uk/webteam/2010/04/13/254/
 6+ * @license: LGPL (GNU Lesser General Public License) http://www.gnu.org/licenses/lgpl.html
 7+ *
 8+ * @file TwitterLogin.php
 9+ * @ingroup TwitterLogin
 10+ *
 11+ * @author David Raison
 12+ *
 13+ * Uses the twitter oauth library by Abraham Williams from https://github.com/abraham/twitteroauth
 14+ *
 15+ */
 16+
 17+if ( !defined( 'MEDIAWIKI' ) ) {
 18+ die( 'This is a MediaWiki extension, and must be run from within MediaWiki.' );
 19+}
 20+
 21+$wgExtensionCredits['specialpage'][] = array(
 22+ 'path' => __FILE__,
 23+ 'name' => 'TwitterLogin',
 24+ 'version' => '0.01',
 25+ 'author' => array( 'Dave Challis', '[http://www.mediawiki.org/wiki/User:Clausekwis David Raison]' ),
 26+ 'url' => 'http://www.mediawiki.org/wiki/Extension:TwitterLogin',
 27+ 'descriptionmsg' => 'twitterlogin-desc'
 28+);
 29+
 30+// Create a twiter group
 31+$wgGroupPermissions['twitter'] = $wgGroupPermissions['user'];
 32+
 33+$wgAutoloadClasses['SpecialTwitterLogin'] = dirname(__FILE__) . '/SpecialTwitterLogin.php';
 34+$wgAutoloadClasses['TwitterOAuth'] = dirname(__FILE__) . '/twitteroauth/twitteroauth.php';
 35+$wgAutoloadClasses['TwitterSigninUI'] = dirname(__FILE__) . '/TwitterLogin.body.php';
 36+
 37+$wgExtensionMessagesFiles['TwitterLogin'] = dirname(__FILE__) .'/TwitterLogin.i18n.php';
 38+$wgSpecialPages['TwitterLogin'] = 'SpecialTwitterLogin';
 39+$wgSpecialPageGroups['TwitterLogin'] = 'login';
 40+
 41+$wgHooks['LanguageGetMagic'][] = 'wfTwitterLoginLanguageGetMagic';
 42+$wgHooks['LoadExtensionSchemaUpdates'][] = 'efSetupTwitterLoginSchema';
 43+
 44+$tsu = new TwitterSigninUI;
 45+$wgHooks['BeforePageDisplay'][] = array( $tsu, 'efAddSigninButton' );
 46+
 47+$stl = new SpecialTwitterLogin;
 48+$wgHooks['UserLoadFromSession'][] = array($stl,'efTwitterAuth');
 49+$wgHooks['UserLogoutComplete'][] = array($stl,'efTwitterLogout');
 50+
 51+function wfTwitterLoginLanguageGetMagic( &$magicWords, $langCode = 'en' ) {
 52+ $magicWords['twitterlogin'] = array( 0, 'twitterlogin' );
 53+ return true;
 54+}
 55+
 56+function efSetupTwitterLoginSchema() {
 57+ $updater->addExtensionUpdate( array( 'addTable', 'twitter_user',
 58+ dirname(__FILE__) . '/twitter_user.sql', true ) );
 59+ return true;
 60+}
Property changes on: trunk/extensions/TwitterLogin/TwitterLogin.php
___________________________________________________________________
Added: svn:eol-style
161 + native
Index: trunk/extensions/TwitterLogin/SpecialTwitterLogin.php
@@ -0,0 +1,267 @@
 2+<?php
 3+/**
 4+ * SpecialTwitterLogin.php
 5+ * Written by David Raison, based on the guideline published by Dave Challis
 6+ * at http://blogs.ecs.soton.ac.uk/webteam/2010/04/13/254/
 7+ * @license: LGPL (GNU Lesser General Public License) http://www.gnu.org/licenses/lgpl.html
 8+ *
 9+ * @file SpecialTwitterLogin.php
 10+ * @ingroup TwitterLogin
 11+ *
 12+ * @author David Raison
 13+ *
 14+ * Uses the twitter oauth library by Abraham Williams from https://github.com/abraham/twitteroauth
 15+ *
 16+ */
 17+
 18+if ( !defined( 'MEDIAWIKI' ) ) {
 19+ die( 'This is a MediaWiki extension, and must be run from within MediaWiki.' );
 20+}
 21+
 22+
 23+class SpecialTwitterLogin extends SpecialPage {
 24+
 25+ private $_consumerKey;
 26+ private $_consumerSecret;
 27+ private $_oauthCallback;
 28+ private $_twUserTable = 'twitter_user';
 29+
 30+ public function __construct(){
 31+ parent::__construct('TwitterLogin');
 32+ global $wgConsumerKey, $wgConsumerSecret, $wgScriptPath;
 33+
 34+ $this->_consumerKey = $wgConsumerKey;
 35+ $this->_consumerSecret = $wgConsumerSecret;
 36+ $this->_oauthCallback = 'https://'.$_SERVER['SERVER_NAME'].$wgScriptPath.'/index.php/Special:TwitterLogin/callback';
 37+ }
 38+
 39+ // default method being called by a specialpage
 40+ public function execute( $parameter ){
 41+ switch($parameter){
 42+ case 'redirect':
 43+ $this->_redirect();
 44+ break;
 45+ case 'callback':
 46+ $this->_handleCallback();
 47+ break;
 48+ default:
 49+ $this->_default();
 50+ break;
 51+ }
 52+
 53+ }
 54+
 55+ private function _default(){
 56+ global $wgOut, $wgUser, $wgScriptPath, $wgExtensionAssetsPath;
 57+ $wgOut->setPagetitle("Twitter Login");
 58+
 59+ /*
 60+ if( $wgUser->isLoggedIn() )
 61+ $wgOut->addWikiText( wfMsg( 'tietoaccount', $wgUser->getName() ) );
 62+ else
 63+ */
 64+ $wgOut->addWikiText( wfMsg( 'signuptwitter') );
 65+
 66+ $wgOut->addHTML( '<a href="' . $wgScriptPath . '/index.php/Special:TwitterLogin/redirect">'
 67+ .'<img src="' . $wgExtensionAssetsPath . '/TwitterLogin/' .
 68+ 'images/sign-in-with-twitter-d.png"/></a>' );
 69+ return true;
 70+ }
 71+
 72+ private function _handleCallback(){
 73+ global $wgScriptPath;
 74+ session_start();
 75+
 76+ if (isset($_REQUEST['oauth_token']) && $_SESSION['oauth_token'] !== $_REQUEST['oauth_token']) {
 77+ $_SESSION['oauth_status'] = 'oldtoken';
 78+ header('Location: '.$wgScriptPath.'/index.php');
 79+ }
 80+
 81+ // Reconnect to twitter and request access token
 82+ $connection = $this->_doTwitterOAuth( $_SESSION['oauth_token'], $_SESSION['oauth_token_secret'] );
 83+ $access_token = $connection->getAccessToken($_REQUEST['oauth_verifier']);
 84+
 85+ /**
 86+ * Save the access tokens. Normally these would be saved in a database for future use.
 87+ * Especially relevant if you'd want to read from or post to this user's timeline
 88+ */
 89+ $_SESSION['access_token'] = $access_token;
 90+
 91+ // Remove no longer needed request tokens
 92+ unset($_SESSION['oauth_token']);
 93+ unset($_SESSION['oauth_token_secret']);
 94+
 95+ /* If HTTP response is 200 continue otherwise send to connect page to retry */
 96+ if ( $connection->http_code == 200 ) {
 97+ /* The user has been verified and the access tokens can be saved for future use */
 98+ $_SESSION['status'] = 'verified';
 99+ $returnto = ( $this->_isFirstLogin() ) ? 'Special:Preferences' : $_SESSION['returnto'];
 100+ header('Location: ' . $wgScriptPath . '/index.php/' . $returnto );
 101+ } else {
 102+ /* Save HTTP status for error dialog on connnect page.*/
 103+ header('Location: /wiki/Special:TwitterLogin');
 104+ }
 105+ }
 106+
 107+ private function _redirect(){
 108+ global $wgRequest, $wgOut, $wgUser;
 109+
 110+ // Creating OAuth object
 111+ $connection = new TwitterOAuth( $this->_consumerKey, $this->_consumerSecret );
 112+
 113+ // Getting temporary credentials
 114+ $request_token = $connection->getRequestToken( $this->_oauthCallback );
 115+
 116+
 117+ // set returnto url
 118+ $_SESSION['returnto'] = ( $wgRequest->getText( 'returnto' ) ) ? $wgRequest->getText( 'returnto' ) : '';
 119+
 120+ // tie to existing account
 121+ /*
 122+ if( $wgUser->isLoggedIn() ) {
 123+ $_SESSION['wiki_username'] = $wgUser->getName();
 124+ $_SESSION['wiki_token'] = $wgUser->getToken();
 125+ }
 126+ */
 127+
 128+ // not sure if this is the proper way to do it in mediawiki ?!
 129+ $_SESSION['oauth_token'] = $request_token['oauth_token'];
 130+ $_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];
 131+
 132+ switch( $connection->http_code ){
 133+ case 200:
 134+ $url = $connection->getAuthorizeURL($request_token['oauth_token']);
 135+ header('Location: '. $url);
 136+ break;
 137+ default:
 138+ $wgOut->addWikiText( wfMsg( 'couldnotconnect' ) );
 139+ break;
 140+ }
 141+ }
 142+
 143+ /**
 144+ * I'm not even sure it is possible to know this
 145+ **/
 146+ private function _isFirstLogin() {
 147+ return false;
 148+ }
 149+
 150+ /**
 151+ * First argument passed is a user object
 152+ * We return here after the callback has redirected us to $returnto with usually valid tokens in the session
 153+ */
 154+ public function efTwitterAuth( $user ){
 155+ if( session_id() == '' )
 156+ session_start();
 157+
 158+ // test if access tokens are set in our session
 159+ if (empty($_SESSION['access_token'])
 160+ || empty($_SESSION['access_token']['oauth_token'])
 161+ || empty($_SESSION['access_token']['oauth_token_secret'])) {
 162+ return false;
 163+ }
 164+
 165+ /* Unverified twitter credentials found, verify them */
 166+ if (!isset($_SESSION['status']) || $_SESSION['status'] != 'verified') {
 167+ $access_token = $_SESSION['access_token'];
 168+ $connection = $this->_doTwitterOAuth( $access_token['oauth_token'], $access_token['oauth_token_secret'] );
 169+
 170+ // verify credentials and create a new user session from the twitter screenname
 171+ $v = $connection->get('account/verify_credentials');
 172+ $user = $this->_userExists( $v->name, $v->screen_name );
 173+ } else {
 174+ // twitter oauth status is verified
 175+ $user = $this->_userExists( $_SESSION['access_token']['name'], $_SESSION['access_token']['screen_name'] );
 176+ }
 177+ unset( $_SESSION['access_token'] ); // or we will not be able to log in as somebody else
 178+ $user->setCookies();
 179+ $user->saveSettings();
 180+ return true;
 181+ }
 182+
 183+ private function _userExists( $name, $screen_name ) {
 184+ $user = User::newFromName( $screen_name );
 185+
 186+ /* let's see if this username already exists or whether it is tied to
 187+ * and already existing native account */
 188+ if( $user->getId() == 0 )
 189+ $this->_createUser( $user, $name, $screen_name );
 190+ else $this->_isCreatedFromTwitter( $user ); // return false if not
 191+ return $user;
 192+ }
 193+
 194+ /**
 195+ * Todo: if we are supposed to tie this account to an existing one, create it but don't use it
 196+ * --> cf _isCreatedFromTwitter --> relation
 197+ * Unfortunately there doesn't seem to be a way to disable or hide an account programmatically
 198+ */
 199+ private function _createUser( $user, $name, $screen_name ){
 200+ global $wgAuth;
 201+
 202+ try {
 203+ wfDebug( __METHOD__ . ':: created user ' . $screen_name . ' from Twitter' );
 204+ $user->addToDatabase();
 205+ $user->setRealName($name);
 206+
 207+ if ( $wgAuth->allowPasswordChange() )
 208+ $user->setPassword(User::randomPassword());
 209+
 210+ $user->addGroup('twitter');
 211+ //$user->confirmEmail();
 212+ $user->setToken();
 213+
 214+ // store the twitter id in our own table
 215+ $this->_storeInTable( $user, $screen_name ); // $relation
 216+ return true;
 217+
 218+ } catch( Exception $e ) {
 219+ print( $e->getTraceAsString() );
 220+ return false;
 221+ }
 222+ }
 223+
 224+ /* should we not use the external_user table since it has the exact same layout? */
 225+ private function _storeInTable( $user, $screen_name ){
 226+ $dbw = wfGetDB(DB_MASTER);
 227+ $dbw->insert( $this->_twUserTable,
 228+ array('user_id' => $user->getId(), 'twitter_id' => $screen_name),
 229+ __METHOD__,
 230+ array()
 231+ );
 232+ }
 233+
 234+ // user already exists... was it created from twitter or did it alread exist before?
 235+ private function _isCreatedFromTwitter( $user ){
 236+ $dbr = wfGetDB(DB_SLAVE);
 237+ $res = $dbr->select( $this->_twUserTable, 'twitter_id', //'relation'
 238+ array( 'user_id' => $user->getId() ),
 239+ __METHOD__
 240+ );
 241+ if ( $row = $dbr->fetchObject( $res ) ) {
 242+ $dbr->freeResult( $res );
 243+ $user->saveToCache();
 244+ } else {
 245+ $dbr->freeResult( $res );
 246+ return false;
 247+ }
 248+ }
 249+
 250+ private function _doTwitterOAuth($at, $ats){
 251+ /* Get user access tokens out of the session. */
 252+ return new TwitterOAuth(
 253+ $this->_consumerKey,
 254+ $this->_consumerSecret,
 255+ $at,
 256+ $ats
 257+ );
 258+ }
 259+
 260+ public function efTwitterLogout(){
 261+ if (session_id() == '') {
 262+ session_start();
 263+ }
 264+ //setcookie(session_name(), session_id(), 1, '/');
 265+ session_destroy();
 266+ return true;
 267+ }
 268+}
Property changes on: trunk/extensions/TwitterLogin/SpecialTwitterLogin.php
___________________________________________________________________
Added: svn:eol-style
1269 + native
Index: trunk/extensions/TwitterLogin/twitteroauth/OAuth.php
@@ -0,0 +1,874 @@
 2+<?php
 3+// vim: foldmethod=marker
 4+
 5+/* Generic exception class
 6+ */
 7+class OAuthException extends Exception {
 8+ // pass
 9+}
 10+
 11+class OAuthConsumer {
 12+ public $key;
 13+ public $secret;
 14+
 15+ function __construct($key, $secret, $callback_url=NULL) {
 16+ $this->key = $key;
 17+ $this->secret = $secret;
 18+ $this->callback_url = $callback_url;
 19+ }
 20+
 21+ function __toString() {
 22+ return "OAuthConsumer[key=$this->key,secret=$this->secret]";
 23+ }
 24+}
 25+
 26+class OAuthToken {
 27+ // access tokens and request tokens
 28+ public $key;
 29+ public $secret;
 30+
 31+ /**
 32+ * key = the token
 33+ * secret = the token secret
 34+ */
 35+ function __construct($key, $secret) {
 36+ $this->key = $key;
 37+ $this->secret = $secret;
 38+ }
 39+
 40+ /**
 41+ * generates the basic string serialization of a token that a server
 42+ * would respond to request_token and access_token calls with
 43+ */
 44+ function to_string() {
 45+ return "oauth_token=" .
 46+ OAuthUtil::urlencode_rfc3986($this->key) .
 47+ "&oauth_token_secret=" .
 48+ OAuthUtil::urlencode_rfc3986($this->secret);
 49+ }
 50+
 51+ function __toString() {
 52+ return $this->to_string();
 53+ }
 54+}
 55+
 56+/**
 57+ * A class for implementing a Signature Method
 58+ * See section 9 ("Signing Requests") in the spec
 59+ */
 60+abstract class OAuthSignatureMethod {
 61+ /**
 62+ * Needs to return the name of the Signature Method (ie HMAC-SHA1)
 63+ * @return string
 64+ */
 65+ abstract public function get_name();
 66+
 67+ /**
 68+ * Build up the signature
 69+ * NOTE: The output of this function MUST NOT be urlencoded.
 70+ * the encoding is handled in OAuthRequest when the final
 71+ * request is serialized
 72+ * @param OAuthRequest $request
 73+ * @param OAuthConsumer $consumer
 74+ * @param OAuthToken $token
 75+ * @return string
 76+ */
 77+ abstract public function build_signature($request, $consumer, $token);
 78+
 79+ /**
 80+ * Verifies that a given signature is correct
 81+ * @param OAuthRequest $request
 82+ * @param OAuthConsumer $consumer
 83+ * @param OAuthToken $token
 84+ * @param string $signature
 85+ * @return bool
 86+ */
 87+ public function check_signature($request, $consumer, $token, $signature) {
 88+ $built = $this->build_signature($request, $consumer, $token);
 89+ return $built == $signature;
 90+ }
 91+}
 92+
 93+/**
 94+ * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
 95+ * where the Signature Base String is the text and the key is the concatenated values (each first
 96+ * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
 97+ * character (ASCII code 38) even if empty.
 98+ * - Chapter 9.2 ("HMAC-SHA1")
 99+ */
 100+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
 101+ function get_name() {
 102+ return "HMAC-SHA1";
 103+ }
 104+
 105+ public function build_signature($request, $consumer, $token) {
 106+ $base_string = $request->get_signature_base_string();
 107+ $request->base_string = $base_string;
 108+
 109+ $key_parts = array(
 110+ $consumer->secret,
 111+ ($token) ? $token->secret : ""
 112+ );
 113+
 114+ $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
 115+ $key = implode('&', $key_parts);
 116+
 117+ return base64_encode(hash_hmac('sha1', $base_string, $key, true));
 118+ }
 119+}
 120+
 121+/**
 122+ * The PLAINTEXT method does not provide any security protection and SHOULD only be used
 123+ * over a secure channel such as HTTPS. It does not use the Signature Base String.
 124+ * - Chapter 9.4 ("PLAINTEXT")
 125+ */
 126+class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
 127+ public function get_name() {
 128+ return "PLAINTEXT";
 129+ }
 130+
 131+ /**
 132+ * oauth_signature is set to the concatenated encoded values of the Consumer Secret and
 133+ * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
 134+ * empty. The result MUST be encoded again.
 135+ * - Chapter 9.4.1 ("Generating Signatures")
 136+ *
 137+ * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
 138+ * OAuthRequest handles this!
 139+ */
 140+ public function build_signature($request, $consumer, $token) {
 141+ $key_parts = array(
 142+ $consumer->secret,
 143+ ($token) ? $token->secret : ""
 144+ );
 145+
 146+ $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
 147+ $key = implode('&', $key_parts);
 148+ $request->base_string = $key;
 149+
 150+ return $key;
 151+ }
 152+}
 153+
 154+/**
 155+ * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
 156+ * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
 157+ * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
 158+ * verified way to the Service Provider, in a manner which is beyond the scope of this
 159+ * specification.
 160+ * - Chapter 9.3 ("RSA-SHA1")
 161+ */
 162+abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
 163+ public function get_name() {
 164+ return "RSA-SHA1";
 165+ }
 166+
 167+ // Up to the SP to implement this lookup of keys. Possible ideas are:
 168+ // (1) do a lookup in a table of trusted certs keyed off of consumer
 169+ // (2) fetch via http using a url provided by the requester
 170+ // (3) some sort of specific discovery code based on request
 171+ //
 172+ // Either way should return a string representation of the certificate
 173+ protected abstract function fetch_public_cert(&$request);
 174+
 175+ // Up to the SP to implement this lookup of keys. Possible ideas are:
 176+ // (1) do a lookup in a table of trusted certs keyed off of consumer
 177+ //
 178+ // Either way should return a string representation of the certificate
 179+ protected abstract function fetch_private_cert(&$request);
 180+
 181+ public function build_signature($request, $consumer, $token) {
 182+ $base_string = $request->get_signature_base_string();
 183+ $request->base_string = $base_string;
 184+
 185+ // Fetch the private key cert based on the request
 186+ $cert = $this->fetch_private_cert($request);
 187+
 188+ // Pull the private key ID from the certificate
 189+ $privatekeyid = openssl_get_privatekey($cert);
 190+
 191+ // Sign using the key
 192+ $ok = openssl_sign($base_string, $signature, $privatekeyid);
 193+
 194+ // Release the key resource
 195+ openssl_free_key($privatekeyid);
 196+
 197+ return base64_encode($signature);
 198+ }
 199+
 200+ public function check_signature($request, $consumer, $token, $signature) {
 201+ $decoded_sig = base64_decode($signature);
 202+
 203+ $base_string = $request->get_signature_base_string();
 204+
 205+ // Fetch the public key cert based on the request
 206+ $cert = $this->fetch_public_cert($request);
 207+
 208+ // Pull the public key ID from the certificate
 209+ $publickeyid = openssl_get_publickey($cert);
 210+
 211+ // Check the computed signature against the one passed in the query
 212+ $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
 213+
 214+ // Release the key resource
 215+ openssl_free_key($publickeyid);
 216+
 217+ return $ok == 1;
 218+ }
 219+}
 220+
 221+class OAuthRequest {
 222+ private $parameters;
 223+ private $http_method;
 224+ private $http_url;
 225+ // for debug purposes
 226+ public $base_string;
 227+ public static $version = '1.0';
 228+ public static $POST_INPUT = 'php://input';
 229+
 230+ function __construct($http_method, $http_url, $parameters=NULL) {
 231+ @$parameters or $parameters = array();
 232+ $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
 233+ $this->parameters = $parameters;
 234+ $this->http_method = $http_method;
 235+ $this->http_url = $http_url;
 236+ }
 237+
 238+
 239+ /**
 240+ * attempt to build up a request from what was passed to the server
 241+ */
 242+ public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
 243+ $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
 244+ ? 'http'
 245+ : 'https';
 246+ @$http_url or $http_url = $scheme .
 247+ '://' . $_SERVER['HTTP_HOST'] .
 248+ ':' .
 249+ $_SERVER['SERVER_PORT'] .
 250+ $_SERVER['REQUEST_URI'];
 251+ @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
 252+
 253+ // We weren't handed any parameters, so let's find the ones relevant to
 254+ // this request.
 255+ // If you run XML-RPC or similar you should use this to provide your own
 256+ // parsed parameter-list
 257+ if (!$parameters) {
 258+ // Find request headers
 259+ $request_headers = OAuthUtil::get_headers();
 260+
 261+ // Parse the query-string to find GET parameters
 262+ $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
 263+
 264+ // It's a POST request of the proper content-type, so parse POST
 265+ // parameters and add those overriding any duplicates from GET
 266+ if ($http_method == "POST"
 267+ && @strstr($request_headers["Content-Type"],
 268+ "application/x-www-form-urlencoded")
 269+ ) {
 270+ $post_data = OAuthUtil::parse_parameters(
 271+ file_get_contents(self::$POST_INPUT)
 272+ );
 273+ $parameters = array_merge($parameters, $post_data);
 274+ }
 275+
 276+ // We have a Authorization-header with OAuth data. Parse the header
 277+ // and add those overriding any duplicates from GET or POST
 278+ if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
 279+ $header_parameters = OAuthUtil::split_header(
 280+ $request_headers['Authorization']
 281+ );
 282+ $parameters = array_merge($parameters, $header_parameters);
 283+ }
 284+
 285+ }
 286+
 287+ return new OAuthRequest($http_method, $http_url, $parameters);
 288+ }
 289+
 290+ /**
 291+ * pretty much a helper function to set up the request
 292+ */
 293+ public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
 294+ @$parameters or $parameters = array();
 295+ $defaults = array("oauth_version" => OAuthRequest::$version,
 296+ "oauth_nonce" => OAuthRequest::generate_nonce(),
 297+ "oauth_timestamp" => OAuthRequest::generate_timestamp(),
 298+ "oauth_consumer_key" => $consumer->key);
 299+ if ($token)
 300+ $defaults['oauth_token'] = $token->key;
 301+
 302+ $parameters = array_merge($defaults, $parameters);
 303+
 304+ return new OAuthRequest($http_method, $http_url, $parameters);
 305+ }
 306+
 307+ public function set_parameter($name, $value, $allow_duplicates = true) {
 308+ if ($allow_duplicates && isset($this->parameters[$name])) {
 309+ // We have already added parameter(s) with this name, so add to the list
 310+ if (is_scalar($this->parameters[$name])) {
 311+ // This is the first duplicate, so transform scalar (string)
 312+ // into an array so we can add the duplicates
 313+ $this->parameters[$name] = array($this->parameters[$name]);
 314+ }
 315+
 316+ $this->parameters[$name][] = $value;
 317+ } else {
 318+ $this->parameters[$name] = $value;
 319+ }
 320+ }
 321+
 322+ public function get_parameter($name) {
 323+ return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
 324+ }
 325+
 326+ public function get_parameters() {
 327+ return $this->parameters;
 328+ }
 329+
 330+ public function unset_parameter($name) {
 331+ unset($this->parameters[$name]);
 332+ }
 333+
 334+ /**
 335+ * The request parameters, sorted and concatenated into a normalized string.
 336+ * @return string
 337+ */
 338+ public function get_signable_parameters() {
 339+ // Grab all parameters
 340+ $params = $this->parameters;
 341+
 342+ // Remove oauth_signature if present
 343+ // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
 344+ if (isset($params['oauth_signature'])) {
 345+ unset($params['oauth_signature']);
 346+ }
 347+
 348+ return OAuthUtil::build_http_query($params);
 349+ }
 350+
 351+ /**
 352+ * Returns the base string of this request
 353+ *
 354+ * The base string defined as the method, the url
 355+ * and the parameters (normalized), each urlencoded
 356+ * and the concated with &.
 357+ */
 358+ public function get_signature_base_string() {
 359+ $parts = array(
 360+ $this->get_normalized_http_method(),
 361+ $this->get_normalized_http_url(),
 362+ $this->get_signable_parameters()
 363+ );
 364+
 365+ $parts = OAuthUtil::urlencode_rfc3986($parts);
 366+
 367+ return implode('&', $parts);
 368+ }
 369+
 370+ /**
 371+ * just uppercases the http method
 372+ */
 373+ public function get_normalized_http_method() {
 374+ return strtoupper($this->http_method);
 375+ }
 376+
 377+ /**
 378+ * parses the url and rebuilds it to be
 379+ * scheme://host/path
 380+ */
 381+ public function get_normalized_http_url() {
 382+ $parts = parse_url($this->http_url);
 383+
 384+ $port = @$parts['port'];
 385+ $scheme = $parts['scheme'];
 386+ $host = $parts['host'];
 387+ $path = @$parts['path'];
 388+
 389+ $port or $port = ($scheme == 'https') ? '443' : '80';
 390+
 391+ if (($scheme == 'https' && $port != '443')
 392+ || ($scheme == 'http' && $port != '80')) {
 393+ $host = "$host:$port";
 394+ }
 395+ return "$scheme://$host$path";
 396+ }
 397+
 398+ /**
 399+ * builds a url usable for a GET request
 400+ */
 401+ public function to_url() {
 402+ $post_data = $this->to_postdata();
 403+ $out = $this->get_normalized_http_url();
 404+ if ($post_data) {
 405+ $out .= '?'.$post_data;
 406+ }
 407+ return $out;
 408+ }
 409+
 410+ /**
 411+ * builds the data one would send in a POST request
 412+ */
 413+ public function to_postdata() {
 414+ return OAuthUtil::build_http_query($this->parameters);
 415+ }
 416+
 417+ /**
 418+ * builds the Authorization: header
 419+ */
 420+ public function to_header($realm=null) {
 421+ $first = true;
 422+ if($realm) {
 423+ $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
 424+ $first = false;
 425+ } else
 426+ $out = 'Authorization: OAuth';
 427+
 428+ $total = array();
 429+ foreach ($this->parameters as $k => $v) {
 430+ if (substr($k, 0, 5) != "oauth") continue;
 431+ if (is_array($v)) {
 432+ throw new OAuthException('Arrays not supported in headers');
 433+ }
 434+ $out .= ($first) ? ' ' : ',';
 435+ $out .= OAuthUtil::urlencode_rfc3986($k) .
 436+ '="' .
 437+ OAuthUtil::urlencode_rfc3986($v) .
 438+ '"';
 439+ $first = false;
 440+ }
 441+ return $out;
 442+ }
 443+
 444+ public function __toString() {
 445+ return $this->to_url();
 446+ }
 447+
 448+
 449+ public function sign_request($signature_method, $consumer, $token) {
 450+ $this->set_parameter(
 451+ "oauth_signature_method",
 452+ $signature_method->get_name(),
 453+ false
 454+ );
 455+ $signature = $this->build_signature($signature_method, $consumer, $token);
 456+ $this->set_parameter("oauth_signature", $signature, false);
 457+ }
 458+
 459+ public function build_signature($signature_method, $consumer, $token) {
 460+ $signature = $signature_method->build_signature($this, $consumer, $token);
 461+ return $signature;
 462+ }
 463+
 464+ /**
 465+ * util function: current timestamp
 466+ */
 467+ private static function generate_timestamp() {
 468+ return time();
 469+ }
 470+
 471+ /**
 472+ * util function: current nonce
 473+ */
 474+ private static function generate_nonce() {
 475+ $mt = microtime();
 476+ $rand = mt_rand();
 477+
 478+ return md5($mt . $rand); // md5s look nicer than numbers
 479+ }
 480+}
 481+
 482+class OAuthServer {
 483+ protected $timestamp_threshold = 300; // in seconds, five minutes
 484+ protected $version = '1.0'; // hi blaine
 485+ protected $signature_methods = array();
 486+
 487+ protected $data_store;
 488+
 489+ function __construct($data_store) {
 490+ $this->data_store = $data_store;
 491+ }
 492+
 493+ public function add_signature_method($signature_method) {
 494+ $this->signature_methods[$signature_method->get_name()] =
 495+ $signature_method;
 496+ }
 497+
 498+ // high level functions
 499+
 500+ /**
 501+ * process a request_token request
 502+ * returns the request token on success
 503+ */
 504+ public function fetch_request_token(&$request) {
 505+ $this->get_version($request);
 506+
 507+ $consumer = $this->get_consumer($request);
 508+
 509+ // no token required for the initial token request
 510+ $token = NULL;
 511+
 512+ $this->check_signature($request, $consumer, $token);
 513+
 514+ // Rev A change
 515+ $callback = $request->get_parameter('oauth_callback');
 516+ $new_token = $this->data_store->new_request_token($consumer, $callback);
 517+
 518+ return $new_token;
 519+ }
 520+
 521+ /**
 522+ * process an access_token request
 523+ * returns the access token on success
 524+ */
 525+ public function fetch_access_token(&$request) {
 526+ $this->get_version($request);
 527+
 528+ $consumer = $this->get_consumer($request);
 529+
 530+ // requires authorized request token
 531+ $token = $this->get_token($request, $consumer, "request");
 532+
 533+ $this->check_signature($request, $consumer, $token);
 534+
 535+ // Rev A change
 536+ $verifier = $request->get_parameter('oauth_verifier');
 537+ $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
 538+
 539+ return $new_token;
 540+ }
 541+
 542+ /**
 543+ * verify an api call, checks all the parameters
 544+ */
 545+ public function verify_request(&$request) {
 546+ $this->get_version($request);
 547+ $consumer = $this->get_consumer($request);
 548+ $token = $this->get_token($request, $consumer, "access");
 549+ $this->check_signature($request, $consumer, $token);
 550+ return array($consumer, $token);
 551+ }
 552+
 553+ // Internals from here
 554+ /**
 555+ * version 1
 556+ */
 557+ private function get_version(&$request) {
 558+ $version = $request->get_parameter("oauth_version");
 559+ if (!$version) {
 560+ // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
 561+ // Chapter 7.0 ("Accessing Protected Ressources")
 562+ $version = '1.0';
 563+ }
 564+ if ($version !== $this->version) {
 565+ throw new OAuthException("OAuth version '$version' not supported");
 566+ }
 567+ return $version;
 568+ }
 569+
 570+ /**
 571+ * figure out the signature with some defaults
 572+ */
 573+ private function get_signature_method(&$request) {
 574+ $signature_method =
 575+ @$request->get_parameter("oauth_signature_method");
 576+
 577+ if (!$signature_method) {
 578+ // According to chapter 7 ("Accessing Protected Ressources") the signature-method
 579+ // parameter is required, and we can't just fallback to PLAINTEXT
 580+ throw new OAuthException('No signature method parameter. This parameter is required');
 581+ }
 582+
 583+ if (!in_array($signature_method,
 584+ array_keys($this->signature_methods))) {
 585+ throw new OAuthException(
 586+ "Signature method '$signature_method' not supported " .
 587+ "try one of the following: " .
 588+ implode(", ", array_keys($this->signature_methods))
 589+ );
 590+ }
 591+ return $this->signature_methods[$signature_method];
 592+ }
 593+
 594+ /**
 595+ * try to find the consumer for the provided request's consumer key
 596+ */
 597+ private function get_consumer(&$request) {
 598+ $consumer_key = @$request->get_parameter("oauth_consumer_key");
 599+ if (!$consumer_key) {
 600+ throw new OAuthException("Invalid consumer key");
 601+ }
 602+
 603+ $consumer = $this->data_store->lookup_consumer($consumer_key);
 604+ if (!$consumer) {
 605+ throw new OAuthException("Invalid consumer");
 606+ }
 607+
 608+ return $consumer;
 609+ }
 610+
 611+ /**
 612+ * try to find the token for the provided request's token key
 613+ */
 614+ private function get_token(&$request, $consumer, $token_type="access") {
 615+ $token_field = @$request->get_parameter('oauth_token');
 616+ $token = $this->data_store->lookup_token(
 617+ $consumer, $token_type, $token_field
 618+ );
 619+ if (!$token) {
 620+ throw new OAuthException("Invalid $token_type token: $token_field");
 621+ }
 622+ return $token;
 623+ }
 624+
 625+ /**
 626+ * all-in-one function to check the signature on a request
 627+ * should guess the signature method appropriately
 628+ */
 629+ private function check_signature(&$request, $consumer, $token) {
 630+ // this should probably be in a different method
 631+ $timestamp = @$request->get_parameter('oauth_timestamp');
 632+ $nonce = @$request->get_parameter('oauth_nonce');
 633+
 634+ $this->check_timestamp($timestamp);
 635+ $this->check_nonce($consumer, $token, $nonce, $timestamp);
 636+
 637+ $signature_method = $this->get_signature_method($request);
 638+
 639+ $signature = $request->get_parameter('oauth_signature');
 640+ $valid_sig = $signature_method->check_signature(
 641+ $request,
 642+ $consumer,
 643+ $token,
 644+ $signature
 645+ );
 646+
 647+ if (!$valid_sig) {
 648+ throw new OAuthException("Invalid signature");
 649+ }
 650+ }
 651+
 652+ /**
 653+ * check that the timestamp is new enough
 654+ */
 655+ private function check_timestamp($timestamp) {
 656+ if( ! $timestamp )
 657+ throw new OAuthException(
 658+ 'Missing timestamp parameter. The parameter is required'
 659+ );
 660+
 661+ // verify that timestamp is recentish
 662+ $now = time();
 663+ if (abs($now - $timestamp) > $this->timestamp_threshold) {
 664+ throw new OAuthException(
 665+ "Expired timestamp, yours $timestamp, ours $now"
 666+ );
 667+ }
 668+ }
 669+
 670+ /**
 671+ * check that the nonce is not repeated
 672+ */
 673+ private function check_nonce($consumer, $token, $nonce, $timestamp) {
 674+ if( ! $nonce )
 675+ throw new OAuthException(
 676+ 'Missing nonce parameter. The parameter is required'
 677+ );
 678+
 679+ // verify that the nonce is uniqueish
 680+ $found = $this->data_store->lookup_nonce(
 681+ $consumer,
 682+ $token,
 683+ $nonce,
 684+ $timestamp
 685+ );
 686+ if ($found) {
 687+ throw new OAuthException("Nonce already used: $nonce");
 688+ }
 689+ }
 690+
 691+}
 692+
 693+class OAuthDataStore {
 694+ function lookup_consumer($consumer_key) {
 695+ // implement me
 696+ }
 697+
 698+ function lookup_token($consumer, $token_type, $token) {
 699+ // implement me
 700+ }
 701+
 702+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {
 703+ // implement me
 704+ }
 705+
 706+ function new_request_token($consumer, $callback = null) {
 707+ // return a new token attached to this consumer
 708+ }
 709+
 710+ function new_access_token($token, $consumer, $verifier = null) {
 711+ // return a new access token attached to this consumer
 712+ // for the user associated with this token if the request token
 713+ // is authorized
 714+ // should also invalidate the request token
 715+ }
 716+
 717+}
 718+
 719+class OAuthUtil {
 720+ public static function urlencode_rfc3986($input) {
 721+ if (is_array($input)) {
 722+ return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
 723+ } else if (is_scalar($input)) {
 724+ return str_replace(
 725+ '+',
 726+ ' ',
 727+ str_replace('%7E', '~', rawurlencode($input))
 728+ );
 729+ } else {
 730+ return '';
 731+ }
 732+}
 733+
 734+
 735+ // This decode function isn't taking into consideration the above
 736+ // modifications to the encoding process. However, this method doesn't
 737+ // seem to be used anywhere so leaving it as is.
 738+ public static function urldecode_rfc3986($string) {
 739+ return urldecode($string);
 740+ }
 741+
 742+ // Utility function for turning the Authorization: header into
 743+ // parameters, has to do some unescaping
 744+ // Can filter out any non-oauth parameters if needed (default behaviour)
 745+ public static function split_header($header, $only_allow_oauth_parameters = true) {
 746+ $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
 747+ $offset = 0;
 748+ $params = array();
 749+ while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
 750+ $match = $matches[0];
 751+ $header_name = $matches[2][0];
 752+ $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
 753+ if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
 754+ $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);
 755+ }
 756+ $offset = $match[1] + strlen($match[0]);
 757+ }
 758+
 759+ if (isset($params['realm'])) {
 760+ unset($params['realm']);
 761+ }
 762+
 763+ return $params;
 764+ }
 765+
 766+ // helper to try to sort out headers for people who aren't running apache
 767+ public static function get_headers() {
 768+ if (function_exists('apache_request_headers')) {
 769+ // we need this to get the actual Authorization: header
 770+ // because apache tends to tell us it doesn't exist
 771+ $headers = apache_request_headers();
 772+
 773+ // sanitize the output of apache_request_headers because
 774+ // we always want the keys to be Cased-Like-This and arh()
 775+ // returns the headers in the same case as they are in the
 776+ // request
 777+ $out = array();
 778+ foreach( $headers AS $key => $value ) {
 779+ $key = str_replace(
 780+ " ",
 781+ "-",
 782+ ucwords(strtolower(str_replace("-", " ", $key)))
 783+ );
 784+ $out[$key] = $value;
 785+ }
 786+ } else {
 787+ // otherwise we don't have apache and are just going to have to hope
 788+ // that $_SERVER actually contains what we need
 789+ $out = array();
 790+ if( isset($_SERVER['CONTENT_TYPE']) )
 791+ $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
 792+ if( isset($_ENV['CONTENT_TYPE']) )
 793+ $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
 794+
 795+ foreach ($_SERVER as $key => $value) {
 796+ if (substr($key, 0, 5) == "HTTP_") {
 797+ // this is chaos, basically it is just there to capitalize the first
 798+ // letter of every word that is not an initial HTTP and strip HTTP
 799+ // code from przemek
 800+ $key = str_replace(
 801+ " ",
 802+ "-",
 803+ ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
 804+ );
 805+ $out[$key] = $value;
 806+ }
 807+ }
 808+ }
 809+ return $out;
 810+ }
 811+
 812+ // This function takes a input like a=b&a=c&d=e and returns the parsed
 813+ // parameters like this
 814+ // array('a' => array('b','c'), 'd' => 'e')
 815+ public static function parse_parameters( $input ) {
 816+ if (!isset($input) || !$input) return array();
 817+
 818+ $pairs = explode('&', $input);
 819+
 820+ $parsed_parameters = array();
 821+ foreach ($pairs as $pair) {
 822+ $split = explode('=', $pair, 2);
 823+ $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
 824+ $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
 825+
 826+ if (isset($parsed_parameters[$parameter])) {
 827+ // We have already recieved parameter(s) with this name, so add to the list
 828+ // of parameters with this name
 829+
 830+ if (is_scalar($parsed_parameters[$parameter])) {
 831+ // This is the first duplicate, so transform scalar (string) into an array
 832+ // so we can add the duplicates
 833+ $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
 834+ }
 835+
 836+ $parsed_parameters[$parameter][] = $value;
 837+ } else {
 838+ $parsed_parameters[$parameter] = $value;
 839+ }
 840+ }
 841+ return $parsed_parameters;
 842+ }
 843+
 844+ public static function build_http_query($params) {
 845+ if (!$params) return '';
 846+
 847+ // Urlencode both keys and values
 848+ $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
 849+ $values = OAuthUtil::urlencode_rfc3986(array_values($params));
 850+ $params = array_combine($keys, $values);
 851+
 852+ // Parameters are sorted by name, using lexicographical byte value ordering.
 853+ // Ref: Spec: 9.1.1 (1)
 854+ uksort($params, 'strcmp');
 855+
 856+ $pairs = array();
 857+ foreach ($params as $parameter => $value) {
 858+ if (is_array($value)) {
 859+ // If two or more parameters share the same name, they are sorted by their value
 860+ // Ref: Spec: 9.1.1 (1)
 861+ natsort($value);
 862+ foreach ($value as $duplicate_value) {
 863+ $pairs[] = $parameter . '=' . $duplicate_value;
 864+ }
 865+ } else {
 866+ $pairs[] = $parameter . '=' . $value;
 867+ }
 868+ }
 869+ // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
 870+ // Each name-value pair is separated by an '&' character (ASCII code 38)
 871+ return implode('&', $pairs);
 872+ }
 873+}
 874+
 875+?>
Property changes on: trunk/extensions/TwitterLogin/twitteroauth/OAuth.php
___________________________________________________________________
Added: svn:eol-style
1876 + native
Index: trunk/extensions/TwitterLogin/twitteroauth/twitteroauth.php
@@ -0,0 +1,245 @@
 2+<?php
 3+
 4+/*
 5+ * Abraham Williams (abraham@abrah.am) http://abrah.am
 6+ *
 7+ * The first PHP Library to support OAuth for Twitter's REST API.
 8+ */
 9+
 10+/* Load OAuth lib. You can find it at http://oauth.net */
 11+require_once('OAuth.php');
 12+
 13+/**
 14+ * Twitter OAuth class
 15+ */
 16+class TwitterOAuth {
 17+ /* Contains the last HTTP status code returned. */
 18+ public $http_code;
 19+ /* Contains the last API call. */
 20+ public $url;
 21+ /* Set up the API root URL. */
 22+ public $host = "https://api.twitter.com/1/";
 23+ /* Set timeout default. */
 24+ public $timeout = 30;
 25+ /* Set connect timeout. */
 26+ public $connecttimeout = 30;
 27+ /* Verify SSL Cert. */
 28+ public $ssl_verifypeer = FALSE;
 29+ /* Respons format. */
 30+ public $format = 'json';
 31+ /* Decode returned json data. */
 32+ public $decode_json = TRUE;
 33+ /* Contains the last HTTP headers returned. */
 34+ public $http_info;
 35+ /* Set the useragnet. */
 36+ public $useragent = 'TwitterOAuth v0.2.0-beta2';
 37+ /* Immediately retry the API call if the response was not successful. */
 38+ //public $retry = TRUE;
 39+
 40+
 41+
 42+
 43+ /**
 44+ * Set API URLS
 45+ */
 46+ function accessTokenURL() { return 'https://api.twitter.com/oauth/access_token'; }
 47+ function authenticateURL() { return 'https://api.twitter.com/oauth/authenticate'; }
 48+ function authorizeURL() { return 'https://api.twitter.com/oauth/authorize'; }
 49+ function requestTokenURL() { return 'https://api.twitter.com/oauth/request_token'; }
 50+
 51+ /**
 52+ * Debug helpers
 53+ */
 54+ function lastStatusCode() { return $this->http_status; }
 55+ function lastAPICall() { return $this->last_api_call; }
 56+
 57+ /**
 58+ * construct TwitterOAuth object
 59+ */
 60+ function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) {
 61+ $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
 62+ $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
 63+ if (!empty($oauth_token) && !empty($oauth_token_secret)) {
 64+ $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret);
 65+ } else {
 66+ $this->token = NULL;
 67+ }
 68+ }
 69+
 70+
 71+ /**
 72+ * Get a request_token from Twitter
 73+ *
 74+ * @returns a key/value array containing oauth_token and oauth_token_secret
 75+ */
 76+ function getRequestToken($oauth_callback = NULL) {
 77+ $parameters = array();
 78+ if (!empty($oauth_callback)) {
 79+ $parameters['oauth_callback'] = $oauth_callback;
 80+ }
 81+ $request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters);
 82+ $token = OAuthUtil::parse_parameters($request);
 83+ $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
 84+ return $token;
 85+ }
 86+
 87+ /**
 88+ * Get the authorize URL
 89+ *
 90+ * @returns a string
 91+ */
 92+ function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) {
 93+ if (is_array($token)) {
 94+ $token = $token['oauth_token'];
 95+ }
 96+ if (empty($sign_in_with_twitter)) {
 97+ return $this->authorizeURL() . "?oauth_token={$token}";
 98+ } else {
 99+ return $this->authenticateURL() . "?oauth_token={$token}";
 100+ }
 101+ }
 102+
 103+ /**
 104+ * Exchange request token and secret for an access token and
 105+ * secret, to sign API calls.
 106+ *
 107+ * @returns array("oauth_token" => "the-access-token",
 108+ * "oauth_token_secret" => "the-access-secret",
 109+ * "user_id" => "9436992",
 110+ * "screen_name" => "abraham")
 111+ */
 112+ function getAccessToken($oauth_verifier = FALSE) {
 113+ $parameters = array();
 114+ if (!empty($oauth_verifier)) {
 115+ $parameters['oauth_verifier'] = $oauth_verifier;
 116+ }
 117+ $request = $this->oAuthRequest($this->accessTokenURL(), 'GET', $parameters);
 118+ $token = OAuthUtil::parse_parameters($request);
 119+ $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
 120+ return $token;
 121+ }
 122+
 123+ /**
 124+ * One time exchange of username and password for access token and secret.
 125+ *
 126+ * @returns array("oauth_token" => "the-access-token",
 127+ * "oauth_token_secret" => "the-access-secret",
 128+ * "user_id" => "9436992",
 129+ * "screen_name" => "abraham",
 130+ * "x_auth_expires" => "0")
 131+ */
 132+ function getXAuthToken($username, $password) {
 133+ $parameters = array();
 134+ $parameters['x_auth_username'] = $username;
 135+ $parameters['x_auth_password'] = $password;
 136+ $parameters['x_auth_mode'] = 'client_auth';
 137+ $request = $this->oAuthRequest($this->accessTokenURL(), 'POST', $parameters);
 138+ $token = OAuthUtil::parse_parameters($request);
 139+ $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
 140+ return $token;
 141+ }
 142+
 143+ /**
 144+ * GET wrapper for oAuthRequest.
 145+ */
 146+ function get($url, $parameters = array()) {
 147+ $response = $this->oAuthRequest($url, 'GET', $parameters);
 148+ if ($this->format === 'json' && $this->decode_json) {
 149+ return json_decode($response);
 150+ }
 151+ return $response;
 152+ }
 153+
 154+ /**
 155+ * POST wrapper for oAuthRequest.
 156+ */
 157+ function post($url, $parameters = array()) {
 158+ $response = $this->oAuthRequest($url, 'POST', $parameters);
 159+ if ($this->format === 'json' && $this->decode_json) {
 160+ return json_decode($response);
 161+ }
 162+ return $response;
 163+ }
 164+
 165+ /**
 166+ * DELETE wrapper for oAuthReqeust.
 167+ */
 168+ function delete($url, $parameters = array()) {
 169+ $response = $this->oAuthRequest($url, 'DELETE', $parameters);
 170+ if ($this->format === 'json' && $this->decode_json) {
 171+ return json_decode($response);
 172+ }
 173+ return $response;
 174+ }
 175+
 176+ /**
 177+ * Format and sign an OAuth / API request
 178+ */
 179+ function oAuthRequest($url, $method, $parameters) {
 180+ if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0) {
 181+ $url = "{$this->host}{$url}.{$this->format}";
 182+ }
 183+ $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters);
 184+ $request->sign_request($this->sha1_method, $this->consumer, $this->token);
 185+ switch ($method) {
 186+ case 'GET':
 187+ return $this->http($request->to_url(), 'GET');
 188+ default:
 189+ return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata());
 190+ }
 191+ }
 192+
 193+ /**
 194+ * Make an HTTP request
 195+ *
 196+ * @return API results
 197+ */
 198+ function http($url, $method, $postfields = NULL) {
 199+ $this->http_info = array();
 200+ $ci = curl_init();
 201+ /* Curl settings */
 202+ curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent);
 203+ curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);
 204+ curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);
 205+ curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
 206+ curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect:'));
 207+ curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);
 208+ curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader'));
 209+ curl_setopt($ci, CURLOPT_HEADER, FALSE);
 210+
 211+ switch ($method) {
 212+ case 'POST':
 213+ curl_setopt($ci, CURLOPT_POST, TRUE);
 214+ if (!empty($postfields)) {
 215+ curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
 216+ }
 217+ break;
 218+ case 'DELETE':
 219+ curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');
 220+ if (!empty($postfields)) {
 221+ $url = "{$url}?{$postfields}";
 222+ }
 223+ }
 224+
 225+ curl_setopt($ci, CURLOPT_URL, $url);
 226+ $response = curl_exec($ci);
 227+ $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
 228+ $this->http_info = array_merge($this->http_info, curl_getinfo($ci));
 229+ $this->url = $url;
 230+ curl_close ($ci);
 231+ return $response;
 232+ }
 233+
 234+ /**
 235+ * Get the header info to store.
 236+ */
 237+ function getHeader($ch, $header) {
 238+ $i = strpos($header, ':');
 239+ if (!empty($i)) {
 240+ $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
 241+ $value = trim(substr($header, $i + 2));
 242+ $this->http_header[$key] = $value;
 243+ }
 244+ return strlen($header);
 245+ }
 246+}
Property changes on: trunk/extensions/TwitterLogin/twitteroauth/twitteroauth.php
___________________________________________________________________
Added: svn:eol-style
1247 + native
Index: trunk/extensions/TwitterLogin/images/sign-in-with-twitter-d.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/TwitterLogin/images/sign-in-with-twitter-d.png
___________________________________________________________________
Added: svn:mime-type
2248 + image/png
Index: trunk/extensions/TwitterLogin/images/sign-in-with-twitter-l-sm.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/TwitterLogin/images/sign-in-with-twitter-l-sm.png
___________________________________________________________________
Added: svn:mime-type
3249 + image/png
Index: trunk/extensions/TwitterLogin/images/sign-in-with-twitter-l.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/TwitterLogin/images/sign-in-with-twitter-l.png
___________________________________________________________________
Added: svn:mime-type
4250 + image/png
Index: trunk/extensions/TwitterLogin/images/sign-in-with-twitter-d-sm.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/TwitterLogin/images/sign-in-with-twitter-d-sm.png
___________________________________________________________________
Added: svn:mime-type
5251 + image/png
Index: trunk/extensions/TwitterLogin/TwitterLogin.body.php
@@ -0,0 +1,33 @@
 2+<?php
 3+/**
 4+ * TwitterLogin.body.php
 5+ * Written by David Raison, based on the guideline published by Dave Challis at http://blogs.ecs.soton.ac.uk/webteam/2010/04/13/254/
 6+ * @license: LGPL (GNU Lesser General Public License) http://www.gnu.org/licenses/lgpl.html
 7+ *
 8+ * @file TwitterLogin.body.php
 9+ * @ingroup TwitterLogin
 10+ *
 11+ * @author David Raison
 12+ *
 13+ * Uses the twitter oauth library by Abraham Williams from https://github.com/abraham/twitteroauth
 14+ *
 15+ */
 16+
 17+class TwitterSigninUI {
 18+ /**
 19+ * Add a sign in with Twitter button but only when a user is not logged in
 20+ */
 21+ public function efAddSigninButton( &$out, &$skin ) {
 22+ global $wgUser, $wgExtensionAssetsPath, $wgScriptPath;
 23+
 24+ if ( !$wgUser->isLoggedIn() ) {
 25+ $out->addInlineScript('$j(document).ready(function(){
 26+ $j("#pt-anonlogin, #pt-login").after(\'<li id="pt-twittersignin">'
 27+ .'<a href="' . $wgScriptPath . '/index.php/Special:TwitterLogin/redirect">'
 28+ .'<img src="' . $wgExtensionAssetsPath . '/TwitterLogin/'
 29+ .'images/sign-in-with-twitter-d.png" width="120px"/></a></li>\');
 30+ })');
 31+ }
 32+ return true;
 33+ }
 34+}
Property changes on: trunk/extensions/TwitterLogin/TwitterLogin.body.php
___________________________________________________________________
Added: svn:eol-style
135 + native
Index: trunk/extensions/TwitterLogin/twitter_user.sql
@@ -0,0 +1,6 @@
 2+CREATE TABLE IF NOT EXISTS `twitter_user` (
 3+ `user_id` int(10) unsigned NOT NULL,
 4+ `twitter_id` varchar(255) NOT NULL,
 5+ PRIMARY KEY (`user_id`),
 6+ UNIQUE KEY `twitter_id` (`twitter_id`)
 7+);

Follow-up revisions

RevisionCommit summaryAuthorDate
r99358implemented alias and messages separation as requested in code review c24014kwisatz16:39, 9 October 2011
r99361Follow up to r99355 and r99357: used TitleObject->getFullURL() and prefixed t...kwisatz18:32, 9 October 2011
r100554Follow up to r99355: now using mediawiki's MwHttpRequest class instead of curlkwisatz18:00, 23 October 2011

Comments

#Comment by Raymond (talk | contribs)   16:12, 9 October 2011

Short i18n/l10n review:

  • Please prefix all message keys with the extension name "twitterlogin-". This avoids key conflicts with MediaWiki core and/or other extensions.
  • Please add message documentation for the newly added messages. Thanks.
  • Separate i18n messages and special page alias. Fot the latter the suggested file name is 'TwitterLogin.alias.php'. Otherwise I cannot add your extension to translatewiki.net
#Comment by Clausekwis (talk | contribs)   16:39, 9 October 2011

Done in r99358 BTW, the manual doesn't specify the separation of alias and messages file: https://secure.wikimedia.org/wikipedia/mediawiki/wiki/Manual:Special_pages#The_Messages.2FInternationalization_File

#Comment by Clausekwis (talk | contribs)   16:13, 9 October 2011

Thanks, will do!

#Comment by MaxSem (talk | contribs)   17:16, 9 October 2011
  • You're duplicating functionality of core's Http class, which also works without cURL installed.
  • user_id field name duplicates one from user table, that's why we recommend to prefix field names uniquely for every table.
  • $this->_oauthCallback = 'https://'.$_SERVER['SERVER_NAME'].$wgScriptPath.'/index.php/Special:TwitterLogin/callback'; will break for installations:
    • with .php5 extension used
    • without pathinfo
recommend using $this->getTitle( 'callback' )->getFullURL()
#Comment by Clausekwis (talk | contribs)   17:24, 9 October 2011

Will have a look at the http class, but that will mean changing a third party library. Is that recommended?

The crippled callback url and table field prefixing will be done it the next release.

#Comment by MaxSem (talk | contribs)   17:27, 9 October 2011

Your call, I can't suggest anything but "if it worth that".

#Comment by Catrope (talk | contribs)   18:04, 10 October 2011

It doesn't mean changing a 3rd party library or even switching to a 3rd party library, does it? The Http class is in MediaWiki core and works even if cURL is not installed.

#Comment by Clausekwis (talk | contribs)   18:13, 10 October 2011

I am already using a 3rd party library, i.e. twitteroauth. What I could do was to create another class that extends twitteroauth and overloads its http method. Good point!

#Comment by Clausekwis (talk | contribs)   18:01, 23 October 2011

Done in r100554

Status & tagging log