Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CentralAuthAntiSpoofManager
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 4
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getSpoofUser
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 testNewAccount
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
110
 getOldRenamedUserName
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace MediaWiki\Extension\CentralAuth\User;
4
5use MediaWiki\Config\ServiceOptions;
6use MediaWiki\Extension\CentralAuth\CentralAuthDatabaseManager;
7use MediaWiki\Language\RawMessage;
8use MediaWiki\User\User;
9use Message;
10use Psr\Log\LoggerInterface;
11use StatusValue;
12use Wikimedia\Rdbms\IConnectionProvider;
13
14class CentralAuthAntiSpoofManager {
15    /** @internal Only public for service wiring use. */
16    public const CONSTRUCTOR_OPTIONS = [
17        'CentralAuthOldNameAntiSpoofWiki',
18    ];
19
20    private ServiceOptions $options;
21    private LoggerInterface $logger;
22    private IConnectionProvider $connectionProvider;
23    private CentralAuthDatabaseManager $databaseManager;
24
25    /**
26     * @param ServiceOptions $options
27     * @param LoggerInterface $logger
28     * @param IConnectionProvider $connectionProvider
29     * @param CentralAuthDatabaseManager $databaseManager
30     */
31    public function __construct(
32        ServiceOptions $options,
33        LoggerInterface $logger,
34        IConnectionProvider $connectionProvider,
35        CentralAuthDatabaseManager $databaseManager
36    ) {
37        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
38        $this->options = $options;
39        $this->logger = $logger;
40        $this->connectionProvider = $connectionProvider;
41        $this->databaseManager = $databaseManager;
42    }
43
44    /**
45     * @param string $name
46     * @return CentralAuthSpoofUser
47     */
48    public function getSpoofUser( string $name ): CentralAuthSpoofUser {
49        return new CentralAuthSpoofUser(
50            $name,
51            $this->databaseManager
52        );
53    }
54
55    /**
56     * Test if an account is acceptable
57     *
58     * @param User $user
59     * @param User $creator
60     * @param bool $enable
61     * @param bool $override
62     * @param LoggerInterface|null $logger
63     *
64     * @return StatusValue
65     */
66    public function testNewAccount( $user, $creator, $enable, $override, $logger = null ) {
67        $logger ??= $this->logger;
68
69        if ( !$enable ) {
70            $mode = 'LOGGING ';
71            $active = false;
72        } elseif ( $override && $creator->isAllowed( 'override-antispoof' ) ) {
73            $mode = 'OVERRIDE ';
74            $active = false;
75        } else {
76            $mode = '';
77            $active = true;
78        }
79
80        $name = $user->getName();
81        $spoof = $this->getSpoofUser( $name );
82        if ( $spoof->isLegal() ) {
83            $normalized = $spoof->getNormalized();
84            $conflicts = $spoof->getConflicts();
85            $oldUserName = $this->getOldRenamedUserName( $name );
86            if ( $oldUserName !== null ) {
87                $conflicts[] = $oldUserName;
88            }
89            if ( !$conflicts ) {
90                $logger->info( "{$mode}PASS new account '$name' [$normalized]" );
91            } else {
92                $logger->info( "{$mode}CONFLICT new account '$name' [$normalized] spoofs " .
93                    implode( ',', $conflicts ) );
94
95                if ( $active ) {
96                    $list = [];
97                    foreach ( $conflicts as $simUser ) {
98                        $list[] = "* " . wfMessage( 'antispoof-conflict-item', $simUser )->plain();
99                    }
100                    $list = implode( "\n", $list );
101
102                    return StatusValue::newFatal(
103                        'antispoof-conflict',
104                        $name,
105                        Message::numParam( count( $conflicts ) ),
106                        // Avoid forced wikitext escaping for params in the Status class
107                        new RawMessage( $list )
108                    );
109                }
110            }
111        } else {
112            $error = $spoof->getErrorStatus();
113            $logger->info( "{mode}ILLEGAL new account '{name}' {error}", [
114                'mode' => $mode,
115                'name' => $name,
116                'error' => $error->getMessage( false, false, 'en' )->text(),
117            ] );
118            if ( $active ) {
119                return StatusValue::newFatal( 'antispoof-name-illegal', $name, $error->getMessage() );
120            }
121        }
122        return StatusValue::newGood();
123    }
124
125    /**
126     * Given a username, find the old name
127     *
128     * @param string $name Name to lookup
129     *
130     * @return null|string Old username, or null
131     */
132    public function getOldRenamedUserName( $name ) {
133        $dbrLogWiki = $this->connectionProvider->getReplicaDatabase(
134            // If nobody has set this variable, it will be false,
135            // which will mean the current wiki, which sounds like as
136            // good a default as we can get.
137            $this->options->get( 'CentralAuthOldNameAntiSpoofWiki' )
138        );
139
140        $newNameOfUser = $dbrLogWiki->newSelectQueryBuilder()
141            ->select( 'log_title' )
142            ->from( 'logging' )
143            ->join( 'log_search', null, 'ls_log_id=log_id' )
144            ->where( [
145                'ls_field' => 'oldname',
146                'ls_value' => $name,
147                'log_type' => 'gblrename',
148                'log_namespace' => NS_SPECIAL
149            ] )
150            ->caller( __METHOD__ )
151            ->fetchField();
152        $slashPos = strpos( $newNameOfUser ?: '', '/' );
153        if ( $newNameOfUser && $slashPos ) {
154            // We have to remove the Special:CentralAuth prefix.
155            return substr( $newNameOfUser, $slashPos + 1 );
156        }
157        return null;
158    }
159}