Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.87% covered (warning)
73.87%
82 / 111
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ResetAuthenticationThrottle
73.87% covered (warning)
73.87%
82 / 111
50.00% covered (danger)
50.00%
3 / 6
41.98
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 execute
96.88% covered (success)
96.88%
31 / 32
0.00% covered (danger)
0.00%
0 / 1
15
 clearLoginThrottle
54.17% covered (warning)
54.17%
13 / 24
0.00% covered (danger)
0.00%
0 / 1
7.41
 clearSignupThrottle
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 clearTempAccountCreationThrottle
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 clearTempAccountNameAcquisitionThrottle
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Reset login/signup throttling for a specified user and/or IP.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Maintenance
22 */
23
24use MediaWiki\Auth\Throttler;
25use MediaWiki\Logger\LoggerFactory;
26use MediaWiki\MainConfigNames;
27use MediaWiki\Maintenance\Maintenance;
28use Wikimedia\IPUtils;
29
30// @codeCoverageIgnoreStart
31require_once __DIR__ . '/Maintenance.php';
32// @codeCoverageIgnoreEnd
33
34/**
35 * Reset login/signup throttling for a specified user and/or IP.
36 *
37 * @ingroup Maintenance
38 * @since 1.32
39 */
40class ResetAuthenticationThrottle extends Maintenance {
41
42    public function __construct() {
43        parent::__construct();
44        $this->addDescription( 'Reset login/signup throttling for a specified user and/or IP. '
45            . "\n\n"
46            . 'When resetting signup or temp account, provide the IP. When resetting login (or both), provide '
47            . 'both username (as entered in login screen) and IP. An easy way to obtain them is '
48            . "the 'throttler' log channel." );
49        $this->addOption( 'login', 'Reset login throttle' );
50        $this->addOption( 'signup', 'Reset account creation throttle' );
51        $this->addOption( 'tempaccount', 'Reset temp account creation throttle' );
52        $this->addOption( 'tempaccountnameacquisition', 'Reset temp account name acquisition throttle' );
53        $this->addOption( 'user', 'Username to reset (when using --login)', false, true );
54        $this->addOption( 'ip', 'IP to reset', false, true );
55    }
56
57    public function execute() {
58        $forLogin = (bool)$this->getOption( 'login' );
59        $forSignup = (bool)$this->getOption( 'signup' );
60        $forTempAccount = (bool)$this->getOption( 'tempaccount' );
61        $forTempAccountNameAcquisition = (bool)$this->getOption( 'tempaccountnameacquisition' );
62        $username = $this->getOption( 'user' );
63        $ip = $this->getOption( 'ip' );
64
65        if ( !$forLogin && !$forSignup && !$forTempAccount && !$forTempAccountNameAcquisition ) {
66            $this->fatalError(
67                'At least one of --login, --signup, --tempaccount, or --tempaccountnameacquisition is required!'
68            );
69        } elseif ( $ip === null ) {
70            $this->fatalError( '--ip is required!' );
71        } elseif ( !IPUtils::isValid( $ip ) ) {
72            $this->fatalError( "Not a valid IP: $ip" );
73        }
74
75        if ( $forLogin ) {
76            $this->clearLoginThrottle( $username, $ip );
77        }
78        if ( $forSignup ) {
79            $this->clearSignupThrottle( $ip );
80        }
81        if ( $forTempAccount ) {
82            $this->clearTempAccountCreationThrottle( $ip );
83        }
84        if ( $forTempAccountNameAcquisition ) {
85            $this->clearTempAccountNameAcquisitionThrottle( $ip );
86        }
87
88        LoggerFactory::getInstance( 'throttler' )->info( 'Manually cleared {type} throttle', [
89            'type' => implode( ' and ', array_filter( [
90                $forLogin ? 'login' : null,
91                $forSignup ? 'signup' : null,
92                $forTempAccount ? 'tempaccount' : null,
93                $forTempAccountNameAcquisition ? 'tempaccountnameacquisition' : null,
94            ] ) ),
95            'username' => $username,
96            'ipKey' => $ip,
97        ] );
98    }
99
100    /**
101     * @param string|null $rawUsername
102     * @param string|null $ip
103     */
104    protected function clearLoginThrottle( $rawUsername, $ip ) {
105        $this->output( 'Clearing login throttle...' );
106
107        $passwordAttemptThrottle = $this->getConfig()->get( MainConfigNames::PasswordAttemptThrottle );
108        if ( !$passwordAttemptThrottle ) {
109            $this->output( "none set\n" );
110            return;
111        }
112
113        $objectCacheFactory = $this->getServiceContainer()->getInstance()->getObjectCacheFactory();
114
115        $throttler = new Throttler( $passwordAttemptThrottle, [
116            'type' => 'password',
117            'cache' => $objectCacheFactory->getLocalClusterInstance(),
118        ] );
119        if ( $rawUsername !== null ) {
120            $usernames = $this->getServiceContainer()->getAuthManager()
121                ->normalizeUsername( $rawUsername );
122            if ( !$usernames ) {
123                $this->fatalError( "Not a valid username: $rawUsername" );
124            }
125        } else {
126            $usernames = [ null ];
127        }
128        foreach ( $usernames as $username ) {
129            $throttler->clear( $username, $ip );
130        }
131
132        $botPasswordThrottler = new Throttler( $passwordAttemptThrottle, [
133            'type' => 'botpassword',
134            'cache' => $objectCacheFactory->getLocalClusterInstance(),
135        ] );
136        // @phan-suppress-next-line PhanPossiblyUndeclaredVariable T240141
137        $botPasswordThrottler->clear( $username, $ip );
138
139        $this->output( "done\n" );
140    }
141
142    /**
143     * @param string $ip
144     */
145    protected function clearSignupThrottle( $ip ) {
146        $this->output( 'Clearing signup throttle...' );
147
148        $accountCreationThrottle = $this->getConfig()->get( MainConfigNames::AccountCreationThrottle );
149        if ( !is_array( $accountCreationThrottle ) ) {
150            $accountCreationThrottle = [ [
151                'count' => $accountCreationThrottle,
152                'seconds' => 86400,
153            ] ];
154        }
155        if ( !$accountCreationThrottle ) {
156            $this->output( "none set\n" );
157            return;
158        }
159        $throttler = new Throttler( $accountCreationThrottle, [
160            'type' => 'acctcreate',
161            'cache' => $this->getServiceContainer()->getObjectCacheFactory()
162                ->getLocalClusterInstance(),
163        ] );
164
165        $throttler->clear( null, $ip );
166
167        $this->output( "done\n" );
168    }
169
170    protected function clearTempAccountCreationThrottle( string $ip ): void {
171        $this->output( 'Clearing temp account creation throttle...' );
172
173        $tempAccountCreationThrottle = $this->getConfig()->get( MainConfigNames::TempAccountCreationThrottle );
174        if ( !$tempAccountCreationThrottle ) {
175            $this->output( "none set\n" );
176            return;
177        }
178        $throttler = new Throttler( $tempAccountCreationThrottle, [
179            'type' => 'tempacctcreate',
180            'cache' => $this->getServiceContainer()->getObjectCacheFactory()
181                ->getLocalClusterInstance(),
182        ] );
183
184        $throttler->clear( null, $ip );
185
186        $this->output( "done\n" );
187    }
188
189    protected function clearTempAccountNameAcquisitionThrottle( string $ip ): void {
190        $this->output( 'Clearing temp account name acquisition throttle...' );
191
192        $tempAccountNameAcquisitionThrottle = $this->getConfig()->get(
193            MainConfigNames::TempAccountNameAcquisitionThrottle
194        );
195        if ( !$tempAccountNameAcquisitionThrottle ) {
196            $this->output( "none set\n" );
197            return;
198        }
199        $throttler = new Throttler( $tempAccountNameAcquisitionThrottle, [
200            'type' => 'tempacctnameacquisition',
201            'cache' => $this->getServiceContainer()->getObjectCacheFactory()
202                ->getLocalClusterInstance(),
203        ] );
204
205        $throttler->clear( null, $ip );
206
207        $this->output( "done\n" );
208    }
209
210}
211
212// @codeCoverageIgnoreStart
213$maintClass = ResetAuthenticationThrottle::class;
214require_once RUN_MAINTENANCE_IF_MAIN;
215// @codeCoverageIgnoreEnd