Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
12.96% covered (danger)
12.96%
14 / 108
11.11% covered (danger)
11.11%
1 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
GlobalBlockingHooks
12.96% covered (danger)
12.96%
14 / 108
11.11% covered (danger)
11.11%
1 / 9
507.66
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 onGetUserBlock
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 onUserIsBlockedGlobally
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 onSpecialPasswordResetOnSubmit
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 onGetBlockErrorMessageKey
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
5.03
 onOtherBlockLogLink
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 onSpecialContributionsBeforeMainOutput
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
 onContributionsToolLinks
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
42
 onGetLogTypesOnUser
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\GlobalBlocking;
4
5use CentralIdLookup;
6use LogicException;
7use MediaWiki\Block\AbstractBlock;
8use MediaWiki\Block\Block;
9use MediaWiki\Block\CompositeBlock;
10use MediaWiki\Block\Hook\GetUserBlockHook;
11use MediaWiki\CommentFormatter\CommentFormatter;
12use MediaWiki\Config\Config;
13use MediaWiki\Extension\GlobalBlocking\Services\GlobalBlockingLinkBuilder;
14use MediaWiki\Extension\GlobalBlocking\Special\GlobalBlockListPager;
15use MediaWiki\Hook\ContributionsToolLinksHook;
16use MediaWiki\Hook\GetBlockErrorMessageKeyHook;
17use MediaWiki\Hook\GetLogTypesOnUserHook;
18use MediaWiki\Hook\OtherBlockLogLinkHook;
19use MediaWiki\Hook\SpecialContributionsBeforeMainOutputHook;
20use MediaWiki\Html\Html;
21use MediaWiki\Permissions\PermissionManager;
22use MediaWiki\SpecialPage\SpecialPage;
23use MediaWiki\Title\Title;
24use MediaWiki\User\Hook\SpecialPasswordResetOnSubmitHook;
25use MediaWiki\User\Hook\UserIsBlockedGloballyHook;
26use MediaWiki\User\User;
27use Message;
28use RequestContext;
29use Wikimedia\IPUtils;
30
31/**
32 * MediaWiki hook handlers for the GlobalBlocking extension
33 *
34 * @license GPL-2.0-or-later
35 */
36class GlobalBlockingHooks implements
37    GetUserBlockHook,
38    UserIsBlockedGloballyHook,
39    SpecialPasswordResetOnSubmitHook,
40    GetBlockErrorMessageKeyHook,
41    OtherBlockLogLinkHook,
42    SpecialContributionsBeforeMainOutputHook,
43    GetLogTypesOnUserHook,
44    ContributionsToolLinksHook
45{
46    private PermissionManager $permissionManager;
47    private Config $config;
48    private CommentFormatter $commentFormatter;
49    private CentralIdLookup $lookup;
50    private GlobalBlockingLinkBuilder $globalBlockLinkBuilder;
51
52    /**
53     * @param PermissionManager $permissionManager
54     * @param Config $mainConfig
55     * @param CommentFormatter $commentFormatter
56     * @param CentralIdLookup $lookup
57     * @param GlobalBlockingLinkBuilder $globalBlockLinkBuilder
58     */
59    public function __construct(
60        PermissionManager $permissionManager,
61        Config $mainConfig,
62        CommentFormatter $commentFormatter,
63        CentralIdLookup $lookup,
64        GlobalBlockingLinkBuilder $globalBlockLinkBuilder
65    ) {
66        $this->permissionManager = $permissionManager;
67        $this->config = $mainConfig;
68        $this->commentFormatter = $commentFormatter;
69        $this->lookup = $lookup;
70        $this->globalBlockLinkBuilder = $globalBlockLinkBuilder;
71    }
72
73    /**
74     * Add a global block. If there are any existing blocks, add
75     * the global block into a CompositeBlock.
76     *
77     * @param User $user
78     * @param string|null $ip null unless we're checking the session user
79     * @param AbstractBlock|null &$block
80     * @return bool
81     */
82    public function onGetUserBlock( $user, $ip, &$block ) {
83        if ( !$this->config->get( 'ApplyGlobalBlocks' ) ) {
84            return true;
85        }
86
87        $globalBlock = GlobalBlocking::getUserBlock( $user, $ip );
88        if ( !$globalBlock ) {
89            return true;
90        }
91
92        if ( !$block ) {
93            $block = $globalBlock;
94            return true;
95        }
96
97        // User is locally blocked and globally blocked. We need a CompositeBlock.
98        $allBlocks = $block->toArray();
99        $allBlocks[] = $globalBlock;
100        $block = new CompositeBlock( [
101            'address' => $ip ?? $user->getName(),
102            'reason' => new Message( 'blockedtext-composite-reason' ),
103            'originalBlocks' => $allBlocks,
104        ] );
105        return true;
106    }
107
108    /**
109     * @param User $user
110     * @param string $ip
111     * @param bool &$blocked
112     * @param AbstractBlock|null &$block
113     *
114     * @return bool
115     */
116    public function onUserIsBlockedGlobally( $user, $ip, &$blocked, &$block ) {
117        $block = GlobalBlocking::getUserBlock( $user, $ip );
118        if ( $block !== null ) {
119            $blocked = true;
120            return false;
121        }
122        return true;
123    }
124
125    /**
126     * @param array &$users
127     * @param array $data
128     * @param string &$error
129     *
130     * @return bool
131     */
132    public function onSpecialPasswordResetOnSubmit( &$users, $data, &$error ) {
133        $requestContext = RequestContext::getMain();
134
135        if ( GlobalBlocking::getUserBlockErrors(
136            $requestContext->getUser(),
137            $requestContext->getRequest()->getIP()
138        ) ) {
139            $error = 'globalblocking-blocked-nopassreset';
140            return false;
141        }
142        return true;
143    }
144
145    /**
146     * @param Block $block
147     * @param string &$key
148     *
149     * @return bool
150     */
151    public function onGetBlockErrorMessageKey( Block $block, string &$key ) {
152        if ( $block instanceof GlobalBlock ) {
153            if ( $block->getXff() ) {
154                $key = 'globalblocking-blockedtext-xff';
155            } elseif ( IPUtils::isValid( $block->getTargetName() ) ) {
156                $key = 'globalblocking-blockedtext-ip';
157            } elseif ( IPUtils::isValidRange( $block->getTargetName() ) ) {
158                $key = 'globalblocking-blockedtext-range';
159            } else {
160                $key = 'globalblocking-blockedtext-user';
161            }
162            return false;
163        }
164        return true;
165    }
166
167    /**
168     * Creates a link to the global block log
169     * @param array &$msg Message with a link to the global block log
170     * @param string $ip The IP address to be checked
171     *
172     * @return bool true
173     */
174    public function onOtherBlockLogLink( &$msg, $ip ) {
175        // Fast return if it is a username. IP addresses can be blocked only.
176        if ( !IPUtils::isIPAddress( $ip ) ) {
177            return true;
178        }
179
180        $block = GlobalBlocking::getGlobalBlockingBlock( $ip, true );
181        if ( !$block ) {
182            // Fast return if not globally blocked
183            return true;
184        }
185
186        $msg[] = Html::rawElement(
187            'span',
188            [ 'class' => 'mw-globalblock-loglink plainlinks' ],
189            wfMessage( 'globalblocking-loglink', $ip )->parse()
190        );
191        return true;
192    }
193
194    /**
195     * Show global block notice on Special:Contributions.
196     * @param int $userId
197     * @param User $user
198     * @param SpecialPage $sp
199     *
200     * @return bool
201     */
202    public function onSpecialContributionsBeforeMainOutput(
203        $userId, $user, $sp
204    ) {
205        $name = $user->getName();
206        if ( !IPUtils::isIPAddress( $name ) ) {
207            return true;
208        }
209
210        $block = GlobalBlocking::getGlobalBlockingBlock( $name, true );
211
212        if ( $block ) {
213            $conds = GlobalBlocking::getRangeCondition( $block->gb_address );
214            $pager = new GlobalBlockListPager(
215                $sp->getContext(),
216                $conds,
217                $sp->getLinkRenderer(),
218                $this->commentFormatter,
219                $this->lookup,
220                $this->globalBlockLinkBuilder
221            );
222            $body = $pager->formatRow( $block );
223
224            $out = $sp->getOutput();
225            $out->addHTML(
226                Html::warningBox(
227                    $sp->msg( 'globalblocking-contribs-notice', $name )->parseAsBlock() .
228                    Html::rawElement(
229                        'ul',
230                        [ 'class' => 'mw-logevent-loglines' ],
231                        $body
232                    ),
233                    'mw-warning-with-logexcerpt'
234                )
235            );
236        }
237
238        return true;
239    }
240
241    /**
242     * Adds a link on Special:Contributions to Special:GlobalBlock for privileged users.
243     * @param int $id User ID
244     * @param Title $title User page title
245     * @param array &$tools Tool links
246     * @param SpecialPage $sp Special page
247     * @return bool|void
248     */
249    public function onContributionsToolLinks(
250        $id, $title, &$tools, $sp
251    ) {
252        $user = $sp->getUser();
253        $linkRenderer = $sp->getLinkRenderer();
254        $ip = $title->getText();
255
256        if ( IPUtils::isIPAddress( $ip ) ) {
257            if ( IPUtils::isValidRange( $ip ) ) {
258                $target = IPUtils::sanitizeRange( $ip );
259            } else {
260                $target = IPUtils::sanitizeIP( $ip );
261            }
262            if ( $target === null ) {
263                throw new LogicException( 'IPUtils::sanitizeIP returned null for a valid IP' );
264            }
265            if ( $this->permissionManager->userHasRight( $user, 'globalblock' ) ) {
266                if ( GlobalBlocking::getGlobalBlockId( $ip ) === 0 ) {
267                    $tools['globalblock'] = $linkRenderer->makeKnownLink(
268                        SpecialPage::getTitleFor( 'GlobalBlock', $target ),
269                        $sp->msg( 'globalblocking-contribs-block' )->text()
270                    );
271                } else {
272                    $tools['globalblock'] = $linkRenderer->makeKnownLink(
273                        SpecialPage::getTitleFor( 'GlobalBlock', $target ),
274                        $sp->msg( 'globalblocking-contribs-modify' )->text()
275                    );
276
277                    $tools['globalunblock'] = $linkRenderer->makeKnownLink(
278                        SpecialPage::getTitleFor( 'RemoveGlobalBlock', $target ),
279                        $sp->msg( 'globalblocking-contribs-remove' )->text()
280                    );
281                }
282            }
283        }
284    }
285
286    /**
287     * So users can just type in a username for target and it'll work
288     * @param array &$types
289     * @return bool
290     */
291    public function onGetLogTypesOnUser( &$types ) {
292        $types[] = 'gblblock';
293
294        return true;
295    }
296}