Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 91 |
|
0.00% |
0 / 2 |
CRAP | |
0.00% |
0 / 1 |
WrapOldPasswordHashes | |
0.00% |
0 / 85 |
|
0.00% |
0 / 2 |
210 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 76 |
|
0.00% |
0 / 1 |
182 |
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 | * @file |
19 | * @ingroup Maintenance |
20 | */ |
21 | |
22 | namespace MediaWiki\Extension\CentralAuth\Maintenance; |
23 | |
24 | use MediaWiki\Extension\CentralAuth\CentralAuthServices; |
25 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
26 | use MediaWiki\Maintenance\Maintenance; |
27 | use MediaWiki\Password\LayeredParameterizedPassword; |
28 | use MediaWiki\Password\ParameterizedPassword; |
29 | use Wikimedia\Rdbms\IExpression; |
30 | use Wikimedia\Rdbms\LikeValue; |
31 | |
32 | $IP = getenv( 'MW_INSTALL_PATH' ); |
33 | if ( $IP === false ) { |
34 | $IP = __DIR__ . '/../../..'; |
35 | } |
36 | require_once "$IP/maintenance/Maintenance.php"; |
37 | |
38 | /** |
39 | * CentralAuth version of WrapOldPasswords |
40 | */ |
41 | class WrapOldPasswordHashes extends Maintenance { |
42 | |
43 | public function __construct() { |
44 | parent::__construct(); |
45 | $this->addDescription( 'Wrap all passwords of a certain type in a new layered type. ' |
46 | . 'The script runs in dry-run mode by default (use --update to update rows)' ); |
47 | $this->addOption( 'type', |
48 | 'Password type to wrap passwords in (must inherit LayeredParameterizedPassword)', true, true ); |
49 | $this->addOption( 'verbose', 'Enables verbose output', false, false, 'v' ); |
50 | $this->addOption( 'update', 'Actually wrap passwords', false, false, 'u' ); |
51 | $this->setBatchSize( 3 ); |
52 | $this->requireExtension( 'CentralAuth' ); |
53 | } |
54 | |
55 | public function execute() { |
56 | $passwordFactory = $this->getServiceContainer()->getPasswordFactory(); |
57 | |
58 | $typeInfo = $passwordFactory->getTypes(); |
59 | $layeredType = $this->getOption( 'type' ); |
60 | |
61 | // Check that type exists and is a layered type |
62 | if ( !isset( $typeInfo[$layeredType] ) ) { |
63 | $this->fatalError( 'Undefined password type: ' . $layeredType ); |
64 | } |
65 | |
66 | $passObj = $passwordFactory->newFromType( $layeredType ); |
67 | if ( !$passObj instanceof LayeredParameterizedPassword ) { |
68 | $this->fatalError( 'Layered parameterized password type must be used.' ); |
69 | } |
70 | |
71 | // Extract the first layer type |
72 | $typeConfig = $typeInfo[$layeredType]; |
73 | $firstType = $typeConfig['types'][0]; |
74 | |
75 | $update = $this->hasOption( 'update' ); |
76 | |
77 | $databaseManager = CentralAuthServices::getDatabaseManager(); |
78 | |
79 | // Get a list of password types that are applicable |
80 | $dbw = $databaseManager->getCentralPrimaryDB(); |
81 | $typeCond = $dbw->expr( 'gu_password', IExpression::LIKE, new LikeValue( ":$firstType:", $dbw->anyString() ) ); |
82 | $batchSize = $this->getBatchSize(); |
83 | |
84 | $count = 0; |
85 | $minUserId = 0; |
86 | while ( true ) { |
87 | if ( $update ) { |
88 | $this->beginTransaction( $dbw, __METHOD__ ); |
89 | } |
90 | $start = microtime( true ); |
91 | $res = $dbw->newSelectQueryBuilder() |
92 | ->select( [ 'gu_id', 'gu_name', 'gu_password' ] ) |
93 | ->from( 'globaluser' ) |
94 | ->where( [ |
95 | $dbw->expr( 'gu_id', '>', $minUserId ), |
96 | $typeCond |
97 | ] ) |
98 | ->orderBy( 'gu_id' ) |
99 | ->limit( $batchSize ) |
100 | ->lockInShareMode() |
101 | ->caller( __METHOD__ ) |
102 | ->fetchResultSet(); |
103 | |
104 | if ( $res->numRows() === 0 ) { |
105 | if ( $update ) { |
106 | $this->commitTransaction( $dbw, __METHOD__ ); |
107 | } |
108 | break; |
109 | } |
110 | |
111 | /** @var CentralAuthUser[] $updateUsers */ |
112 | $updateUsers = []; |
113 | foreach ( $res as $row ) { |
114 | $user = CentralAuthUser::getPrimaryInstanceByName( $row->gu_name ); |
115 | |
116 | /** @var ParameterizedPassword $password */ |
117 | $password = $passwordFactory->newFromCiphertext( $row->gu_password ); |
118 | '@phan-var ParameterizedPassword $password'; |
119 | /** @var LayeredParameterizedPassword $layeredPassword */ |
120 | $layeredPassword = $passwordFactory->newFromType( $layeredType ); |
121 | '@phan-var LayeredParameterizedPassword $layeredPassword'; |
122 | $layeredPassword->partialCrypt( $password ); |
123 | if ( $this->hasOption( 'verbose' ) ) { |
124 | $this->output( |
125 | "Updating password for user {$row->gu_name} ({$row->gu_id}) from " . |
126 | "type {$password->getType()} to {$layeredPassword->getType()}.\n" |
127 | ); |
128 | } |
129 | |
130 | if ( $update ) { |
131 | $count++; |
132 | $updateUsers[] = $user; |
133 | $dbw->newUpdateQueryBuilder() |
134 | ->update( 'globaluser' ) |
135 | ->set( [ 'gu_password' => $layeredPassword->toString() ] ) |
136 | ->where( [ 'gu_id' => $row->gu_id ] ) |
137 | ->caller( __METHOD__ ) |
138 | ->execute(); |
139 | } |
140 | |
141 | $minUserId = $row->gu_id; |
142 | } |
143 | |
144 | if ( $update ) { |
145 | $this->commitTransaction( $dbw, __METHOD__ ); |
146 | |
147 | // Clear memcached so old passwords are wiped out |
148 | foreach ( $updateUsers as $user ) { |
149 | $user->invalidateCache(); |
150 | } |
151 | } |
152 | |
153 | $this->output( "Last id processed: $minUserId; Actually updated: $count...\n" ); |
154 | $delta = microtime( true ) - $start; |
155 | $this->output( sprintf( |
156 | "%4d passwords wrapped in %6.2fms (%6.2fms each)\n", |
157 | $res->numRows(), |
158 | $delta * 1000.0, |
159 | ( $delta / $res->numRows() ) * 1000.0 |
160 | ) ); |
161 | } |
162 | |
163 | if ( $update ) { |
164 | $this->output( "$count users rows updated.\n" ); |
165 | } else { |
166 | $this->output( "$count user rows found using old password formats. " |
167 | . "Run script again with --update to update these rows.\n" ); |
168 | } |
169 | } |
170 | } |
171 | |
172 | $maintClass = WrapOldPasswordHashes::class; |
173 | require_once RUN_MAINTENANCE_IF_MAIN; |