MediaWiki REL1_31
Throttler.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Auth;
23
24use BagOStuff;
27use Psr\Log\LoggerAwareInterface;
28use Psr\Log\LoggerInterface;
29use Psr\Log\LogLevel;
30
37class Throttler implements LoggerAwareInterface {
39 protected $type;
46 protected $conditions;
48 protected $cache;
50 protected $logger;
52 protected $warningLimit;
53
63 public function __construct( array $conditions = null, array $params = [] ) {
64 $invalidParams = array_diff_key( $params,
65 array_fill_keys( [ 'type', 'cache', 'warningLimit' ], true ) );
66 if ( $invalidParams ) {
67 throw new \InvalidArgumentException( 'unrecognized parameters: '
68 . implode( ', ', array_keys( $invalidParams ) ) );
69 }
70
71 if ( $conditions === null ) {
72 $config = MediaWikiServices::getInstance()->getMainConfig();
73 $conditions = $config->get( 'PasswordAttemptThrottle' );
74 $params += [
75 'type' => 'password',
76 'cache' => \ObjectCache::getLocalClusterInstance(),
77 'warningLimit' => 50,
78 ];
79 } else {
80 $params += [
81 'type' => 'custom',
82 'cache' => \ObjectCache::getLocalClusterInstance(),
83 'warningLimit' => INF,
84 ];
85 }
86
87 $this->type = $params['type'];
88 $this->conditions = static::normalizeThrottleConditions( $conditions );
89 $this->cache = $params['cache'];
90 $this->warningLimit = $params['warningLimit'];
91
92 $this->setLogger( LoggerFactory::getInstance( 'throttler' ) );
93 }
94
95 public function setLogger( LoggerInterface $logger ) {
96 $this->logger = $logger;
97 }
98
113 public function increase( $username = null, $ip = null, $caller = null ) {
114 if ( $username === null && $ip === null ) {
115 throw new \InvalidArgumentException( 'Either username or IP must be set for throttling' );
116 }
117
118 $userKey = $username ? md5( $username ) : null;
119 foreach ( $this->conditions as $index => $throttleCondition ) {
120 $ipKey = isset( $throttleCondition['allIPs'] ) ? null : $ip;
121 $count = $throttleCondition['count'];
122 $expiry = $throttleCondition['seconds'];
123
124 // a limit of 0 is used as a disable flag in some throttling configuration settings
125 // throttling the whole world is probably a bad idea
126 if ( !$count || $userKey === null && $ipKey === null ) {
127 continue;
128 }
129
130 $throttleKey = $this->cache->makeGlobalKey( 'throttler', $this->type, $index, $ipKey, $userKey );
131 $throttleCount = $this->cache->get( $throttleKey );
132
133 if ( !$throttleCount ) { // counter not started yet
134 $this->cache->add( $throttleKey, 1, $expiry );
135 } elseif ( $throttleCount < $count ) { // throttle limited not yet reached
136 $this->cache->incr( $throttleKey );
137 } else { // throttled
138 $this->logRejection( [
139 'throttle' => $this->type,
140 'index' => $index,
141 'ip' => $ipKey,
142 'username' => $username,
143 'count' => $count,
144 'expiry' => $expiry,
145 // @codeCoverageIgnoreStart
146 'method' => $caller ?: __METHOD__,
147 // @codeCoverageIgnoreEnd
148 ] );
149
150 return [
151 'throttleIndex' => $index,
152 'count' => $count,
153 'wait' => $expiry,
154 ];
155 }
156 }
157 return false;
158 }
159
169 public function clear( $username = null, $ip = null ) {
170 $userKey = $username ? md5( $username ) : null;
171 foreach ( $this->conditions as $index => $specificThrottle ) {
172 $ipKey = isset( $specificThrottle['allIPs'] ) ? null : $ip;
173 $throttleKey = $this->cache->makeGlobalKey( 'throttler', $this->type, $index, $ipKey, $userKey );
174 $this->cache->delete( $throttleKey );
175 }
176 }
177
184 protected static function normalizeThrottleConditions( $throttleConditions ) {
185 if ( !is_array( $throttleConditions ) ) {
186 return [];
187 }
188 if ( isset( $throttleConditions['count'] ) ) { // old style
189 $throttleConditions = [ $throttleConditions ];
190 }
191 return $throttleConditions;
192 }
193
194 protected function logRejection( array $context ) {
195 $logMsg = 'Throttle {throttle} hit, throttled for {expiry} seconds due to {count} attempts '
196 . 'from username {username} and IP {ip}';
197
198 // If we are hitting a throttle for >= warningLimit attempts, it is much more likely to be
199 // an attack than someone simply forgetting their password, so log it at a higher level.
200 $level = $context['count'] >= $this->warningLimit ? LogLevel::WARNING : LogLevel::INFO;
201
202 // It should be noted that once the throttle is hit, every attempt to login will
203 // generate the log message until the throttle expires, not just the attempt that
204 // puts the throttle over the top.
205 $this->logger->log( $level, $logMsg, $context );
206 }
207
208}
and give any other recipients of the Program a copy of this License along with the Program You may charge a fee for the physical act of transferring a and you may at your option offer warranty protection in exchange for a fee You may modify your copy or copies of the Program or any portion of thus forming a work based on the and copy and distribute such modifications or work under the terms of Section provided that you also meet all of these conditions
to add to the query
Definition COPYING.txt:98
interface is intended to be more or less compatible with the PHP memcached client.
Definition BagOStuff.php:47
increase( $username=null, $ip=null, $caller=null)
Increase the throttle counter and return whether the attempt should be throttled.
clear( $username=null, $ip=null)
Clear the throttle counter.
array $conditions
See documentation of $wgPasswordAttemptThrottle for format.
Definition Throttler.php:46
setLogger(LoggerInterface $logger)
Definition Throttler.php:95
__construct(array $conditions=null, array $params=[])
Definition Throttler.php:63
logRejection(array $context)
static normalizeThrottleConditions( $throttleConditions)
Handles B/C for $wgPasswordAttemptThrottle.
LoggerInterface $logger
Definition Throttler.php:50
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition hooks.txt:2811
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:785
you have access to all of the normal MediaWiki so you can get a DB use the cache
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
Definition postgres.txt:30
$params