Gergő Tisza has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/287500

Change subject: Update for AuthManager
......................................................................

Update for AuthManager

This is a breaking change for the API as the capitalization of the
parameter has changed from ignoreantispoof to ignoreAntiSpoof.

Bug: T110449
Change-Id: If71135d1cae428abb7e6375ccb8c371cba53d147
---
M AntiSpoof.php
A AntiSpoofAuthenticationRequest.php
M AntiSpoofHooks.php
A AntiSpoofPreAuthenticationProvider.php
M i18n/en.json
M i18n/qqq.json
A tests/AntiSpoofAuthenticationRequestTest.php
A tests/AntiSpoofPreAuthenticationProviderTest.php
8 files changed, 272 insertions(+), 5 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/AntiSpoof 
refs/changes/00/287500/1

diff --git a/AntiSpoof.php b/AntiSpoof.php
index 566b145..f5f5239 100644
--- a/AntiSpoof.php
+++ b/AntiSpoof.php
@@ -1,4 +1,5 @@
 <?php
+
 if ( !defined( 'MEDIAWIKI' ) ) {
        exit( 1 );
 }
@@ -54,17 +55,17 @@
 $wgAutoloadClasses['AntiSpoof'] = "$dir/AntiSpoof_body.php";
 $wgAutoloadClasses['AntiSpoofHooks'] = "$dir/AntiSpoofHooks.php";
 $wgAutoloadClasses['SpoofUser'] = "$dir/SpoofUser.php";
+$wgAutoloadClasses['AntiSpoofAuthenticationRequest'] = 
"$dir/AntiSpoofAuthenticationRequest.php";
+$wgAutoloadClasses['AntiSpoofPreAuthenticationProvider']
+       = "$dir/AntiSpoofPreAuthenticationProvider.php";
 
 // Register the API method
 $wgAutoloadClasses['ApiAntiSpoof'] = "$dir/api/ApiAntiSpoof.php";
 $wgAPIModules['antispoof'] = 'ApiAntiSpoof';
 
 $wgHooks['LoadExtensionSchemaUpdates'][] = 'AntiSpoofHooks::asUpdateSchema';
-$wgHooks['AbortNewAccount'][] = 'AntiSpoofHooks::asAbortNewAccountHook';
-$wgHooks['UserCreateForm'][] = 'AntiSpoofHooks::asUserCreateFormHook';
-$wgHooks['AddNewAccount'][] = 'AntiSpoofHooks::asAddNewAccountHook';
 $wgHooks['RenameUserComplete'][] = 'AntiSpoofHooks::asAddRenameUserHook';
 $wgHooks['DeleteAccount'][] = 'AntiSpoofHooks::asDeleteAccount';
 $wgHooks['UnitTestsList'][] = 'AntiSpoofHooks::asUnitTestsList';
-$wgHooks['APIGetAllowedParams'][] = 'AntiSpoofHooks::onAPIGetAllowedParams';
-$wgHooks['AddNewAccountApiForm'][] = 'AntiSpoofHooks::addNewAccountApiForm';
+
+$wgExtensionFunctions[] = 'AntiSpoofHooks::onRegistration';
diff --git a/AntiSpoofAuthenticationRequest.php 
b/AntiSpoofAuthenticationRequest.php
new file mode 100644
index 0000000..cafd872
--- /dev/null
+++ b/AntiSpoofAuthenticationRequest.php
@@ -0,0 +1,18 @@
+<?php
+
+use MediaWiki\Auth\AuthenticationRequest;
+
+class AntiSpoofAuthenticationRequest extends AuthenticationRequest {
+       public $ignoreAntiSpoof;
+
+       public function getFieldInfo() {
+               return [
+                       'ignoreAntiSpoof' => [
+                               'type' => 'checkbox',
+                               'label' => wfMessage( 'antispoof-ignore' ),
+                               'help' => wfMessage( 'antispoof-ignore-help' ),
+                               'optional' => true,
+                       ],
+               ];
+       }
+}
diff --git a/AntiSpoofHooks.php b/AntiSpoofHooks.php
index a28a902..1ca126a 100644
--- a/AntiSpoofHooks.php
+++ b/AntiSpoofHooks.php
@@ -1,6 +1,27 @@
 <?php
 
