Refactor extension key storage

This takes out the actual key information from
OATHUser and puts it into an OATHKey class, which OATHUser
depends on. This allows easily swapping keys in/out from
a user.

Change-Id: Ife5f1bae4ad65b66c5e20017cc43c0576b4aba19
This commit is contained in:
Tyler Anthony Romeo 2014-05-18 20:05:59 -04:00 committed by Darian Anthony Patrick
parent 6a0bba4579
commit 89455cdfb2
11 changed files with 379 additions and 584 deletions

View file

@ -76,14 +76,17 @@ class OATHAuthHooks {
global $wgRequest;
$token = $wgRequest->getText( 'wpOATHToken' );
$oathuser = OATHUser::newFromUser( $user );
$oathrepo = new OATHUserRepository( wfGetLB() );
$oathuser = $oathrepo->findByUser( $user );
# Though it's weird to default to true, we only want to deny
# users who have two-factor enabled and have validated their
# token.
$result = true;
if ( $oathuser && $oathuser->isEnabled() && $oathuser->isValidated() ) {
$result = $oathuser->verifyToken( $token );
if ( $oathuser->getKey() !== null ) {
$result = $oathuser->getKey()->verifyToken( $token, $oathuser );
}
return $result;
}
@ -97,8 +100,9 @@ class OATHAuthHooks {
static function TwoFactorIsEnabled( &$isEnabled ) {
global $wgUser;
$user = OATHUser::newFromUser( $wgUser );
if ( $user && $user->isEnabled() && $user->isValidated() ) {
$oathrepo = new OATHUserRepository( wfGetLB() );
$user = $oathrepo->findByUser( $wgUser );
if ( $user && $user->getKey() !== null ) {
$isEnabled = true;
# This two-factor extension is enabled by the user,
# we don't need to check others.
@ -120,10 +124,11 @@ class OATHAuthHooks {
* @return bool
*/
public static function manageOATH( User $user, array &$preferences ) {
$oathUser = OATHUser::newFromUser( $user );
$oathrepo = new OATHUserRepository( wfGetLB() );
$oathUser = $oathrepo->findByUser( $user );
$title = SpecialPage::getTitleFor( 'OATH' );
if ( $oathUser->isEnabled() && $oathUser->isValidated() ) {
if ( $oathUser->getKey() !== null ) {
$preferences['oath-disable'] = array(
'type' => 'info',
'raw' => 'true',
@ -139,20 +144,6 @@ class OATHAuthHooks {
'label-message' => 'oathauth-prefs-label',
'section' => 'personal/info',
);
$preferences['oath-reset'] = array(
'type' => 'info',
'raw' => 'true',
'default' => Linker::link(
$title,
wfMessage( 'oathauth-reset' )->escaped(),
array(),
array(
'action' => 'reset',
'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
)
),
'section' => 'personal/info',
);
} else {
$preferences['oath-enable'] = array(
'type' => 'info',
@ -183,9 +174,55 @@ class OATHAuthHooks {
switch ( $updater->getDB()->getType() ) {
case 'mysql':
case 'sqlite':
$updater->addExtensionTable( 'oathauth_users', "$base/oathauth.sql" );
$updater->addExtensionTable( 'oathauth_users', "$base/sql/mysql/tables.sql" );
$updater->addExtensionUpdate( array( array( __CLASS__, 'schemaUpdateOldUsersFromInstaller' ) ) );
$updater->dropExtensionField( 'oathauth_users', 'secret_reset',
"$base/sql/mysql/patch-remove_reset.sql" );
break;
}
return true;
}
/**
* Helper function for converting old users to the new schema
* @see OATHAuthHooks::OATHAuthSchemaUpdates
*
* @param DatabaseUpdater $updater
*
* @return bool
*/
public static function schemaUpdateOldUsersFromInstaller( DatabaseUpdater $updater ) {
return self::schemaUpdateOldUsers($updater->getDB());
}
/**
* Helper function for converting old users to the new schema
* @see OATHAuthHooks::OATHAuthSchemaUpdates
*
* @param DatabaseUpdater $updater
*
* @return bool
*/
public static function schemaUpdateOldUsers( DatabaseBase $db ) {
if ( !$db->fieldExists( 'oathauth_users', 'secret_reset' ) ) {
return true;
}
$res = $db->select( 'oathauth_users', array( 'id', 'scratch_tokens' ), '', __METHOD__ );
foreach ( $res as $row ) {
$scratchTokens = unserialize( base64_decode( $row->scratch_tokens ) );
if ( $scratchTokens ) {
$db->update(
'oathauth_users',
array( 'scratch_tokens' => implode( ',', $scratchTokens ) ),
array( 'id' => $row->id ),
__METHOD__
);
}
}
return true;
}
}

131
OATHAuthKey.php Normal file
View file

@ -0,0 +1,131 @@
<?php
/**
* Class representing a two-factor key
*
* Keys can be tied to OAUTHUsers
*/
class OATHAuthKey {
/** @var string Two factor binary secret */
private $secret;
/** @var string[] List of scratch tokens */
private $scratchTokens;
/**
* Make a new key from random values
*
* @return OATHAuthKey
*/
public static function newFromRandom() {
$object = new self(
Base32::encode( MWCryptRand::generate( 10, true ) ),
array()
);
$object->regenerateScratchTokens();
return $object;
}
/**
* @param string $secret
* @param array $scratchTokens
*/
public function __construct( $secret, array $scratchTokens ) {
// Currently harcoded values; might be used in future
$this->secret = array(
'mode' => 'hotp',
'secret' => $secret,
'period' => 30,
'algorithm' => 'SHA1',
);
$this->scratchTokens = $scratchTokens;
}
/**
* @return String
*/
public function getSecret() {
return $this->secret['secret'];
}
/**
* @return Array
*/
public function getScratchTokens() {
return $this->scratchTokens;
}
/**
* Verify a token against the secret or scratch tokens
*
* @param string $token Token to verify
* @param OATHUser $user
*
* @return bool True on match, false otherwise
*/
public function verifyToken( $token, $user ) {
global $wgOATHAuthWindowRadius;
if ($this->secret['mode'] !== 'hotp') {
throw new \DomainException( 'OATHAuth extension does not support non-HOTP tokens' );
}
// Prevent replay attacks
$memc = ObjectCache::newAnything( array() );
$memcKey = wfMemcKey( 'oauthauth', 'usedtokens', $user->getUser()->getId() );
$lastWindow = (int)$memc->get( $memcKey );
$retval = false;
$results = HOTP::generateByTimeWindow(
Base32::decode( $this->secret['secret'] ),
$this->secret['period'], -$wgOATHAuthWindowRadius, $wgOATHAuthWindowRadius );
// Check to see if the user's given token is in the list of tokens generated
// for the time window.
foreach ( $results as $window => $result ) {
if ( $window > $lastWindow && $result->toHOTP( 6 ) === $token ) {
$lastWindow = $window;
$retval = true;
break;
}
}
// See if the user is using a scratch token
if ( !$retval ) {
$length = count( $this->scratchTokens );
// Detect condition where all scratch tokens have been used
if ( $length == 1 && "" === $this->scratchTokens[0] ) {
$retval = false;
} else {
for ( $i = 0; $i < $length; $i++ ) {
if ( $token === $this->scratchTokens[$i] ) {
// If there is a scratch token, remove it from the scratch token list
unset( $this->scratchTokens[$i] );
$oathrepo = new OATHUserRepository( wfGetLB() );
$user->setKey( $this );
$oathrepo->persist( $user );
// Only return true if we removed it from the database
$retval = true;
break;
}
}
}
}
if ( $retval ) {
$memc->set( $memcKey, $lastWindow,
$this->secret['period'] * (1 + 2 * $wgOATHAuthWindowRadius) );
}
return $retval;
}
public function regenerateScratchTokens() {
$scratchTokens = array();
for ( $i = 0; $i < 5; $i++ ) {
array_push( $scratchTokens, Base32::encode( MWCryptRand::generate( 10, true ) ) );
}
$this->scratchTokens = $scratchTokens;
}
}

View file

@ -7,319 +7,50 @@
* @ingroup Extensions
*/
class OATHUser {
/** @var int User ID */
private $id;
/** @var User */
private $user;
/** @var string Two factor binary secret */
private $secret;
/** @var string New two factor secret when resetting */
private $secretReset;
/** @var string[] List of scratch tokens */
private $scratchTokens;
/** @var string[] New scratch tokens when resetting */
private $scratchTokensReset;
/** @var string Name for the two-factor account */
private $account;
/** @var bool Whether two-factor is enabled */
private $isEnabled;
/** @var bool Whether two-factor is validated */
private $isValidated;
/** @var OATHAuthKey|null */
private $key;
/**
* Constructor. Can't be called directly. Call one of the static NewFrom* methods
* @param $id Int Database id for the group
* @param $account
* @param $secret
* @param $secretReset
* @param $scratchTokens
* @param $scratchTokensReset
* @param bool $isValidated bool
* @todo Get rid of telescoping constructor anti-pattern
* @param User $user
* @param OATHAuthKey $key
*/
public function __construct( $id, $account, $secret = null, $secretReset = null,
$scratchTokens = null, $scratchTokensReset = null, $isValidated = false
) {
$this->id = $id;
$this->account = $account;
$this->isEnabled = true;
if ( $secret ) {
$this->secret = $secret;
} else {
$this->secret = Base32::encode( MWCryptRand::generate( 10, true ) );
$this->isEnabled = false;
}
if ( $secretReset ) {
$this->secretReset = $secretReset;
} else {
$this->secretReset = Base32::encode( MWCryptRand::generate( 10, true ) );
}
if ( $scratchTokens ) {
$this->scratchTokens = $scratchTokens;
} else {
$this->regenerateScratchTokens( false );
$this->isEnabled = false;
}
if ( $scratchTokensReset ) {
$this->scratchTokensReset = $scratchTokensReset;
} else {
$this->regenerateScratchTokens( true );
}
$this->isValidated = $isValidated;
public function __construct( User $user, OATHAuthKey $key = null ) {
$this->user = $user;
$this->key = $key;
}
/**
* @param $reset bool
*/
public function regenerateScratchTokens( $reset ) {
$scratchTokens = array();
for ( $i = 0; $i < 5; $i++ ) {
array_push( $scratchTokens, Base32::encode( MWCryptRand::generate( 10, true ) ) );
}
if ( $reset ) {
$this->scratchTokensReset = $scratchTokens;
} else {
$this->scratchTokens = $scratchTokens;
}
public function getUser() {
return $this->user;
}
/**
* @return String
*/
public function getAccount() {
return $this->account;
}
/**
* @return String
*/
public function getSecret() {
return $this->secret;
}
/**
* @return String
*/
public function getSecretReset() {
return $this->secretReset;
}
/**
* @return Array
*/
public function getScratchTokens() {
return $this->scratchTokens;
}
/**
* @return Array
*/
public function getScratchTokensReset() {
return $this->scratchTokensReset;
}
/**
* @return Boolean
*/
public function isEnabled() {
return $this->isEnabled;
}
/**
* @return Boolean
*/
public function isValidated() {
return $this->isValidated;
}
/**
* @param $token
* @param $reset bool
* @return Boolean
*/
public function verifyToken( $token, $reset = false ) {
global $wgOATHAuthWindowRadius;
$memc = ObjectCache::newAnything( array() );
// Prevent replay attacks
$memcKey = wfMemcKey( 'oauthauth', 'usedtokens', $reset ? 'reset' : null, $this->getAccount() );
$lastWindow = (int)$memc->get( $memcKey );
$retval = false;
$secret = $reset ? $this->secretReset : $this->secret;
$results = HOTP::generateByTimeWindow(
Base32::decode( $secret ),
30, -$wgOATHAuthWindowRadius, $wgOATHAuthWindowRadius );
// Check to see if the user's given token is in the list of tokens generated
// for the time window.
foreach ( $results as $window => $result ) {
if ( $window > $lastWindow && $result->toHOTP( 6 ) === $token ) {
$lastWindow = $window;
$retval = true;
break;
}
}
// See if the user is using a scratch token
$length = count( $this->scratchTokens );
for ( $i = 0; $i < $length; $i++ ) {
if ( $token === $this->scratchTokens[$i] ) {
// If there is a scratch token, remove it from the scratch token list
unset( $this->scratchTokens[$i] );
// Only return true if we removed it from the database
$retval = $this->updateScratchTokens();
break;
}
}
if ( $retval ) {
$memc->set( $memcKey, $lastWindow, 30 * (1 + 2 * $wgOATHAuthWindowRadius) );
}
return $retval;
}
/**
* @param $user User
* @return OATHUser|null
*/
public static function newFromUser( $user ) {
global $wgSitename;
$id = $user->getId();
if ( $id === 0 ) {
return null;
}
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'oathauth_users',
array( 'id',
'secret',
'secret_reset',
'scratch_tokens',
'scratch_tokens_reset',
'is_validated' ),
array( 'id' => $id ),
__METHOD__ );
if ( $row ) {
return new OATHUser(
$id,
urlencode( $user->getName() ) . '@' . $wgSitename,
$row->secret,
$row->secret_reset,
unserialize( base64_decode( $row->scratch_tokens ) ),
unserialize( base64_decode( $row->scratch_tokens_reset ) ),
$row->is_validated
);
} else {
return new OATHUser(
$id,
urlencode( $user->getName() ) . '@' . $wgSitename
);
}
return "$wgSitename:{$this->user->getName()}";
}
/**
* @param $username string
* @return OATHUser|null
* Get the key associated with this user.
*
* @return null|OATHAuthKey
*/
public static function newFromUsername( $username ) {
$user = User::newFromName( $username, true );
return OATHUser::newFromUser( $user );
public function getKey() {
return $this->key;
}
/**
* @return bool
* Set the key associated with this user.
*
* @param OATHAuthKey|null $key
*/
public function enable() {
$dbw = wfGetDB( DB_MASTER );
return $dbw->insert(
'oathauth_users',
array( 'id' => $this->id,
'secret' => $this->secret,
'scratch_tokens' => base64_encode( serialize( $this->scratchTokens ) ),
'is_validated' => false,
),
__METHOD__
);
}
/**
* @return bool
*/
public function setReset() {
$dbw = wfGetDB( DB_MASTER );
return $dbw->update(
'oathauth_users',
array(
'secret_reset' => $this->secretReset,
'scratch_tokens_reset' => base64_encode( serialize( $this->scratchTokensReset ) )
),
array( 'id' => $this->id ),
__METHOD__
);
}
/**
* @return bool
*/
public function reset() {
$dbw = wfGetDB( DB_MASTER );
return $dbw->update(
'oathauth_users',
array(
'secret' => $this->secretReset,
'secret_reset' => null,
'scratch_tokens' => base64_encode( serialize( $this->scratchTokensReset ) ),
'scratch_tokens_reset' => null,
),
array( 'id' => $this->id ),
__METHOD__
);
}
/**
* @return bool
*/
public function validate() {
$dbw = wfGetDB( DB_MASTER );
return $dbw->update(
'oathauth_users',
array( 'is_validated' => true ),
array( 'id' => $this->id ),
__METHOD__
);
}
/**
* @return bool
*/
public function updateScratchTokens() {
$dbw = wfGetDB( DB_MASTER );
return $dbw->update(
'oathauth_users',
array( 'scratch_tokens' => base64_encode( serialize( $this->scratchTokens ) ) ),
array( 'id' => $this->id ),
__METHOD__
);
}
/**
* @return bool
*/
public function disable() {
$dbw = wfGetDB( DB_MASTER );
return $dbw->delete(
'oathauth_users',
array( 'id' => $this->id ),
__METHOD__
);
public function setKey( OATHAuthKey $key = null ) {
$this->key = $key;
}
}

45
OATHUserRepository.php Normal file
View file

@ -0,0 +1,45 @@
<?php
class OATHUserRepository {
private $dbr;
private $dbw;
public function __construct( LoadBalancer $lb ) {
$this->dbr = $lb->getConnection( DB_SLAVE );
$this->dbw = $lb->getConnection( DB_MASTER );
}
public function findByUser( User $user ) {
$oathUser = new OATHUser( $user, null );
$res = $this->dbr->selectRow( 'oathauth_users', '*', array( 'id' => $user->getId() ), __METHOD__ );
if ($res) {
$key = new OATHAuthKey( $res->secret, explode( ',', $res->scratch_tokens ) );
$oathUser->setKey( $key );
}
return $oathUser;
}
public function persist( OATHUser $user ) {
$this->dbw->replace(
'oathauth_users',
array( 'id' ),
array(
'id' => $user->getUser()->getId(),
'secret' => $user->getKey()->getSecret(),
'scratch_tokens' => implode( ',', $user->getKey()->getScratchTokens() ),
),
__METHOD__
);
}
public function remove( OATHUser $user ) {
$this->dbw->delete(
'oathauth_users',
array( 'id' => $user->getUser()->getId() ),
__METHOD__
);
}
}

View file

@ -7,6 +7,8 @@
"type": "other",
"AutoloadClasses": {
"OATHAuthHooks": "OATHAuth.hooks.php",
"OATHAuthKey": "OATHAuthKey.php",
"OATHUserRepository": "OATHUserRepository.php",
"HOTP": "lib/hotp.php",
"HOTPResult": "lib/hotp.php",
"Base32": "lib/base32.php",

View file

@ -27,7 +27,7 @@ class HOTP {
$bin_counter = implode( $cur_counter );
// Pad to 8 chars
if ( strlen( $bin_counter ) < 8) {
if ( strlen( $bin_counter ) < 8 ) {
$bin_counter = str_repeat( "\0", 8 - strlen( $bin_counter ) ) . $bin_counter;
}

View file

@ -0,0 +1,54 @@
<?php
/**
* Update scratch_token column format
*
* Usage: php update_scratch_token_format.php
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @author Darian Anthony Patrick
* @ingroup Maintenance
*/
if ( getenv( 'MW_INSTALL_PATH' ) ) {
$IP = getenv( 'MW_INSTALL_PATH' );
} else {
$IP = dirname( __FILE__ ) . '/../../..';
}
require_once( "$IP/maintenance/Maintenance.php" );
class UpdateScratchTokenFormat extends Maintenance {
private $mPurgeDays = null;
function __construct() {
parent::__construct();
$this->mDescription = 'Script to update scratch_token column format';
}
public function execute() {
$dbr = wfGetDB( DB_SLAVE );
if ( !OATHAuthHooks::schemaUpdateOldUsers( $dbr ) ) {
$this->error( "Failed to update scratch_token rows.\n", 1);
}
$this->output( "Done.\n" );
}
}
$maintClass = "UpdateScratchTokenFormat";
require_once( RUN_MAINTENANCE_IF_MAIN );

View file

@ -1,20 +0,0 @@
CREATE TABLE /*_*/oathauth_users (
-- User ID
id int not null primary key,
-- Secret key
secret varchar(255) binary not null,
-- Secret key used for resets
secret_reset varchar(255) binary,
-- List of tokens
scratch_tokens varchar(512) binary not null,
-- List of tokens used for resets
scratch_tokens_reset varchar(512) binary not null,
-- Whether the user has validated their token
is_validated boolean not null
) /*$wgDBTableOptions*/;

View file

@ -8,7 +8,6 @@
*/
class SpecialOATH extends UnlistedSpecialPage {
/** @var OATHUser|null */
private $OATHUser;
@ -18,7 +17,8 @@ class SpecialOATH extends UnlistedSpecialPage {
public function __construct() {
parent::__construct( 'OATH' );
$this->OATHUser = OATHUser::newFromUser( $this->getUser() );
$this->OATHRepository = new OATHUserRepository( wfGetLB() );
$this->OATHUser = $this->OATHRepository->findByUser( $this->getUser() );
}
/**
@ -33,13 +33,10 @@ class SpecialOATH extends UnlistedSpecialPage {
$this->getOutput()->addWikiMsg( 'oathauth-mustbeloggedin' );
return;
}
$action = $this->getRequest()->getVal( 'action' );
if ( $action == "enable" ) {
$this->enable();
} elseif ( $action == "validate" ) {
$this->validate();
} elseif ( $action == "reset" ) {
$this->reset();
} elseif ( $action == "disable" ) {
$this->disable();
}
@ -53,16 +50,16 @@ class SpecialOATH extends UnlistedSpecialPage {
$this->getOutput()->setPagetitle( $this->msg( 'oathauth-enable' ) );
$returnto = $this->getRequest()->getVal( 'returnto' );
if ( !$this->OATHUser->isEnabled() ) {
$result = $this->OATHUser->enable();
if ( !$result ) {
$this->getOutput()->addWikiMsg( 'oathauth-failedtoenableoauth' );
return true;
}
} elseif ( $this->OATHUser->isEnabled() && $this->OATHUser->isValidated() ) {
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' => '',
@ -81,7 +78,7 @@ class SpecialOATH extends UnlistedSpecialPage {
);
$info['action'] = array(
'type' => 'hidden',
'default' => 'validate',
'default' => 'enable',
'name' => 'action',
);
$form = new HTMLForm(
@ -91,23 +88,20 @@ class SpecialOATH extends UnlistedSpecialPage {
);
$form->setSubmitID( 'oathauth-validate-submit' );
$form->setSubmitCallback( array( $this, 'tryValidateSubmit' ) );
$form->show();
$this->displaySecret();
if ( !$form->show() ) {
$this->displaySecret();
}
return true;
}
/**
* @param $reset bool
*/
private function displaySecret( $reset = false ) {
private function displaySecret() {
$this->getOutput()->addModules( 'ext.oathauth' );
if ( $reset ) {
$secret = $this->OATHUser->getSecretReset();
} else {
$secret = $this->OATHUser->getSecret();
}
/** @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> '
@ -130,89 +124,8 @@ class SpecialOATH extends UnlistedSpecialPage {
$this->getOutput()->addHTML( $out );
$this->getOutput()->addWikiMsg( 'openstackmanager-scratchtokens' );
if ( $reset ) {
$this->getOutput()->addHTML(
$this->createResourceList( $this->OATHUser->getScratchTokensReset() ) );
} else {
$this->getOutput()->addHTML(
$this->createResourceList( $this->OATHUser->getScratchTokens() ) );
}
}
/**
* @return bool
*/
private function validate() {
$this->setHeaders();
$this->getOutput()->setPagetitle( $this->msg( 'oathauth-enable' ) );
$mode = $this->getRequest()->getVal( 'mode' );
$returnto = $this->getRequest()->getVal( 'returnto' );
$info['token'] = array(
'type' => 'text',
'default' => '',
'label-message' => 'oathauth-token',
'name' => 'token',
);
$info['mode'] = array(
'type' => 'hidden',
'default' => $mode,
'name' => 'mode',
);
$info['returnto'] = array(
'type' => 'hidden',
'default' => $returnto,
'name' => 'returnto',
);
$info['action'] = array(
'type' => 'hidden',
'default' => 'validate',
'name' => 'action',
);
$form = new HTMLForm(
$info,
$this->getContext(),
'oathauth-verify'
);
$form->setSubmitID( 'oathauth-validate-submit' );
$form->setSubmitCallback( array( $this, 'tryValidateSubmit' ) );
$form->show();
return true;
}
/**
* @return bool
*/
private function reset() {
$this->setHeaders();
$this->getOutput()->setPagetitle( $this->msg( 'oathauth-reset' ) );
$returnto = $this->getRequest()->getVal( 'returnto' );
$info['token'] = array(
'type' => 'text',
'label-message' => 'oathauth-currenttoken',
'name' => 'token',
);
$info['returnto'] = array(
'type' => 'hidden',
'default' => $returnto,
'name' => 'returnto',
);
$info['action'] = array(
'type' => 'hidden',
'default' => 'reset',
'name' => 'action',
);
$form = new HTMLForm(
$info,
$this->getContext(),
'oathauth-reset'
);
$form->setSubmitID( 'oauth-form-disablesubmit' );
$form->setSubmitCallback( array( $this, 'tryResetSubmit' ) );
$form->show();
return true;
$this->getOutput()->addHTML(
$this->createResourceList( $key->getScratchTokens() ) );
}
/**
@ -266,26 +179,16 @@ class SpecialOATH extends UnlistedSpecialPage {
* @return bool
*/
public function tryValidateSubmit( $formData ) {
$mode = $formData['mode'];
if ( $mode == "reset" ) {
$reset = true;
} else {
$reset = false;
}
$verify = $this->OATHUser->verifyToken( $formData['token'], $reset );
if ( $verify ) {
if ( $reset ) {
$result = $this->OATHUser->reset();
} else {
$result = $this->OATHUser->validate();
}
} else {
$result = false;
}
/** @var OATHAuthKey $key */
$key = $this->getRequest()->getSessionData( 'oathauth_key' );
$verify = $key->verifyToken( $formData['token'], $this->OATHUser );
$out = '';
if ( $result ) {
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 />';
@ -296,28 +199,15 @@ class SpecialOATH extends UnlistedSpecialPage {
$this->getOutput()->addWikiMsg( 'oathauth-failedtovalidateoauth' );
$out = '<br />';
if ( $reset ) {
$out .= Linker::link(
$this->getPageTitle(),
$this->msg( 'oathauth-reattemptreset' )->escaped(),
array(),
array(
'action' => 'enable',
'mode' => 'reset',
'returnto' => $formData['returnto']
)
);
} else {
$out .= Linker::link(
$this->getPageTitle(),
$this->msg( 'oathauth-reattemptenable' )->escaped(),
array(),
array(
'action' => 'enable',
'returnto' => $formData['returnto']
)
);
}
$out .= Linker::link(
$this->getPageTitle(),
$this->msg( 'oathauth-reattemptenable' )->escaped(),
array(),
array(
'action' => 'enable',
'returnto' => $formData['returnto']
)
);
}
$this->getOutput()->addHTML( $out );
@ -330,7 +220,7 @@ class SpecialOATH extends UnlistedSpecialPage {
* @return bool
*/
public function tryDisableSubmit( $formData ) {
$verify = $this->OATHUser->verifyToken( $formData['token'] );
$verify = $this->OATHUser->getKey()->verifyToken( $formData['token'], $this->OATHUser );
if ( !$verify ) {
$this->getOutput()->addWikiMsg( 'oathauth-failedtovalidateoauth' );
$out = '<br />';
@ -344,107 +234,13 @@ class SpecialOATH extends UnlistedSpecialPage {
return true;
}
$result = $this->OATHUser->disable();
if ( $result ) {
$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 );
}
} else {
$this->getOutput()->addWikiMsg( 'oathauth-failedtodisableoauth' );
$this->OATHRepository->remove( $this->OATHUser );
$this->getOutput()->addWikiMsg( 'oathauth-disabledoath' );
if ( $formData['returnto'] ) {
$out = '<br />';
$out .= Linker::link(
$this->getPageTitle(),
$this->msg( 'oathauth-reattemptdisable' )->escaped(),
array(
'action' => 'disable',
'returnto' => $formData['returnto'],
)
);
$this->getOutput()->addHTML( $out );
}
return true;
}
/**
* @param $formData array
* @return bool
*/
public function tryResetSubmit( $formData ) {
$verify = $this->OATHUser->verifyToken( $formData['token'] );
if ( !$verify ) {
$this->getOutput()->addWikiMsg( 'oathauth-failedtovalidateoauth' );
$out = '<br />';
$out .= Linker::link(
$this->getPageTitle(),
$this->msg( 'oathauth-reattemptreset' )->escaped(),
array(),
array(
'action' => 'reset',
'returnto' => $formData['returnto']
)
);
$this->getOutput()->addHTML( $out );
return true;
}
$this->getOutput()->addWikiMsg( 'oathauth-donotdeleteoldsecret' );
$info['token'] = array(
'type' => 'text',
'default' => '',
'label-message' => 'oathauth-newtoken',
'name' => 'token',
);
$info['mode'] = array(
'type' => 'hidden',
'default' => 'reset',
'name' => 'mode',
);
$info['returnto'] = array(
'type' => 'hidden',
'default' => $formData['returnto'],
'name' => 'returnto',
);
$info['action'] = array(
'type' => 'hidden',
'default' => 'validate',
'name' => 'action',
);
$myContext = new DerivativeContext( $this->getContext() );
$myRequest = new DerivativeRequest( $this->getRequest(),
array(
'action' => 'validate',
'mode' => 'reset',
'token' => '',
'returnto' => $formData['returnto']
), false );
$myContext->setRequest( $myRequest );
$form = new HTMLForm( $info, $myContext );
$form->setSubmitID( 'oathauth-validate-submit' );
$form->setSubmitCallback( array( $this, 'tryValidateSubmit' ) );
$form->show();
$result = $this->OATHUser->setReset();
if ( $result ) {
$this->displaySecret( true );
} else {
$this->getOutput()->addWikiMsg( 'oathauth-failedtoresetoath' );
$out = '<br />';
$out .= Linker::link(
$this->getPageTitle(),
$this->msg( 'oathauth-reattemptreset' )->escaped(),
array(),
array(
'action' => 'reset',
'returnto' => $formData['returnto']
)
);
$title = Title::newFromText( $formData['returnto'] );
$out .= Linker::link( $title, $this->msg( 'oathauth-backtopreferences' )->escaped() );
$this->getOutput()->addHTML( $out );
}

View file

@ -0,0 +1,8 @@
ALTER TABLE /*_*/oathauth_users
DROP COLUMN secret_reset;
ALTER TABLE /*_*/oathauth_users
DROP COLUMN scratch_tokens_reset;
ALTER TABLE /*_*/oathauth_users
DROP COLUMN is_validated;

11
sql/mysql/tables.sql Normal file
View file

@ -0,0 +1,11 @@
CREATE TABLE /*_*/oathauth_users (
-- User ID
id int not null primary key,
-- Secret key
secret varbinary(255) null,
-- Scratch tokens
scratch_tokens varbinary(511) null
) /*$wgDBTableOptions*/;