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