+use MediaWiki\Auth\AuthManager;
+
 class AntiSpoofHooks {
+       /**
+        * Called after global variables have been set up
+        */
+       public static function onRegistration() {
+               global $wgDisableAuthManager, $wgAuthManagerConfig;
+
+               if ( class_exists( AuthManager::class ) && 
!$wgDisableAuthManager ) {
+                       
$wgAuthManagerConfig['preauth'][AntiSpoofPreAuthenticationProvider::class] =
+                               [ 'class' => 
AntiSpoofPreAuthenticationProvider::class ];
+                       Hooks::register( 'LocalUserCreated', 
'AntiSpoofHooks::asLocalUserCreated' );
+               } else {
+                       Hooks::register( 'AbortNewAccount', 
'AntiSpoofHooks::asAbortNewAccountHook' );
+                       Hooks::register( 'UserCreateForm', 
'AntiSpoofHooks::asUserCreateFormHook' );
+                       Hooks::register( 'AddNewAccount', 
'AntiSpoofHooks::asAddNewAccountHook' );
+                       Hooks::register( 'APIGetAllowedParams', 
'AntiSpoofHooks::onAPIGetAllowedParams' );
+                       Hooks::register( 'AddNewAccountApiForm', 
'AntiSpoofHooks::addNewAccountApiForm' );
+               }
+       }
+
        /**
         * @param $updater DatabaseUpdater
         * @return bool
@@ -97,6 +118,19 @@
        }
 
        /**
+        * On new account creation, record the username's thing-bob.
+        * Replaces AddNewAccountHook for more modern MediaWiki versions-
+        *
+        * @param $user User
+        * @return bool
+        */
+       public static function asLocalUserCreated( $user ) {
+               $spoof = new SpoofUser( $user->getName() );
+               $spoof->record();
+               return true;
+       }
+
+       /**
         * On rename, remove the old entry and add the new
         * (After a sucessful user rename)
         *
diff --git a/AntiSpoofPreAuthenticationProvider.php 
b/AntiSpoofPreAuthenticationProvider.php
new file mode 100644
index 0000000..e93d360
--- /dev/null
+++ b/AntiSpoofPreAuthenticationProvider.php
@@ -0,0 +1,98 @@
+<?php
+
+use MediaWiki\Auth\AbstractPreAuthenticationProvider;
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthManager;
+
+class AntiSpoofPreAuthenticationProvider extends 
AbstractPreAuthenticationProvider {
+       /** @var bool False effectively disables this provider, but spoofed 
names will still be logged. */
+       protected $antiSpoofAccounts;
+
+       /**
+        * Options:
+        * - antiSpoofAccounts: (bool) stop spoofed accounts from being 
created. When false, only log.
+        * @param array $params
+        */
+       public function __construct( array $params = [] ) {
+               global $wgAntiSpoofAccounts;
+
+               $params += [ 'antiSpoofAccounts' => $wgAntiSpoofAccounts ];
+
+               $this->antiSpoofAccounts = $params['antiSpoofAccounts'];
+       }
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               $needed = false;
+               switch ( $action ) {
+                       case AuthManager::ACTION_CREATE:
+                               $user = User::newFromName( $options['username'] 
) ?: new User();
+                               $needed = $this->antiSpoofAccounts && 
$user->isAllowed( 'override-antispoof' );
+                               break;
+               }
+
+               return $needed ? [ new AntiSpoofAuthenticationRequest() ] : [];
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               /** @var AntiSpoofAuthenticationRequest $req */
+               $req = AuthenticationRequest::getRequestByClass( $reqs, 
AntiSpoofAuthenticationRequest::class );
+               $spoofUser = $this->getSpoofUser( $user );
+               $override = $req && $req->ignoreAntiSpoof && 
$creator->isAllowed( 'override-antispoof' );
+               $active = $this->antiSpoofAccounts && !$override;
+               $mode = !$this->antiSpoofAccounts ? 'LOGGING ' : $override ? 
'OVERRIDE ' : '';
+
+               if ( $spoofUser->isLegal() ) {
+                       $normalized = $spoofUser->getNormalized();
+                       $conflicts = $spoofUser->getConflicts();
+                       if ( empty( $conflicts ) ) {
+                               $this->logger->debug( "{mode}PASS new account 
'{name}' [{normalized}]", [
+                                       'mode' => $mode,
+                                       'name' => $user->getName(),
+                                       'normalized' => $normalized,
+                               ] );
+                       } else {
+                               $this->logger->info( "{mode}CONFLICT new 
account '{name}' [{normalized}] spoofs {spoofs}", [
+                                       'mode' => $mode,
+                                       'name' => $user->getName(),
+                                       'normalized' => $normalized,
+                                       'spoofs' => $conflicts,
+                               ] );
+                               if ( $active ) {
+                                       $cnt = count( $conflicts );
+                                       $list = '';
+                                       foreach ( $conflicts as $simUser ) {
+                                               $list .= Html::element( 'li', 
[],
+                                                       wfMessage( 
'antispoof-conflict-item', $simUser ) );
+                                       }
+                                       $list = Html::rawElement( 'ul', [], 
$list );
+
+                                       $message = new RawMessage( '$1$2$3', [
+                                               wfMessage( 
'antispoof-conflict-top', $user->getName() )->numParams( $cnt ),
+                                               $list,
+                                               wfMessage( 
'antispoof-conflict-bottom' )
+                                       ] );
+                                       return StatusValue::newFatal( $message 
);
+                               }
+                       }
+               } else {
+                       $error = $spoofUser->getError();
+                       $this->logger->info( "{mode}ILLEGAL new account 
'{name}' {error}", [
+                               'mode' => $mode,
+                               'name' => $user->getName(),
+                               'error' => $error,
+                       ] );
+                       if ( $active ) {
+                               return StatusValue::newFatal( 
'antispoof-name-illegal', $user->getName(), $error );
+                       }
+               }
+               return StatusValue::newGood();
+       }
+
+       /**
+        * @param User $user
+        * @return SpoofUser
+        */
+       protected function getSpoofUser( User $user ) {
+               return new SpoofUser( $user->getName() );
+       }
+}
diff --git a/i18n/en.json b/i18n/en.json
index c382244..a49d48e 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -19,6 +19,7 @@
        "antispoof-mixedscripts": "Contains incompatible mixed scripts",
        "antispoof-tooshort": "Canonicalized name too short",
        "antispoof-ignore": "Ignore spoofing checks",
+       "antispoof-ignore-help": "Allows users with sufficient privileges to 
create an account even if its name is similar to that of an existing account.",
        "right-override-antispoof": "Override the spoofing checks",
        "apihelp-antispoof-description": "Check a username against AntiSpoof's 
normalisation checks.",
        "apihelp-antispoof-param-username": "The username to check against 
AntiSpoof.",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index dce6734..9665dbd 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -27,6 +27,7 @@
        "antispoof-mixedscripts": "Reason for failed account creation.",
        "antispoof-tooshort": "Reason for failed account creation.",
        "antispoof-ignore": "This is a checkbox shown on [[Special:UserLogin|a 
signup page]] when a user with both 
[[MediaWiki:Right-createaccount/qqq|createaccount]] and 
[[MediaWiki:Right-override-antispoof/qqq|override-antispoof]] rights tries to 
register a new user account. It allows to register a username that would 
otherwise be blocked by the [[mw:Extension:AntiSpoof|AntiSpoof extension]].",
+       "antispoof-ignore-help": "API help message describing 
{{msg-mw|antispoof-ignore}}.",
        "right-override-antispoof": "{{doc-right|override-antispoof}}",
        "apihelp-antispoof-description": 
"{{doc-apihelp-description|antispoof}}",
        "apihelp-antispoof-param-username": 
"{{doc-apihelp-param|antispoof|username}}",
diff --git a/tests/AntiSpoofAuthenticationRequestTest.php 
b/tests/AntiSpoofAuthenticationRequestTest.php
new file mode 100644
index 0000000..a485538
--- /dev/null
+++ b/tests/AntiSpoofAuthenticationRequestTest.php
@@ -0,0 +1,17 @@
+<?php
+
+use MediaWiki\Auth\AuthenticationRequestTestCase;
+
+class AntiSpoofAuthenticationRequestTest extends AuthenticationRequestTestCase 
{
+
+       protected function getInstance( array $args = [] ) {
+               return new AntiSpoofAuthenticationRequest();
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'empty' => [ [], [], [ 'ignoreAntiSpoof' => false ] ],
+                       'true' => [ [], [ 'ignoreAntiSpoof' => '1' ], [ 
'ignoreAntiSpoof' => true ] ],
+               ];
+       }
+}
diff --git a/tests/AntiSpoofPreAuthenticationProviderTest.php 
b/tests/AntiSpoofPreAuthenticationProviderTest.php
new file mode 100644
index 0000000..abac19a
--- /dev/null
+++ b/tests/AntiSpoofPreAuthenticationProviderTest.php
@@ -0,0 +1,97 @@
+<?php
+
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * @group Database
+ */
+class AntiSpoofPreAuthenticationProviderTest extends MediaWikiTestCase {
+       public function setUp() {
+               global $wgDisableAuthManager;
+               if ( !class_exists( AuthManager::class ) || 
$wgDisableAuthManager ) {
+                       $this->markTestSkipped( 'AuthManager is disabled' );
+               }
+
+               parent::setUp();
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        */
+       public function testGetAuthenticationRequests( $action, $params, 
$username, $expectedReqs ) {
+               $this->setMwGlobals( 'wgAntiSpoofAccounts', false );
+               $provider = new AntiSpoofPreAuthenticationProvider( $params );
+               $provider->setManager( AuthManager::singleton() );
+               $reqs = $provider->getAuthenticationRequests( $action, [ 
'username' => $username ] );
+               $this->assertEquals( $expectedReqs, $reqs );
+       }
+
+       public function provideGetAuthenticationRequests() {
+               return [
+                       [ AuthManager::ACTION_LOGIN, [], null, [] ],
+                       [ AuthManager::ACTION_CREATE, [],  null, [] ],
+                       [ AuthManager::ACTION_CREATE, [ 'antiSpoofAccounts' => 
true ],  null, [] ],
+                       [ AuthManager::ACTION_CREATE, [], 'UTSysop', [] ],
+                       [ AuthManager::ACTION_CREATE, [ 'antiSpoofAccounts' => 
true ], 'UTSysop',
+                               [ new AntiSpoofAuthenticationRequest() ] ],
+                       [ AuthManager::ACTION_CHANGE, [], null, [] ],
+                       [ AuthManager::ACTION_REMOVE, [], null, [] ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideTestForAccountCreation
+        */
+       public function testTestForAccountCreation(
+               $enabled, $isLegal, $conflicts,  $creator, $reqs, $error
+       ) {
+               $provider = $this->getMockBuilder( 
AntiSpoofPreAuthenticationProvider::class )
+                       ->setConstructorArgs( [ [ 'antiSpoofAccounts' => 
$enabled ] ] )
+                       ->setMethods( [ 'getSpoofUser' ] )->getMock();
+               $spoofUser = $this->getMockBuilder( SpoofUser::class )
+                       ->disableOriginalConstructor()->getMock();
+               $provider->expects( $this->any() )->method( 'getSpoofUser' 
)->willReturn( $spoofUser );
+               /** @var $provider \MediaWiki\Auth\PreAuthenticationProvider */
+               $provider->setManager( AuthManager::singleton() );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+
+               $spoofUser->expects( $this->any() )->method( 'isLegal' 
)->willReturn( $isLegal );
+               $spoofUser->expects( $this->any() )->method( 'getConflicts' 
)->willReturn( $conflicts );
+
+               /** @var StatusValue $status */
+               $status = $provider->testForAccountCreation( new User(), 
$creator, $reqs );
+
+               if ( $error ) {
+                       $this->assertFalse( $status->isGood() );
+                       $this->assertEquals( $error, Status::wrap( $status 
)->getMessage()->getKey() );
+               } else {
+                       $this->assertTrue( $status->isGood() );
+               }
+       }
+
+       public function provideTestForAccountCreation() {
+               $user = new User();
+               $sysop = User::newFromName( 'UTSysop' );
+               $noSkip = new AntiSpoofAuthenticationRequest();
+               $skip = new AntiSpoofAuthenticationRequest();
+               $skip->ignoreAntiSpoof = true;
+
+               return [
+                       // enabled, isLegal, conflicts,  creator, reqs, error
+                       'no spoofing' => [ true, true, [], $user, [], null ],
+                       'illegal' => [ true, false, [], $user, [], 
'antispoof-name-illegal' ],
+                       'illegal, inactve' => [ false, false, [], $user, [], 
null ],
+                       'illegal, sysop w/o skipping' => [ true, false, [], 
$sysop, [],
+                               'antispoof-name-illegal' ],
+                       'illegal, sysop w/o skipping #2' => [ true, false, [], 
$sysop, [ $noSkip ],
+                               'antispoof-name-illegal' ],
+                       'illegal, sysop skipping' => [ true, false, [], $sysop, 
[ $skip ], null ],
+                       // this should never happen but is good for layered 
defense
+                       'fake skipping' => [ true, false, [], $user, [ $skip ], 
'antispoof-name-illegal' ],
+                       'conflicts' => [ true, true, [ 'x' ], $user, [], 
'$1$2$3' ],
+                       'conflicts w/ skipping' => [ true, true, [ 'x' ], 
$sysop, [ $skip ], null ],
+                       'conflicts w/ fake skipping' => [ true, true, [ 'x' ], 
$user, [ $skip ], '$1$2$3' ],
+                       'illegal takes priority' => [ true, false, [ 'x' ], 
$user, [], 'antispoof-name-illegal' ],
+               ];
+       }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/287500
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: If71135d1cae428abb7e6375ccb8c371cba53d147
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/AntiSpoof
Gerrit-Branch: master
Gerrit-Owner: Gergő Tisza <gti...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to