Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.11% covered (success)
91.11%
41 / 45
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
BlockedDomainFilter
93.18% covered (success)
93.18%
41 / 44
33.33% covered (danger)
33.33%
1 / 3
11.04
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filter
93.55% covered (success)
93.55%
29 / 31
0.00% covered (danger)
0.00%
0 / 1
7.01
 logFilterHit
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20namespace MediaWiki\Extension\AbuseFilter\BlockedDomains;
21
22use MediaWiki\CheckUser\Services\CheckUserInsert;
23use MediaWiki\Extension\AbuseFilter\Variables\UnsetVariableException;
24use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
25use MediaWiki\Extension\AbuseFilter\Variables\VariablesManager;
26use MediaWiki\Logging\LogPage;
27use MediaWiki\Logging\ManualLogEntry;
28use MediaWiki\MediaWikiServices;
29use MediaWiki\Message\Message;
30use MediaWiki\Page\PageReference;
31use MediaWiki\Registration\ExtensionRegistry;
32use MediaWiki\Status\Status;
33use MediaWiki\User\UserIdentity;
34
35/**
36 * Filters blocked domains
37 *
38 * @ingroup SpecialPage
39 */
40class BlockedDomainFilter implements IBlockedDomainFilter {
41    public function __construct(
42        private readonly VariablesManager $variablesManager,
43        private readonly IBlockedDomainStorage $blockedDomainStorage
44    ) {
45    }
46
47    /** @inheritDoc */
48    public function filter( VariableHolder $vars, UserIdentity $user, PageReference $title ): Status {
49        // whether the feature is enabled is checked in ServiceWiring
50
51        $status = Status::newGood();
52        try {
53            $urls = $this->variablesManager->getVar( $vars, 'added_links', VariablesManager::GET_STRICT );
54        } catch ( UnsetVariableException ) {
55            return $status;
56        }
57
58        $addedDomains = [];
59        foreach ( $urls->toArray() as $addedUrl ) {
60            $parsedHost = parse_url( (string)$addedUrl->getData(), PHP_URL_HOST );
61            if ( !is_string( $parsedHost ) ) {
62                continue;
63            }
64            $parsedHost = strtolower( $parsedHost );
65            $addedDomains[$parsedHost] = true;
66            // Given that we block subdomains of blocked domains too
67            // pretend that all the higher-level domains are added as well
68            // so for foo.bar.com, you will have three domains to check:
69            // foo.bar.com, bar.com, and com
70            // This saves string search in the large list of blocked domains
71            // making it much faster.
72            $pos = 0;
73            // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition
74            while ( ( $pos = strpos( $parsedHost, '.', $pos ) ) !== false ) {
75                // Skip the dot we just found
76                $pos++;
77                // It should be a map, benchmark at https://phabricator.wikimedia.org/P48956
78                $addedDomains[ substr( $parsedHost, $pos ) ] = true;
79            }
80        }
81        if ( !$addedDomains ) {
82            return $status;
83        }
84        $blockedDomains = $this->blockedDomainStorage->loadComputed();
85        $blockedDomainsAdded = array_intersect_key( $addedDomains, $blockedDomains );
86        if ( !$blockedDomainsAdded ) {
87            return $status;
88        }
89        $blockedDomainsAdded = array_keys( $blockedDomainsAdded );
90        $error = Message::newFromSpecifier( 'abusefilter-blocked-domains-attempted' );
91        $error->params( Message::listParam( $blockedDomainsAdded ) );
92        $status->fatal( $error );
93        $this->logFilterHit(
94            $user,
95            $title,
96            implode( ' ', $blockedDomainsAdded )
97        );
98        return $status;
99    }
100
101    /**
102     * Logs the filter hit to Special:Log
103     *
104     * @param UserIdentity $user
105     * @param PageReference $title
106     * @param string $blockedDomain The blocked domain the user attempted to add
107     */
108    private function logFilterHit( UserIdentity $user, PageReference $title, string $blockedDomain ) {
109        $logEntry = new ManualLogEntry( 'abusefilterblockeddomainhit', 'hit' );
110        $logEntry->setPerformer( $user );
111        $logEntry->setTarget( $title );
112        $logEntry->setParameters( [ '4::blocked' => $blockedDomain ] );
113        $logid = $logEntry->insert();
114        $log = new LogPage( 'abusefilterblockeddomainhit' );
115        if ( $log->isRestricted() ) {
116            // Make sure checkusers can see this action if the log is restricted
117            // (which is the default)
118            if ( ExtensionRegistry::getInstance()->isLoaded( 'CheckUser' ) ) {
119                $rc = $logEntry->getRecentChange( $logid );
120                /** @var CheckUserInsert $checkUserInsert */
121                $checkUserInsert = MediaWikiServices::getInstance()->get( 'CheckUserInsert' );
122                $checkUserInsert->updateCheckUserData( $rc );
123            }
124        } else {
125            // If the log is unrestricted, publish normally to RC,
126            // which will also update checkuser
127            $logEntry->publish( $logid, "rc" );
128        }
129    }
130}
131
132// @deprecated since 1.44
133class_alias( BlockedDomainFilter::class, 'MediaWiki\Extension\AbuseFilter\BlockedDomainFilter' );