Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
66.67% covered (warning)
66.67%
4 / 6
CRAP
89.47% covered (warning)
89.47%
51 / 57
AntiSpoofPreAuthenticationProvider
0.00% covered (danger)
0.00%
0 / 1
66.67% covered (warning)
66.67%
4 / 6
24.67
89.47% covered (warning)
89.47%
51 / 57
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 getAuthenticationRequests
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
7 / 7
 testForAccountCreation
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
3 / 3
 testUserInternal
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
37 / 37
 testUserForCreation
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 5
 getSpoofUser
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
<?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
 */
namespace MediaWiki\Extension\AntiSpoof;
use Html;
use MediaWiki\Auth\AbstractPreAuthenticationProvider;
use MediaWiki\Auth\AuthenticationRequest;
use MediaWiki\Auth\AuthManager;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\User\UserIdentity;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use RawMessage;
use RequestContext;
use StatusValue;
use User;
class AntiSpoofPreAuthenticationProvider extends AbstractPreAuthenticationProvider {
    /** @var bool False effectively disables this provider, but spoofed names will still be logged. */
    protected $antiSpoofAccounts;
    /** @var PermissionManager */
    private $permissionManager;
    /**
     * @param PermissionManager $permissionManager
     * @param array $params Options:
     * - antiSpoofAccounts: (bool) stop spoofed accounts from being created. When false, only log.
     */
    public function __construct( PermissionManager $permissionManager, array $params = [] ) {
        global $wgAntiSpoofAccounts;
        $params += [ 'antiSpoofAccounts' => $wgAntiSpoofAccounts ];
        $this->antiSpoofAccounts = $params['antiSpoofAccounts'];
        $this->permissionManager = $permissionManager;
    }
    /** @inheritDoc */
    public function getAuthenticationRequests( $action, array $options ) {
        $needed = false;
        switch ( $action ) {
            case AuthManager::ACTION_CREATE:
                $user = User::newFromName( $options['username'] ) ?: new User();
                $needed = $this->antiSpoofAccounts
                    && $this->permissionManager->userHasAnyRight( $user, 'override-antispoof' );
                break;
        }
        return $needed ? [ new AntiSpoofAuthenticationRequest() ] : [];
    }
    /** @inheritDoc */
    public function testForAccountCreation( $user, $creator, array $reqs ) {
        /** @var AntiSpoofAuthenticationRequest $req */
        $req = AuthenticationRequest::getRequestByClass( $reqs, AntiSpoofAuthenticationRequest::class );
        $override = $req && $req->ignoreAntiSpoof && $creator->isAllowed( 'override-antispoof' );
        return $this->testUserInternal( $user, $override, $this->logger );
    }
    /**
     * @param UserIdentity $user
     * @param bool $override
     * @param LoggerInterface $logger
     * @return StatusValue
     */
    private function testUserInternal( UserIdentity $user, $override, LoggerInterface $logger ) {
        $spoofUser = $this->getSpoofUser( $user );
        $mode = !$this->antiSpoofAccounts ? 'LOGGING ' : ( $override ? 'OVERRIDE ' : '' );
        $active = $this->antiSpoofAccounts && !$override;
        if ( $spoofUser->isLegal() ) {
            $normalized = $spoofUser->getNormalized();
            $conflicts = $spoofUser->getConflicts();
            if ( empty( $conflicts ) ) {
                $logger->debug( "{mode}PASS new account '{name}' [{normalized}]", [
                    'mode' => $mode,
                    'name' => $user->getName(),
                    'normalized' => $normalized,
                ] );
            } else {
                $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 )->text()
                        );
                    }
                    $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->getErrorStatus();
            $logger->info( "{mode}ILLEGAL new account '{name}' {error}", [
                'mode' => $mode,
                'name' => $user->getName(),
                'error' => $error->getMessage( false, false, 'en' )->text(),
            ] );
            if ( $active ) {
                return StatusValue::newFatal( 'antispoof-name-illegal', $user->getName(),
                    $error->getMessage() );
            }
        }
        return StatusValue::newGood();
    }
    /** @inheritDoc */
    public function testUserForCreation( $user, $autocreate, array $options = [] ) {
        $sv = StatusValue::newGood();
        // For "cancreate" checks via the API, test if the current user could
        // create the username.
        if ( $this->antiSpoofAccounts && !$autocreate && empty( $options['creating'] ) &&
            !$this->permissionManager->userHasAnyRight( RequestContext::getMain()->getUser(), 'override-antispoof' )
        ) {
            $sv->merge( $this->testUserInternal( $user, false, new NullLogger ) );
        }
        return $sv;
    }
    /**
     * @param UserIdentity $user
     * @return SpoofUser
     */
    protected function getSpoofUser( UserIdentity $user ) {
        return new SpoofUser( $user->getName() );
    }
}