Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 42
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 / 42
0.00% covered (danger)
0.00%
0 / 2
600
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 / 31
0.00% covered (danger)
0.00%
0 / 1
306
1<?php
2// phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
3
4namespace MediaWiki\Extension\AbuseFilter\Hooks\Handlers;
5
6use JsonContent;
7use MediaWiki\Content\Hook\JsonValidateSaveHook;
8use MediaWiki\Extension\AbuseFilter\BlockedDomainStorage;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\Page\PageIdentity;
11use MediaWiki\Permissions\Hook\GetUserPermissionsErrorsHook;
12use MediaWiki\Title\Title;
13use MediaWiki\Title\TitleValue;
14use MediaWiki\User\User;
15use MessageSpecifier;
16use StatusValue;
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() !== BlockedDomainStorage::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', BlockedDomainStorage::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.
78        if ( !$services->getMainConfig()->get( 'AbuseFilterEnableBlockedExternalDomain' ) ) {
79            return;
80        }
81
82        $title = TitleValue::newFromPage( $pageIdentity );
83        if ( !$title->inNamespace( NS_MEDIAWIKI ) || $title->getText() !== BlockedDomainStorage::TARGET_PAGE ) {
84            return;
85        }
86        $data = $content->getData()->getValue();
87
88        if ( !is_array( $data ) ) {
89            $status->fatal( 'abusefilter-blocked-domains-json-error' );
90            return;
91        }
92
93        $isValid = true;
94        $entryNumber = 0;
95        foreach ( $data as $element ) {
96            $entryNumber++;
97            // Check if each element is an object with all known fields, allow optional fields but no other fields
98            if ( is_object( $element ) && count( get_object_vars( $element ) ) >= count( self::JSON_OBJECT_FIELDS ) ) {
99                foreach ( self::JSON_OBJECT_FIELDS as $field ) {
100                    if ( !property_exists( $element, $field ) || !is_string( $element->{$field} ) ) {
101                        $isValid = false;
102                        break 2;
103                    }
104                }
105
106                foreach ( self::JSON_OPTIONAL_FIELDS as $field ) {
107                    if ( property_exists( $element, $field ) && !is_string( $element->{$field} ) ) {
108                        $isValid = false;
109                        break 2;
110                    }
111                }
112                foreach ( $element as $field => $value ) {
113                    if (
114                        !in_array( $field, array_merge( self::JSON_OPTIONAL_FIELDS, self::JSON_OBJECT_FIELDS ) )
115                    ) {
116                        $isValid = false;
117                        break;
118                    }
119                }
120            } else {
121                $isValid = false;
122                break;
123            }
124        }
125
126        if ( !$isValid ) {
127            $status->fatal( 'abusefilter-blocked-domains-invalid-entry', $entryNumber );
128        }
129    }
130
131}