MediaWiki REL1_32
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 'ipKey' => $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 {ipKey}';
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}
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:58
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:2885
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:815
you have access to all of the normal MediaWiki so you can get a DB use the cache
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
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 as and are nearing end of 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:36
$params