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