Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
GrantsInfo
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
8 / 8
19
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getValidGrants
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRightsByGrant
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getGrantRights
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 grantsAreValid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getGrantGroups
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 getHiddenGrants
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getRiskGroupsByGrant
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
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 */
20
21namespace MediaWiki\Permissions;
22
23use MediaWiki\Config\ServiceOptions;
24use MediaWiki\MainConfigNames;
25
26/**
27 * Users can authorize applications to use their account via OAuth. Grants are used to
28 * limit permissions for these application. This service allows application logic to
29 * access grants.
30 *
31 * @since 1.38
32 */
33class GrantsInfo {
34    /**
35     * Risk level classification for grants which aren't particularly risky. These grants might
36     * be abused, e.g. for vandalism, but the effect is easy to undo and the efficiency of abusing
37     * them isn't particularly different from registering new user accounts and using those for
38     * abuse.
39     * Note that risk levels depend on the use case; the default classification is meant for
40     * "normal" (public, open registration) wikis. Classification for e.g. a private wiki holding
41     * confidential information could be quite different.
42     */
43    public const RISK_LOW = 'low';
44
45    /**
46     * Risk level classification for grants which can be used for disruptive vandalism or other
47     * kinds of abuse that couldn't be achieved just by registering new accounts, such as main
48     * page vandalism, vandalism of popular templates, page merge vandalism, or blocks.
49     */
50    public const RISK_VANDALISM = 'vandalism';
51
52    /**
53     * Risk level classification for grants which can be used to cause damage that is hard or
54     * impossible to undo, such as exfiltrating sensitive private data or creating security
55     * vulnerabilities.
56     */
57    public const RISK_SECURITY = 'security';
58
59    /**
60     * Risk level classification for grants which are used for internal purposes and should not
61     * be handed out.
62     */
63    public const RISK_INTERNAL = 'internal';
64
65    /**
66     * @internal For use by ServiceWiring
67     */
68    public const CONSTRUCTOR_OPTIONS = [
69        MainConfigNames::GrantPermissions,
70        MainConfigNames::GrantPermissionGroups,
71        MainConfigNames::GrantRiskGroups,
72    ];
73
74    /** @var ServiceOptions */
75    private $options;
76
77    /**
78     * @param ServiceOptions $options
79     */
80    public function __construct(
81        ServiceOptions $options
82    ) {
83        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
84        $this->options = $options;
85    }
86
87    /**
88     * List all known grants.
89     * @return string[]
90     */
91    public function getValidGrants(): array {
92        return array_keys( $this->options->get( MainConfigNames::GrantPermissions ) );
93    }
94
95    /**
96     * Map all grants to corresponding user rights.
97     * @return string[][] grant => array of rights in the grant
98     */
99    public function getRightsByGrant(): array {
100        $res = [];
101        foreach ( $this->options->get( MainConfigNames::GrantPermissions ) as $grant => $rights ) {
102            $res[$grant] = array_keys( array_filter( $rights ) );
103        }
104        return $res;
105    }
106
107    /**
108     * Fetch the rights allowed by a set of grants.
109     * @param string[]|string $grants
110     * @return string[]
111     */
112    public function getGrantRights( $grants ): array {
113        $rights = [];
114        foreach ( (array)$grants as $grant ) {
115            if ( isset( $this->options->get( MainConfigNames::GrantPermissions )[$grant] ) ) {
116                $rights = array_merge(
117                    $rights,
118                    array_keys( array_filter( $this->options->get( MainConfigNames::GrantPermissions )[$grant] ) )
119                );
120            }
121        }
122        return array_unique( $rights );
123    }
124
125    /**
126     * Test that all grants in the list are known.
127     * @param string[] $grants
128     * @return bool
129     */
130    public function grantsAreValid( array $grants ): bool {
131        return array_diff( $grants, $this->getValidGrants() ) === [];
132    }
133
134    /**
135     * Divide the grants into groups.
136     * @param string[]|null $grantsFilter
137     * @return string[][] Map of (group => (grant list))
138     */
139    public function getGrantGroups( array $grantsFilter = null ): array {
140        if ( is_array( $grantsFilter ) ) {
141            $grantsFilter = array_fill_keys( $grantsFilter, true );
142        }
143
144        $groups = [];
145        foreach ( $this->options->get( MainConfigNames::GrantPermissions ) as $grant => $rights ) {
146            if ( $grantsFilter !== null && !isset( $grantsFilter[$grant] ) ) {
147                continue;
148            }
149            if ( isset( $this->options->get( MainConfigNames::GrantPermissionGroups )[$grant] ) ) {
150                $groups[$this->options->get( MainConfigNames::GrantPermissionGroups )[$grant]][] = $grant;
151            } else {
152                $groups['other'][] = $grant;
153            }
154        }
155
156        return $groups;
157    }
158
159    /**
160     * Get the list of grants that are hidden and should always be granted.
161     * @return string[]
162     */
163    public function getHiddenGrants(): array {
164        $grants = [];
165        foreach ( $this->options->get( MainConfigNames::GrantPermissionGroups ) as $grant => $group ) {
166            if ( $group === 'hidden' ) {
167                $grants[] = $grant;
168            }
169        }
170        return $grants;
171    }
172
173    /**
174     * Returns a map of grant name => risk group. The risk groups are the GrantsInfo::RISK_*
175     * constants, plus $default for grants where the risk level is not defined.
176     * @param string $default Default risk group to assign to grants for which no risk group
177     * is configured. $default does not have to be one of the RISK_* constants.
178     * @return string[]
179     * @since 1.42
180     */
181    public function getRiskGroupsByGrant( string $default = 'unknown' ): array {
182        $res = [];
183        $grantRiskGroups = $this->options->get( MainConfigNames::GrantRiskGroups );
184        foreach ( $this->options->get( MainConfigNames::GrantPermissions ) as $grant => $_ ) {
185            $res[$grant] = $grantRiskGroups[$grant] ?? $default;
186        }
187        return $res;
188    }
189}