28use Psr\Log\LoggerAwareInterface;
29use Psr\Log\LoggerInterface;
65 $invalidParams = array_diff_key( $params,
66 array_fill_keys( [
'type',
'cache',
'warningLimit' ],
true ) );
67 if ( $invalidParams ) {
68 throw new \InvalidArgumentException(
'unrecognized parameters: '
69 . implode(
', ', array_keys( $invalidParams ) ) );
77 'cache' => \ObjectCache::getLocalClusterInstance(),
83 'cache' => \ObjectCache::getLocalClusterInstance(),
84 'warningLimit' => INF,
88 $this->type = $params[
'type'];
89 $this->conditions = static::normalizeThrottleConditions(
$conditions );
90 $this->cache = $params[
'cache'];
91 $this->warningLimit = $params[
'warningLimit'];
93 $this->
setLogger( LoggerFactory::getInstance(
'throttler' ) );
114 public function increase( $username =
null, $ip =
null, $caller =
null ) {
115 if ( $username ===
null && $ip ===
null ) {
116 throw new \InvalidArgumentException(
'Either username or IP must be set for throttling' );
119 $userKey = $username ? md5( $username ) :
null;
120 foreach ( $this->conditions as $index => $throttleCondition ) {
121 $ipKey = isset( $throttleCondition[
'allIPs'] ) ? null : $ip;
122 $count = $throttleCondition[
'count'];
123 $expiry = $throttleCondition[
'seconds'];
127 if ( !$count || $userKey ===
null && $ipKey ===
null ) {
131 $throttleKey = $this->cache->makeGlobalKey(
138 $throttleCount = $this->cache->get( $throttleKey );
139 if ( $throttleCount && $throttleCount >= $count ) {
142 'throttle' => $this->type,
145 'username' => $username,
149 'method' => $caller ?: __METHOD__,
153 return [
'throttleIndex' => $index,
'count' => $count,
'wait' => $expiry ];
155 $this->cache->incrWithInit( $throttleKey, $expiry, 1 );
171 public function clear( $username =
null, $ip =
null ) {
172 $userKey = $username ? md5( $username ) :
null;
173 foreach ( $this->conditions as $index => $specificThrottle ) {
174 $ipKey = isset( $specificThrottle[
'allIPs'] ) ? null : $ip;
175 $throttleKey = $this->cache->makeGlobalKey(
'throttler', $this->type, $index, $ipKey, $userKey );
176 $this->cache->delete( $throttleKey );
187 if ( !is_array( $throttleConditions ) ) {
190 if ( isset( $throttleConditions[
'count'] ) ) {
191 $throttleConditions = [ $throttleConditions ];
193 return $throttleConditions;
197 $logMsg =
'Throttle {throttle} hit, throttled for {expiry} seconds due to {count} attempts '
198 .
'from username {username} and IP {ipKey}';
202 $level = $context[
'count'] >= $this->warningLimit ? LogLevel::WARNING : LogLevel::INFO;
207 $this->logger->log( $level, $logMsg, $context );
Class representing a cache/ephemeral data store.
A class containing constants representing the names of configuration variables.
const PasswordAttemptThrottle
Name constant for the PasswordAttemptThrottle setting, for use with Config::get()