Parent5446 has submitted this change and it was merged. Change subject: Fix extension functionality and backwards compatibility ......................................................................
Fix extension functionality and backwards compatibility This updates the CA certificate and login endpoint to match Mozilla. Also changes some things to make the extension backwards-compatible with version 1.19 of MediaWiki. Finally, this fixes a few minor bugs: * Infinite looping when authentication is unsuccessful * Non-internationalized error messages Bug: 55975 Change-Id: If61cf139256e411440eb5a327f32f37bd6dfb962 --- M ApiPersona.php M Persona.i18n.php M Persona.php D js/persona.js M js/persona_hooks.js A js/persona_hooks_old.js M persona.crt 7 files changed, 340 insertions(+), 132 deletions(-) Approvals: Parent5446: Verified; Looks good to me, approved diff --git a/ApiPersona.php b/ApiPersona.php index dfbaa5b..4283cad 100644 --- a/ApiPersona.php +++ b/ApiPersona.php @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - + * * Extension:Persona 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 General Public License for more details. - + * * You should have received a copy of the GNU General Public License * along with Extension:Persona. If not, see <http://www.gnu.org/licenses/>. */ @@ -114,7 +114,8 @@ function execute() { global $wgSecureLogin; - if( $wgSecureLogin && WebRequest::detectProtocol() !== 'https' ) { + + if ( $wgSecureLogin && WebRequest::detectProtocol() !== 'https' ) { $this->dieUsage( 'Secure login is enabled, and an insecure (non-HTTPS) request was made.', 'insecure' ); } @@ -123,50 +124,62 @@ // Check login token and throttling as is done in LoginForm::authenticateUserData. // Note that since we do not yet know the username of the login target, the throttle // is set for an empty user, effectively making this a per-IP only throttle. - if( !LoginForm::getLoginToken() ) { + if ( !LoginForm::getLoginToken() ) { LoginForm::setLoginToken(); $this->dieUsageMsg( 'sessionfailure' ); - } elseif( LoginForm::incLoginThrottle( '' ) === true ) { + } elseif ( LoginForm::incLoginThrottle( '' ) === true ) { $this->dieUsageMsg( 'actionthrottledtext' ); - } elseif( $params['token'] !== LoginForm::getLoginToken() ) { + } elseif ( $params['token'] !== LoginForm::getLoginToken() ) { $this->dieUsageMsg( 'sessionfailure' ); } // Contact the verification server. $assertion = $params['assertion']; - $response = Http::post( - 'https://verifier.login.persona.org/verify', - array( - 'caInfo' => __DIR__ . '/persona.crt', - 'postData' => wfArrayToCgi( array( - 'assertion' => $assertion, - 'audience' => wfExpandUrl( '/', $wgSecureLogin ? PROTO_HTTPS : PROTO_HTTP ) - ) ) - ) - ); - $result = (array) FormatJson::decode( $response ); + $request = MWHttpRequest::factory( 'https://login.persona.org/verify', array( + 'method' => 'post', + 'caInfo' => __DIR__ . '/persona.crt', + 'sslVerifyHost' => true, + 'sslVerifyCert' => true, + 'postData' => wfArrayToCgi( array( + 'assertion' => $assertion, + 'audience' => wfExpandUrl( '/', $wgSecureLogin ? PROTO_HTTPS : PROTO_HTTP ) + ) ), + ) ); + $request->setHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); - if( !isset( $result['status'] ) || $result['status'] !== 'okay' ) { + $status = $request->execute(); + $response = $request->getContent(); + $result = (array)FormatJson::decode( $response ); + + if ( !isset( $result['status'] ) || $result['status'] !== 'okay' ) { // Bad assertion. Do nothing, as the response itself has // sufficient information. $result['status'] = 'failure'; - } elseif( $result['audience'] != wfExpandUrl( '/', $wgSecureLogin ? PROTO_HTTPS : PROTO_HTTP ) ) { + } elseif ( $result['audience'] != wfExpandUrl( '/', $wgSecureLogin ? PROTO_HTTPS : PROTO_HTTP ) ) { // Weird. Audience was returned differently. $result['status'] = 'error'; } else { // Valid token. Login the user. + + // BC: User::selectFields is new in MediaWiki 1.21 + if ( function_exists( array( 'User', 'selectFields' ) ) ) { + $fields = User::selectFields(); + } else { + $fields = '*'; + } + $dbr = wfGetDB( DB_MASTER ); $res = $dbr->select( 'user', - User::selectFields(), + $fields, array( 'user_email' => $result['email'] ) ); - if( $res === false ) { + if ( $res === false ) { $result['status'] = 'dberror'; - } elseif( $res->numRows() == 0 ) { + } elseif ( $res->numRows() == 0 ) { $result['status'] = 'invaliduser'; - } elseif( $res->numRows() > 1 ) { + } elseif ( $res->numRows() > 1 ) { $result['status'] = 'multipleusers'; } else { // We're good to go. Login the user. @@ -182,7 +195,10 @@ } LoginForm::clearLoginToken(); - ApiQueryInfo::resetTokenCache(); + // BC: ApiQueryInfo::resetTokenCache didn't exist in MediaWiki 1.20 + if ( function_exists( array( 'ApiQueryInfo', 'resetTokenCache' ) ) ) { + ApiQueryInfo::resetTokenCache(); + } // Add injected HTML as an optional message. $injected_html = ''; diff --git a/Persona.i18n.php b/Persona.i18n.php index e5d6d0e..6764e28 100644 --- a/Persona.i18n.php +++ b/Persona.i18n.php @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - + * * Extension:Persona 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 General Public License for more details. - + * * You should have received a copy of the GNU General Public License * along with Extension:Persona. If not, see <http://www.gnu.org/licenses/>. */ @@ -22,7 +22,12 @@ $messages['en'] = array( 'persona-login' => 'Login with Persona', - 'persona-desc' => 'Allows users to log in with their Mozilla Persona account.' + 'persona-desc' => 'Allows users to log in with their Mozilla Persona account.', + 'persona-error-insecure' => 'Logging in over an insecure connection is not allowed.', + 'persona-error-failure' => 'Persona failed to verify your identity.', + 'persona-error-dberror' => 'An internal database error occurred.', + 'persona-error-invaliduser' => 'There is no user on {{SITENAME}} matching your Persona account.', + 'persona-error-multipleusers' => 'There are multiple users with the same email address as your Persona account. Your account must have a unique email address to log in with Persona.', ); /** Message documentation (Message documentation) @@ -32,6 +37,11 @@ $messages['qqq'] = array( 'persona-login' => 'Label for the Persona login link and button', 'persona-desc' => '{{desc|name=Persona|url=http://www.mediawiki.org/wiki/Extension:Persona}}', + 'persona-error-insecure' => 'Error message displayed using mw.notify when attempting insecure login with $wgSecureLogin enabled', + 'persona-error-failure' => 'Error message displayed using mw.notify when the Persona API fails to verify', + 'persona-error-dberror' => 'Error message displayed using mw.notify when an internal error occurs', + 'persona-error-invaliduser' => 'Error message displayed using mw.notify for invalid logins', + 'persona-error-multipleusers' => 'Error message displayed using mw.notify when a Persona account matches multiple MediaWiki accounts', ); /** Asturian (asturianu) diff --git a/Persona.php b/Persona.php index cccf21a..6e053c2 100644 --- a/Persona.php +++ b/Persona.php @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - + * * Extension:Persona 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 General Public License for more details. - + * * You should have received a copy of the GNU General Public License * along with Extension:Persona. If not, see <http://www.gnu.org/licenses/>. */ @@ -27,6 +27,12 @@ 'descriptionmsg' => 'persona-desc' ); +/** + * Determines if the persona login link is displayed on every page in the personal URLs bar (true) + * or if it is only displayed on the login page (false). + * + * @var bool + */ $wgPersonaLoginAnywhere = true; $wgHooks['BeforePageDisplay'][] = 'efAddPersonaModule'; @@ -37,37 +43,110 @@ $wgExtensionMessagesFiles['Persona'] = __DIR__ . '/Persona.i18n.php'; $wgResourceModules['ext.persona'] = array( - 'scripts' => array( 'js/persona.js', 'js/persona_hooks.js' ), - 'styles' => array(), - 'messages' => array(), - 'dependencies' => array( 'mediawiki.api', 'mediawiki.Title' ), + 'scripts' => array( 'js/persona_hooks.js' ), + 'messages' => array( + 'sessionfailure', + 'actionthrottledtext', + 'persona-error-insecure', + 'persona-error-failure', + 'persona-error-dberror', + 'persona-error-invaliduser', + 'persona-error-multipleusers', + ), + 'dependencies' => array( + 'mediawiki.api', + 'mediawiki.Title', + 'mediawiki.notify', + 'mediawiki.jqueryMsg', + ), + 'localBasePath' => __DIR__, + 'remoteExtPath' => 'Persona' +); +$wgResourceModules['ext.persona.old'] = array( + 'scripts' => array( 'js/persona_hooks_old.js' ), + 'messages' => array( + 'sessionfailure', + 'actionthrottledtext', + 'persona-error-insecure', + 'persona-error-failure', + 'persona-error-dberror', + 'persona-error-invaliduser', + 'persona-error-multipleusers', + ), + 'dependencies' => array( + 'mediawiki.api', + 'mediawiki.Title', + 'mediawiki.notify', + 'mediawiki.jqueryMsg', + ), 'localBasePath' => __DIR__, 'remoteExtPath' => 'Persona' ); /** - * Add the Persona module to the OutputPage. + * Add the Persona JS module and variables to the output page. Also make sure a session + * is started and a login token is set. * - * @param &$out OutputPage object - * @param &$skin Skin object + * @param User $user Current user that is logged in + * @param OutputPage $out Output page to add scripts to */ -function efAddPersonaModule( OutputPage &$out, Skin &$skin ) { - global $wgPersonaLoginAnywhere; - if( !$wgPersonaLoginAnywhere ) { - return true; - } elseif( !isset( $_SESSION ) ) { +function efPersonaAddScripts( User $user, OutputPage $out ) { + global $wgVersion; + + if ( !isset( $_SESSION ) ) { wfSetupSession(); } - - $out->addModules( 'ext.persona' ); - if( !LoginForm::getLoginToken() ) { + if ( !LoginForm::getLoginToken() ) { LoginForm::setLoginToken(); } + + // Persona requires that IE compatibility mode be disabled + // Add the meta tag here in case MediaWiki core doesn't do it + $out->addMeta( 'http:X-UA-Compatible', 'IE=Edge' ); + + if ( ResourceLoader::inDebugMode() ) { + $out->addScriptFile( 'https://login.persona.org/include.orig.js' ); + } else { + $out->addScriptFile( 'https://login.persona.org/include.js' ); + } + + if ( version_compare( $wgVersion, '1.20', '<' ) ) { + $out->addModules( 'ext.persona.old' ); + } else { + $out->addModules( 'ext.persona' ); + } + + $out->addJsConfigVars( 'wgPersonaUserEmail', + $user->isEmailConfirmed() ? $user->getEmail() : null ); +} + +/** + * Add the Persona module to the OutputPage. + * + * @param OutputPage &$out + * + * @return bool true + */ +function efAddPersonaModule( OutputPage &$out ) { + global $wgPersonaLoginAnywhere; + + // Only add the modules and whatnot if necessary. + if ( + !$wgPersonaLoginAnywhere && + $out->getTitle()->equals( SpecialPage::getTitleFor( 'Userlogin' ) ) + ) { + return true; + } + + $context = RequestContext::getMain(); + efPersonaAddScripts( $context->getUser(), $out ); + $out->addHTML( Html::input( 'wpLoginToken', LoginForm::getLoginToken(), 'hidden' ) ); + return true; } @@ -76,15 +155,21 @@ * to the login form. * * @param $template QuickTemplate + * + * @return bool true */ function efAddPersonaLogin( $template ) { - $context = RequestContext::getMain(); - $out = $context->getOutput(); - $out->addModules( 'ext.persona' ); - - $label = wfMessage( 'persona-login' )->escaped(); - $personaButton = Html::input( 'wpPersona', $label, 'button', array( 'id' => 'wpPersona' ) ); + $personaButton = Html::input( + 'wpPersona', + wfMessage( 'persona-login' )->text(), + 'button', + array( + 'id' => 'wpPersona', + 'class' => 'mw-ui-button', + ) + ); $template->set( 'header', $personaButton ); + return true; } @@ -93,19 +178,19 @@ * * @param $personal_urls Array of personal URLs * @param $title Title currently being viewed + * + * @return bool true */ function efAddPersonaLinks( array &$personal_urls, Title $title ) { global $wgPersonaLoginAnywhere; - if( $wgPersonaLoginAnywhere && !isset( $personal_urls['logout'] ) ) { - $context = RequestContext::getMain(); - $out = $context->getOutput(); - $out->addModules( 'ext.persona' ); + if ( $wgPersonaLoginAnywhere && !isset( $personal_urls['logout'] ) ) { $personal_urls['personalogin'] = array( 'text' => wfMessage( 'persona-login' ), 'href' => '#', 'active' => $title->isSpecial( 'Userlogin' ) ); } + return true; } diff --git a/js/persona.js b/js/persona.js deleted file mode 100644 index d005a70..0000000 --- a/js/persona.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Uncompressed source can be found at https://login.persona.org/include.orig.js - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -(function(){var a=function(){function e(a){return Array.isArray?Array.isArray(a):a.constructor.toString().indexOf("Array")!=-1}function d(a,c,d){var e=b[c][d];for(var f=0;f<e.length;f++)e[f].win===a&&e.splice(f,1);b[c][d].length===0&&delete b[c][d]}function c(a,c,d,e){function f(b){for(var c=0;c<b.length;c++)if(b[c].win===a)return!0;return!1}var g=!1;if(c==="*")for(var h in b){if(!b.hasOwnProperty(h))continue;if(h==="*")continue;if(typeof b[h][d]=="object"){g=f(b[h][d]);if(g)break}}else b["*"]&&b["*"][d]&&(g=f(b["*"][d])),!g&&b[c]&&b[c][d]&&(g=f(b[c][d]));if(g)throw"A channel is already bound to the same window which overlaps with origin '"+c+"' and has scope '"+d+"'";typeof b[c]!="object"&&(b[c]={}),typeof b[c][d]!="object"&&(b[c][d]=[]),b[c][d].push({win:a,handler:e})}"use strict";var a=Math.floor(Math.random()*1000001),b={},f={},g=function(a){try{var c=JSON.parse(a.data);if(typeof c!="object"||c===null)throw"malformed"}catch(a){return}var d=a.source,e=a.origin,g,h,i;if(typeof c.method=="string"){var j=c.method.split("::");j.length==2?(g=j[0],i=j[1]):i=c.method}typeof c.id!="undefined"&&(h=c.id);if(typeof i=="string"){var k=!1;if(b[e]&&b[e][g])for(var l=0;l<b[e][g].length;l++)if(b[e][g][l].win===d){b[e][g][l].handler(e,i,c),k=!0;break}if(!k&&b["*"]&&b["*"][g])for(var l=0;l<b["*"][g].length;l++)if(b["*"][g][l].win===d){b["*"][g][l].handler(e,i,c);break}}else typeof h!="undefined"&&f[h]&&f[h](e,i,c)};window.addEventListener?window.addEventListener("message",g,!1):window.attachEvent&&window.attachEvent("onmessage",g);return{build:function(b){var g=function(a){if(b.debugOutput&&window.console&&window.console.log){try{typeof a!="string"&&(a=JSON.stringify(a))}catch(c){}console.log("["+j+"] "+a)}};if(!window.postMessage)throw"jschannel cannot run this browser, no postMessage";if(!window.JSON||!window.JSON.stringify||!window.JSON.parse)throw"jschannel cannot run this browser, no JSON parsing/serialization";if(typeof b!="object")throw"Channel build invoked without a proper object argument";if(!b.window||!b.window.postMessage)throw"Channel.build() called without a valid window argument";if(window===b.window)throw"target window is same as present window -- not allowed";var h=!1;if(typeof b.origin=="string"){var i;b.origin==="*"?h=!0:null!==(i=b.origin.match(/^https?:\/\/(?:[-a-zA-Z0-9_\.])+(?::\d+)?/))&&(b.origin=i[0].toLowerCase(),h=!0)}if(!h)throw"Channel.build() called with an invalid origin";if(typeof b.scope!="undefined"){if(typeof b.scope!="string")throw"scope, when specified, must be a string";if(b.scope.split("::").length>1)throw"scope may not contain double colons: '::'"}var j=function(){var a="",b="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";for(var c=0;c<5;c++)a+=b.charAt(Math.floor(Math.random()*b.length));return a}(),k={},l={},m={},n=!1,o=[],p=function(a,b,c){var d=!1,e=!1;return{origin:b,invoke:function(b,d){if(!m[a])throw"attempting to invoke a callback of a nonexistent transaction: "+a;var e=!1;for(var f=0;f<c.length;f++)if(b===c[f]){e=!0;break}if(!e)throw"request supports no such callback '"+b+"'";t({id:a,callback:b,params:d})},error:function(b,c){e=!0;if(!m[a])throw"error called for nonexistent message: "+a;delete m[a],t({id:a,error:b,message:c})},complete:function(b){e=!0;if(!m[a])throw"complete called for nonexistent message: "+a;delete m[a],t({id:a,result:b})},delayReturn:function(a){typeof a=="boolean"&&(d=a===!0);return d},completed:function(){return e}}},q=function(a,b,c){return window.setTimeout(function(){if(l[a]){var d="timeout ("+b+"ms) exceeded on method '"+c+"'";(1,l[a].error)("timeout_error",d),delete l[a],delete f[a]}},b)},r=function(a,c,d){if(typeof b.gotMessageObserver=="function")try{b.gotMessageObserver(a,d)}catch(h){g("gotMessageObserver() raised an exception: "+h.toString())}if(d.id&&c){if(k[c]){var i=p(d.id,a,d.callbacks?d.callbacks:[]);m[d.id]={};try{if(d.callbacks&&e(d.callbacks)&&d.callbacks.length>0)for(var j=0;j<d.callbacks.length;j++){var n=d.callbacks[j],o=d.params,q=n.split("/");for(var r=0;r<q.length-1;r++){var s=q[r];typeof o[s]!="object"&&(o[s]={}),o=o[s]}o[q[q.length-1]]=function(){var a=n;return function(b){return i.invoke(a,b)}}()}var t=k[c](i,d.params);!i.delayReturn()&&!i.completed()&&i.complete(t)}catch(h){var u="runtime_error",v=null;typeof h=="string"?v=h:typeof h=="object"&&(h&&e(h)&&h.length==2?(u=h[0],v=h[1]):typeof h.error=="string"&&(u=h.error,h.message?typeof h.message=="string"?v=h.message:h=h.message:v=""));if(v===null)try{v=JSON.stringify(h),typeof v=="undefined"&&(v=h.toString())}catch(w){v=h.toString()}i.error(u,v)}}}else d.id&&d.callback?!l[d.id]||!l[d.id].callbacks||!l[d.id].callbacks[d.callback]?g("ignoring invalid callback, id:"+d.id+" ("+d.callback+")"):l[d.id].callbacks[d.callback](d.params):d.id?l[d.id]?(d.error?(1,l[d.id].error)(d.error,d.message):d.result!==undefined?(1,l[d.id].success)(d.result):(1,l[d.id].success)(),delete l[d.id],delete f[d.id]):g("ignoring invalid response: "+d.id):c&&k[c]&&k[c](null,d.params)};c(b.window,b.origin,typeof b.scope=="string"?b.scope:"",r);var s=function(a){typeof b.scope=="string"&&b.scope.length&&(a=[b.scope,a].join("::"));return a},t=function(a,c){if(!a)throw"postMessage called with null message";var d=n?"post ":"queue ";g(d+" message: "+JSON.stringify(a));if(!c&&!n)o.push(a);else{if(typeof b.postMessageObserver=="function")try{b.postMessageObserver(b.origin,a)}catch(e){g("postMessageObserver() raised an exception: "+e.toString())}b.window.postMessage(JSON.stringify(a),b.origin)}},u=function(a,c){g("ready msg received");if(n)throw"received ready message while in ready state. help!";c==="ping"?j+="-R":j+="-L",v.unbind("__ready"),n=!0,g("ready msg accepted."),c==="ping"&&v.notify({method:"__ready",params:"pong"});while(o.length)t(o.pop());typeof b.onReady=="function"&&b.onReady(v)},v={unbind:function(a){if(k[a]){if(delete k[a])return!0;throw"can't delete method: "+a}return!1},bind:function(a,b){if(!a||typeof a!="string")throw"'method' argument to bind must be string";if(!b||typeof b!="function")throw"callback missing from bind params";if(k[a])throw"method '"+a+"' is already bound!";k[a]=b;return this},call:function(b){if(!b)throw"missing arguments to call function";if(!b.method||typeof b.method!="string")throw"'method' argument to call must be string";if(!b.success||typeof b.success!="function")throw"'success' callback missing from call";var c={},d=[],e=function(a,b){if(typeof b=="object")for(var f in b){if(!b.hasOwnProperty(f))continue;var g=a+(a.length?"/":"")+f;typeof b[f]=="function"?(c[g]=b[f],d.push(g),delete b[f]):typeof b[f]=="object"&&e(g,b[f])}};e("",b.params);var g={id:a,method:s(b.method),params:b.params};d.length&&(g.callbacks=d),b.timeout&&q(a,b.timeout,s(b.method)),l[a]={callbacks:c,error:b.error,success:b.success},f[a]=r,a++,t(g)},notify:function(a){if(!a)throw"missing arguments to notify function";if(!a.method||typeof a.method!="string")throw"'method' argument to notify must be string";t({method:s(a.method),params:a.params})},destroy:function(){d(b.window,b.origin,typeof b.scope=="string"?b.scope:""),window.removeEventListener?window.removeEventListener("message",r,!1):window.detachEvent&&window.detachEvent("onmessage",r),n=!1,k={},m={},l={},b.origin=null,o=[],g("channel destroyed"),j=""}};v.bind("__ready",u),setTimeout(function(){},0);return v}}}();WinChan=function(){function i(){var b=window.location,c=window.opener.frames,d=b.protocol+"//"+b.host;for(var e=c.length-1;e>=0;e--)try{if(c[e].location.href.indexOf(d)===0&&c[e].name===a)return c[e]}catch(f){}return}function h(a){/^https?:\/\//.test(a)||(a=window.location.href);var b=/^(https?:\/\/[\-_a-zA-Z\.0-9:]+)/.exec(a);return b?b[1]:a}function g(){return window.JSON&&window.JSON.stringify&&window.JSON.parse&&window.postMessage}function f(){try{var a=navigator.userAgent;return a.indexOf("Fennec/")!=-1||a.indexOf("Firefox/")!=-1&&a.indexOf("Android")!=-1}catch(b){}return!1}function e(){var a=-1;if(navigator.appName==="Microsoft Internet Explorer"){var b=navigator.userAgent,c=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");c.exec(b)!=null&&(a=parseFloat(RegExp.$1))}return a>=8}function d(a,b,c){a.detachEvent?a.detachEvent("on"+b,c):a.removeEventListener&&a.removeEventListener(b,c,!1)}function c(a,b,c){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener&&a.addEventListener(b,c,!1)}var a="__winchan_relay_frame",b="die",j=e();return g()?{open:function(e,g){function q(a){try{var b=JSON.parse(a.data);b.a==="ready"?m.postMessage(o,l):b.a==="error"?g&&(g(b.d),g=null):b.a==="response"&&(d(window,"message",q),d(window,"unload",p),p(),g&&(g(null,b.d),g=null))}catch(c){}}function p(){k&&document.body.removeChild(k),k=undefined;if(n)try{n.close()}catch(a){m.postMessage(b,l)}n=m=undefined}if(!g)throw"missing required callback argument";var i;e.url||(i="missing required 'url' parameter"),e.relay_url||(i="missing required 'relay_url' parameter"),i&&setTimeout(function(){g(i)},0),e.window_name||(e.window_name=null);if(!e.window_features||f())e.window_features=undefined;var k,l=h(e.url);if(l!==h(e.relay_url))return setTimeout(function(){g("invalid arguments: origin of url and relay_url must match")},0);var m;j&&(k=document.createElement("iframe"),k.setAttribute("src",e.relay_url),k.style.display="none",k.setAttribute("name",a),document.body.appendChild(k),m=k.contentWindow);var n=window.open(e.url,e.window_name,e.window_features);m||(m=n);var o=JSON.stringify({a:"request",d:e.params});c(window,"unload",p),c(window,"message",q);return{close:p,focus:function(){if(n)try{n.focus()}catch(a){}}}}}:{open:function(a,b,c,d){setTimeout(function(){d("unsupported browser")},0)}}}();var b=function(){function l(){return c}function k(){c=g()||h()||i()||j();return!c}function j(){if(!(window.JSON&&window.JSON.stringify&&window.JSON.parse))return"JSON_NOT_SUPPORTED"}function i(){if(!a.postMessage)return"POSTMESSAGE_NOT_SUPPORTED"}function h(){try{var b="localStorage"in a&&a.localStorage!==null;if(b)a.localStorage.setItem("test","true"),a.localStorage.removeItem("test");else return"LOCALSTORAGE_NOT_SUPPORTED"}catch(c){return"LOCALSTORAGE_DISABLED"}}function g(){return f()}function f(){var a=e(),b=a>-1&&a<8;if(b)return"BAD_IE_VERSION"}function e(){var a=-1;if(b.appName=="Microsoft Internet Explorer"){var c=b.userAgent,d=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");d.exec(c)!=null&&(a=parseFloat(RegExp.$1))}return a}function d(c,d){b=c,a=d}var a=window,b=navigator,c;return{setTestEnv:d,isSupported:k,getNoSupportReason:l}}();navigator.id||(navigator.id={});if(!navigator.id.request||navigator.id._shimmed){var c="https://login.persona.org",d=navigator.userAgent,e=d.indexOf("Fennec/")!=-1||d.indexOf("Firefox/")!=-1&&d.indexOf("Android")!=-1,f=e?undefined:"menubar=0,location=1,resizable=1,scrollbars=1,status=0,dialog=1,minimizable=1,width=700,height=375",g,h={login:null,logout:null,ready:null},i=undefined;function j(a){a!==!0;if(i===undefined)i=a;else if(i!=a)throw"you cannot combine the navigator.id.watch() API with navigator.id.getVerifiedEmail() or navigator.id.get()this site should instead use navigator.id.request() and navigator.id.watch()"}var k,l=b.isSupported();function m(){if(!!l)try{if(!k){var b=window.document,d=b.createElement("iframe");d.style.display="none",b.body.appendChild(d),d.src=c+"/communication_iframe",k=a.build({window:d.contentWindow,origin:c,scope:"mozid_ni",onReady:function(){k.call({method:"loaded",success:function(){h.ready&&h.ready()},error:function(){}})}}),k.bind("logout",function(a,b){h.logout&&h.logout()}),k.bind("login",function(a,b){h.login&&h.login(b)})}}catch(e){k=undefined}}function n(a){return typeof a!="undefined"}function o(a){try{console.warn(a)}catch(b){}}function p(a,b){if(n(a[b])){o(b+" has been deprecated");return!0}}function q(a,b,c){if(n(a[b])&&n(a[c]))throw"you cannot supply *both* "+b+" and "+c;p(a,b)&&(a[c]=a[b],delete a[b])}function r(a){if(typeof a=="object"){if(a.onlogin&&typeof a.onlogin!="function"||a.onlogout&&typeof a.onlogout!="function"||a.onready&&typeof a.onready!="function")throw"non-function where function expected in parameters to navigator.id.watch()";if(!a.onlogin)throw"'onlogin' is a required argument to navigator.id.watch()";if(!a.onlogout)throw"'onlogout' is a required argument to navigator.id.watch()";h.login=a.onlogin||null,h.logout=a.onlogout||null,h.ready=a.onready||null,m(),q(a,"loggedInEmail","loggedInUser"),typeof a.loggedInUser!="undefined"&&k&&k.notify({method:"loggedInUser",params:a.loggedInUser})}}function s(a){p(a,"requiredEmail"),q(a,"tosURL","termsOfService"),q(a,"privacyURL","privacyPolicy"),a.termsOfService&&!a.privacyPolicy&&o("termsOfService ignored unless privacyPolicy also defined"),a.privacyPolicy&&!a.termsOfService&&o("privacyPolicy ignored unless termsOfService also defined");if(g)try{g.focus()}catch(d){}else{if(!b.isSupported()){var e=b.getNoSupportReason(),i="unsupported_dialog";e==="LOCALSTORAGE_DISABLED"&&(i="cookies_disabled"),g=window.open(c+"/"+i,null,f);return}k&&k.notify({method:"dialog_running"}),g=WinChan.open({url:c+"/sign_in",relay_url:c+"/relay",window_features:f,window_name:"__persona_dialog",params:{method:"get",params:a}},function(b,c){k&&(!b&&c&&c.email&&k.notify({method:"loggedInUser",params:c.email}),k.notify({method:"dialog_complete"})),g=undefined;if(!b&&c&&c.assertion)try{h.login&&h.login(c.assertion)}catch(d){}if(b==="client closed window"||!c)a&&a.oncancel&&a.oncancel(),delete a.oncancel})}}navigator.id={request:function(a){if(this!=navigator.id)throw new Error("all navigator.id calls must be made on the navigator.id object");a=a||{},j(!1),a.returnTo||(a.returnTo=document.location.pathname);return s(a)},watch:function(a){if(this!=navigator.id)throw new Error("all navigator.id calls must be made on the navigator.id object");j(!1),r(a)},logout:function(a){if(this!=navigator.id)throw new Error("all navigator.id calls must be made on the navigator.id object");m(),k&&k.notify({method:"logout"}),typeof a=="function"&&setTimeout(a,0)},get:function(a,b){var c={};b=b||{},c.privacyPolicy=b.privacyPolicy||undefined,c.termsOfService=b.termsOfService||undefined,c.privacyURL=b.privacyURL||undefined,c.tosURL=b.tosURL||undefined;p(b,"silent")?a&&setTimeout(function(){a(null)},0):(j(!0),r({onlogin:function(b){a&&(a(b),a=null)},onlogout:function(){}}),c.oncancel=function(){a&&(a(null),a=null),h.login=h.logout=h.ready=null},s(c))},getVerifiedEmail:function(a){o("navigator.id.getVerifiedEmail has been deprecated"),j(!0),navigator.id.get(a)},_shimmed:!0}}})() \ No newline at end of file diff --git a/js/persona_hooks.js b/js/persona_hooks.js index 7b93338..1272c7f 100644 --- a/js/persona_hooks.js +++ b/js/persona_hooks.js @@ -1,57 +1,75 @@ -jQuery( function( $ ) { - $( '#wpPersona' ).click( function() { - navigator.id.request(); - } ); +( function( $, mw ) { + 'use strict'; - $( '#pt-personalogin' ).click( function( event ) { - navigator.id.request(); - } ); + $( function( $ ) { + $( '#wpPersona' ).click( function() { + navigator.id.request(); + } ); - $( '#pt-logout > a' ).click( function( event ) { - navigator.id.logout(); - } ); + $( '#pt-personalogin' ).click( function() { + navigator.id.request(); + } ); - navigator.id.watch( { - loggedInUser: null, - onlogin: function( assertion ) { - var api = new mw.Api(); - api.post( { - 'action': 'persona', - 'assertion': assertion, - 'token': $( 'input[name="wpLoginToken"]' ).val(), - 'stickhttps': $( '#wpStickHTTPS' ).val() - } ) - .done( function ( data ) { - console.log( 'Persona login result:', data ); - var vars = [], hash; - var q = document.URL.split( '?' )[1]; - if( q != undefined ){ - q = q.split( '&' ); - for( var i = 0; i < q.length; i++ ){ - hash = q[i].split( '=' ); - vars.push( hash[1] ); - vars[hash[0]] = hash[1]; - } - } else { - q = {}; - } + $( '#pt-logout > a' ).click( function() { + navigator.id.logout(); + } ); - var title; - if( q['returnto'] != undefined ) { - title = new mw.Title( q['returnto'] ); - window.location.href = title.getUrl(); - } else if( q['title'] != 'Special:Userlogin' ) { - window.location.reload(); - } else { - title = new mw.Title( 'Main Page' ); - window.location.href = title.getUrl(); - } - } ) - .fail( function ( error ) { - console.log( 'Persona login failed.', error ); - mw.util.jsMessage( 'Persona login failed.' ); - } ); - }, - onlogout: function() {} - } ); -} ); + navigator.id.watch( { + loggedInUser: mw.config.get( 'wgPersonaUserEmail' ), + onlogin: function( assertion ) { + var api = new mw.Api(); + api.post( { + 'action': 'persona', + 'assertion': assertion, + 'token': $( 'input[name="wpLoginToken"]' ).val() + } ) + .done( function ( data ) { + if ( data.login.status !== 'okay' ) { + console.log( 'Persona login failed.', data ); + mw.notify( mw.message( 'persona-error-' + data.login.status ).text() ); + navigator.id.logout(); + return; + } + + var vars, url, queryPos, fragPos, hash, q, title, lowercaseTitle; + + console.log( 'Persona login result:', data ); + vars = []; + url = document.URL; + queryPos = url.indexOf( '?' ) + 1; + fragPos = url.indexOf( '#', queryPos ); + q = document.URL.substring( queryPos, fragPos ); + if ( queryPos < fragPos && q !== "" ){ + q = q.split( '&' ); + for ( var i = 0; i < q.length; i++ ) { + hash = q[i].split( '=' ); + vars[hash[0]] = decodeURIComponent( hash[1] ).replace( '+', ' ' ); + } + } else { + vars = {}; + } + + lowercaseTitle = vars.title !== undefined ? vars.title.toLowerCase() : undefined; + if ( vars.returnto !== undefined ) { + title = new mw.Title( vars.returnto ); + window.location.href = title.getUrl(); + } else if ( lowercaseTitle !== 'special:userlogin' && + lowercaseTitle !== 'special:userlogout' + ) { + window.location.reload(); + } else { + title = new mw.Title( 'Main_Page' ); + window.location.href = title.getUrl(); + } + } ) + .fail( function ( error ) { + console.log( 'Persona login failed.', error ); + mw.notify( mw.message( error ).text() ); + navigator.id.logout(); + } ); + }, + onlogout: function() {} + } ); + } ); + +} )( jQuery, mediaWiki ); diff --git a/js/persona_hooks_old.js b/js/persona_hooks_old.js new file mode 100644 index 0000000..b0a09e2 --- /dev/null +++ b/js/persona_hooks_old.js @@ -0,0 +1,85 @@ +/* + * Old version of Persona hooks for compatibility with MediaWiki version + * 1.20 and possibly earlier. The main differences are: + * * Old method of passing closures to mw.Api rather than using a promise + * * Use of jsMessage rather than the newer mw.notify + */ + +( function( $, mw ) { + 'use strict'; + + $( function( $ ) { + $( '#wpPersona' ).click( function() { + navigator.id.request(); + } ); + + $( '#pt-personalogin' ).click( function() { + navigator.id.request(); + } ); + + $( '#pt-logout > a' ).click( function() { + navigator.id.logout(); + } ); + + navigator.id.watch( { + loggedInUser: mw.config.get( 'wgPersonaUserEmail' ), + onlogin: function( assertion ) { + var api = new mw.Api(); + api.post( + { + 'action': 'persona', + 'assertion': assertion, + 'token': $( 'input[name="wpLoginToken"]' ).val() + }, + { + 'ok': function ( data ) { + if ( data.login.status !== "okay" ) { + console.log( 'Persona login failed.', data ); + mw.util.jsMessage( mw.msg( 'persona-error-' + data.login.status ) ); + navigator.id.logout(); + return; + } + + var vars, url, queryPos, fragPos, hash, q, title, lowercaseTitle; + + console.log( 'Persona login result:', data ); + vars = []; + url = document.URL; + queryPos = url.indexOf( '?' ) + 1; + fragPos = url.indexOf( '#', queryPos ); + q = document.URL.substring( queryPos, fragPos ); + if ( queryPos < fragPos && q !== "" ){ + q = q.split( '&' ); + for ( var i = 0; i < q.length; i++ ) { + hash = q[i].split( '=' ); + vars[hash[0]] = decodeURIComponent( hash[1] ).replace( '+', ' ' ); + } + } else { + vars = {}; + } + + lowercaseTitle = vars.title !== undefined ? vars.title.toLowerCase() : undefined; + if ( vars.returnto !== undefined ) { + title = new mw.Title( vars.returnto ); + window.location.href = title.getUrl(); + } else if ( lowercaseTitle !== 'special:userlogin' && + lowercaseTitle !== 'special:userlogout' + ) { + window.location.reload(); + } else { + title = new mw.Title( 'Main_Page' ); + window.location.href = title.getUrl(); + } + }, + 'err': function ( error ) { + console.log( 'Persona login failed.', error ); + mw.util.jsMessage( mw.msg( error ) ); + navigator.id.logout(); + } + } + ); + }, + onlogout: function() {} + } ); + } ); +} )( jQuery, mediaWiki ); diff --git a/persona.crt b/persona.crt index ed0bd76..4137243 100644 --- a/persona.crt +++ b/persona.crt @@ -1,19 +1,21 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- -- To view, visit https://gerrit.wikimedia.org/r/91417 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: If61cf139256e411440eb5a327f32f37bd6dfb962 Gerrit-PatchSet: 4 Gerrit-Project: mediawiki/extensions/Persona Gerrit-Branch: master Gerrit-Owner: Parent5446 <[email protected]> Gerrit-Reviewer: Krinkle <[email protected]> Gerrit-Reviewer: Parent5446 <[email protected]> Gerrit-Reviewer: Siebrand <[email protected]> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
