2019-04-05 19:13:17 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
*
|
|
|
|
|
* @file
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace MediaWiki\Block;
|
|
|
|
|
|
2019-09-07 23:44:46 +00:00
|
|
|
use LogicException;
|
2019-06-26 14:06:01 +00:00
|
|
|
use MediaWiki\Config\ServiceOptions;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
use MediaWiki\HookContainer\HookContainer;
|
|
|
|
|
use MediaWiki\HookContainer\HookRunner;
|
2022-04-26 15:48:03 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2023-10-09 05:10:06 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2024-02-08 18:57:07 +00:00
|
|
|
use MediaWiki\Message\Message;
|
2023-10-31 07:57:33 +00:00
|
|
|
use MediaWiki\Request\ProxyLookup;
|
2023-09-07 11:46:15 +00:00
|
|
|
use MediaWiki\Request\WebRequest;
|
2023-03-01 17:02:32 +00:00
|
|
|
use MediaWiki\Request\WebResponse;
|
2023-09-19 12:13:45 +00:00
|
|
|
use MediaWiki\User\User;
|
2021-05-26 20:14:10 +00:00
|
|
|
use MediaWiki\User\UserFactory;
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
use MediaWiki\User\UserIdentity;
|
2023-08-07 09:56:43 +00:00
|
|
|
use MediaWiki\User\UserIdentityUtils;
|
2019-06-06 18:00:20 +00:00
|
|
|
use MWCryptHash;
|
2019-08-20 17:29:59 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
2019-04-05 19:13:17 +00:00
|
|
|
use Wikimedia\IPSet;
|
2019-06-25 18:53:15 +00:00
|
|
|
use Wikimedia\IPUtils;
|
2019-04-05 19:13:17 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A service class for checking blocks.
|
|
|
|
|
* To obtain an instance, use MediaWikiServices::getInstance()->getBlockManager().
|
|
|
|
|
*
|
|
|
|
|
* @since 1.34 Refactored from User and Block.
|
|
|
|
|
*/
|
|
|
|
|
class BlockManager {
|
2019-06-26 14:06:01 +00:00
|
|
|
/**
|
2019-10-25 08:07:22 +00:00
|
|
|
* @internal For use by ServiceWiring
|
2019-08-05 17:00:00 +00:00
|
|
|
*/
|
2019-10-08 18:24:22 +00:00
|
|
|
public const CONSTRUCTOR_OPTIONS = [
|
2022-04-26 15:48:03 +00:00
|
|
|
MainConfigNames::ApplyIpBlocksToXff,
|
|
|
|
|
MainConfigNames::CookieSetOnAutoblock,
|
|
|
|
|
MainConfigNames::CookieSetOnIpBlock,
|
|
|
|
|
MainConfigNames::DnsBlacklistUrls,
|
|
|
|
|
MainConfigNames::EnableDnsBlacklist,
|
|
|
|
|
MainConfigNames::ProxyList,
|
|
|
|
|
MainConfigNames::ProxyWhitelist,
|
|
|
|
|
MainConfigNames::SecretKey,
|
|
|
|
|
MainConfigNames::SoftBlockRanges,
|
2019-06-26 14:06:01 +00:00
|
|
|
];
|
2019-04-05 19:13:17 +00:00
|
|
|
|
2024-07-30 21:52:32 +00:00
|
|
|
private ServiceOptions $options;
|
|
|
|
|
private UserFactory $userFactory;
|
|
|
|
|
private UserIdentityUtils $userIdentityUtils;
|
|
|
|
|
private LoggerInterface $logger;
|
|
|
|
|
private HookRunner $hookRunner;
|
|
|
|
|
private DatabaseBlockStore $blockStore;
|
|
|
|
|
private ProxyLookup $proxyLookup;
|
2023-10-31 07:57:33 +00:00
|
|
|
|
2024-07-30 21:52:32 +00:00
|
|
|
private BlockCache $userBlockCache;
|
|
|
|
|
private BlockCache $createAccountBlockCache;
|
2023-10-31 07:57:33 +00:00
|
|
|
|
2019-04-05 19:13:17 +00:00
|
|
|
public function __construct(
|
2019-06-26 14:06:01 +00:00
|
|
|
ServiceOptions $options,
|
2021-05-26 20:14:10 +00:00
|
|
|
UserFactory $userFactory,
|
2023-08-07 09:56:43 +00:00
|
|
|
UserIdentityUtils $userIdentityUtils,
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
LoggerInterface $logger,
|
2023-10-31 07:57:33 +00:00
|
|
|
HookContainer $hookContainer,
|
|
|
|
|
DatabaseBlockStore $blockStore,
|
|
|
|
|
ProxyLookup $proxyLookup
|
2019-04-05 19:13:17 +00:00
|
|
|
) {
|
2019-10-08 18:24:22 +00:00
|
|
|
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
2019-06-26 14:06:01 +00:00
|
|
|
$this->options = $options;
|
2021-05-26 20:14:10 +00:00
|
|
|
$this->userFactory = $userFactory;
|
2023-08-07 09:56:43 +00:00
|
|
|
$this->userIdentityUtils = $userIdentityUtils;
|
2019-08-20 17:29:59 +00:00
|
|
|
$this->logger = $logger;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->hookRunner = new HookRunner( $hookContainer );
|
2023-10-31 07:57:33 +00:00
|
|
|
$this->blockStore = $blockStore;
|
|
|
|
|
$this->proxyLookup = $proxyLookup;
|
|
|
|
|
|
2023-10-04 00:26:46 +00:00
|
|
|
$this->userBlockCache = new BlockCache;
|
2023-10-11 22:18:26 +00:00
|
|
|
$this->createAccountBlockCache = new BlockCache;
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-03-19 18:56:10 +00:00
|
|
|
* Get the blocks that apply to a user. If there is only one, return that, otherwise
|
|
|
|
|
* return a composite block that combines the strictest features of the applicable
|
|
|
|
|
* blocks.
|
2019-04-05 19:13:17 +00:00
|
|
|
*
|
2019-08-23 16:11:45 +00:00
|
|
|
* Different blocks may be sought, depending on the user and their permissions. The
|
|
|
|
|
* user may be:
|
|
|
|
|
* (1) The global user (and can be affected by IP blocks). The global request object
|
|
|
|
|
* is needed for checking the IP address, the XFF header and the cookies.
|
|
|
|
|
* (2) The global user (and exempt from IP blocks). The global request object is
|
2020-02-07 02:11:08 +00:00
|
|
|
* available.
|
2019-08-23 16:11:45 +00:00
|
|
|
* (3) Another user (not the global user). No request object is available or needed;
|
|
|
|
|
* just look for a block against the user account.
|
|
|
|
|
*
|
|
|
|
|
* Cases #1 and #2 check whether the global user is blocked in practice; the block
|
|
|
|
|
* may due to their user account being blocked or to an IP address block or cookie
|
|
|
|
|
* block (or multiple of these). Case #3 simply checks whether a user's account is
|
|
|
|
|
* blocked, and does not determine whether the person using that account is affected
|
|
|
|
|
* in practice by any IP address or cookie blocks.
|
2019-04-05 19:13:17 +00:00
|
|
|
*
|
2023-10-11 22:18:26 +00:00
|
|
|
* @deprecated since 1.42 Use getBlock(), which is the same except that it expects
|
|
|
|
|
* the caller to do ipblock-exempt permission checking and to set $request to null
|
|
|
|
|
* if the user is exempt from IP blocks.
|
|
|
|
|
*
|
2021-05-26 20:14:10 +00:00
|
|
|
* @param UserIdentity $user
|
2022-10-27 13:14:16 +00:00
|
|
|
* @param WebRequest|null $request The global request object if the user is the
|
2019-08-23 16:11:45 +00:00
|
|
|
* global user (cases #1 and #2), otherwise null (case #3). The IP address and
|
|
|
|
|
* information from the request header are needed to find some types of blocks.
|
2019-04-05 19:13:17 +00:00
|
|
|
* @param bool $fromReplica Whether to check the replica DB first.
|
|
|
|
|
* To improve performance, non-critical checks are done against replica DBs.
|
2021-04-19 01:02:08 +00:00
|
|
|
* Check when actually saving should be done against primary.
|
2021-01-08 21:21:38 +00:00
|
|
|
* @param bool $disableIpBlockExemptChecking This is used internally to prevent
|
|
|
|
|
* a infinite recursion with autopromote. See T270145.
|
2019-05-14 12:42:50 +00:00
|
|
|
* @return AbstractBlock|null The most relevant block, or null if there is no block.
|
2019-04-05 19:13:17 +00:00
|
|
|
*/
|
2021-05-26 20:14:10 +00:00
|
|
|
public function getUserBlock(
|
|
|
|
|
UserIdentity $user,
|
|
|
|
|
$request,
|
|
|
|
|
$fromReplica,
|
|
|
|
|
$disableIpBlockExemptChecking = false
|
|
|
|
|
) {
|
2019-08-23 16:11:45 +00:00
|
|
|
// If this is the global user, they may be affected by IP blocks (case #1),
|
|
|
|
|
// or they may be exempt (case #2). If affected, look for additional blocks
|
2020-02-07 02:11:08 +00:00
|
|
|
// against the IP address and referenced in a cookie.
|
2019-08-23 16:11:45 +00:00
|
|
|
$checkIpBlocks = $request &&
|
2021-01-08 21:21:38 +00:00
|
|
|
// Because calling getBlock within Autopromote leads back to here,
|
|
|
|
|
// thus causing a infinite recursion. We fix this by not checking for
|
|
|
|
|
// ipblock-exempt when calling getBlock within Autopromote.
|
2020-12-16 20:07:38 +00:00
|
|
|
// See T270145.
|
2021-01-08 21:21:38 +00:00
|
|
|
!$disableIpBlockExemptChecking &&
|
2023-10-09 05:10:06 +00:00
|
|
|
!$this->isIpBlockExempt( $user );
|
2019-08-23 16:11:45 +00:00
|
|
|
|
2023-10-11 22:18:26 +00:00
|
|
|
return $this->getBlock(
|
|
|
|
|
$user,
|
|
|
|
|
$checkIpBlocks ? $request : null,
|
|
|
|
|
$fromReplica
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the blocks that apply to a user. If there is only one, return that, otherwise
|
|
|
|
|
* return a composite block that combines the strictest features of the applicable
|
|
|
|
|
* blocks.
|
|
|
|
|
*
|
|
|
|
|
* If the user is exempt from IP blocks, the request should be null.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.42
|
|
|
|
|
* @param UserIdentity $user The user performing the action
|
|
|
|
|
* @param WebRequest|null $request The request to use for IP and cookie
|
|
|
|
|
* blocks, or null to skip checking for such blocks. If the user has the
|
|
|
|
|
* ipblock-exempt right, the request should be null.
|
|
|
|
|
* @param bool $fromReplica Whether to check the replica DB first.
|
|
|
|
|
* To improve performance, non-critical checks are done against replica DBs.
|
|
|
|
|
* Check when actually saving should be done against primary.
|
|
|
|
|
* @return AbstractBlock|null
|
|
|
|
|
*/
|
|
|
|
|
public function getBlock(
|
|
|
|
|
UserIdentity $user,
|
|
|
|
|
?WebRequest $request,
|
|
|
|
|
$fromReplica = true
|
|
|
|
|
): ?AbstractBlock {
|
|
|
|
|
$fromPrimary = !$fromReplica;
|
|
|
|
|
$ip = null;
|
2023-10-04 00:26:46 +00:00
|
|
|
|
|
|
|
|
// TODO: normalise the fromPrimary parameter when replication is not configured.
|
|
|
|
|
// Maybe DatabaseBlockStore can tell us about the LoadBalancer configuration.
|
|
|
|
|
$cacheKey = new BlockCacheKey(
|
|
|
|
|
$request,
|
|
|
|
|
$user,
|
|
|
|
|
$fromPrimary
|
|
|
|
|
);
|
|
|
|
|
$block = $this->userBlockCache->get( $cacheKey );
|
|
|
|
|
if ( $block !== null ) {
|
|
|
|
|
$this->logger->debug( "Block cache hit with key {$cacheKey}" );
|
|
|
|
|
return $block ?: null;
|
|
|
|
|
}
|
|
|
|
|
$this->logger->debug( "Block cache miss with key {$cacheKey}" );
|
|
|
|
|
|
|
|
|
|
if ( $request ) {
|
2019-08-23 16:11:45 +00:00
|
|
|
|
|
|
|
|
// Case #1: checking the global user, including IP blocks
|
|
|
|
|
$ip = $request->getIP();
|
2023-12-14 21:07:33 +00:00
|
|
|
// For soft blocks, i.e. blocks that don't block logged-in users,
|
|
|
|
|
// temporary users are treated as anon users, and are blocked.
|
|
|
|
|
$applySoftBlocks = !$this->userIdentityUtils->isNamed( $user );
|
2021-09-23 13:25:06 +00:00
|
|
|
|
|
|
|
|
$xff = $request->getHeader( 'X-Forwarded-For' );
|
|
|
|
|
|
2021-10-11 16:48:44 +00:00
|
|
|
$blocks = array_merge(
|
2023-10-31 07:57:33 +00:00
|
|
|
$this->blockStore->newListFromTarget( $user, $ip, $fromPrimary ),
|
2023-08-07 09:56:43 +00:00
|
|
|
$this->getSystemIpBlocks( $ip, $applySoftBlocks ),
|
2023-08-08 17:07:45 +00:00
|
|
|
$this->getXffBlocks( $ip, $xff, $applySoftBlocks, $fromPrimary ),
|
2021-10-11 16:48:44 +00:00
|
|
|
$this->getCookieBlock( $user, $request )
|
|
|
|
|
);
|
2019-08-23 16:11:45 +00:00
|
|
|
} else {
|
|
|
|
|
|
2020-02-07 02:11:08 +00:00
|
|
|
// Case #2: checking the global user, but they are exempt from IP blocks
|
|
|
|
|
// and cookie blocks, so we only check for a user account block.
|
|
|
|
|
// Case #3: checking whether another user's account is blocked.
|
2023-10-31 07:57:33 +00:00
|
|
|
$blocks = $this->blockStore->newListFromTarget( $user, null, $fromPrimary );
|
2019-04-05 19:13:17 +00:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-23 13:25:06 +00:00
|
|
|
$block = $this->createGetBlockResult( $ip, $blocks );
|
|
|
|
|
|
|
|
|
|
$legacyUser = $this->userFactory->newFromUserIdentity( $user );
|
|
|
|
|
$this->hookRunner->onGetUserBlock( clone $legacyUser, $ip, $block );
|
|
|
|
|
|
2023-10-04 00:26:46 +00:00
|
|
|
$this->userBlockCache->set( $cacheKey, $block ?: false );
|
2021-09-23 13:25:06 +00:00
|
|
|
return $block;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 00:26:46 +00:00
|
|
|
/**
|
|
|
|
|
* Clear the cache of any blocks that refer to the specified user
|
|
|
|
|
*
|
|
|
|
|
* @param UserIdentity $user
|
|
|
|
|
*/
|
|
|
|
|
public function clearUserCache( UserIdentity $user ) {
|
|
|
|
|
$this->userBlockCache->clearUser( $user );
|
2023-10-11 22:18:26 +00:00
|
|
|
$this->createAccountBlockCache->clearUser( $user );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the block which applies to a create account action, if there is any
|
|
|
|
|
*
|
|
|
|
|
* @since 1.42
|
|
|
|
|
* @param UserIdentity $user
|
|
|
|
|
* @param WebRequest|null $request The request, or null to omit IP address
|
|
|
|
|
* and cookie blocks. If the user has the ipblock-exempt right, null
|
|
|
|
|
* should be passed.
|
|
|
|
|
* @param bool $fromReplica
|
|
|
|
|
* @return AbstractBlock|null
|
|
|
|
|
*/
|
|
|
|
|
public function getCreateAccountBlock(
|
|
|
|
|
UserIdentity $user,
|
|
|
|
|
?WebRequest $request,
|
|
|
|
|
$fromReplica
|
|
|
|
|
) {
|
|
|
|
|
$key = new BlockCacheKey( $request, $user, $fromReplica );
|
|
|
|
|
$cachedBlock = $this->createAccountBlockCache->get( $key );
|
|
|
|
|
if ( $cachedBlock !== null ) {
|
|
|
|
|
$this->logger->debug( "Create account block cache hit with key {$key}" );
|
|
|
|
|
return $cachedBlock ?: null;
|
|
|
|
|
}
|
|
|
|
|
$this->logger->debug( "Create account block cache miss with key {$key}" );
|
|
|
|
|
|
|
|
|
|
$applicableBlocks = [];
|
|
|
|
|
$userBlock = $this->getBlock( $user, $request, $fromReplica );
|
|
|
|
|
if ( $userBlock ) {
|
|
|
|
|
$applicableBlocks = $userBlock->toArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// T15611: if the IP address the user is trying to create an account from is
|
|
|
|
|
// blocked with createaccount disabled, prevent new account creation there even
|
|
|
|
|
// when the user is logged in
|
|
|
|
|
if ( $request ) {
|
2023-10-31 07:57:33 +00:00
|
|
|
$ipBlock = $this->blockStore->newFromTarget(
|
2023-10-11 22:18:26 +00:00
|
|
|
null, $request->getIP()
|
|
|
|
|
);
|
|
|
|
|
if ( $ipBlock ) {
|
|
|
|
|
$applicableBlocks = array_merge( $applicableBlocks, $ipBlock->toArray() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ( $applicableBlocks as $i => $block ) {
|
|
|
|
|
if ( !$block->appliesToRight( 'createaccount' ) ) {
|
|
|
|
|
unset( $applicableBlocks[$i] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$result = $this->createGetBlockResult(
|
|
|
|
|
$request ? $request->getIP() : null,
|
|
|
|
|
$applicableBlocks
|
|
|
|
|
);
|
|
|
|
|
$this->createAccountBlockCache->set( $key, $result ?: false );
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove elements of a block which fail a callback test.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.42
|
|
|
|
|
* @param Block|null $block The block, or null to pass in zero blocks.
|
|
|
|
|
* @param callable $callback The callback, which will be called once for
|
|
|
|
|
* each non-composite component of the block. The only parameter is the
|
|
|
|
|
* non-composite Block. It should return true, to keep that component,
|
|
|
|
|
* or false, to remove that component.
|
|
|
|
|
* @return Block|null
|
|
|
|
|
* - If there are zero remaining elements, null will be returned.
|
|
|
|
|
* - If there is one remaining element, a DatabaseBlock or some other
|
|
|
|
|
* non-composite block will be returned.
|
|
|
|
|
* - If there is more than one remaining element, a CompositeBlock will
|
|
|
|
|
* be returned.
|
|
|
|
|
*/
|
|
|
|
|
public function filter( ?Block $block, $callback ) {
|
|
|
|
|
if ( !$block ) {
|
|
|
|
|
return null;
|
|
|
|
|
} elseif ( $block instanceof CompositeBlock ) {
|
|
|
|
|
$blocks = $block->getOriginalBlocks();
|
|
|
|
|
$originalCount = count( $blocks );
|
|
|
|
|
foreach ( $blocks as $i => $originalBlock ) {
|
|
|
|
|
if ( !$callback( $originalBlock ) ) {
|
|
|
|
|
unset( $blocks[$i] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( !$blocks ) {
|
|
|
|
|
return null;
|
|
|
|
|
} elseif ( count( $blocks ) === 1 ) {
|
|
|
|
|
return $blocks[ array_key_first( $blocks ) ];
|
|
|
|
|
} elseif ( count( $blocks ) === $originalCount ) {
|
|
|
|
|
return $block;
|
|
|
|
|
} else {
|
|
|
|
|
return $block->withOriginalBlocks( array_values( $blocks ) );
|
|
|
|
|
}
|
|
|
|
|
} elseif ( !$callback( $block ) ) {
|
|
|
|
|
return null;
|
|
|
|
|
} else {
|
|
|
|
|
return $block;
|
|
|
|
|
}
|
2023-10-04 00:26:46 +00:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 05:10:06 +00:00
|
|
|
/**
|
|
|
|
|
* Determine if a user is exempt from IP blocks
|
|
|
|
|
* @param UserIdentity $user
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
private function isIpBlockExempt( UserIdentity $user ) {
|
|
|
|
|
return MediaWikiServices::getInstance()->getPermissionManager()
|
|
|
|
|
->userHasRight( $user, 'ipblock-exempt' );
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-23 13:25:06 +00:00
|
|
|
/**
|
|
|
|
|
* @param string|null $ip
|
|
|
|
|
* @param AbstractBlock[] $blocks
|
|
|
|
|
* @return AbstractBlock|null
|
|
|
|
|
*/
|
|
|
|
|
private function createGetBlockResult( ?string $ip, array $blocks ): ?AbstractBlock {
|
2019-08-23 16:11:45 +00:00
|
|
|
// Filter out any duplicated blocks, e.g. from the cookie
|
|
|
|
|
$blocks = $this->getUniqueBlocks( $blocks );
|
2019-04-05 19:13:17 +00:00
|
|
|
|
2021-09-23 13:25:06 +00:00
|
|
|
if ( count( $blocks ) === 0 ) {
|
|
|
|
|
return null;
|
|
|
|
|
} elseif ( count( $blocks ) === 1 ) {
|
|
|
|
|
return $blocks[ 0 ];
|
|
|
|
|
} else {
|
2022-11-16 09:34:39 +00:00
|
|
|
$compositeBlock = CompositeBlock::createFromBlocks( ...$blocks );
|
|
|
|
|
$compositeBlock->setTarget( $ip );
|
|
|
|
|
return $compositeBlock;
|
2019-08-23 16:11:45 +00:00
|
|
|
}
|
2021-09-23 13:25:06 +00:00
|
|
|
}
|
2019-08-23 16:11:45 +00:00
|
|
|
|
2021-09-23 13:25:06 +00:00
|
|
|
/**
|
|
|
|
|
* Get the blocks that apply to an IP address. If there is only one, return that, otherwise
|
|
|
|
|
* return a composite block that combines the strictest features of the applicable blocks.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.38
|
|
|
|
|
* @param string $ip
|
|
|
|
|
* @param bool $fromReplica
|
|
|
|
|
* @return AbstractBlock|null
|
|
|
|
|
*/
|
|
|
|
|
public function getIpBlock( string $ip, bool $fromReplica ): ?AbstractBlock {
|
|
|
|
|
if ( !IPUtils::isValid( $ip ) ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2019-08-23 16:11:45 +00:00
|
|
|
|
2021-10-11 16:48:44 +00:00
|
|
|
$blocks = array_merge(
|
2023-10-31 07:57:33 +00:00
|
|
|
$this->blockStore->newListFromTarget( $ip, $ip, !$fromReplica ),
|
2021-10-11 16:48:44 +00:00
|
|
|
$this->getSystemIpBlocks( $ip, true )
|
|
|
|
|
);
|
2021-09-23 13:25:06 +00:00
|
|
|
|
|
|
|
|
return $this->createGetBlockResult( $ip, $blocks );
|
2019-08-23 16:11:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-10-11 16:48:44 +00:00
|
|
|
* Get the cookie block, if there is one.
|
2019-08-23 16:11:45 +00:00
|
|
|
*
|
|
|
|
|
* @param UserIdentity $user
|
2022-10-27 13:14:16 +00:00
|
|
|
* @param WebRequest $request
|
2021-10-11 16:48:44 +00:00
|
|
|
* @return AbstractBlock[]
|
2019-08-23 16:11:45 +00:00
|
|
|
*/
|
2021-10-11 16:48:44 +00:00
|
|
|
private function getCookieBlock( UserIdentity $user, WebRequest $request ): array {
|
2019-03-19 18:56:10 +00:00
|
|
|
$cookieBlock = $this->getBlockFromCookieValue( $user, $request );
|
2021-10-11 16:48:44 +00:00
|
|
|
|
|
|
|
|
return $cookieBlock instanceof DatabaseBlock ? [ $cookieBlock ] : [];
|
2019-08-23 16:11:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-10-11 16:48:44 +00:00
|
|
|
* Get any system blocks against the IP address.
|
2019-08-23 16:11:45 +00:00
|
|
|
*
|
2021-09-23 13:25:06 +00:00
|
|
|
* @param string $ip
|
2023-08-07 09:56:43 +00:00
|
|
|
* @param bool $applySoftBlocks
|
2021-10-11 16:48:44 +00:00
|
|
|
* @return AbstractBlock[]
|
2019-08-23 16:11:45 +00:00
|
|
|
*/
|
2023-08-07 09:56:43 +00:00
|
|
|
private function getSystemIpBlocks( string $ip, bool $applySoftBlocks ): array {
|
2021-10-11 16:48:44 +00:00
|
|
|
$blocks = [];
|
|
|
|
|
|
2019-04-05 19:13:17 +00:00
|
|
|
// Proxy blocking
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) ) {
|
2019-04-05 19:13:17 +00:00
|
|
|
// Local list
|
|
|
|
|
if ( $this->isLocallyBlockedProxy( $ip ) ) {
|
2019-03-19 18:56:10 +00:00
|
|
|
$blocks[] = new SystemBlock( [
|
2019-10-20 00:04:00 +00:00
|
|
|
'reason' => new Message( 'proxyblockreason' ),
|
2019-04-05 19:13:17 +00:00
|
|
|
'address' => $ip,
|
|
|
|
|
'systemBlock' => 'proxy',
|
|
|
|
|
] );
|
2023-08-07 09:56:43 +00:00
|
|
|
} elseif ( $applySoftBlocks && $this->isDnsBlacklisted( $ip ) ) {
|
2019-03-19 18:56:10 +00:00
|
|
|
$blocks[] = new SystemBlock( [
|
2019-10-20 00:04:00 +00:00
|
|
|
'reason' => new Message( 'sorbsreason' ),
|
2019-04-05 19:13:17 +00:00
|
|
|
'address' => $ip,
|
2020-10-02 14:28:10 +00:00
|
|
|
'anonOnly' => true,
|
2019-04-05 19:13:17 +00:00
|
|
|
'systemBlock' => 'dnsbl',
|
|
|
|
|
] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-19 18:56:10 +00:00
|
|
|
// Soft blocking
|
2023-08-07 09:56:43 +00:00
|
|
|
if ( $applySoftBlocks && IPUtils::isInRanges( $ip, $this->options->get( MainConfigNames::SoftBlockRanges ) ) ) {
|
2019-03-19 18:56:10 +00:00
|
|
|
$blocks[] = new SystemBlock( [
|
2019-04-05 19:13:17 +00:00
|
|
|
'address' => $ip,
|
2019-10-20 00:04:00 +00:00
|
|
|
'reason' => new Message( 'softblockrangesreason', [ $ip ] ),
|
2019-04-05 19:13:17 +00:00
|
|
|
'anonOnly' => true,
|
|
|
|
|
'systemBlock' => 'wgSoftBlockRanges',
|
|
|
|
|
] );
|
|
|
|
|
}
|
2021-10-11 16:48:44 +00:00
|
|
|
|
|
|
|
|
return $blocks;
|
2021-09-23 13:25:06 +00:00
|
|
|
}
|
2019-04-05 19:13:17 +00:00
|
|
|
|
2021-09-23 13:25:06 +00:00
|
|
|
/**
|
|
|
|
|
* If `$wgApplyIpBlocksToXff` is truthy and the IP that the user is accessing the wiki from is not in
|
|
|
|
|
* `$wgProxyWhitelist`, then get the blocks that apply to the IP(s) in the X-Forwarded-For HTTP
|
2021-10-11 16:48:44 +00:00
|
|
|
* header.
|
2021-09-23 13:25:06 +00:00
|
|
|
*
|
|
|
|
|
* @param string $ip
|
|
|
|
|
* @param string $xff
|
2023-08-08 17:07:45 +00:00
|
|
|
* @param bool $applySoftBlocks
|
2021-09-23 13:25:06 +00:00
|
|
|
* @param bool $fromPrimary
|
2021-10-11 16:48:44 +00:00
|
|
|
* @return AbstractBlock[]
|
2021-09-23 13:25:06 +00:00
|
|
|
*/
|
2023-08-08 17:07:45 +00:00
|
|
|
private function getXffBlocks(
|
|
|
|
|
string $ip,
|
|
|
|
|
string $xff,
|
|
|
|
|
bool $applySoftBlocks,
|
|
|
|
|
bool $fromPrimary
|
|
|
|
|
): array {
|
2019-08-23 16:11:45 +00:00
|
|
|
// (T25343) Apply IP blocks to the contents of XFF headers, if enabled
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( $this->options->get( MainConfigNames::ApplyIpBlocksToXff )
|
|
|
|
|
&& !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) )
|
2019-08-23 16:11:45 +00:00
|
|
|
) {
|
|
|
|
|
$xff = array_map( 'trim', explode( ',', $xff ) );
|
|
|
|
|
$xff = array_diff( $xff, [ $ip ] );
|
2023-08-08 17:07:45 +00:00
|
|
|
$xffblocks = $this->getBlocksForIPList( $xff, $applySoftBlocks, $fromPrimary );
|
2021-12-07 17:17:02 +00:00
|
|
|
|
|
|
|
|
// (T285159) Exclude autoblocks from XFF headers to prevent spoofed
|
|
|
|
|
// headers uncovering the IPs of autoblocked users
|
|
|
|
|
$xffblocks = array_filter( $xffblocks, static function ( $block ) {
|
|
|
|
|
return $block->getType() !== Block::TYPE_AUTO;
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
return $xffblocks;
|
2019-03-19 18:56:10 +00:00
|
|
|
}
|
2021-10-11 16:48:44 +00:00
|
|
|
|
|
|
|
|
return [];
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-26 17:35:17 +00:00
|
|
|
/**
|
Move DatabaseBlock read query methods to DatabaseBlockStore
* Move to DatabaseBlockStore the DatabaseBlock methods newFromID,
getQueryInfo, getRangeCond, newFromRow, isExemptedFromAutoblocks,
doAutoblock, updateTimestamp, getAutoblockExpiry, newFromTarget,
newListFromTarget.
* Split DatabaseBlock::getBlocksForIPList. Now
BlockManager::getBlocksForIPList() is responsible for XFF header
validation and trusted proxy handling. DatabaseBlockStore::
newListFromIPs() just does the queries and constructs the Block
objects.
* In DatabaseBlockStore::newFromRow() and doAutoblock(), use the
DatabaseBlock constructor instead of calling many setter methods. Add
constructor options decodedExpiry, decodedTimestamp, id,
parentBlockId and restrictions to support this.
* Move isExemptedFromAutoblocks() to its own service. Remove the cache
since in my testing with production eval.php, the WAN cache fetch is
10 times slower than just using the message cache, contradicting the
comment written in 2008.
* Fix AuthManagerTest which was previously passing an unrecognised
"restrictions" option to DatabaseBlock. Now that the option actually
works, we have to use the right type.
Bug: T255433
Change-Id: I5049e60be1681f67fcca133e569e315792dc42dd
2023-10-31 05:58:23 +00:00
|
|
|
* Get all blocks that match any IP from an array of IP addresses
|
2023-07-26 17:35:17 +00:00
|
|
|
*
|
Move DatabaseBlock read query methods to DatabaseBlockStore
* Move to DatabaseBlockStore the DatabaseBlock methods newFromID,
getQueryInfo, getRangeCond, newFromRow, isExemptedFromAutoblocks,
doAutoblock, updateTimestamp, getAutoblockExpiry, newFromTarget,
newListFromTarget.
* Split DatabaseBlock::getBlocksForIPList. Now
BlockManager::getBlocksForIPList() is responsible for XFF header
validation and trusted proxy handling. DatabaseBlockStore::
newListFromIPs() just does the queries and constructs the Block
objects.
* In DatabaseBlockStore::newFromRow() and doAutoblock(), use the
DatabaseBlock constructor instead of calling many setter methods. Add
constructor options decodedExpiry, decodedTimestamp, id,
parentBlockId and restrictions to support this.
* Move isExemptedFromAutoblocks() to its own service. Remove the cache
since in my testing with production eval.php, the WAN cache fetch is
10 times slower than just using the message cache, contradicting the
comment written in 2008.
* Fix AuthManagerTest which was previously passing an unrecognised
"restrictions" option to DatabaseBlock. Now that the option actually
works, we have to use the right type.
Bug: T255433
Change-Id: I5049e60be1681f67fcca133e569e315792dc42dd
2023-10-31 05:58:23 +00:00
|
|
|
* @internal Public to support deprecated method in DatabaseBlock
|
|
|
|
|
*
|
|
|
|
|
* @param array $ipChain List of IPs (strings), usually retrieved from the
|
|
|
|
|
* X-Forwarded-For header of the request
|
|
|
|
|
* @param bool $applySoftBlocks Include soft blocks (anonymous-only blocks). These
|
|
|
|
|
* should only block anonymous and temporary users.
|
|
|
|
|
* @param bool $fromPrimary Whether to query the primary or replica DB
|
2023-07-26 17:35:17 +00:00
|
|
|
* @return DatabaseBlock[]
|
|
|
|
|
*/
|
Move DatabaseBlock read query methods to DatabaseBlockStore
* Move to DatabaseBlockStore the DatabaseBlock methods newFromID,
getQueryInfo, getRangeCond, newFromRow, isExemptedFromAutoblocks,
doAutoblock, updateTimestamp, getAutoblockExpiry, newFromTarget,
newListFromTarget.
* Split DatabaseBlock::getBlocksForIPList. Now
BlockManager::getBlocksForIPList() is responsible for XFF header
validation and trusted proxy handling. DatabaseBlockStore::
newListFromIPs() just does the queries and constructs the Block
objects.
* In DatabaseBlockStore::newFromRow() and doAutoblock(), use the
DatabaseBlock constructor instead of calling many setter methods. Add
constructor options decodedExpiry, decodedTimestamp, id,
parentBlockId and restrictions to support this.
* Move isExemptedFromAutoblocks() to its own service. Remove the cache
since in my testing with production eval.php, the WAN cache fetch is
10 times slower than just using the message cache, contradicting the
comment written in 2008.
* Fix AuthManagerTest which was previously passing an unrecognised
"restrictions" option to DatabaseBlock. Now that the option actually
works, we have to use the right type.
Bug: T255433
Change-Id: I5049e60be1681f67fcca133e569e315792dc42dd
2023-10-31 05:58:23 +00:00
|
|
|
public function getBlocksForIPList( array $ipChain, bool $applySoftBlocks, bool $fromPrimary ) {
|
|
|
|
|
if ( $ipChain === [] ) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$ips = [];
|
|
|
|
|
foreach ( array_unique( $ipChain ) as $ipaddr ) {
|
|
|
|
|
// Discard invalid IP addresses. Since XFF can be spoofed and we do not
|
|
|
|
|
// necessarily trust the header given to us, make sure that we are only
|
|
|
|
|
// checking for blocks on well-formatted IP addresses (IPv4 and IPv6).
|
|
|
|
|
// Do not treat private IP spaces as special as it may be desirable for wikis
|
|
|
|
|
// to block those IP ranges in order to stop misbehaving proxies that spoof XFF.
|
|
|
|
|
if ( !IPUtils::isValid( $ipaddr ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// Don't check trusted IPs (includes local CDNs which will be in every request)
|
2023-10-31 07:57:33 +00:00
|
|
|
if ( $this->proxyLookup->isTrustedProxy( $ipaddr ) ) {
|
Move DatabaseBlock read query methods to DatabaseBlockStore
* Move to DatabaseBlockStore the DatabaseBlock methods newFromID,
getQueryInfo, getRangeCond, newFromRow, isExemptedFromAutoblocks,
doAutoblock, updateTimestamp, getAutoblockExpiry, newFromTarget,
newListFromTarget.
* Split DatabaseBlock::getBlocksForIPList. Now
BlockManager::getBlocksForIPList() is responsible for XFF header
validation and trusted proxy handling. DatabaseBlockStore::
newListFromIPs() just does the queries and constructs the Block
objects.
* In DatabaseBlockStore::newFromRow() and doAutoblock(), use the
DatabaseBlock constructor instead of calling many setter methods. Add
constructor options decodedExpiry, decodedTimestamp, id,
parentBlockId and restrictions to support this.
* Move isExemptedFromAutoblocks() to its own service. Remove the cache
since in my testing with production eval.php, the WAN cache fetch is
10 times slower than just using the message cache, contradicting the
comment written in 2008.
* Fix AuthManagerTest which was previously passing an unrecognised
"restrictions" option to DatabaseBlock. Now that the option actually
works, we have to use the right type.
Bug: T255433
Change-Id: I5049e60be1681f67fcca133e569e315792dc42dd
2023-10-31 05:58:23 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$ips[] = $ipaddr;
|
|
|
|
|
}
|
2023-10-31 07:57:33 +00:00
|
|
|
return $this->blockStore->newListFromIPs( $ips, $applySoftBlocks, $fromPrimary );
|
2023-07-26 17:35:17 +00:00
|
|
|
}
|
|
|
|
|
|
2019-06-17 09:56:49 +00:00
|
|
|
/**
|
2019-06-20 10:29:01 +00:00
|
|
|
* Given a list of blocks, return a list of unique blocks.
|
|
|
|
|
*
|
|
|
|
|
* This usually means that each block has a unique ID. For a block with ID null,
|
|
|
|
|
* if it's an autoblock, it will be filtered out if the parent block is present;
|
|
|
|
|
* if not, it is assumed to be a unique system block, and kept.
|
2019-06-17 09:56:49 +00:00
|
|
|
*
|
|
|
|
|
* @param AbstractBlock[] $blocks
|
|
|
|
|
* @return AbstractBlock[]
|
|
|
|
|
*/
|
2019-06-27 20:42:54 +00:00
|
|
|
private function getUniqueBlocks( array $blocks ) {
|
2019-06-20 10:29:01 +00:00
|
|
|
$systemBlocks = [];
|
|
|
|
|
$databaseBlocks = [];
|
|
|
|
|
|
2019-06-17 09:56:49 +00:00
|
|
|
foreach ( $blocks as $block ) {
|
2019-06-20 10:29:01 +00:00
|
|
|
if ( $block instanceof SystemBlock ) {
|
|
|
|
|
$systemBlocks[] = $block;
|
|
|
|
|
} elseif ( $block->getType() === DatabaseBlock::TYPE_AUTO ) {
|
2019-08-31 16:14:38 +00:00
|
|
|
/** @var DatabaseBlock $block */
|
|
|
|
|
'@phan-var DatabaseBlock $block';
|
2019-06-20 10:29:01 +00:00
|
|
|
if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
|
|
|
|
|
$databaseBlocks[$block->getParentBlockId()] = $block;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2021-10-25 19:15:52 +00:00
|
|
|
// @phan-suppress-next-line PhanTypeMismatchDimAssignment getId is not null here
|
2019-06-20 10:29:01 +00:00
|
|
|
$databaseBlocks[$block->getId()] = $block;
|
2019-06-17 09:56:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-06-20 10:29:01 +00:00
|
|
|
|
2019-07-24 15:27:52 +00:00
|
|
|
return array_values( array_merge( $systemBlocks, $databaseBlocks ) );
|
2019-06-17 09:56:49 +00:00
|
|
|
}
|
|
|
|
|
|
2019-04-05 19:13:17 +00:00
|
|
|
/**
|
2019-09-16 12:18:56 +00:00
|
|
|
* Try to load a block from an ID given in a cookie value.
|
|
|
|
|
*
|
|
|
|
|
* If the block is invalid, doesn't exist, or the cookie value is malformed, no
|
|
|
|
|
* block will be loaded. In these cases the cookie will either (1) be replaced
|
|
|
|
|
* with a valid cookie or (2) removed, next time trackBlockWithCookie is called.
|
2019-04-05 19:13:17 +00:00
|
|
|
*
|
|
|
|
|
* @param UserIdentity $user
|
2022-10-27 13:14:16 +00:00
|
|
|
* @param WebRequest $request
|
2022-11-09 00:11:22 +00:00
|
|
|
* @return DatabaseBlock|false The block object, or false if none could be loaded.
|
2019-04-05 19:13:17 +00:00
|
|
|
*/
|
|
|
|
|
private function getBlockFromCookieValue(
|
|
|
|
|
UserIdentity $user,
|
|
|
|
|
WebRequest $request
|
|
|
|
|
) {
|
2019-08-19 18:59:25 +00:00
|
|
|
$cookieValue = $request->getCookie( 'BlockID' );
|
2020-01-09 23:48:34 +00:00
|
|
|
if ( $cookieValue === null ) {
|
2019-08-19 18:59:25 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2019-04-05 19:13:17 +00:00
|
|
|
|
2019-08-19 18:59:25 +00:00
|
|
|
$blockCookieId = $this->getIdFromCookieValue( $cookieValue );
|
2020-01-09 23:48:34 +00:00
|
|
|
if ( $blockCookieId !== null ) {
|
2023-10-31 07:57:33 +00:00
|
|
|
$block = $this->blockStore->newFromID( $blockCookieId );
|
2019-06-26 10:04:50 +00:00
|
|
|
if (
|
|
|
|
|
$block instanceof DatabaseBlock &&
|
2019-08-26 23:21:04 +00:00
|
|
|
$this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
|
2019-06-26 10:04:50 +00:00
|
|
|
) {
|
|
|
|
|
return $block;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-05 19:13:17 +00:00
|
|
|
|
2019-06-26 10:04:50 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if the block loaded from the cookie should be applied.
|
|
|
|
|
*
|
|
|
|
|
* @param DatabaseBlock $block
|
|
|
|
|
* @param bool $isAnon The user is logged out
|
2021-12-26 11:13:47 +00:00
|
|
|
* @return bool The block should be applied
|
2019-06-26 10:04:50 +00:00
|
|
|
*/
|
|
|
|
|
private function shouldApplyCookieBlock( DatabaseBlock $block, $isAnon ) {
|
|
|
|
|
if ( !$block->isExpired() ) {
|
|
|
|
|
switch ( $block->getType() ) {
|
|
|
|
|
case DatabaseBlock::TYPE_IP:
|
|
|
|
|
case DatabaseBlock::TYPE_RANGE:
|
|
|
|
|
// If block is type IP or IP range, load only
|
|
|
|
|
// if user is not logged in (T152462)
|
2019-06-26 14:06:01 +00:00
|
|
|
return $isAnon &&
|
2022-04-26 15:48:03 +00:00
|
|
|
$this->options->get( MainConfigNames::CookieSetOnIpBlock );
|
2019-06-26 10:04:50 +00:00
|
|
|
case DatabaseBlock::TYPE_USER:
|
2019-06-26 14:06:01 +00:00
|
|
|
return $block->isAutoblocking() &&
|
2022-04-26 15:48:03 +00:00
|
|
|
$this->options->get( MainConfigNames::CookieSetOnAutoblock );
|
2019-06-26 10:04:50 +00:00
|
|
|
default:
|
|
|
|
|
return false;
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if an IP address is in the local proxy list
|
|
|
|
|
*
|
|
|
|
|
* @param string $ip
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
private function isLocallyBlockedProxy( $ip ) {
|
2022-04-26 15:48:03 +00:00
|
|
|
$proxyList = $this->options->get( MainConfigNames::ProxyList );
|
2019-06-26 14:06:01 +00:00
|
|
|
if ( !$proxyList ) {
|
2019-04-05 19:13:17 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-26 14:06:01 +00:00
|
|
|
if ( !is_array( $proxyList ) ) {
|
2019-04-05 19:13:17 +00:00
|
|
|
// Load values from the specified file
|
2019-06-26 14:06:01 +00:00
|
|
|
$proxyList = array_map( 'trim', file( $proxyList ) );
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
2019-07-24 17:17:36 +00:00
|
|
|
$proxyListIPSet = new IPSet( $proxyList );
|
2019-04-05 19:13:17 +00:00
|
|
|
return $proxyListIPSet->match( $ip );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether the given IP is in a DNS blacklist.
|
|
|
|
|
*
|
|
|
|
|
* @param string $ip IP to check
|
2021-03-19 16:12:50 +00:00
|
|
|
* @param bool $checkAllowed Whether to check $wgProxyWhitelist first
|
2019-04-05 19:13:17 +00:00
|
|
|
* @return bool True if blacklisted.
|
|
|
|
|
*/
|
2021-03-19 16:12:50 +00:00
|
|
|
public function isDnsBlacklisted( $ip, $checkAllowed = false ) {
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( !$this->options->get( MainConfigNames::EnableDnsBlacklist ) ||
|
|
|
|
|
( $checkAllowed && in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) )
|
2019-04-05 19:13:17 +00:00
|
|
|
) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-26 15:48:03 +00:00
|
|
|
return $this->inDnsBlacklist( $ip, $this->options->get( MainConfigNames::DnsBlacklistUrls ) );
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether the given IP is in a given DNS blacklist.
|
|
|
|
|
*
|
|
|
|
|
* @param string $ip IP to check
|
2020-10-28 10:01:33 +00:00
|
|
|
* @param string[] $bases URL of the DNS blacklist
|
2019-04-05 19:13:17 +00:00
|
|
|
* @return bool True if blacklisted.
|
|
|
|
|
*/
|
|
|
|
|
private function inDnsBlacklist( $ip, array $bases ) {
|
|
|
|
|
$found = false;
|
|
|
|
|
// @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
|
2019-06-25 18:53:15 +00:00
|
|
|
if ( IPUtils::isIPv4( $ip ) ) {
|
2019-04-05 19:13:17 +00:00
|
|
|
// Reverse IP, T23255
|
|
|
|
|
$ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
|
|
|
|
|
|
|
|
|
|
foreach ( $bases as $base ) {
|
|
|
|
|
// Make hostname
|
|
|
|
|
// If we have an access key, use that too (ProjectHoneypot, etc.)
|
|
|
|
|
$basename = $base;
|
|
|
|
|
if ( is_array( $base ) ) {
|
|
|
|
|
if ( count( $base ) >= 2 ) {
|
|
|
|
|
// Access key is 1, base URL is 0
|
2019-05-27 21:53:23 +00:00
|
|
|
$hostname = "{$base[1]}.$ipReversed.{$base[0]}";
|
2019-04-05 19:13:17 +00:00
|
|
|
} else {
|
2019-05-27 21:53:23 +00:00
|
|
|
$hostname = "$ipReversed.{$base[0]}";
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|
|
|
|
|
$basename = $base[0];
|
|
|
|
|
} else {
|
2019-05-27 21:53:23 +00:00
|
|
|
$hostname = "$ipReversed.$base";
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send query
|
2019-05-27 21:53:23 +00:00
|
|
|
$ipList = $this->checkHost( $hostname );
|
2019-04-05 19:13:17 +00:00
|
|
|
|
|
|
|
|
if ( $ipList ) {
|
2019-08-20 17:29:59 +00:00
|
|
|
$this->logger->info(
|
2020-10-21 03:42:43 +00:00
|
|
|
'Hostname {hostname} is {ipList}, it\'s a proxy says {basename}!',
|
|
|
|
|
[
|
|
|
|
|
'hostname' => $hostname,
|
|
|
|
|
'ipList' => $ipList[0],
|
|
|
|
|
'basename' => $basename,
|
|
|
|
|
]
|
2019-05-27 21:53:23 +00:00
|
|
|
);
|
2019-04-05 19:13:17 +00:00
|
|
|
$found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-20 17:29:59 +00:00
|
|
|
$this->logger->debug( "Requested $hostname, not found in $basename." );
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $found;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-27 21:53:23 +00:00
|
|
|
/**
|
|
|
|
|
* Wrapper for mocking in tests.
|
|
|
|
|
*
|
|
|
|
|
* @param string $hostname DNSBL query
|
2022-11-09 00:11:22 +00:00
|
|
|
* @return string[]|false IPv4 array, or false if the IP is not blacklisted
|
2019-05-27 21:53:23 +00:00
|
|
|
*/
|
|
|
|
|
protected function checkHost( $hostname ) {
|
|
|
|
|
return gethostbynamel( $hostname );
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-06 18:00:20 +00:00
|
|
|
/**
|
|
|
|
|
* Set the 'BlockID' cookie depending on block type and user authentication status.
|
|
|
|
|
*
|
2019-09-16 12:18:56 +00:00
|
|
|
* If a block cookie is already set, this will check the block that the cookie references
|
|
|
|
|
* and do the following:
|
2019-10-15 12:38:04 +00:00
|
|
|
* - If the block is a valid block that should be applied, do nothing and return early.
|
|
|
|
|
* This ensures that the cookie's expiry time is based on the time of the first page
|
|
|
|
|
* load or attempt. (See discussion on T233595.)
|
2019-09-16 12:18:56 +00:00
|
|
|
* - If the block is invalid (e.g. has expired), clear the cookie and continue to check
|
|
|
|
|
* whether there is another block that should be tracked.
|
|
|
|
|
* - If the block is a valid block, but should not be tracked by a cookie, clear the
|
|
|
|
|
* cookie and continue to check whether there is another block that should be tracked.
|
|
|
|
|
*
|
2019-06-06 18:00:20 +00:00
|
|
|
* @since 1.34
|
|
|
|
|
* @param User $user
|
2019-09-07 23:44:46 +00:00
|
|
|
* @param WebResponse $response The response on which to set the cookie.
|
|
|
|
|
* @throws LogicException If called before the User object was loaded.
|
|
|
|
|
* @throws LogicException If not called pre-send.
|
2019-06-06 18:00:20 +00:00
|
|
|
*/
|
2019-09-07 23:44:46 +00:00
|
|
|
public function trackBlockWithCookie( User $user, WebResponse $response ) {
|
2024-07-29 12:37:33 +00:00
|
|
|
if ( !$this->options->get( MainConfigNames::CookieSetOnIpBlock ) &&
|
|
|
|
|
!$this->options->get( MainConfigNames::CookieSetOnAutoblock ) ) {
|
|
|
|
|
// Cookie blocks are disabled, return early to prevent executing unnecessary logic.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-06 18:00:20 +00:00
|
|
|
$request = $user->getRequest();
|
2019-09-16 12:18:56 +00:00
|
|
|
|
2019-06-26 06:00:07 +00:00
|
|
|
if ( $request->getCookie( 'BlockID' ) !== null ) {
|
2019-09-16 12:18:56 +00:00
|
|
|
$cookieBlock = $this->getBlockFromCookieValue( $user, $request );
|
2019-10-15 12:38:04 +00:00
|
|
|
if ( $cookieBlock && $this->shouldApplyCookieBlock( $cookieBlock, $user->isAnon() ) ) {
|
2019-09-16 12:18:56 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// The block pointed to by the cookie is invalid or should not be tracked.
|
|
|
|
|
$this->clearBlockCookie( $response );
|
2019-06-26 06:00:07 +00:00
|
|
|
}
|
|
|
|
|
|
2019-09-07 23:44:46 +00:00
|
|
|
if ( !$user->isSafeToLoad() ) {
|
|
|
|
|
// Prevent a circular dependency by not allowing this method to be called
|
|
|
|
|
// before or while the user is being loaded.
|
|
|
|
|
// E.g. User > BlockManager > Block > Message > getLanguage > User.
|
|
|
|
|
// See also T180050 and T226777.
|
|
|
|
|
throw new LogicException( __METHOD__ . ' requires a loaded User object' );
|
|
|
|
|
}
|
|
|
|
|
if ( $response->headersSent() ) {
|
|
|
|
|
throw new LogicException( __METHOD__ . ' must be called pre-send' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$block = $user->getBlock();
|
|
|
|
|
$isAnon = $user->isAnon();
|
|
|
|
|
|
|
|
|
|
if ( $block ) {
|
2023-09-21 00:05:26 +00:00
|
|
|
foreach ( $block->toArray() as $originalBlock ) {
|
2019-09-07 23:44:46 +00:00
|
|
|
// TODO: Improve on simply tracking the first trackable block (T225654)
|
2023-09-21 00:05:26 +00:00
|
|
|
if ( $originalBlock instanceof DatabaseBlock
|
|
|
|
|
&& $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon )
|
|
|
|
|
) {
|
|
|
|
|
$this->setBlockCookie( $originalBlock, $response );
|
|
|
|
|
return;
|
2019-09-07 23:44:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-06 18:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the 'BlockID' cookie to this block's ID and expiry time. The cookie's expiry will be
|
|
|
|
|
* the same as the block's, to a maximum of 24 hours.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.34
|
|
|
|
|
* @param DatabaseBlock $block
|
|
|
|
|
* @param WebResponse $response The response on which to set the cookie.
|
|
|
|
|
*/
|
Blocks cleanup
* Make BlockManager internal methods private, since nothing calls them
anymore.
* In AbstractBlock and DatabaseBlock, remove deprecated public
properties mExpiry, mHideName, mTimestamp, mAuto and mParentBlockId.
* In BlockRestrictionStore, remove all the "instanceof Restriction"
checks. If someone passes in something that's not a Restriction, we
should throw, not ignore it, because we don't know the caller's
intention. Add a type declaration to $hasher in equals() so that it
will throw.
* Remove the "m" prefix from all private and protected properties.
AbstractBlock is not stable to override so this is not a stable
interface break.
* In BlockRestrictionStore::restrictionsToRemove(), use an O(N)
algorithm.
* In BlockRestrictionStore::rowToRestriction(), use a switch instead of
a type map, so that the calls are statically analyzable.
* In BlockUser::__construct(), fix the initialisation order issue by
inlining the relevant logic.
* Rename variable $actionRestriction.
* In Special:Block, fix call to deprecated method getTargetAndType(),
and hard deprecate it. @deprecated has the effect of deprecating a
method for both internal and external callers, there's no such thing
as an external-only deprecation. So it's necessary to rename it if you
want to keep it as a private method.
Bug: T345683
Change-Id: If4a4a18d7b5fec825417de81302266119c215fd3
2023-09-18 01:32:32 +00:00
|
|
|
private function setBlockCookie( DatabaseBlock $block, WebResponse $response ) {
|
2019-06-06 18:00:20 +00:00
|
|
|
// Calculate the default expiry time.
|
2019-09-15 15:12:06 +00:00
|
|
|
$maxExpiryTime = wfTimestamp( TS_MW, (int)wfTimestamp() + ( 24 * 60 * 60 ) );
|
2019-06-06 18:00:20 +00:00
|
|
|
|
|
|
|
|
// Use the block's expiry time only if it's less than the default.
|
|
|
|
|
$expiryTime = $block->getExpiry();
|
|
|
|
|
if ( $expiryTime === 'infinity' || $expiryTime > $maxExpiryTime ) {
|
|
|
|
|
$expiryTime = $maxExpiryTime;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-18 22:03:26 +00:00
|
|
|
// Set the cookie
|
|
|
|
|
$expiryValue = (int)wfTimestamp( TS_UNIX, $expiryTime );
|
2019-06-06 18:00:20 +00:00
|
|
|
$cookieOptions = [ 'httpOnly' => false ];
|
|
|
|
|
$cookieValue = $this->getCookieValue( $block );
|
|
|
|
|
$response->setCookie( 'BlockID', $cookieValue, $expiryValue, $cookieOptions );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if the block should be tracked with a cookie.
|
|
|
|
|
*
|
2023-09-21 00:05:26 +00:00
|
|
|
* @param DatabaseBlock $block
|
2019-06-06 18:00:20 +00:00
|
|
|
* @param bool $isAnon The user is logged out
|
2021-12-26 11:13:47 +00:00
|
|
|
* @return bool The block should be tracked with a cookie
|
2019-06-06 18:00:20 +00:00
|
|
|
*/
|
2023-09-21 00:05:26 +00:00
|
|
|
private function shouldTrackBlockWithCookie( DatabaseBlock $block, $isAnon ) {
|
|
|
|
|
switch ( $block->getType() ) {
|
|
|
|
|
case DatabaseBlock::TYPE_IP:
|
|
|
|
|
case DatabaseBlock::TYPE_RANGE:
|
|
|
|
|
return $isAnon && $this->options->get( MainConfigNames::CookieSetOnIpBlock );
|
|
|
|
|
case DatabaseBlock::TYPE_USER:
|
|
|
|
|
return !$isAnon &&
|
|
|
|
|
$this->options->get( MainConfigNames::CookieSetOnAutoblock ) &&
|
|
|
|
|
$block->isAutoblocking();
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
2019-06-06 18:00:20 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Unset the 'BlockID' cookie.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.34
|
|
|
|
|
* @param WebResponse $response
|
|
|
|
|
*/
|
|
|
|
|
public static function clearBlockCookie( WebResponse $response ) {
|
|
|
|
|
$response->clearCookie( 'BlockID', [ 'httpOnly' => false ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the stored ID from the 'BlockID' cookie. The cookie's value is usually a combination of
|
2023-10-31 07:57:33 +00:00
|
|
|
* the ID and a HMAC (see self::getCookieValue), but will sometimes only be the ID.
|
2019-06-06 18:00:20 +00:00
|
|
|
*
|
|
|
|
|
* @since 1.34
|
|
|
|
|
* @param string $cookieValue The string in which to find the ID.
|
|
|
|
|
* @return int|null The block ID, or null if the HMAC is present and invalid.
|
|
|
|
|
*/
|
Blocks cleanup
* Make BlockManager internal methods private, since nothing calls them
anymore.
* In AbstractBlock and DatabaseBlock, remove deprecated public
properties mExpiry, mHideName, mTimestamp, mAuto and mParentBlockId.
* In BlockRestrictionStore, remove all the "instanceof Restriction"
checks. If someone passes in something that's not a Restriction, we
should throw, not ignore it, because we don't know the caller's
intention. Add a type declaration to $hasher in equals() so that it
will throw.
* Remove the "m" prefix from all private and protected properties.
AbstractBlock is not stable to override so this is not a stable
interface break.
* In BlockRestrictionStore::restrictionsToRemove(), use an O(N)
algorithm.
* In BlockRestrictionStore::rowToRestriction(), use a switch instead of
a type map, so that the calls are statically analyzable.
* In BlockUser::__construct(), fix the initialisation order issue by
inlining the relevant logic.
* Rename variable $actionRestriction.
* In Special:Block, fix call to deprecated method getTargetAndType(),
and hard deprecate it. @deprecated has the effect of deprecating a
method for both internal and external callers, there's no such thing
as an external-only deprecation. So it's necessary to rename it if you
want to keep it as a private method.
Bug: T345683
Change-Id: If4a4a18d7b5fec825417de81302266119c215fd3
2023-09-18 01:32:32 +00:00
|
|
|
private function getIdFromCookieValue( $cookieValue ) {
|
2019-06-26 10:04:50 +00:00
|
|
|
// The cookie value must start with a number
|
|
|
|
|
if ( !is_numeric( substr( $cookieValue, 0, 1 ) ) ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-06 18:00:20 +00:00
|
|
|
// Extract the ID prefix from the cookie value (may be the whole value, if no bang found).
|
|
|
|
|
$bangPos = strpos( $cookieValue, '!' );
|
|
|
|
|
$id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
|
2019-06-06 18:00:20 +00:00
|
|
|
// If there's no secret key, just use the ID as given.
|
2019-09-15 15:12:06 +00:00
|
|
|
return (int)$id;
|
2019-06-06 18:00:20 +00:00
|
|
|
}
|
|
|
|
|
$storedHmac = substr( $cookieValue, $bangPos + 1 );
|
2022-04-26 15:48:03 +00:00
|
|
|
$calculatedHmac = MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ), false );
|
2019-06-06 18:00:20 +00:00
|
|
|
if ( $calculatedHmac === $storedHmac ) {
|
2019-09-15 15:12:06 +00:00
|
|
|
return (int)$id;
|
2019-06-06 18:00:20 +00:00
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the BlockID cookie's value for this block. This is usually the block ID concatenated
|
|
|
|
|
* with an HMAC in order to avoid spoofing (T152951), but if wgSecretKey is not set will just
|
|
|
|
|
* be the block ID.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.34
|
|
|
|
|
* @param DatabaseBlock $block
|
|
|
|
|
* @return string The block ID, probably concatenated with "!" and the HMAC.
|
|
|
|
|
*/
|
Blocks cleanup
* Make BlockManager internal methods private, since nothing calls them
anymore.
* In AbstractBlock and DatabaseBlock, remove deprecated public
properties mExpiry, mHideName, mTimestamp, mAuto and mParentBlockId.
* In BlockRestrictionStore, remove all the "instanceof Restriction"
checks. If someone passes in something that's not a Restriction, we
should throw, not ignore it, because we don't know the caller's
intention. Add a type declaration to $hasher in equals() so that it
will throw.
* Remove the "m" prefix from all private and protected properties.
AbstractBlock is not stable to override so this is not a stable
interface break.
* In BlockRestrictionStore::restrictionsToRemove(), use an O(N)
algorithm.
* In BlockRestrictionStore::rowToRestriction(), use a switch instead of
a type map, so that the calls are statically analyzable.
* In BlockUser::__construct(), fix the initialisation order issue by
inlining the relevant logic.
* Rename variable $actionRestriction.
* In Special:Block, fix call to deprecated method getTargetAndType(),
and hard deprecate it. @deprecated has the effect of deprecating a
method for both internal and external callers, there's no such thing
as an external-only deprecation. So it's necessary to rename it if you
want to keep it as a private method.
Bug: T345683
Change-Id: If4a4a18d7b5fec825417de81302266119c215fd3
2023-09-18 01:32:32 +00:00
|
|
|
private function getCookieValue( DatabaseBlock $block ) {
|
2022-03-18 15:05:06 +00:00
|
|
|
$id = (string)$block->getId();
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
|
2019-06-06 18:00:20 +00:00
|
|
|
// If there's no secret key, don't append a HMAC.
|
|
|
|
|
return $id;
|
|
|
|
|
}
|
2022-04-26 15:48:03 +00:00
|
|
|
$hmac = MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ), false );
|
2023-08-03 03:20:34 +00:00
|
|
|
return $id . '!' . $hmac;
|
2019-06-06 18:00:20 +00:00
|
|
|
}
|
2019-04-05 19:13:17 +00:00
|
|
|
}
|