Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
AbuseLogConditionFactory
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
3 / 3
5
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserFilterByIPAddress
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
3
 getUserFilterByUserIdentity
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter;
4
5use MediaWiki\User\TempUser\TempUserConfig;
6use MediaWiki\User\UserIdentity;
7use Wikimedia\IPUtils;
8use Wikimedia\Rdbms\IConnectionProvider;
9use Wikimedia\Rdbms\IExpression;
10
11/**
12 * Factory used for constructing query expressions for filtering records in
13 * the abuse_filter_log table.
14 *
15 * @since 1.46
16 */
17class AbuseLogConditionFactory {
18
19    public const SERVICE_NAME = 'AbuseLogConditionFactory';
20
21    public function __construct(
22        private readonly IConnectionProvider $dbProvider,
23        private readonly TempUserConfig $tempUserConfig,
24    ) {
25    }
26
27    /**
28     * Returns an expression for filtering out log entries not associated with a
29     * given IP or IP range.
30     *
31     * The condition selects the following records:
32     *
33     * - Log entries associated with legacy IP actors whose IP is $address.
34     *   Please note that, if $address is an IP range, legacy IP actors whose
35     *   IPs are included within said range would NOT be included (that is,
36     *   range lookups of anonymous legacy IP users are not supported).
37     *
38     * - Log entries associated with temporary accounts using the IP provided in
39     *   $address or, if $address is an IP range, using an IP included within
40     *   said range.
41     *
42     * @param string $address IP address or range to filter by
43     */
44    public function getUserFilterByIPAddress( string $address ): ?IExpression {
45        // Lookup for log entries associated with temp accounts where the
46        // IP matches the value provided (lookups for a single IP) or is
47        // included in the range provided (lookups for an IP range).
48
49        [ $rangeStart, $rangeEnd ] = IPUtils::parseRange( $address );
50
51        if ( $rangeStart === false ) {
52            // When the range is invalid, both $rangeStart and $rangeEnd
53            // fill be false: Return early in that case
54            return null;
55        }
56
57        $dbr = $this->dbProvider->getReplicaDatabase();
58        $tempAccountsMatchExpression = $this->tempUserConfig->getMatchCondition(
59            $dbr,
60            'afl_user_text',
61            IExpression::LIKE
62        );
63
64        if ( $rangeStart === $rangeEnd ) {
65            // If rangeStart is equal to rangeEnd, the target is actually an IP
66            // hex: Return temporary accounts or anonymous users matching the IP
67            $readableIPAddress = IPUtils::formatHex( $rangeStart );
68
69            return $dbr->orExpr( [
70                $dbr->expr( 'afl_ip_hex', '=', $rangeStart )
71                    ->andExpr( $tempAccountsMatchExpression ),
72                $dbr->expr( 'afl_user_text', '=', $readableIPAddress )
73                    ->and( 'afl_user', '=', 0 )
74            ] );
75        }
76
77        // parseRange() return false for both values if the input is invalid,
78        // both values set to the same IP it $address is a single IP, or
79        // different values if the input was indeed a valid IP range.
80        //
81        // For the latter case, query for temporary accounts that used any IP in
82        // that range, as range lookups of anonymous edits aren't supported.
83
84        return $dbr
85            ->expr( 'afl_ip_hex', '>=', $rangeStart )
86            ->and( 'afl_ip_hex', '<=', $rangeEnd )
87            ->andExpr( $tempAccountsMatchExpression );
88    }
89
90    /**
91     * Returns a query condition for filtering out log entries not associated
92     * with the provided user.
93     *
94     * Note that both the ID and username are used: For local users, the caller
95     * needs to know the local user's ID; for external users, the caller should
96     * provide the user ID as zero.
97     *
98     * @param UserIdentity $userIdentity User to filter by
99     * @return array List of conditions
100     */
101    public function getUserFilterByUserIdentity(
102        UserIdentity $userIdentity
103    ): array {
104        return [
105            'afl_user' => $userIdentity->getId(),
106            'afl_user_text' => $userIdentity->getName(),
107        ];
108    }
109}