mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/OATHAuth
synced 2024-11-24 00:05:24 +00:00
Re-instate "Add some logging of OATHAuth actions"
This reverts commit 69b6292c12
.
Bug: T151010
Change-Id: I6f610551bc4bd1e78c0282011b80a3f3e70b8885
This commit is contained in:
parent
ab6246991d
commit
1871a9abe1
|
@ -16,6 +16,9 @@
|
||||||
* http://www.gnu.org/copyleft/gpl.html
|
* http://www.gnu.org/copyleft/gpl.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use \MediaWiki\Logger\LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing a two-factor key
|
* Class representing a two-factor key
|
||||||
*
|
*
|
||||||
|
@ -119,12 +122,21 @@ class OATHAuthKey {
|
||||||
// or trimmeable whitespace
|
// or trimmeable whitespace
|
||||||
$token = preg_replace( '/\s+/', '', $token );
|
$token = preg_replace( '/\s+/', '', $token );
|
||||||
|
|
||||||
|
$clientIP = $user->getUser()->getRequest()->getIP();
|
||||||
|
|
||||||
|
$logger = $this->getLogger();
|
||||||
|
|
||||||
// Check to see if the user's given token is in the list of tokens generated
|
// Check to see if the user's given token is in the list of tokens generated
|
||||||
// for the time window.
|
// for the time window.
|
||||||
foreach ( $results as $window => $result ) {
|
foreach ( $results as $window => $result ) {
|
||||||
if ( $window > $lastWindow && $result->toHOTP( 6 ) === $token ) {
|
if ( $window > $lastWindow && $result->toHOTP( 6 ) === $token ) {
|
||||||
$lastWindow = $window;
|
$lastWindow = $window;
|
||||||
$retval = self::MAIN_TOKEN;
|
$retval = self::MAIN_TOKEN;
|
||||||
|
|
||||||
|
$logger->info( 'OATHAuth user {user} entered a valid OTP from {clientip}', [
|
||||||
|
'user' => $user->getAccount(),
|
||||||
|
'clientip' => $clientIP,
|
||||||
|
] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,16 +145,22 @@ class OATHAuthKey {
|
||||||
if ( !$retval ) {
|
if ( !$retval ) {
|
||||||
$length = count( $this->scratchTokens );
|
$length = count( $this->scratchTokens );
|
||||||
// Detect condition where all scratch tokens have been used
|
// Detect condition where all scratch tokens have been used
|
||||||
if ( $length == 1 && "" === $this->scratchTokens[0] ) {
|
if ( $length === 1 && $this->scratchTokens[0] === "" ) {
|
||||||
$retval = false;
|
$retval = false;
|
||||||
} else {
|
} else {
|
||||||
for ( $i = 0; $i < $length; $i++ ) {
|
for ( $i = 0; $i < $length; $i++ ) {
|
||||||
if ( $token === $this->scratchTokens[$i] ) {
|
if ( $token === $this->scratchTokens[$i] ) {
|
||||||
// If there is a scratch token, remove it from the scratch token list
|
// If there is a scratch token, remove it from the scratch token list
|
||||||
unset( $this->scratchTokens[$i] );
|
unset( $this->scratchTokens[$i] );
|
||||||
|
|
||||||
|
$logger->info( 'OATHAuth user {user} used a scratch token from {clientip}', [
|
||||||
|
'user' => $user->getAccount(),
|
||||||
|
'clientip' => $clientIP,
|
||||||
|
] );
|
||||||
|
|
||||||
$oathrepo = OATHAuthHooks::getOATHUserRepository();
|
$oathrepo = OATHAuthHooks::getOATHUserRepository();
|
||||||
$user->setKey( $this );
|
$user->setKey( $this );
|
||||||
$oathrepo->persist( $user );
|
$oathrepo->persist( $user, $clientIP );
|
||||||
// Only return true if we removed it from the database
|
// Only return true if we removed it from the database
|
||||||
$retval = self::SCRATCH_TOKEN;
|
$retval = self::SCRATCH_TOKEN;
|
||||||
break;
|
break;
|
||||||
|
@ -158,6 +176,12 @@ class OATHAuthKey {
|
||||||
$this->secret['period'] * ( 1 + 2 * $wgOATHAuthWindowRadius )
|
$this->secret['period'] * ( 1 + 2 * $wgOATHAuthWindowRadius )
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
$logger->info( 'OATHAuth user {user} failed OTP/scratch token from {clientip}', [
|
||||||
|
'user' => $user->getAccount(),
|
||||||
|
'clientip' => $clientIP,
|
||||||
|
] );
|
||||||
|
|
||||||
// Increase rate limit counter for failed request
|
// Increase rate limit counter for failed request
|
||||||
$user->getUser()->pingLimiter( 'badoath' );
|
$user->getUser()->pingLimiter( 'badoath' );
|
||||||
}
|
}
|
||||||
|
@ -184,4 +208,11 @@ class OATHAuthKey {
|
||||||
$token = preg_replace( '/\s+/', '', $token );
|
$token = preg_replace( '/\s+/', '', $token );
|
||||||
return in_array( $token, $this->scratchTokens, true );
|
return in_array( $token, $this->scratchTokens, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return LoggerInterface
|
||||||
|
*/
|
||||||
|
private function getLogger() {
|
||||||
|
return LoggerFactory::getInstance( 'authentication' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* http://www.gnu.org/copyleft/gpl.html
|
* http://www.gnu.org/copyleft/gpl.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
use Wikimedia\Rdbms\ILoadBalancer;
|
use Wikimedia\Rdbms\ILoadBalancer;
|
||||||
use Wikimedia\Rdbms\DBConnRef;
|
use Wikimedia\Rdbms\DBConnRef;
|
||||||
|
|
||||||
|
@ -26,6 +27,9 @@ class OATHUserRepository {
|
||||||
/** @var BagOStuff */
|
/** @var BagOStuff */
|
||||||
protected $cache;
|
protected $cache;
|
||||||
|
|
||||||
|
/** @var LoggerInterface */
|
||||||
|
private $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OATHUserRepository constructor.
|
* OATHUserRepository constructor.
|
||||||
* @param ILoadBalancer $lb
|
* @param ILoadBalancer $lb
|
||||||
|
@ -34,6 +38,15 @@ class OATHUserRepository {
|
||||||
public function __construct( ILoadBalancer $lb, BagOStuff $cache ) {
|
public function __construct( ILoadBalancer $lb, BagOStuff $cache ) {
|
||||||
$this->lb = $lb;
|
$this->lb = $lb;
|
||||||
$this->cache = $cache;
|
$this->cache = $cache;
|
||||||
|
|
||||||
|
$this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param LoggerInterface $logger
|
||||||
|
*/
|
||||||
|
public function setLogger( LoggerInterface $logger ) {
|
||||||
|
$this->logger = $logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,8 +77,11 @@ class OATHUserRepository {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param OATHUser $user
|
* @param OATHUser $user
|
||||||
|
* @param string $clientInfo
|
||||||
*/
|
*/
|
||||||
public function persist( OATHUser $user ) {
|
public function persist( OATHUser $user, $clientInfo ) {
|
||||||
|
$prevUser = $this->findByUser( $user->getUser() );
|
||||||
|
|
||||||
$this->getDB( DB_MASTER )->replace(
|
$this->getDB( DB_MASTER )->replace(
|
||||||
'oathauth_users',
|
'oathauth_users',
|
||||||
[ 'id' ],
|
[ 'id' ],
|
||||||
|
@ -76,23 +92,46 @@ class OATHUserRepository {
|
||||||
],
|
],
|
||||||
__METHOD__
|
__METHOD__
|
||||||
);
|
);
|
||||||
$this->cache->set( $user->getUser()->getName(), $user );
|
|
||||||
|
$userName = $user->getUser()->getName();
|
||||||
|
$this->cache->set( $userName, $user );
|
||||||
|
|
||||||
|
if ( $prevUser !== false ) {
|
||||||
|
$this->logger->info( 'OATHAuth updated for {user} from {clientip}', [
|
||||||
|
'user' => $userName,
|
||||||
|
'clientip' => $clientInfo,
|
||||||
|
] );
|
||||||
|
} else {
|
||||||
|
// If findByUser() has returned false, there was no user row or cache entry
|
||||||
|
$this->logger->info( 'OATHAuth enabled for {user} from {clientip}', [
|
||||||
|
'user' => $userName,
|
||||||
|
'clientip' => $clientInfo,
|
||||||
|
] );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param OATHUser $user
|
* @param OATHUser $user
|
||||||
|
* @param string $clientInfo
|
||||||
*/
|
*/
|
||||||
public function remove( OATHUser $user ) {
|
public function remove( OATHUser $user, $clientInfo ) {
|
||||||
$this->getDB( DB_MASTER )->delete(
|
$this->getDB( DB_MASTER )->delete(
|
||||||
'oathauth_users',
|
'oathauth_users',
|
||||||
[ 'id' => CentralIdLookup::factory()->centralIdFromLocalUser( $user->getUser() ) ],
|
[ 'id' => CentralIdLookup::factory()->centralIdFromLocalUser( $user->getUser() ) ],
|
||||||
__METHOD__
|
__METHOD__
|
||||||
);
|
);
|
||||||
$this->cache->delete( $user->getUser()->getName() );
|
|
||||||
|
$userName = $user->getUser()->getName();
|
||||||
|
$this->cache->delete( $userName );
|
||||||
|
|
||||||
|
$this->logger->info( 'OATHAuth disabled for {user} from {clientip}', [
|
||||||
|
'user' => $userName,
|
||||||
|
'clientip' => $clientInfo,
|
||||||
|
] );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param integer $index DB_MASTER/DB_REPLICA
|
* @param int $index DB_MASTER/DB_REPLICA
|
||||||
* @return DBConnRef
|
* @return DBConnRef
|
||||||
*/
|
*/
|
||||||
private function getDB( $index ) {
|
private function getDB( $index ) {
|
||||||
|
|
|
@ -91,7 +91,15 @@ class SpecialDisableOATHForUser extends FormSpecialPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
$oathUser->setKey( null );
|
$oathUser->setKey( null );
|
||||||
$this->OATHRepository->remove( $oathUser );
|
$this->OATHRepository->remove( $oathUser, $this->getRequest()->getIP() );
|
||||||
|
|
||||||
|
\MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )->info(
|
||||||
|
'OATHAuth disabled for {usertarget} by {user} from {clientip}', [
|
||||||
|
'user' => $this->getUser()->getName(),
|
||||||
|
'usertarget' => $formData['user'],
|
||||||
|
'clientip' => $this->getRequest()->getIP(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,15 +116,27 @@ class SpecialOATHDisable extends FormSpecialPage {
|
||||||
// Don't increase pingLimiter, just check for limit exceeded.
|
// Don't increase pingLimiter, just check for limit exceeded.
|
||||||
if ( $this->OATHUser->getUser()->pingLimiter( 'badoath', 0 ) ) {
|
if ( $this->OATHUser->getUser()->pingLimiter( 'badoath', 0 ) ) {
|
||||||
// Arbitrary duration given here
|
// Arbitrary duration given here
|
||||||
|
\MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )->info(
|
||||||
|
'OATHAuth {user} rate limited while disabling 2FA from {clientip}', [
|
||||||
|
'user' => $this->getUser()->getName(),
|
||||||
|
'clientip' => $this->getRequest()->getIP(),
|
||||||
|
]
|
||||||
|
);
|
||||||
return [ 'oathauth-throttled', Message::durationParam( 60 ) ];
|
return [ 'oathauth-throttled', Message::durationParam( 60 ) ];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !$this->OATHUser->getKey()->verifyToken( $formData['token'], $this->OATHUser ) ) {
|
if ( !$this->OATHUser->getKey()->verifyToken( $formData['token'], $this->OATHUser ) ) {
|
||||||
|
\MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )->info(
|
||||||
|
'OATHAuth {user} failed to provide a correct token while disabling 2FA from {clientip}', [
|
||||||
|
'user' => $this->getUser()->getName(),
|
||||||
|
'clientip' => $this->getRequest()->getIP(),
|
||||||
|
]
|
||||||
|
);
|
||||||
return [ 'oathauth-failedtovalidateoath' ];
|
return [ 'oathauth-failedtovalidateoath' ];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->OATHUser->setKey( null );
|
$this->OATHUser->setKey( null );
|
||||||
$this->OATHRepository->remove( $this->OATHUser );
|
$this->OATHRepository->remove( $this->OATHUser, $this->getRequest()->getIP() );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,8 @@ class SpecialOATHEnable extends FormSpecialPage {
|
||||||
'returntoquery' => [
|
'returntoquery' => [
|
||||||
'type' => 'hidden',
|
'type' => 'hidden',
|
||||||
'default' => $this->getRequest()->getVal( 'returntoquery' ),
|
'default' => $this->getRequest()->getVal( 'returntoquery' ),
|
||||||
'name' => 'returntoquery', ]
|
'name' => 'returntoquery',
|
||||||
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,15 +176,27 @@ class SpecialOATHEnable extends FormSpecialPage {
|
||||||
|
|
||||||
if ( $key->isScratchToken( $formData['token'] ) ) {
|
if ( $key->isScratchToken( $formData['token'] ) ) {
|
||||||
// A scratch token is not allowed for enrollement
|
// A scratch token is not allowed for enrollement
|
||||||
|
\MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )->info(
|
||||||
|
'OATHAuth {user} attempted to enable 2FA using a scratch token from {clientip}', [
|
||||||
|
'user' => $this->getUser()->getName(),
|
||||||
|
'clientip' => $this->getRequest()->getIP(),
|
||||||
|
]
|
||||||
|
);
|
||||||
return [ 'oathauth-noscratchforvalidation' ];
|
return [ 'oathauth-noscratchforvalidation' ];
|
||||||
}
|
}
|
||||||
if ( !$key->verifyToken( $formData['token'], $this->OATHUser ) ) {
|
if ( !$key->verifyToken( $formData['token'], $this->OATHUser ) ) {
|
||||||
|
\MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )->info(
|
||||||
|
'OATHAuth {user} failed to provide a correct token while enabling 2FA from {clientip}', [
|
||||||
|
'user' => $this->getUser()->getName(),
|
||||||
|
'clientip' => $this->getRequest()->getIP(),
|
||||||
|
]
|
||||||
|
);
|
||||||
return [ 'oathauth-failedtovalidateoath' ];
|
return [ 'oathauth-failedtovalidateoath' ];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->getRequest()->setSessionData( 'oathauth_key', null );
|
$this->getRequest()->setSessionData( 'oathauth_key', null );
|
||||||
$this->OATHUser->setKey( $key );
|
$this->OATHUser->setKey( $key );
|
||||||
$this->OATHRepository->persist( $this->OATHUser );
|
$this->OATHRepository->persist( $this->OATHUser, $this->getRequest()->getIP() );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class DisableOATHAuthForUser extends Maintenance {
|
||||||
$this->error( "User $username doesn't have OATHAuth enabled!", 1 );
|
$this->error( "User $username doesn't have OATHAuth enabled!", 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
$repo->remove( $oathUser );
|
$repo->remove( $oathUser, 'Maintenance script' );
|
||||||
$this->output( "OATHAuth disabled for $username.\n" );
|
$this->output( "OATHAuth disabled for $username.\n" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue