Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
86.67% |
52 / 60 |
|
71.43% |
10 / 14 |
CRAP | |
0.00% |
0 / 1 |
SpoofUser | |
86.67% |
52 / 60 |
|
71.43% |
10 / 14 |
22.05 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
isLegal | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getErrorStatus | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getNormalized | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTableName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUserColumn | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getConflicts | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
record | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
insertFields | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
batchRecord | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
3.01 | |||
update | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
remove | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getDBReplica | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDBPrimary | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | */ |
18 | |
19 | namespace MediaWiki\Extension\AntiSpoof; |
20 | |
21 | use MediaWiki\MediaWikiServices; |
22 | use MediaWiki\Status\Status; |
23 | use Wikimedia\Rdbms\IDatabase; |
24 | use Wikimedia\Rdbms\IReadableDatabase; |
25 | |
26 | class SpoofUser { |
27 | /** @var bool */ |
28 | private $legal; |
29 | |
30 | /** @var string */ |
31 | private $name; |
32 | |
33 | /** @var string|null */ |
34 | private $normalized; |
35 | |
36 | /** @var null|Status */ |
37 | private $error; |
38 | |
39 | /** |
40 | * @param string $name |
41 | */ |
42 | public function __construct( $name ) { |
43 | $this->name = strval( $name ); |
44 | $status = AntiSpoof::checkUnicodeStringStatus( $this->name ); |
45 | $this->legal = $status->isOK(); |
46 | if ( $this->legal ) { |
47 | $this->normalized = $status->getValue(); |
48 | $this->error = null; |
49 | } else { |
50 | $this->normalized = null; |
51 | $this->error = $status; |
52 | } |
53 | } |
54 | |
55 | /** |
56 | * Does the username pass Unicode legality and script-mixing checks? |
57 | * @return bool |
58 | */ |
59 | public function isLegal() { |
60 | return $this->legal; |
61 | } |
62 | |
63 | /** |
64 | * Describe the error. |
65 | * @return null|Status |
66 | * @since 1.32 |
67 | */ |
68 | public function getErrorStatus() { |
69 | return $this->error; |
70 | } |
71 | |
72 | /** |
73 | * Get the normalized key form |
74 | * @return string|null |
75 | */ |
76 | public function getNormalized() { |
77 | return $this->normalized; |
78 | } |
79 | |
80 | /** |
81 | * @return string |
82 | */ |
83 | protected function getTableName() { |
84 | return 'user'; |
85 | } |
86 | |
87 | /** |
88 | * @return string |
89 | */ |
90 | protected function getUserColumn() { |
91 | return 'user_name'; |
92 | } |
93 | |
94 | /** |
95 | * Does the username pass Unicode legality and script-mixing checks? |
96 | * |
97 | * @return array empty if no conflict, or array containing conflicting usernames |
98 | */ |
99 | public function getConflicts() { |
100 | if ( !$this->isLegal() ) { |
101 | return []; |
102 | } |
103 | |
104 | $dbr = $this->getDBReplica(); |
105 | |
106 | // Join against the user table to ensure that we skip stray |
107 | // entries left after an account is renamed or otherwise munged. |
108 | $spoofedUsers = $dbr->newSelectQueryBuilder() |
109 | ->select( [ 'su_name' ] ) |
110 | ->from( 'spoofuser' ) |
111 | ->join( $this->getTableName(), null, 'su_name = ' . $this->getUserColumn() ) |
112 | ->where( [ 'su_normalized' => $this->normalized ] ) |
113 | ->limit( 5 ) |
114 | ->caller( __METHOD__ ) |
115 | ->fetchFieldValues(); |
116 | |
117 | return $spoofedUsers; |
118 | } |
119 | |
120 | /** |
121 | * Record the username's normalized form into the database |
122 | * for later comparison of future names... |
123 | * @return bool |
124 | */ |
125 | public function record() { |
126 | return self::batchRecord( $this->getDBPrimary(), [ $this ] ); |
127 | } |
128 | |
129 | /** |
130 | * @return array |
131 | */ |
132 | private function insertFields() { |
133 | return [ |
134 | 'su_name' => $this->name, |
135 | 'su_normalized' => $this->normalized, |
136 | 'su_legal' => $this->legal ? 1 : 0, |
137 | 'su_error' => $this->error ? $this->error->getMessage()->text() : null, |
138 | ]; |
139 | } |
140 | |
141 | /** |
142 | * Insert a batch of spoof normalization records into the database. |
143 | * @param IDatabase $dbw |
144 | * @param SpoofUser[] $items |
145 | * @return bool |
146 | */ |
147 | public static function batchRecord( IDatabase $dbw, $items ) { |
148 | if ( !count( $items ) ) { |
149 | return false; |
150 | } |
151 | |
152 | $rqb = $dbw->newReplaceQueryBuilder() |
153 | ->replaceInto( 'spoofuser' ); |
154 | /** |
155 | * @var $item SpoofUser |
156 | */ |
157 | foreach ( $items as $item ) { |
158 | $rqb->row( $item->insertFields() ); |
159 | } |
160 | $rqb->uniqueIndexFields( 'su_name' ) |
161 | ->caller( __METHOD__ )->execute(); |
162 | return true; |
163 | } |
164 | |
165 | /** |
166 | * @param string $oldName |
167 | */ |
168 | public function update( $oldName ) { |
169 | $method = __METHOD__; |
170 | $dbw = $this->getDBPrimary(); |
171 | // Avoid user rename triggered deadlocks |
172 | $dbw->onTransactionPreCommitOrIdle( |
173 | function () use ( $dbw, $method, $oldName ) { |
174 | if ( $this->record() ) { |
175 | $dbw->newDeleteQueryBuilder() |
176 | ->deleteFrom( 'spoofuser' ) |
177 | ->where( [ 'su_name' => $oldName ] ) |
178 | ->caller( $method )->execute(); |
179 | } |
180 | }, |
181 | $method |
182 | ); |
183 | } |
184 | |
185 | /** |
186 | * Remove a user from the spoofuser table |
187 | */ |
188 | public function remove() { |
189 | $this->getDBPrimary() |
190 | ->newDeleteQueryBuilder() |
191 | ->deleteFrom( 'spoofuser' ) |
192 | ->where( [ 'su_name' => $this->name ] ) |
193 | ->caller( __METHOD__ )->execute(); |
194 | } |
195 | |
196 | /** |
197 | * Allows to override database connection in sub classes. |
198 | * @return IReadableDatabase |
199 | */ |
200 | protected function getDBReplica() { |
201 | return MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
202 | } |
203 | |
204 | /** |
205 | * Allows to override database connection in sub classes. |
206 | * @return IDatabase |
207 | */ |
208 | protected function getDBPrimary() { |
209 | return MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase(); |
210 | } |
211 | } |