Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 130 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
CheckLocalUser | |
0.00% |
0 / 124 |
|
0.00% |
0 / 6 |
650 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
2 | |||
initialize | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
execute | |
0.00% |
0 / 48 |
|
0.00% |
0 / 1 |
110 | |||
report | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getWikis | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
getUsers | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CentralAuth\Maintenance; |
4 | |
5 | use Generator; |
6 | use MediaWiki\Extension\CentralAuth\CentralAuthServices; |
7 | use MediaWiki\Maintenance\Maintenance; |
8 | use MediaWiki\WikiMap\WikiMap; |
9 | use Wikimedia\Rdbms\SelectQueryBuilder; |
10 | |
11 | $IP = getenv( 'MW_INSTALL_PATH' ); |
12 | if ( $IP === false ) { |
13 | $IP = __DIR__ . '/../../..'; |
14 | } |
15 | require_once "$IP/maintenance/Maintenance.php"; |
16 | |
17 | class CheckLocalUser extends Maintenance { |
18 | |
19 | /** @var float */ |
20 | protected $start; |
21 | |
22 | /** @var int */ |
23 | protected $deleted; |
24 | |
25 | /** @var int */ |
26 | protected $total; |
27 | |
28 | /** @var bool */ |
29 | protected $dryrun; |
30 | |
31 | /** @var string|null */ |
32 | protected $wiki; |
33 | |
34 | /** @var string|null|false */ |
35 | protected $user; |
36 | |
37 | /** @var bool */ |
38 | protected $verbose; |
39 | |
40 | public function __construct() { |
41 | parent::__construct(); |
42 | $this->requireExtension( 'CentralAuth' ); |
43 | $this->addDescription( 'Checks the contents of the localuser table and deletes invalid entries' ); |
44 | $this->start = microtime( true ); |
45 | $this->deleted = 0; |
46 | $this->total = 0; |
47 | $this->dryrun = true; |
48 | $this->wiki = null; |
49 | $this->user = null; |
50 | $this->verbose = false; |
51 | |
52 | $this->addOption( 'delete', |
53 | 'Performs delete operations on the offending entries', false, false |
54 | ); |
55 | $this->addOption( 'delete-nowiki', |
56 | 'Delete entries associated with invalid wikis', false, false |
57 | ); |
58 | $this->addOption( 'wiki', |
59 | 'If specified, only runs against local names from this wiki', false, true, 'u' |
60 | ); |
61 | $this->addOption( 'allwikis', 'If specified, checks all wikis', false, false ); |
62 | $this->addOption( 'user', 'If specified, only checks the given user', false, true ); |
63 | $this->addOption( 'verbose', 'Prints more information', false, true, 'v' ); |
64 | $this->setBatchSize( 1000 ); |
65 | } |
66 | |
67 | protected function initialize() { |
68 | if ( $this->getOption( 'delete', false ) !== false ) { |
69 | $this->dryrun = false; |
70 | } |
71 | |
72 | $wiki = $this->getOption( 'wiki', false ); |
73 | if ( $wiki !== false && !$this->getOption( 'allwikis' ) ) { |
74 | $this->wiki = $wiki; |
75 | } |
76 | |
77 | $user = $this->getOption( 'user', false ); |
78 | if ( $user !== false ) { |
79 | $userNameUtils = $this->getServiceContainer()->getUserNameUtils(); |
80 | $this->user = $userNameUtils->getCanonical( $user ); |
81 | } |
82 | |
83 | if ( $this->getOption( 'verbose', false ) !== false ) { |
84 | $this->verbose = true; |
85 | } |
86 | } |
87 | |
88 | /** |
89 | * @throws \MediaWiki\Extension\CentralAuth\CentralAuthReadOnlyError |
90 | */ |
91 | public function execute() { |
92 | $this->initialize(); |
93 | |
94 | $centralPrimaryDb = CentralAuthServices::getDatabaseManager()->getCentralPrimaryDB(); |
95 | |
96 | // since the keys on localnames are not conducive to batch operations and |
97 | // because of the database shards, grab a list of the wikis and we will |
98 | // iterate from there |
99 | foreach ( $this->getWikis() as $wiki ) { |
100 | $this->output( "Checking localuser for $wiki ...\n" ); |
101 | |
102 | if ( !WikiMap::getWiki( $wiki ) ) { |
103 | // localuser record is left over from some wiki that has been disabled |
104 | if ( !$this->dryrun ) { |
105 | if ( $this->getOption( 'delete-nowiki' ) ) { |
106 | $this->output( "$wiki does not exist, deleting entries...\n" ); |
107 | $conds = [ 'lu_wiki' => $wiki ]; |
108 | if ( $this->user ) { |
109 | $conds['lu_name'] = $this->user; |
110 | } |
111 | $centralPrimaryDb->newDeleteQueryBuilder() |
112 | ->deleteFrom( 'localuser' ) |
113 | ->where( $conds ) |
114 | ->caller( __METHOD__ ) |
115 | ->execute(); |
116 | $this->deleted++; |
117 | } else { |
118 | $this->output( |
119 | "$wiki does not exist, use --delete-nowiki to delete entries...\n" |
120 | ); |
121 | } |
122 | } else { |
123 | $this->output( "$wiki does not exist\n" ); |
124 | } |
125 | continue; |
126 | } |
127 | |
128 | $localdb = CentralAuthServices::getDatabaseManager()->getLocalDB( DB_REPLICA, $wiki ); |
129 | |
130 | // batch query local users from the wiki; iterate through and verify each one |
131 | foreach ( $this->getUsers( $wiki ) as $username ) { |
132 | $localUser = $localdb->newSelectQueryBuilder() |
133 | ->select( 'user_name' ) |
134 | ->from( 'user' ) |
135 | ->where( [ 'user_name' => $username ] ) |
136 | ->caller( __METHOD__ ) |
137 | ->fetchResultSet(); |
138 | |
139 | // check to see if the user did not exist in the local user table |
140 | if ( $localUser->numRows() == 0 ) { |
141 | if ( $this->verbose ) { |
142 | $this->output( |
143 | "Local user not found for localuser entry $username@$wiki\n" |
144 | ); |
145 | } |
146 | $this->total++; |
147 | if ( !$this->dryrun ) { |
148 | // go ahead and delete the extraneous entry |
149 | $centralPrimaryDb->newDeleteQueryBuilder() |
150 | ->deleteFrom( 'localuser' ) |
151 | ->where( [ |
152 | "lu_wiki" => $wiki, |
153 | "lu_name" => $username |
154 | ] ) |
155 | ->caller( __METHOD__ ) |
156 | ->execute(); |
157 | // TODO: is there anyway to check the success of the delete? |
158 | $this->deleted++; |
159 | } |
160 | } |
161 | } |
162 | } |
163 | |
164 | $this->report(); |
165 | $this->output( "done.\n" ); |
166 | } |
167 | |
168 | private function report() { |
169 | $this->output( sprintf( "%s found %d invalid localuser, %d (%.1f%%) deleted\n", |
170 | wfTimestamp( TS_DB ), |
171 | $this->total, |
172 | $this->deleted, |
173 | $this->total > 0 ? ( $this->deleted / $this->total * 100.0 ) : 0 |
174 | ) ); |
175 | } |
176 | |
177 | /** |
178 | * @return array|null[]|string[] |
179 | */ |
180 | protected function getWikis() { |
181 | $centralReplica = CentralAuthServices::getDatabaseManager()->getCentralReplicaDB(); |
182 | |
183 | if ( $this->wiki !== null ) { |
184 | return [ $this->wiki ]; |
185 | } else { |
186 | $conds = []; |
187 | if ( $this->user !== null ) { |
188 | $conds['lu_name'] = $this->user; |
189 | } |
190 | return $centralReplica->newSelectQueryBuilder() |
191 | ->select( 'lu_wiki' ) |
192 | ->distinct() |
193 | ->from( 'localuser' ) |
194 | ->where( $conds ) |
195 | ->orderBy( 'lu_wiki', SelectQueryBuilder::SORT_ASC ) |
196 | ->caller( __METHOD__ ) |
197 | ->fetchFieldValues(); |
198 | } |
199 | } |
200 | |
201 | /** |
202 | * @param string $wiki |
203 | * |
204 | * @return Generator |
205 | */ |
206 | protected function getUsers( $wiki ) { |
207 | if ( $this->user !== null ) { |
208 | $this->output( "\t ... querying '$this->user'\n" ); |
209 | yield $this->user; |
210 | return; |
211 | } |
212 | |
213 | $centralReplica = CentralAuthServices::getDatabaseManager()->getCentralReplicaDB(); |
214 | $lastUsername = ''; |
215 | do { |
216 | $this->output( "\t ... querying from '$lastUsername'\n" ); |
217 | $result = $centralReplica->newSelectQueryBuilder() |
218 | ->select( 'lu_name' ) |
219 | ->from( 'localuser' ) |
220 | ->where( [ |
221 | 'lu_wiki' => $wiki, |
222 | $centralReplica->expr( 'lu_name', '>', $lastUsername ), |
223 | ] ) |
224 | ->orderBy( 'lu_name', SelectQueryBuilder::SORT_ASC ) |
225 | ->limit( $this->mBatchSize ) |
226 | ->caller( __METHOD__ ) |
227 | ->fetchResultSet(); |
228 | |
229 | foreach ( $result as $u ) { |
230 | yield $u->lu_name; |
231 | } |
232 | |
233 | $lastUsername = $u->lu_name ?? null; |
234 | } while ( $result->numRows() > 0 ); |
235 | } |
236 | } |
237 | |
238 | $maintClass = CheckLocalUser::class; |
239 | require_once RUN_MAINTENANCE_IF_MAIN; |