27use Psr\Log\LoggerAwareInterface;
28use Psr\Log\LoggerInterface;
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 ) ) );
73 $conditions = $config->get(
'PasswordAttemptThrottle' );
76 'cache' => \ObjectCache::getLocalClusterInstance(),
82 'cache' => \ObjectCache::getLocalClusterInstance(),
83 'warningLimit' => INF,
87 $this->type = $params[
'type'];
88 $this->conditions = static::normalizeThrottleConditions(
$conditions );
89 $this->cache = $params[
'cache'];
90 $this->warningLimit = $params[
'warningLimit'];
92 $this->
setLogger( LoggerFactory::getInstance(
'throttler' ) );
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' );
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'];
126 if ( !$count || $userKey ===
null && $ipKey ===
null ) {
130 $throttleKey = $this->cache->makeGlobalKey(
137 $throttleCount = $this->cache->get( $throttleKey );
138 if ( $throttleCount && $throttleCount >= $count ) {
141 'throttle' => $this->type,
144 'username' => $username,
148 'method' => $caller ?: __METHOD__,
152 return [
'throttleIndex' => $index,
'count' => $count,
'wait' => $expiry ];
154 $this->cache->incrWithInit( $throttleKey, $expiry, 1 );
170 public function clear( $username =
null, $ip =
null ) {
171 $userKey = $username ? md5( $username ) :
null;
172 foreach ( $this->conditions as $index => $specificThrottle ) {
173 $ipKey = isset( $specificThrottle[
'allIPs'] ) ? null : $ip;
174 $throttleKey = $this->cache->makeGlobalKey(
'throttler', $this->type, $index, $ipKey, $userKey );
175 $this->cache->delete( $throttleKey );
186 if ( !is_array( $throttleConditions ) ) {
189 if ( isset( $throttleConditions[
'count'] ) ) {
190 $throttleConditions = [ $throttleConditions ];
192 return $throttleConditions;
196 $logMsg =
'Throttle {throttle} hit, throttled for {expiry} seconds due to {count} attempts '
197 .
'from username {username} and IP {ipKey}';
201 $level =
$context[
'count'] >= $this->warningLimit ? LogLevel::WARNING : LogLevel::INFO;
206 $this->logger->log( $level, $logMsg,
$context );
Class representing a cache/ephemeral data store.