MediaWiki REL1_33
Throttler.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Auth;
23
24use BagOStuff;
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}
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
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:2848
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition hooks.txt:783
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:782
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 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