Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
EditPermissionHandler
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 2
650
0.00% covered (danger)
0.00%
0 / 1
 onGetUserPermissionsErrors
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 onJsonValidateSave
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
342
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\Hooks\Handlers;
4
5use MediaWiki\Content\Hook\JsonValidateSaveHook;
6use MediaWiki\Content\JsonContent;
7use MediaWiki\Extension\AbuseFilter\BlockedDomains\CustomBlockedDomainStorage;
8use MediaWiki\MediaWikiServices;
9use MediaWiki\Page\PageIdentity;
10use MediaWiki\Permissions\Hook\GetUserPermissionsErrorsHook;
11use MediaWiki\Registration\ExtensionRegistry;
12use MediaWiki\Title\Title;
13use MediaWiki\Title\TitleValue;
14use MediaWiki\User\User;
15use StatusValue;
16use Wikimedia\Message\MessageSpecifier;
17
18/**
19 * This hook handler is for very simple checks, rather than the much more advanced ones
20 * undertaken by the FilteredActionsHandler.
21 */
22class EditPermissionHandler implements GetUserPermissionsErrorsHook, JsonValidateSaveHook {
23
24    /** @var string[] */
25    private const JSON_OBJECT_FIELDS = [
26        'domain',
27        'notes'
28    ];
29
30    private const JSON_OPTIONAL_FIELDS = [ 'addedBy' ];
31
32    /**
33     * @see https://www.mediawiki.org/wiki/Manual:Hooks/getUserPermissionsErrors
34     *
35     * @param Title $title
36     * @param User $user
37     * @param string $action
38     * @param array|string|MessageSpecifier &$result
39     * @return bool|void
40     */
41    public function onGetUserPermissionsErrors( $title, $user, $action, &$result ) {
42        $services = MediaWikiServices::getInstance();
43
44        // Only do anything if we're enabled on this wiki.
45        if ( !$services->getMainConfig()->get( 'AbuseFilterEnableBlockedExternalDomain' ) ) {
46            return;
47        }
48
49        // Ignore all actions and pages except MediaWiki: edits (and creates)
50        // to the page we care about
51        if (
52            !( $action == 'create' || $action == 'edit' ) ||
53            !$title->inNamespace( NS_MEDIAWIKI ) ||
54            $title->getDBkey() !== CustomBlockedDomainStorage::TARGET_PAGE
55        ) {
56            return;
57        }
58
59        if ( $services->getPermissionManager()->userHasRight( $user, 'editinterface' ) ) {
60            return;
61        }
62
63        // Prohibit direct actions on our page.
64        $result = [ 'abusefilter-blocked-domains-cannot-edit-directly', CustomBlockedDomainStorage::TARGET_PAGE ];
65        return false;
66    }
67
68    /**
69     * @param JsonContent $content
70     * @param PageIdentity $pageIdentity
71     * @param StatusValue $status
72     * @return bool|void
73     */
74    public function onJsonValidateSave( JsonContent $content, PageIdentity $pageIdentity, StatusValue $status ) {
75        $services = MediaWikiServices::getInstance();
76
77        // Only do anything if we're enabled on this wiki as a standalone tool
78        if (
79            !$services->getMainConfig()->get( 'AbuseFilterEnableBlockedExternalDomain' ) ||
80            ExtensionRegistry::getInstance()->isLoaded( 'CommunityConfiguration' )
81        ) {
82            return;
83        }
84
85        $title = TitleValue::newFromPage( $pageIdentity );
86        if ( !$title->inNamespace( NS_MEDIAWIKI ) || $title->getText() !== CustomBlockedDomainStorage::TARGET_PAGE ) {
87            return;
88        }
89        $data = $content->getData()->getValue();
90
91        if ( !is_array( $data ) ) {
92            $status->fatal( 'abusefilter-blocked-domains-json-error' );
93            return;
94        }
95
96        $isValid = true;
97        $entryNumber = 0;
98        foreach ( $data as $element ) {
99            $entryNumber++;
100            // Check if each element is an object with all known fields, allow optional fields but no other fields
101            if ( is_object( $element ) && count( get_object_vars( $element ) ) >= count( self::JSON_OBJECT_FIELDS ) ) {
102                foreach ( self::JSON_OBJECT_FIELDS as $field ) {
103                    if ( !property_exists( $element, $field ) || !is_string( $element->{$field} ) ) {
104                        $isValid = false;
105                        break 2;
106                    }
107                }
108
109                foreach ( self::JSON_OPTIONAL_FIELDS as $field ) {
110                    if ( property_exists( $element, $field ) && !is_string( $element->{$field} ) ) {
111                        $isValid = false;
112                        break 2;
113                    }
114                }
115                foreach ( $element as $field => $value ) {
116                    if (
117                        !in_array( $field, array_merge( self::JSON_OPTIONAL_FIELDS, self::JSON_OBJECT_FIELDS ) )
118                    ) {
119                        $isValid = false;
120                        break;
121                    }
122                }
123            } else {
124                $isValid = false;
125                break;
126            }
127        }
128
129        if ( !$isValid ) {
130            $status->fatal( 'abusefilter-blocked-domains-invalid-entry', $entryNumber );
131        }
132    }
133
134}