From 47973faa7c7d48321551dd0cc6bb761bcd785942 Mon Sep 17 00:00:00 2001 From: cenarium Date: Tue, 26 Jan 2016 00:52:56 +0100 Subject: [PATCH] Add $wgRateLimits types ip-all and subnet-all These apply to all ping-limitable users, unlike ip and subnet that apply only to anons and non-autoconfirmed users. However, if an autoconfirmed user is in a group with less restrictive limits, then those are ignored. This is necessary to move the account creation throttle into the ping limiter. Bug: T50373 Change-Id: Ifc70a605bb8848070bdf2af8f2c3f80f68dcc085 --- includes/user/User.php | 66 ++++++++++++++++++++++++++++++------------ includes/utils/IP.php | 19 ++++++++++++ 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/includes/user/User.php b/includes/user/User.php index 47e67e5601..1fb0b8a9e4 100644 --- a/includes/user/User.php +++ b/includes/user/User.php @@ -1726,40 +1726,43 @@ class User implements IDBAccessObject { $keys = array(); $id = $this->getId(); $userLimit = false; + $isNewbie = $this->isNewbie(); - if ( isset( $limits['anon'] ) && $id == 0 ) { - $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon']; - } - - if ( isset( $limits['user'] ) && $id != 0 ) { - $userLimit = $limits['user']; - } - if ( $this->isNewbie() ) { - if ( isset( $limits['newbie'] ) && $id != 0 ) { + if ( $id == 0 ) { + // limits for anons + if ( isset( $limits['anon'] ) ) { + $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon']; + } + } else { + // limits for logged-in users + if ( isset( $limits['user'] ) ) { + $userLimit = $limits['user']; + } + // limits for newbie logged-in users + if ( $isNewbie && isset( $limits['newbie'] ) ) { $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie']; } + } + + // limits for anons and for newbie logged-in users + if ( $isNewbie ) { + // ip-based limits if ( isset( $limits['ip'] ) ) { $ip = $this->getRequest()->getIP(); $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip']; } + // subnet-based limits if ( isset( $limits['subnet'] ) ) { $ip = $this->getRequest()->getIP(); - $matches = array(); - $subnet = false; - if ( IP::isIPv6( $ip ) ) { - $parts = IP::parseRange( "$ip/64" ); - $subnet = $parts[0]; - } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) { - // IPv4 - $subnet = $matches[1]; - } + $subnet = IP::getSubnet( $ip ); if ( $subnet !== false ) { $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet']; } } } + // Check for group-specific permissions - // If more than one group applies, use the group with the highest limit + // If more than one group applies, use the group with the highest limit ratio (max/period) foreach ( $this->getGroups() as $group ) { if ( isset( $limits[$group] ) ) { if ( $userLimit === false @@ -1769,6 +1772,7 @@ class User implements IDBAccessObject { } } } + // Set the user limit key if ( $userLimit !== false ) { list( $max, $period ) = $userLimit; @@ -1776,6 +1780,30 @@ class User implements IDBAccessObject { $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit; } + // ip-based limits for all ping-limitable users + if ( isset( $limits['ip-all'] ) ) { + $ip = $this->getRequest()->getIP(); + // ignore if user limit is more permissive + if ( $isNewbie || $userLimit === false + || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) { + $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all']; + } + } + + // subnet-based limits for all ping-limitable users + if ( isset( $limits['subnet-all'] ) ) { + $ip = $this->getRequest()->getIP(); + $subnet = IP::getSubnet( $ip ); + if ( $subnet !== false ) { + // ignore if user limit is more permissive + if ( $isNewbie || $userLimit === false + || $limits['ip-all'][0] / $limits['ip-all'][1] + > $userLimit[0] / $userLimit[1] ) { + $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all']; + } + } + } + $cache = ObjectCache::getLocalClusterInstance(); $triggered = false; diff --git a/includes/utils/IP.php b/includes/utils/IP.php index 8abca5b9d3..b352ecc0e7 100644 --- a/includes/utils/IP.php +++ b/includes/utils/IP.php @@ -766,4 +766,23 @@ class IP { public static function clearCaches() { self::$proxyIpSet = null; } + + /** + * Returns the subnet of a given IP + * + * @param string $ip + * @return string|false + */ + public static function getSubnet( $ip ) { + $matches = array(); + $subnet = false; + if ( IP::isIPv6( $ip ) ) { + $parts = IP::parseRange( "$ip/64" ); + $subnet = $parts[0]; + } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) { + // IPv4 + $subnet = $matches[1]; + } + return $subnet; + } } -- 2.20.1