Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 120
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ResetAuthenticationThrottle
0.00% covered (danger)
0.00%
0 / 117
0.00% covered (danger)
0.00%
0 / 6
1406
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
600
 clearLoginThrottle
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
30
 clearSignupThrottle
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 clearTempAccountCreationThrottle
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 clearTempAccountNameAcquisitionThrottle
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
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\MediaWikiServices;
28use Wikimedia\IPUtils;
29
30require_once __DIR__ . '/Maintenance.php';
31
32/**
33 * Reset login/signup throttling for a specified user and/or IP.
34 *
35 * @ingroup Maintenance
36 * @since 1.32
37 */
38class ResetAuthenticationThrottle extends Maintenance {
39
40    public function __construct() {
41        parent::__construct();
42        $this->addDescription( 'Reset login/signup throttling for a specified user and/or IP. '
43            . "\n\n"
44            . 'When resetting signup or temp account, provide the IP. When resetting login (or both), provide '
45            . 'both username (as entered in login screen) and IP. An easy way to obtain them is '
46            . "the 'throttler' log channel." );
47        $this->addOption( 'login', 'Reset login throttle' );
48        $this->addOption( 'signup', 'Reset account creation throttle' );
49        $this->addOption( 'tempaccount', 'Reset temp account creation throttle' );
50        $this->addOption( 'tempaccountnameacquisition', 'Reset temp account name acquisition throttle' );
51        $this->addOption( 'user', 'Username to reset', false, true );
52        $this->addOption( 'ip', 'IP to reset', false, true );
53    }
54
55    public function execute() {
56        $forLogin = (bool)$this->getOption( 'login' );
57        $forSignup = (bool)$this->getOption( 'signup' );
58        $forTempAccount = (bool)$this->getOption( 'tempaccount' );
59        $forTempAccountNameAcquisition = (bool)$this->getOption( 'tempaccountnameacquisition' );
60        $username = $this->getOption( 'user' );
61        $ip = $this->getOption( 'ip' );
62
63        if ( !$forLogin && !$forSignup && !$forTempAccount && !$forTempAccountNameAcquisition ) {
64            $this->fatalError(
65                'At least one of --login, --signup, --tempaccount, or --tempaccountnameacquisition is required!'
66            );
67        } elseif ( $forLogin && ( $ip === null || $username === null ) ) {
68            $this->fatalError( '--user and --ip are both required when using --login!' );
69        } elseif ( $forSignup && $ip === null ) {
70            $this->fatalError( '--ip is required when using --signup!' );
71        } elseif ( $forTempAccount && $ip === null ) {
72            $this->fatalError( '--ip is required when using --tempaccount!' );
73        } elseif ( $forTempAccountNameAcquisition && $ip === null ) {
74            $this->fatalError( '--ip is required when using --tempaccountnameacquisition!' );
75        } elseif ( $ip !== null && !IPUtils::isValid( $ip ) ) {
76            $this->fatalError( "Not a valid IP: $ip" );
77        }
78
79        if ( $forLogin ) {
80            $this->clearLoginThrottle( $username, $ip );
81        }
82        if ( $forSignup ) {
83            $this->clearSignupThrottle( $ip );
84        }
85        if ( $forTempAccount ) {
86            $this->clearTempAccountCreationThrottle( $ip );
87        }
88        if ( $forTempAccountNameAcquisition ) {
89            $this->clearTempAccountNameAcquisitionThrottle( $ip );
90        }
91
92        LoggerFactory::getInstance( 'throttler' )->info( 'Manually cleared {type} throttle', [
93            'type' => implode( ' and ', array_filter( [
94                $forLogin ? 'login' : null,
95                $forSignup ? 'signup' : null,
96                $forTempAccount ? 'tempaccount' : null,
97                $forTempAccountNameAcquisition ? 'tempaccountnameacquisition' : null,
98            ] ) ),
99            'username' => $username,
100            'ipKey' => $ip,
101        ] );
102    }
103
104    /**
105     * @param string|null $rawUsername
106     * @param string|null $ip
107     */
108    protected function clearLoginThrottle( $rawUsername, $ip ) {
109        $this->output( 'Clearing login throttle...' );
110
111        $passwordAttemptThrottle = $this->getConfig()->get( MainConfigNames::PasswordAttemptThrottle );
112        if ( !$passwordAttemptThrottle ) {
113            $this->output( "none set\n" );
114            return;
115        }
116
117        $objectCacheFactory = $this->getServiceContainer()->getInstance()->getObjectCacheFactory();
118
119        $throttler = new Throttler( $passwordAttemptThrottle, [
120            'type' => 'password',
121            'cache' => $objectCacheFactory->getLocalClusterInstance(),
122        ] );
123        if ( $rawUsername !== null ) {
124            $usernames = $this->getServiceContainer()->getAuthManager()
125                ->normalizeUsername( $rawUsername );
126            if ( !$usernames ) {
127                $this->fatalError( "Not a valid username: $rawUsername" );
128            }
129        } else {
130            $usernames = [ null ];
131        }
132        foreach ( $usernames as $username ) {
133            $throttler->clear( $username, $ip );
134        }
135
136        $botPasswordThrottler = new Throttler( $passwordAttemptThrottle, [
137            'type' => 'botpassword',
138            'cache' => $objectCacheFactory->getLocalClusterInstance(),
139        ] );
140        // @phan-suppress-next-line PhanPossiblyUndeclaredVariable T240141
141        $botPasswordThrottler->clear( $username, $ip );
142
143        $this->output( "done\n" );
144    }
145
146    /**
147     * @param string $ip
148     */
149    protected function clearSignupThrottle( $ip ) {
150        $this->output( 'Clearing signup throttle...' );
151
152        $accountCreationThrottle = $this->getConfig()->get( MainConfigNames::AccountCreationThrottle );
153        if ( !is_array( $accountCreationThrottle ) ) {
154            $accountCreationThrottle = [ [
155                'count' => $accountCreationThrottle,
156                'seconds' => 86400,
157            ] ];
158        }
159        if ( !$accountCreationThrottle ) {
160            $this->output( "none set\n" );
161            return;
162        }
163        $throttler = new Throttler( $accountCreationThrottle, [
164            'type' => 'acctcreate',
165            'cache' => MediaWikiServices::getInstance()->getObjectCacheFactory()
166                ->getLocalClusterInstance(),
167        ] );
168
169        $throttler->clear( null, $ip );
170
171        $this->output( "done\n" );
172    }
173
174    protected function clearTempAccountCreationThrottle( string $ip ): void {
175        $this->output( 'Clearing temp account creation throttle...' );
176
177        $tempAccountCreationThrottle = $this->getConfig()->get( MainConfigNames::TempAccountCreationThrottle );
178        if ( !$tempAccountCreationThrottle ) {
179            $this->output( "none set\n" );
180            return;
181        }
182        $throttler = new Throttler( $tempAccountCreationThrottle, [
183            'type' => 'tempacctcreate',
184            'cache' => MediaWikiServices::getInstance()->getObjectCacheFactory()
185                ->getLocalClusterInstance(),
186        ] );
187
188        $throttler->clear( null, $ip );
189
190        $this->output( "done\n" );
191    }
192
193    protected function clearTempAccountNameAcquisitionThrottle( string $ip ): void {
194        $this->output( 'Clearing temp account name acquisition throttle...' );
195
196        $tempAccountNameAcquisitionThrottle = $this->getConfig()->get(
197            MainConfigNames::TempAccountNameAcquisitionThrottle
198        );
199        if ( !$tempAccountNameAcquisitionThrottle ) {
200            $this->output( "none set\n" );
201            return;
202        }
203        $throttler = new Throttler( $tempAccountNameAcquisitionThrottle, [
204            'type' => 'tempacctnameacquisition',
205            'cache' => MediaWikiServices::getInstance()->getObjectCacheFactory()
206                ->getLocalClusterInstance(),
207        ] );
208
209        $throttler->clear( null, $ip );
210
211        $this->output( "done\n" );
212    }
213
214}
215
216$maintClass = ResetAuthenticationThrottle::class;
217require_once RUN_MAINTENANCE_IF_MAIN;