return $this->getName();
}
+ /**
+ * Test if it's safe to load this User object
+ * @return bool
+ */
+ public function isSafeToLoad() {
+ global $wgFullyInitialised;
+ return $wgFullyInitialised || $this->mLoadedItems === true || $this->mFrom !== 'session';
+ }
+
/**
* Load the user table data for this object from the source given by mFrom.
*
$this->queryFlagsUsed = $flags;
// If this is called too early, things are likely to break.
- if ( $this->mFrom === 'session' && empty( $wgFullyInitialised ) ) {
+ if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
\MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
->warning( 'User::loadFromSession called before the end of Setup.php', array(
'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
// Other code expects these to be set in the session, so set them.
$session->set( 'wsUserID', $this->getId() );
$session->set( 'wsUserName', $this->getName() );
- $session->set( 'wsToken', $this->mToken );
+ $session->set( 'wsToken', $this->getToken() );
return true;
}
# We only need to worry about passing the IP address to the Block generator if the
# user is not immune to autoblocks/hardblocks, and they are the current user so we
# know which IP address they're actually coming from
- if ( !$this->isAllowed( 'ipblock-exempt' ) && $this->equals( $wgUser ) ) {
- $ip = $this->getRequest()->getIP();
- } else {
- $ip = null;
+ $ip = null;
+ if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
+ // $wgUser->getName() only works after the end of Setup.php. Until
+ // then, assume it's a logged-out user.
+ $globalUserName = $wgUser->isSafeToLoad()
+ ? $wgUser->getName()
+ : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
+ if ( $this->getName() === $globalUserName ) {
+ $ip = $this->getRequest()->getIP();
+ }
}
// User/IP blocking
$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
}
}
}
+
// Set the user limit key
if ( $userLimit !== false ) {
list( $max, $period ) = $userLimit;
$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;
* Get the user's current token.
* @param bool $forceCreation Force the generation of a new token if the
* user doesn't have one (default=true for backwards compatibility).
- * @return string Token
+ * @return string|null Token
*/
public function getToken( $forceCreation = true ) {
+ global $wgAuthenticationTokenVersion;
+
$this->load();
if ( !$this->mToken && $forceCreation ) {
$this->setToken();
}
- return $this->mToken;
+
+ // If the user doesn't have a token, return null to indicate that.
+ // Otherwise, hmac the version with the secret if we have a version.
+ if ( !$this->mToken ) {
+ return null;
+ } elseif ( $wgAuthenticationTokenVersion === null ) {
+ return $this->mToken;
+ } else {
+ $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
+
+ // The raw hash can be overly long. Shorten it up.
+ $len = max( 32, self::TOKEN_LENGTH );
+ if ( strlen( $ret ) < $len ) {
+ // Should never happen, even md5 is 128 bits
+ throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
+ }
+ return substr( $ret, -$len );
+ }
}
/**