jenkins-bot has submitted this change and it was merged. Change subject: Refactored special pages into HTMLForm and proxy ......................................................................
Refactored special pages into HTMLForm and proxy Made new class ProxySpecialPage, which acts as a proxy object to another SpecialPage object that is determined based on context information other than the title. Then Special:OATH has been split into two separate special page classes (both FormSpecialPages using HTMLForm) that are routed to by a ProxySpecialPage object. In addition, the form for enabling two-factor auth has been refactored into vform style, with some better instructions on how to enable two-factor authentication. Change-Id: Ib9117cbc9d7f044de9607db81a157e1b472b5ec0 (cherry picked from commit 0c389f50255325338a03fed8739a923ad2aefc1e) --- M OATHAuth.hooks.php M OATHAuthKey.php M OATHUser.php M extension.json M i18n/en.json M i18n/qqq.json A special/ProxySpecialPage.php M special/SpecialOATH.php A special/SpecialOATHDisable.php A special/SpecialOATHEnable.php 10 files changed, 575 insertions(+), 279 deletions(-) Approvals: CSteipp: Looks good to me, approved jenkins-bot: Verified diff --git a/OATHAuth.hooks.php b/OATHAuth.hooks.php index 8bbc24d..2df0313 100644 --- a/OATHAuth.hooks.php +++ b/OATHAuth.hooks.php @@ -2,6 +2,8 @@ /** * Hooks for Extension:OATHAuth + * + * @ingroup Extensions */ class OATHAuthHooks { /** @@ -128,39 +130,20 @@ $oathUser = $oathrepo->findByUser( $user ); $title = SpecialPage::getTitleFor( 'OATH' ); - if ( $oathUser->getKey() !== null ) { - $preferences['oath-disable'] = array( - 'type' => 'info', - 'raw' => 'true', - 'default' => Linker::link( - $title, - wfMessage( 'oathauth-disable' )->escaped(), - array(), - array( - 'action' => 'disable', - 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() - ) - ), - 'label-message' => 'oathauth-prefs-label', - 'section' => 'personal/info', - ); - } else { - $preferences['oath-enable'] = array( - 'type' => 'info', - 'raw' => 'true', - 'default' => Linker::link( - $title, - wfMessage( 'oathauth-enable' )->escaped(), - array(), - array( - 'action' => 'enable', - 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() - ) - ), - 'label-message' => 'oathauth-prefs-label', - 'section' => 'personal/info', - ); - } + $msg = $oathUser->getKey() !== null ? 'oathauth-disable' : 'oathauth-enable'; + + $preferences[$msg] = array( + 'type' => 'info', + 'raw' => 'true', + 'default' => Linker::link( + $title, + wfMessage( $msg )->escaped(), + array(), + array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) + ), + 'label-message' => 'oathauth-prefs-label', + 'section' => 'personal/info', + ); return true; } diff --git a/OATHAuthKey.php b/OATHAuthKey.php index e50826d..ac4a26b 100644 --- a/OATHAuthKey.php +++ b/OATHAuthKey.php @@ -4,8 +4,22 @@ * Class representing a two-factor key * * Keys can be tied to OAUTHUsers + * + * @ingroup Extensions */ class OATHAuthKey { + /** + * Represents that a token corresponds to the main secret + * @see verifyToken + */ + const MAIN_TOKEN = 1; + + /** + * Represents that a token corresponds to a scratch token + * @see verifyToken + */ + const SCRATCH_TOKEN = -1; + /** @var string Two factor binary secret */ private $secret; @@ -63,7 +77,8 @@ * @param string $token Token to verify * @param OATHUser $user * - * @return bool True on match, false otherwise + * @return int|false Returns a constant represent what type of token was matched, + * or false for no match */ public function verifyToken( $token, $user ) { global $wgOATHAuthWindowRadius; @@ -86,7 +101,7 @@ foreach ( $results as $window => $result ) { if ( $window > $lastWindow && $result->toHOTP( 6 ) === $token ) { $lastWindow = $window; - $retval = true; + $retval = self::MAIN_TOKEN; break; } } @@ -106,7 +121,7 @@ $user->setKey( $this ); $oathrepo->persist( $user ); // Only return true if we removed it from the database - $retval = true; + $retval = self::SCRATCH_TOKEN; break; } } diff --git a/OATHUser.php b/OATHUser.php index 7f19d0d..64efd33 100644 --- a/OATHUser.php +++ b/OATHUser.php @@ -3,7 +3,6 @@ /** * Class representing a user from OATH's perspective * - * @file * @ingroup Extensions */ class OATHUser { diff --git a/extension.json b/extension.json index ae0677d..9a47fa4 100644 --- a/extension.json +++ b/extension.json @@ -13,7 +13,10 @@ "HOTPResult": "lib/hotp.php", "Base32": "lib/base32.php", "OATHUser": "OATHUser.php", - "SpecialOATH": "special/SpecialOATH.php" + "SpecialOATH": "special/SpecialOATH.php", + "SpecialOATHEnable": "special/SpecialOATHEnable.php", + "SpecialOATHDisable": "special/SpecialOATHDisable.php", + "ProxySpecialPage": "special/ProxySpecialPage.php" }, "ExtensionMessagesFiles": { "OATHAuthAlias": "OATHAuth.alias.php" diff --git a/i18n/en.json b/i18n/en.json index 54b89b2..66568c1 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1,13 +1,15 @@ { "@metadata": { "authors": [ - "Ryan Lane <rl...@wikimedia.org>" + "Ryan Lane <rl...@wikimedia.org>", + "Tyler Romeo <tylerro...@gmail.com>" ] }, "oathauth-desc": "Provides authentication support using HMAC based one-time passwords", "oath": "OATHAuth", "specialpages-group-oath": "Two-factor authentication", "oathauth-account": "Two-factor account name:", + "oathauth-legend": "Verify your credentials", "oathauth-secret": "Two-factor secret key:", "oathauth-enable": "Enable two-factor authentication", "oathauth-failedtoenableoauth": "Failed to enable two-factor authentication.", @@ -33,5 +35,12 @@ "oathauth-notloggedin": "Login required", "oathauth-mustbeloggedin": "You must be logged in to perform this action.", "oathauth-prefs-label": "Two-factor authentication:", - "oathauth-abortlogin": "The two-factor authentication token provided was invalid." + "oathauth-abortlogin": "The two-factor authentication token provided was invalid.", + "oathauth-step1": "Step 1: Download the app", + "oathauth-step1-test": "Download a mobile app for two-factor authentication (such as Google Authenticator) on to your phone.", + "oathauth-step2": "Step 2: Scan the QR code", + "oathauth-step2alt": "Or enter the secret manually:", + "oathauth-step3": "Step 3: Write down the scratch codes", + "oathauth-step4": "Step 4: Verification", + "oathauth-entertoken": "Enter a code from your mobile app to verify:" } diff --git a/i18n/qqq.json b/i18n/qqq.json index a45e378..369a1e3 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -5,13 +5,15 @@ "Ryan Lane <rl...@wikimedia.org>", "Shirayuki", "Purodha", - "Umherirrender" + "Umherirrender", + "Tyler Romeo <tylerro...@gmail.com>" ] }, "oathauth-desc": "{{desc|name=OATH Auth|url=https://www.mediawiki.org/wiki/Extension:OATHAuth}}", "oath": "{{optional}}\n{{doc-special|OATH}}", "specialpages-group-oath": "{{doc-special-group|like=[[Special:OATH]]}}\n\nSee [[w:en:Two_factor_authentication|Wikipedia article on two factor authentication]].\n\n{{Identical|Two factor authentication}}", "oathauth-account": "Plain text associated with [https://en.wikipedia.org/wiki/Two_factor_authentication two factor authentication] on this wiki (username@<wiki name>) found on Special:OATH.", + "oathauth-legend": "Label for the legend on Special:OATH", "oathauth-secret": "Plain text found on Special:OATH while enabling OATH\n\nSee [https://en.wikipedia.org/wiki/Two_factor_authentication two factor authentication]", "oathauth-enable": "Page title on Special:OATH, when enabling OATH.\n\nSee [https://en.wikipedia.org/wiki/Two_factor_authentication two factor authentication]", "oathauth-failedtoenableoauth": "Plain text, found on Special:OATH when failing to enable OATH.\n\nSee [https://en.wikipedia.org/wiki/Two_factor_authentication two factor authentication]", @@ -37,5 +39,12 @@ "oathauth-notloggedin": "Page title seen on Special:OATH when a user is not logged in.\n{{Identical|Login required}}", "oathauth-mustbeloggedin": "Plain text seen on Special:OATH when a user is not logged in.", "oathauth-prefs-label": "Plain text label seen on Special:Preferences\n\nSee [https://en.wikipedia.org/wiki/Two_factor_authentication two factor authentication]", - "oathauth-abortlogin": "Error message shown on login and password change pages when authentication is aborted.\n\nSee [https://en.wikipedia.org/wiki/Two_factor_authentication two factor authentication]" + "oathauth-abortlogin": "Error message shown on login and password change pages when authentication is aborted.\n\nSee [https://en.wikipedia.org/wiki/Two_factor_authentication two factor authentication]", + "oathauth-step1": "Label for step 1 on Special:OATH form", + "oathauth-step1-test": "Text for step 1 on Special:OATH form", + "oathauth-step2": "Label for step 2, the QR code, on Special:OATH", + "oathauth-step2alt": "Label for information on how to manually do step 2 on Special:OATH", + "oathauth-step3": "Label for step 3 information on Special:OATH", + "oathauth-step4": "Label for step 4 information on Special:OATH", + "oathauth-entertoken": "Label on input field on Special:OATH asking user to enter token" } diff --git a/special/ProxySpecialPage.php b/special/ProxySpecialPage.php new file mode 100644 index 0000000..23e84de --- /dev/null +++ b/special/ProxySpecialPage.php @@ -0,0 +1,212 @@ +<?php + +/** + * A proxy class that routes a special page to other special pages based on + * request parameters + */ +abstract class ProxySpecialPage extends SpecialPage { + /** + * @var SpecialPage|null Target page to execute + */ + private $target = null; + + /** + * Instantiate a SpecialPage based on request parameters + * + * The page returned by this function will be cached and used as + * the target page for this proxy object. + * + * @return SpecialPage + */ + abstract protected function getTargetPage(); + + /** + * Helper function that initializes the target SpecialPage object + */ + private function init() { + if ( $this->target === null ) { + $this->target = $this->getTargetPage(); + } + } + + /** + * Magic function that proxies function calls to the target object + * + * @param string $method Method name being called + * @param array $args Array of arguments + * + * @return mixed + */ + public function __call( $method, $args ) { + $this->init(); + return call_user_func_array( array( $this->target, $method ), $args ); + } + + + /** + * @return string + */ + function getName() { + $this->init(); + return $this->target->getName(); + } + + /** + * @param string|bool $subpage + * @return Title + */ + function getPageTitle( $subpage = false ) { + $this->init(); + return $this->target->getPageTitle( $subpage ); + } + + /** + * @return string + */ + function getLocalName() { + $this->init(); + return $this->target->getLocalName(); + } + + /** + * @return string + */ + function getRestriction() { + $this->init(); + return $this->target->getRestriction(); + } + + /** + * @return bool + */ + function isListed() { + $this->init(); + return $this->target->isListed(); + } + + /** + * @param bool $listed + * @return bool + */ + function setListed( $listed ) { + $this->init(); + return $this->target->setListed( $listed ); + } + + /** + * @param bool $x + * @return bool + */ + function listed( $x = null ) { + $this->init(); + return $this->target->listed( $x ); + } + + /** + * @return bool + */ + public function isIncludable() { + $this->init(); + return $this->target->isIncludable(); + } + + /** + * @param bool $x + * @return bool + */ + function including( $x = null ) { + $this->init(); + return $this->target->including( $x ); + } + + /** + * @return bool + */ + public function isRestricted() { + $this->init(); + return $this->target->isRestricted(); + } + + /** + * @param User $user + * @return bool + */ + public function userCanExecute( User $user ) { + $this->init(); + return $this->target->userCanExecute( $user ); + } + + /** + * @throws PermissionsError + */ + function displayRestrictionError() { + $this->init(); + $this->target->displayRestrictionError(); + } + + /** + * @return void + * @throws PermissionsError + */ + public function checkPermissions() { + $this->init(); + $this->target->checkPermissions(); + } + + /** + * @param string|null $subPage + */ + protected function beforeExecute( $subPage ) { + $this->init(); + $this->target->beforeExecute( $subPage ); + } + + /** + * @param string|null $subPage + */ + protected function afterExecute( $subPage ) { + $this->init(); + $this->target->afterExecute( $subPage ); + } + + /** + * @param string|null $subPage + */ + public function execute( $subPage ) { + $this->init(); + $this->target->execute( $subPage ); + } + + /** + * @return string + */ + function getDescription() { + $this->init(); + return $this->target->getDescription(); + } + + /** + * @param IContextSource $context + */ + public function setContext( $context ) { + $this->init(); + $this->target->setContext( $context ); + parent::setContext( $context ); + } + + /** + * @return string + */ + protected function getRobotPolicy() { + $this->init(); + return $this->target->getRobotPolicy(); + } + + /** + * @return string + */ + protected function getGroupName() { + $this->init(); + return $this->target->getGroupName(); + } +} diff --git a/special/SpecialOATH.php b/special/SpecialOATH.php index a4c826e..5ab08dd 100644 --- a/special/SpecialOATH.php +++ b/special/SpecialOATH.php @@ -1,250 +1,24 @@ <?php /** - * Special page to display key information to the user - * - * @file - * @ingroup Extensions + * Proxy page that redirects to the proper OATH special page */ - -class SpecialOATH extends UnlistedSpecialPage { - /** @var OATHUser|null */ - private $OATHUser; - +class SpecialOATH extends ProxySpecialPage { /** - * Initialize the OATH user based on the current local User object in the context - */ - public function __construct() { - parent::__construct( 'OATH' ); - - $this->OATHRepository = new OATHUserRepository( wfGetLB() ); - $this->OATHUser = $this->OATHRepository->findByUser( $this->getUser() ); - } - - /** - * Perform the correct form based on the action + * If the user already has OATH enabled, show them a page to disable + * If the user has OATH disabled, show them a page to enable * - * @param null|string $par Sub-page + * @return SpecialOATHDisable|SpecialOATHEnable|SpecialPage */ - public function execute( $par ) { - if ( !$this->getUser()->isLoggedIn() ) { - $this->setHeaders(); - $this->getOutput()->setPagetitle( $this->msg( 'oathauth-notloggedin' ) ); - $this->getOutput()->addWikiMsg( 'oathauth-mustbeloggedin' ); - return; - } + protected function getTargetPage() { + $repo = new OATHUserRepository( wfGetLB() ); + $user = $repo->findByUser( $this->getUser() ); - $action = $this->getRequest()->getVal( 'action' ); - if ( $action == "enable" ) { - $this->enable(); - } elseif ( $action == "disable" ) { - $this->disable(); - } - } - - /** - * @return bool - */ - private function enable() { - $this->setHeaders(); - $this->getOutput()->setPagetitle( $this->msg( 'oathauth-enable' ) ); - $returnto = $this->getRequest()->getVal( 'returnto' ); - - if ( $this->OATHUser->getKey() ) { - $this->getOutput()->addWikiMsg( 'oathauth-alreadyenabled' ); - - return true; - } - - if ( null === $this->getRequest()->getSessionData( 'oathauth_key' ) ) { - $this->getRequest()->setSessionData( 'oathauth_key', OATHAuthKey::newFromRandom() ); - } - - $info['token'] = array( - 'type' => 'text', - 'default' => '', - 'label-message' => 'oathauth-token', - 'name' => 'token', - ); - $info['mode'] = array( - 'type' => 'hidden', - 'default' => 'enable', - 'name' => 'mode', - ); - $info['returnto'] = array( - 'type' => 'hidden', - 'default' => $returnto, - 'name' => 'returnto', - ); - $info['action'] = array( - 'type' => 'hidden', - 'default' => 'enable', - 'name' => 'action', - ); - $form = new HTMLForm( - $info, - $this->getContext(), - 'oathauth-verify' - ); - $form->setSubmitID( 'oathauth-validate-submit' ); - $form->setSubmitCallback( array( $this, 'tryValidateSubmit' ) ); - if ( !$form->show() ) { - $this->displaySecret(); - } - - return true; - } - - private function displaySecret() { - $this->getOutput()->addModules( 'ext.oathauth' ); - - /** @var OATHAuthKey $key */ - $key = $this->getRequest()->getSessionData( 'oathauth_key' ); - $secret = $key->getSecret(); - - $out = '<strong>' . $this->msg( 'oathauth-account' )->escaped() . '</strong> ' - . $this->OATHUser->getAccount() . '<br/>' - . '<strong>' . $this->msg( 'oathauth-secret' )->escaped() . '</strong> ' - . $secret . '<br/>' - . '<br/>' - . '<div id="qrcode"></div>'; - - $this->getOutput()->addHTML( ResourceLoader::makeInlineScript( - Xml::encodeJsCall( 'mw.loader.using', array( - array( 'ext.oathauth' ), - new XmlJsCode( - 'function () {' - . '$("#qrcode").qrcode("otpauth://totp/' - . $this->OATHUser->getAccount() - . '?secret=' . $secret. '");' - . '}' - ) - ) ) - ) ); - - $this->getOutput()->addHTML( $out ); - $this->getOutput()->addWikiMsg( 'openstackmanager-scratchtokens' ); - $this->getOutput()->addHTML( - $this->createResourceList( $key->getScratchTokens() ) ); - } - - /** - * @return bool - */ - private function disable() { - $this->setHeaders(); - $this->getOutput()->setPagetitle( $this->msg( 'oathauth-disable' ) ); - $returnto = $this->getRequest()->getVal( 'returnto' ); - - $info['token'] = array( - 'type' => 'text', - 'label-message' => 'oathauth-token', - 'name' => 'token', - ); - $info['returnto'] = array( - 'type' => 'hidden', - 'default' => $returnto, - 'name' => 'returnto', - ); - $info['action'] = array( - 'type' => 'hidden', - 'default' => 'disable', - 'name' => 'action', - ); - $form = new HTMLForm( - $info, - $this->getContext(), - 'oathauth-disable' - ); - $form->setSubmitID( 'oauth-form-disablesubmit' ); - $form->setSubmitCallback( array( $this, 'tryDisableSubmit' ) ); - $form->show(); - return true; - } - - /** - * @param $resources array - * @return string - */ - private function createResourceList( $resources ) { - $resourceList = ''; - foreach ( $resources as $resource ) { - $resourceList .= Html::rawElement( 'li', array(), $resource ); - } - return Html::rawElement( 'ul', array(), $resourceList ); - } - - /** - * @param $formData array - * @return bool - */ - public function tryValidateSubmit( $formData ) { - /** @var OATHAuthKey $key */ - $key = $this->getRequest()->getSessionData( 'oathauth_key' ); - - $verify = $key->verifyToken( $formData['token'], $this->OATHUser ); - $out = ''; - if ( $verify ) { - $this->OATHUser->setKey( $key ); - $this->OATHRepository->persist( $this->OATHUser ); - $this->getRequest()->setSessionData( 'oathauth_key', null ); - - $this->getOutput()->addWikiMsg( 'oathauth-validatedoath' ); - if ( $formData['returnto'] ) { - $out = '<br />'; - $title = Title::newFromText( $formData['returnto'] ); - $out .= Linker::link( $title, $this->msg( 'oathauth-backtopreferences' )->escaped() ); - } + if ( $user->getKey() === null ) { + return new SpecialOATHEnable( $repo, $user ); } else { - $this->getOutput()->addWikiMsg( 'oathauth-failedtovalidateoauth' ); - $out = '<br />'; - - $out .= Linker::link( - $this->getPageTitle(), - $this->msg( 'oathauth-reattemptenable' )->escaped(), - array(), - array( - 'action' => 'enable', - 'returnto' => $formData['returnto'] - ) - ); + return new SpecialOATHDisable( $repo, $user ); } - - $this->getOutput()->addHTML( $out ); - - return true; - } - - /** - * @param $formData array - * @return bool - */ - public function tryDisableSubmit( $formData ) { - $verify = $this->OATHUser->getKey()->verifyToken( $formData['token'], $this->OATHUser ); - if ( !$verify ) { - $this->getOutput()->addWikiMsg( 'oathauth-failedtovalidateoauth' ); - $out = '<br />'; - $out .= Linker::link( - $this->getPageTitle(), - $this->msg( 'oathauth-reattemptdisable' )->escaped(), - array(), - array( 'action' => 'disable' ) - ); - $this->getOutput()->addHTML( $out ); - return true; - } - - $this->OATHRepository->remove( $this->OATHUser ); - - $this->getOutput()->addWikiMsg( 'oathauth-disabledoath' ); - if ( $formData['returnto'] ) { - $out = '<br />'; - $title = Title::newFromText( $formData['returnto'] ); - $out .= Linker::link( $title, $this->msg( 'oathauth-backtopreferences' )->escaped() ); - $this->getOutput()->addHTML( $out ); - } - - return true; } protected function getGroupName() { diff --git a/special/SpecialOATHDisable.php b/special/SpecialOATHDisable.php new file mode 100644 index 0000000..a0db4ae --- /dev/null +++ b/special/SpecialOATHDisable.php @@ -0,0 +1,110 @@ +<?php + +/** + * Special page to display key information to the user + * + * @file + * @ingroup Extensions + */ +class SpecialOATHDisable extends FormSpecialPage { + /** @var OATHUserRepository */ + private $OATHRepository; + + /** @var OATHUser */ + private $OATHUser; + + /** + * Initialize the OATH user based on the current local User object in the context + * + * @param OATHUserRepository $repository + * @param OATHUser $user + */ + public function __construct( OATHUserRepository $repository, OATHUser $user ) { + parent::__construct( 'OATH', '', false ); + $this->OATHRepository = $repository; + $this->OATHUser = $user; + } + + /** + * Set the page title and add JavaScript RL modules + * + * @param HTMLForm $form + */ + public function alterForm( HTMLForm $form ) { + $form->setMessagePrefix( 'oathauth' ); + $form->setWrapperLegend( false ); + $form->getOutput()->setPagetitle( $this->msg( 'oathauth-disable' ) ); + $form->getOutput()->addModules( 'ext.oathauth' ); + } + + /** + * @return string + */ + protected function getDisplayFormat() { + return 'vform'; + } + + /** + * @return bool + */ + public function requiresUnblock() { + return false; + } + + /** + * Require users to be logged in + * + * @param User $user + * + * @return bool|void + */ + protected function checkExecutePermissions( User $user ) { + parent::checkExecutePermissions( $user ); + + $this->requireLogin(); + } + + /** + * @return array[] + */ + protected function getFormFields() { + return array( + 'token' => array( + 'type' => 'text', + 'label-message' => 'oathauth-entertoken', + 'name' => 'token', + ), + 'returnto' => array( + 'type' => 'hidden', + 'default' => $this->getRequest()->getVal( 'returnto' ), + 'name' => 'returnto', + ), + 'returntoquery' => array( + 'type' => 'hidden', + 'default' => $this->getRequest()->getVal( 'returntoquery' ), + 'name' => 'returntoquery', + ) + ); + } + + /** + * @param array $formData + * + * @return array|bool + */ + public function onSubmit( array $formData ) { + if ( !$this->OATHUser->getKey()->verifyToken( $formData['token'], $this->OATHUser ) ) { + return array( 'oathauth-failedtovalidateoauth' ); + } + + $this->OATHUser->setKey( null ); + $this->OATHRepository->remove( $this->OATHUser ); + + return true; + } + + public function onSuccess() { + $this->getOutput()->addWikiMsg( 'oathauth-disabledoath' ); + $this->getOutput()->returnToMain(); + } +} diff --git a/special/SpecialOATHEnable.php b/special/SpecialOATHEnable.php new file mode 100644 index 0000000..1fb4bc4 --- /dev/null +++ b/special/SpecialOATHEnable.php @@ -0,0 +1,182 @@ +<?php + +/** + * Special page to display key information to the user + * + * @file + * @ingroup Extensions + */ +class SpecialOATHEnable extends FormSpecialPage { + /** @var OATHUserRepository */ + private $OATHRepository; + + /** @var OATHUser */ + private $OATHUser; + + /** + * Initialize the OATH user based on the current local User object in the context + * + * @param OATHUserRepository $repository + * @param OATHUser $user + */ + public function __construct( OATHUserRepository $repository, OATHUser $user ) { + parent::__construct( 'OATH', '', false ); + + $this->OATHRepository = $repository; + $this->OATHUser = $user; + } + + /** + * Set the page title and add JavaScript RL modules + * + * @param HTMLForm $form + */ + public function alterForm( HTMLForm $form ) { + $form->setMessagePrefix( 'oathauth' ); + $form->setWrapperLegend( false ); + $form->getOutput()->setPagetitle( $this->msg( 'oathauth-enable' ) ); + $form->getOutput()->addModules( 'ext.oathauth' ); + } + + /** + * @return string + */ + protected function getDisplayFormat() { + return 'vform'; + } + + /** + * @return bool + */ + public function requiresUnblock() { + return false; + } + + /** + * Require users to be logged in + * + * @param User $user + * + * @return bool|void + */ + protected function checkExecutePermissions( User $user ) { + parent::checkExecutePermissions( $user ); + + $this->requireLogin(); + } + + /** + * @return array[] + */ + protected function getFormFields() { + $key = $this->getRequest()->getSessionData( 'oathauth_key' ); + + if ( $key === null ) { + $key = OATHAuthKey::newFromRandom(); + $this->getRequest()->setSessionData( 'oathauth_key', $key ); + } + + $secret = $key->getSecret(); + + $this->getOutput()->addHTML( ResourceLoader::makeInlineScript( + Xml::encodeJsCall( 'mw.loader.using', array( + array( 'ext.oathauth' ), + new XmlJsCode( + 'function () {' + . '$("#qrcode").qrcode("otpauth://totp/' + . $this->OATHUser->getAccount() + . '?secret=' . $secret. '");' + . '}' + ) + ) ) + ) ); + + return array( + 'app' => array( + 'type' => 'info', + 'default' => $this->msg( 'oathauth-step1-test' )->escaped(), + 'raw' => true, + 'section' => 'step1', + ), + 'qrcode' => array( + 'type' => 'info', + 'default' => '<div id="qrcode"></div>', + 'raw' => true, + 'section' => 'step2', + ), + 'manual' => array( + 'type' => 'info', + 'label-message' => 'oathauth-step2alt', + 'default' => + '<strong>' . $this->msg( 'oathauth-account' )->escaped() . '</strong><br/>' + . $this->OATHUser->getAccount() . '<br/><br/>' + . '<strong>' . $this->msg( 'oathauth-secret' )->escaped() . '</strong><br/>' + . $key->getSecret() . '<br/>', + 'raw' => true, + 'section' => 'step2', + ), + 'scratchtokens' => array( + 'type' => 'info', + 'default' => + $this->msg( 'openstackmanager-scratchtokens' ) + . $this->createResourceList( $key->getScratchTokens() ), + 'raw' => true, + 'section' => 'step3', + ), + 'token' => array( + 'type' => 'text', + 'default' => '', + 'label-message' => 'oathauth-entertoken', + 'name' => 'token', + 'section' => 'step4', + ), + 'returnto' => array( + 'type' => 'hidden', + 'default' => $this->getRequest()->getVal( 'returnto' ), + 'name' => 'returnto', + ), + 'returntoquery' => array( + 'type' => 'hidden', + 'default' => $this->getRequest()->getVal( 'returntoquery' ), + 'name' => 'returntoquery', + ) + ); + } + + /** + * @param array $formData + * + * @return array|bool + */ + public function onSubmit( array $formData ) { + /** @var OATHAuthKey $key */ + $key = $this->getRequest()->getSessionData( 'oathauth_key' ); + + if ( !$key->verifyToken( $formData['token'], $this->OATHUser ) ) { + return array( 'oathauth-failedtovalidateoauth' ); + } + + $this->getRequest()->setSessionData( 'oathauth_key', null ); + $this->OATHUser->setKey( $key ); + $this->OATHRepository->persist( $this->OATHUser ); + + return true; + } + + public function onSuccess() { + $this->getOutput()->addWikiMsg( 'oathauth-validatedoath' ); + $this->getOutput()->returnToMain(); + } + + /** + * @param $resources array + * @return string + */ + private function createResourceList( $resources ) { + $resourceList = ''; + foreach ( $resources as $resource ) { + $resourceList .= Html::rawElement( 'li', array(), $resource ); + } + return Html::rawElement( 'ul', array(), $resourceList ); + } +} -- To view, visit https://gerrit.wikimedia.org/r/279253 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ib9117cbc9d7f044de9607db81a157e1b472b5ec0 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/OATHAuth Gerrit-Branch: wmf/1.27.0-wmf.18 Gerrit-Owner: CSteipp <cste...@wikimedia.org> Gerrit-Reviewer: CSteipp <cste...@wikimedia.org> Gerrit-Reviewer: Parent5446 <tylerro...@gmail.com> Gerrit-Reviewer: Siebrand <siebr...@kitano.nl> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits