94 $this->logger = LoggerFactory::getInstance(
'ratelimit' );
98 $this->options = $options;
99 $this->wrstatsFactory = $wrstatsFactory;
100 $this->centralIdLookup = $centralIdLookup;
101 $this->userFactory = $userFactory;
102 $this->userGroupManager = $userGroupManager;
103 $this->hookContainer = $hookContainer;
104 $this->hookRunner =
new HookRunner( $hookContainer );
186 if ( $this->nonLimitableActions[$action] ??
false ) {
191 $ip = $subject->
getIP();
195 $legacyUser = $this->userFactory->newFromUserIdentity( $user );
196 if ( !$this->hookRunner->onPingLimiter( $legacyUser, $action, $result, $incrBy ) ) {
197 $this->incrementStats(
"limit.$action.result." . ( $result ?
'tripped_by_hook' :
'passed_by_hook' ) );
201 if ( !isset( $this->rateLimits[$action] ) ) {
206 if ( $this->canBypass( $action ) && $this->
isExempt( $subject ) ) {
207 $this->incrementStats(
"limit.$action.result.exempt" );
211 $conds = $this->getConditions( $action );
212 $limiter = $this->wrstatsFactory->createRateLimiter( $conds, [
'limiter', $action ] );
213 $peekMode = $incrBy === 0;
214 $limitBatch = $limiter->createBatch( $incrBy ?: 1 );
215 $this->logger->debug( __METHOD__ .
": limiting $action rate for {$user->getName()}" );
217 $id = $user->getId();
218 $isNewbie = $subject->
is( RateLimitSubject::NEWBIE );
222 if ( isset( $conds[
'anon'] ) ) {
223 $limitBatch->localOp(
'anon', [] );
227 if ( isset( $conds[
'user-global'] ) ) {
229 $centralId = $this->centralIdLookup
230 ? $this->centralIdLookup->centralIdFromLocalUser( $user, CentralIdLookup::AUDIENCE_RAW )
235 $realm = $this->centralIdLookup->getProviderId();
236 $limitBatch->globalOp(
'user-global', [ $realm, $centralId ] );
239 $limitBatch->localOp(
'user-global', [
'local', $id ] );
244 if ( $isNewbie && $ip ) {
246 if ( isset( $conds[
'ip'] ) ) {
247 $limitBatch->globalOp(
'ip', $ip );
250 if ( isset( $conds[
'subnet'] ) ) {
251 $subnet = IPUtils::getSubnet( $ip );
252 if ( $subnet !==
false ) {
253 $limitBatch->globalOp(
'subnet', $subnet );
259 $userEntityType =
false;
260 if ( $id !== 0 && isset( $conds[
'user'] ) ) {
262 $userEntityType =
'user';
265 if ( $id !== 0 && $isNewbie && isset( $conds[
'newbie'] ) ) {
266 $userEntityType =
'newbie';
270 $userGroups = $this->userGroupManager->getUserGroups( $user );
271 foreach ( $userGroups as $group ) {
272 if ( isset( $conds[$group] ) ) {
273 if ( $userEntityType ===
false
274 || $conds[$group]->perSecond() > $conds[$userEntityType]->perSecond()
276 $userEntityType = $group;
283 if ( $userEntityType !==
false ) {
284 $limitBatch->localOp( $userEntityType, $id );
288 if ( isset( $conds[
'ip-all'] ) && $ip ) {
290 if ( $isNewbie || $userEntityType ===
false
291 || $conds[
'ip-all']->perSecond() > $conds[$userEntityType]->perSecond()
293 $limitBatch->globalOp(
'ip-all', $ip );
298 if ( isset( $conds[
'subnet-all'] ) && $ip ) {
299 $subnet = IPUtils::getSubnet( $ip );
300 if ( $subnet !==
false ) {
302 if ( $isNewbie || $userEntityType ===
false
303 || $conds[
'ip-all']->perSecond() > $conds[$userEntityType]->perSecond()
305 $limitBatch->globalOp(
'subnet-all', $subnet );
311 'name' => $user->getName(),
315 $batchResult = $peekMode ? $limitBatch->peek() : $limitBatch->tryIncr();
316 foreach ( $batchResult->getFailedResults() as $type => $result ) {
318 'User::pingLimiter: User tripped rate limit',
321 'limit' => $result->condition->limit,
322 'period' => $result->condition->window,
323 'count' => $result->prevTotal,
328 $this->incrementStats(
"limit.$action.tripped_by.$type" );
331 $allowed = $batchResult->isAllowed();
333 $this->incrementStats(
"limit.$action.result." . ( $allowed ?
'passed' :
'tripped' ) );