Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.55% covered (success)
93.55%
58 / 62
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
GroupPermissionsLookup
93.55% covered (success)
93.55%
58 / 62
83.33% covered (warning)
83.33%
5 / 6
25.17
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 groupHasPermission
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
10
 getGrantedPermissions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getRevokedPermissions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getGroupPermissions
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
7
 getGroupsWithPermission
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
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 * A service class for looking up permissions bestowed to groups, groups bestowed with
28 * permissions, and permissions bestowed by membership in a combination of groups, solely
29 * according to site configuration for group permissions and inheritence thereof.
30 *
31 * This class does *not* account for implicit rights (which are not associated with groups).
32 * Callers might want to use {@see PermissionManager} if this is an issue.
33 *
34 * This class does *not* infer membership in one group (e.g. '*') from membership in another
35 * (e.g. 'user'). Callers must account for this when using {@see self::getGroupPermissions()}.
36 *
37 * @since 1.36
38 * @package MediaWiki\Permissions
39 */
40class GroupPermissionsLookup {
41
42    /**
43     * @internal
44     * @var string[]
45     */
46    public const CONSTRUCTOR_OPTIONS = [
47        MainConfigNames::GroupInheritsPermissions,
48        MainConfigNames::GroupPermissions,
49        MainConfigNames::RevokePermissions,
50    ];
51
52    /** @var array[] */
53    private $groupPermissions;
54
55    /** @var array[] */
56    private $revokePermissions;
57
58    /** @var string[] */
59    private $groupInheritance;
60
61    /**
62     * @param ServiceOptions $options
63     */
64    public function __construct( ServiceOptions $options ) {
65        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
66        $this->groupPermissions = $options->get( MainConfigNames::GroupPermissions );
67        $this->revokePermissions = $options->get( MainConfigNames::RevokePermissions );
68        $this->groupInheritance = $options->get( MainConfigNames::GroupInheritsPermissions );
69    }
70
71    /**
72     * Check, if the given group has the given permission
73     *
74     * If you're wanting to check whether all users have a permission,
75     * use PermissionManager::isEveryoneAllowed() instead.
76     * That properly checks if it's revoked from anyone.
77     *
78     * @param string $group Group to check
79     * @param string $permission Role to check
80     *
81     * @return bool
82     */
83    public function groupHasPermission( string $group, string $permission ): bool {
84        $inheritsFrom = $this->groupInheritance[$group] ?? false;
85        $has = isset( $this->groupPermissions[$group][$permission] ) &&
86            $this->groupPermissions[$group][$permission];
87        // If the group doesn't have the permission and inherits from somewhere,
88        // check that group too
89        if ( !$has && $inheritsFrom !== false ) {
90            $has = isset( $this->groupPermissions[$inheritsFrom][$permission] ) &&
91                $this->groupPermissions[$inheritsFrom][$permission];
92        }
93        if ( !$has ) {
94            // If they don't have the permission, exit early
95            return false;
96        }
97
98        // Check if the permission has been revoked
99        $revoked = isset( $this->revokePermissions[$group][$permission] ) &&
100            $this->revokePermissions[$group][$permission];
101        if ( !$revoked && $inheritsFrom !== false ) {
102            $revoked = isset( $this->revokePermissions[$inheritsFrom][$permission] ) &&
103                $this->revokePermissions[$inheritsFrom][$permission];
104        }
105
106        return !$revoked;
107    }
108
109    /**
110     * Get a list of permissions granted to this group. This
111     * must *NOT* be used for permissions checking as it
112     * does not check whether a permission has been revoked
113     * from this group.
114     *
115     * @param string $group Group to get permissions of
116     * @return string[]
117     * @since 1.38
118     */
119    public function getGrantedPermissions( string $group ): array {
120        $rights = array_keys( array_filter( $this->groupPermissions[$group] ?? [] ) );
121        $inheritsFrom = $this->groupInheritance[$group] ?? false;
122        if ( $inheritsFrom !== false ) {
123            $rights = array_merge(
124                $rights,
125                // array_filter removes empty items
126                array_keys( array_filter( $this->groupPermissions[$inheritsFrom] ?? [] ) )
127            );
128        }
129
130        return array_unique( $rights );
131    }
132
133    /**
134     * Get a list of permissions revoked from this group
135     *
136     * @param string $group Group to get revoked permissions of
137     * @return string[]
138     * @since 1.38
139     */
140    public function getRevokedPermissions( string $group ): array {
141        $rights = array_keys( array_filter( $this->revokePermissions[$group] ?? [] ) );
142        $inheritsFrom = $this->groupInheritance[$group] ?? false;
143        if ( $inheritsFrom !== false ) {
144            $rights = array_merge(
145                $rights,
146                // array_filter removes empty items
147                array_keys( array_filter( $this->revokePermissions[$inheritsFrom] ?? [] ) )
148            );
149        }
150
151        return array_unique( $rights );
152    }
153
154    /**
155     * Get the permissions associated with membership in a combination of groups
156     *
157     * Group-based revocation of a permission negates all group-based assignments of that
158     * permission.
159     *
160     * @param string[] $groups internal group names
161     * @return string[] permission key names for given groups combined
162     */
163    public function getGroupPermissions( array $groups ): array {
164        $rights = [];
165        $checkGroups = [];
166
167        // Add inherited groups to the list of groups to check
168        foreach ( $groups as $group ) {
169            $checkGroups[] = $group;
170            if ( isset( $this->groupInheritance[$group] ) ) {
171                $checkGroups[] = $this->groupInheritance[$group];
172            }
173        }
174
175        // grant every granted permission first
176        foreach ( $checkGroups as $group ) {
177            if ( isset( $this->groupPermissions[$group] ) ) {
178                $rights = array_merge(
179                    $rights,
180                    // array_filter removes empty items
181                    array_keys( array_filter( $this->groupPermissions[$group] ) )
182                );
183            }
184        }
185        // now revoke the revoked permissions
186        foreach ( $checkGroups as $group ) {
187            if ( isset( $this->revokePermissions[$group] ) ) {
188                $rights = array_diff(
189                    $rights,
190                    array_keys( array_filter( $this->revokePermissions[$group] ) )
191                );
192            }
193        }
194        return array_unique( $rights );
195    }
196
197    /**
198     * Get all the groups who have a given permission
199     *
200     * @param string $permission
201     * @return string[] internal group names with the given permission
202     */
203    public function getGroupsWithPermission( string $permission ): array {
204        $allowedGroups = [];
205        $groups = array_unique( array_merge(
206            array_keys( $this->groupPermissions ),
207            array_keys( $this->groupInheritance )
208        ) );
209        foreach ( $groups as $group ) {
210            if ( $this->groupHasPermission( $group, $permission ) ) {
211                $allowedGroups[] = $group;
212            }
213        }
214        return $allowedGroups;
215    }
216}