Completed
Push — rbac ( 1ec5d5...5c33ef )
by Simon
04:39
created

RoleConfiguration::getApplicableRoles()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6.0493

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 8
nop 1
dl 0
loc 28
ccs 16
cts 18
cp 0.8889
crap 6.0493
rs 8.439
c 0
b 0
f 0
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\Security;
10
11
use Waca\Pages\PageBan;
12
use Waca\Pages\PageEditComment;
13
use Waca\Pages\PageEmailManagement;
14
use Waca\Pages\PageExpandedRequestList;
15
use Waca\Pages\PageLog;
16
use Waca\Pages\PageMain;
17
use Waca\Pages\PageOAuth;
18
use Waca\Pages\PagePreferences;
19
use Waca\Pages\PageSearch;
20
use Waca\Pages\PageSiteNotice;
21
use Waca\Pages\PageTeam;
22
use Waca\Pages\PageUserManagement;
23
use Waca\Pages\PageViewRequest;
24
use Waca\Pages\PageWelcomeTemplateManagement;
25
use Waca\Pages\RequestAction\PageBreakReservation;
26
use Waca\Pages\RequestAction\PageCloseRequest;
27
use Waca\Pages\RequestAction\PageComment;
28
use Waca\Pages\RequestAction\PageCustomClose;
29
use Waca\Pages\RequestAction\PageDeferRequest;
30
use Waca\Pages\RequestAction\PageDropRequest;
31
use Waca\Pages\RequestAction\PageReservation;
32
use Waca\Pages\RequestAction\PageSendToUser;
33
use Waca\Pages\Statistics\StatsFastCloses;
34
use Waca\Pages\Statistics\StatsInactiveUsers;
35
use Waca\Pages\Statistics\StatsMain;
36
use Waca\Pages\Statistics\StatsMonthlyStats;
37
use Waca\Pages\Statistics\StatsReservedRequests;
38
use Waca\Pages\Statistics\StatsTemplateStats;
39
use Waca\Pages\Statistics\StatsTopCreators;
40
use Waca\Pages\Statistics\StatsUsers;
41
42
class RoleConfiguration
43
{
44
    const ACCESS_ALLOW = 1;
45
    const ACCESS_DENY = -1;
46
    const ACCESS_DEFAULT = 0;
47
    const MAIN = 'main';
48
    const ALL = '*';
49
    /**
50
     * A map of roles to rights
51
     *
52
     * For example:
53
     *
54
     * array(
55
     *   'myrole' => array(
56
     *       PageMyPage::class => array(
57
     *           'edit' => self::ACCESS_ALLOW,
58
     *           'create' => self::ACCESS_DENY,
59
     *       )
60
     *   )
61
     * )
62
     *
63
     * Note that DENY takes precedence over everything else when roles are combined, followed by ALLOW, followed by
64
     * DEFAULT. Thus, if you have the following ([A]llow, [D]eny, [-] (default)) grants in different roles, this should
65
     * be the expected result:
66
     *
67
     * - (-,-,-) = - (default because nothing to explicitly say allowed or denied equates to a denial)
68
     * - (A,-,-) = A
69
     * - (D,-,-) = D
70
     * - (A,D,-) = D (deny takes precedence over allow)
71
     * - (A,A,A) = A (repetition has no effect)
72
     *
73
     * The public role is special, and is applied to all users automatically. Avoid using deny on this role.
74
     *
75
     * @var array
76
     */
77
    private $roleConfig = array(
78
        'public'            => array(
79
            /*
80
             * THIS ROLE IS GRANTED TO ALL LOGGED *OUT* USERS IMPLICITLY.
81
             *
82
             * USERS IN THIS ROLE DO NOT HAVE TO BE IDENTIFIED TO GET THE RIGHTS CONFERRED HERE.
83
             * DO NOT ADD ANY SECURITY-SENSITIVE RIGHTS HERE.
84
             */
85
            '_childRoles'    => array(
86
                'publicStats',
87
            ),
88
            PageOAuth::class => array(
89
                'callback' => self::ACCESS_ALLOW,
90
            ),
91
            PageTeam::class  => array(
92
                self::MAIN => self::ACCESS_ALLOW,
93
            ),
94
        ),
95
        'loggedIn'            => array(
96
            /*
97
             * THIS ROLE IS GRANTED TO ALL LOGGED IN USERS IMPLICITLY.
98
             *
99
             * USERS IN THIS ROLE DO NOT HAVE TO BE IDENTIFIED TO GET THE RIGHTS CONFERRED HERE.
100
             * DO NOT ADD ANY SECURITY-SENSITIVE RIGHTS HERE.
101
             */
102
            '_childRoles'    => array(
103
                'public',
104
            ),
105
            PagePreferences::class               => array(
106
                self::MAIN       => self::ACCESS_ALLOW,
107
                'changePassword' => self::ACCESS_ALLOW,
108
            ),
109
            PageOAuth::class                     => array(
110
                'attach' => self::ACCESS_ALLOW,
111
                'detach' => self::ACCESS_ALLOW,
112
            ),
113
        ),
114
        'user'              => array(
115
            '_description' => 'A standard tool user.',
116
            '_editableBy' => array('admin', 'toolRoot'),
117
            '_childRoles'                        => array(
118
                'internalStats',
119
            ),
120
            PageMain::class                      => array(
121
                self::MAIN => self::ACCESS_ALLOW,
122
            ),
123
            PageBan::class                       => array(
124
                self::MAIN => self::ACCESS_ALLOW,
125
            ),
126
            PageEditComment::class               => array(
127
                self::MAIN => self::ACCESS_ALLOW,
128
            ),
129
            PageEmailManagement::class           => array(
130
                self::MAIN => self::ACCESS_ALLOW,
131
                'view'     => self::ACCESS_ALLOW,
132
            ),
133
            PageExpandedRequestList::class       => array(
134
                self::MAIN => self::ACCESS_ALLOW,
135
            ),
136
            PageLog::class                       => array(
137
                self::MAIN => self::ACCESS_ALLOW,
138
            ),
139
            PageSearch::class                    => array(
140
                self::MAIN => self::ACCESS_ALLOW,
141
            ),
142
            PageWelcomeTemplateManagement::class => array(
143
                self::MAIN => self::ACCESS_ALLOW,
144
                'select'   => self::ACCESS_ALLOW,
145
                'view'     => self::ACCESS_ALLOW,
146
            ),
147
            PageViewRequest::class               => array(
148
                self::MAIN => self::ACCESS_ALLOW,
149
            ),
150
            'RequestData'                        => array(
151
                'seePrivateDataWhenReserved' => self::ACCESS_ALLOW,
152
                'seePrivateDataWithHash'     => self::ACCESS_ALLOW,
153
            ),
154
            PageCustomClose::class               => array(
155
                self::MAIN => self::ACCESS_ALLOW,
156
            ),
157
            PageComment::class                   => array(
158
                self::MAIN => self::ACCESS_ALLOW,
159
            ),
160
            PageCloseRequest::class              => array(
161
                self::MAIN => self::ACCESS_ALLOW,
162
            ),
163
            PageDeferRequest::class              => array(
164
                self::MAIN => self::ACCESS_ALLOW,
165
            ),
166
            PageDropRequest::class               => array(
167
                self::MAIN => self::ACCESS_ALLOW,
168
            ),
169
            PageReservation::class               => array(
170
                self::MAIN => self::ACCESS_ALLOW,
171
            ),
172
            PageSendToUser::class                => array(
173
                self::MAIN => self::ACCESS_ALLOW,
174
            ),
175
            PageBreakReservation::class          => array(
176
                self::MAIN => self::ACCESS_ALLOW,
177
            ),
178
179
        ),
180
        'admin'             => array(
181
            '_description' => 'A tool administrator.',
182
            '_editableBy' => array('admin', 'toolRoot'),
183
            '_childRoles'                        => array(
184
                'user', 'requestAdminTools',
185
            ),
186
            PageEmailManagement::class           => array(
187
                'edit'   => self::ACCESS_ALLOW,
188
                'create' => self::ACCESS_ALLOW,
189
            ),
190
            PageSiteNotice::class                => array(
191
                self::MAIN => self::ACCESS_ALLOW,
192
            ),
193
            PageUserManagement::class            => array(
194
                self::MAIN  => self::ACCESS_ALLOW,
195
                'approve'   => self::ACCESS_ALLOW,
196
                'decline'   => self::ACCESS_ALLOW,
197
                'rename'    => self::ACCESS_ALLOW,
198
                'editUser'  => self::ACCESS_ALLOW,
199
                'suspend'   => self::ACCESS_ALLOW,
200
                'editRoles' => self::ACCESS_ALLOW,
201
            ),
202
            PageWelcomeTemplateManagement::class => array(
203
                'edit'   => self::ACCESS_ALLOW,
204
                'delete' => self::ACCESS_ALLOW,
205
                'add'    => self::ACCESS_ALLOW,
206
            ),
207
        ),
208
        'checkuser'         => array(
209
            '_description' => 'A user with CheckUser access',
210
            '_editableBy' => array('checkuser', 'toolRoot'),
211
            '_childRoles'             => array(
212
                'user', 'requestAdminTools',
213
            ),
214
            PageUserManagement::class => array(
215
                self::MAIN  => self::ACCESS_ALLOW,
216
                'suspend'   => self::ACCESS_ALLOW,
217
                'editRoles' => self::ACCESS_ALLOW,
218
            ),
219
            'RequestData'             => array(
220
                'seeUserAgentData' => self::ACCESS_ALLOW,
221
            ),
222
        ),
223
        'toolRoot'         => array(
224
            '_description' => 'A user with shell access to the servers running the tool',
225
            '_editableBy' => array('toolRoot'),
226
            '_childRoles'             => array(
227
                'admin', 'checkuser',
228
            ),
229
        ),
230
231
        // Child roles go below this point
232
        'publicStats'       => array(
233
            '_hidden'               => true,
234
            StatsUsers::class       => array(
235
                self::MAIN => self::ACCESS_ALLOW,
236
                'detail'   => self::ACCESS_ALLOW,
237
            ),
238
            StatsTopCreators::class => array(
239
                self::MAIN => self::ACCESS_ALLOW,
240
            ),
241
        ),
242
        'internalStats'     => array(
243
            '_hidden'                    => true,
244
            StatsMain::class             => array(
245
                self::MAIN => self::ACCESS_ALLOW,
246
            ),
247
            StatsFastCloses::class       => array(
248
                self::MAIN => self::ACCESS_ALLOW,
249
            ),
250
            StatsInactiveUsers::class    => array(
251
                self::MAIN => self::ACCESS_ALLOW,
252
            ),
253
            StatsMonthlyStats::class     => array(
254
                self::MAIN => self::ACCESS_ALLOW,
255
            ),
256
            StatsReservedRequests::class => array(
257
                self::MAIN => self::ACCESS_ALLOW,
258
            ),
259
            StatsTemplateStats::class    => array(
260
                self::MAIN => self::ACCESS_ALLOW,
261
            ),
262
        ),
263
        'requestAdminTools' => array(
264
            '_hidden'                   => true,
265
            PageBan::class              => array(
266
                self::MAIN => self::ACCESS_ALLOW,
267
                'set'      => self::ACCESS_ALLOW,
268
                'remove'   => self::ACCESS_ALLOW,
269
            ),
270
            PageEditComment::class      => array(
271
                'editOthers' => self::ACCESS_ALLOW,
272
            ),
273
            PageBreakReservation::class => array(
274
                'force' => self::ACCESS_ALLOW,
275
            ),
276
            PageCustomClose::class      => array(
277
                'skipCcMailingList' => self::ACCESS_ALLOW,
278
            ),
279
            'RequestData'               => array(
280
                'reopenOldRequest'      => self::ACCESS_ALLOW,
281
                'alwaysSeePrivateData'  => self::ACCESS_ALLOW,
282
                'alwaysSeeHash'         => self::ACCESS_ALLOW,
283
                'seeRestrictedComments' => self::ACCESS_ALLOW,
284
            ),
285
        ),
286
    );
287
    /** @var array
288
     * List of roles which are *exempt* from the identification requirements
289
     *
290
     * Think twice about adding roles to this list.
291
     *
292
     * @category Security-Critical
293
     */
294
    private $identificationExempt = array('public', 'loggedIn');
295
296
    /**
297
     * RoleConfiguration constructor.
298
     *
299
     * @param array $roleConfig           Set to non-null to override the default configuration.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $roleConfig not be null|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
300
     * @param array $identificationExempt Set to non-null to override the default configuration.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $identificationExempt not be null|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
301
     */
302 6
    public function __construct(array $roleConfig = null, array $identificationExempt = null)
303
    {
304 6
        if ($roleConfig !== null) {
305 4
            $this->roleConfig = $roleConfig;
306 4
        }
307
308 6
        if ($identificationExempt !== null) {
309 4
            $this->identificationExempt = $identificationExempt;
310 4
        }
311 6
    }
312
313
    /**
314
     * @param array $roles The roles to check
315
     *
316
     * @return array
317
     */
318 3
    public function getApplicableRoles(array $roles)
319
    {
320 3
        $available = array();
321
322 3
        foreach ($roles as $role) {
323 3
            if (!isset($this->roleConfig[$role])) {
324
                // wat
325 1
                continue;
326
            }
327
328 3
            $available[$role] = $this->roleConfig[$role];
329
330 3
            if (isset($available[$role]['_childRoles'])) {
331 1
                $childRoles = self::getApplicableRoles($available[$role]['_childRoles']);
332 1
                $available = array_merge($available, $childRoles);
333
334 1
                unset($available[$role]['_childRoles']);
335 1
            }
336
337 3
            foreach (array('_hidden', '_editableBy', '_description') as $item) {
338 3
                if (isset($available[$role][$item])) {
339
                    unset($available[$role][$item]);
340
                }
341 3
            }
342 3
        }
343
344 3
        return $available;
345
    }
346
347 1
    public function getAvailableRoles()
348
    {
349 1
        $possible = array_diff(array_keys($this->roleConfig), array('public', 'loggedIn'));
350
351 1
        $actual = array();
352
353 1
        foreach ($possible as $role) {
354 1
            if (!isset($this->roleConfig[$role]['_hidden'])) {
355
                $actual[$role] = array(
356 1
                    'description' => $this->roleConfig[$role]['_description'],
357
                    'editableBy'  => $this->roleConfig[$role]['_editableBy'],
358
                );
359
            }
360
        }
361
362
        return $actual;
363
    }
364
365
    /**
366
     * @param string $role
367
     *
368
     * @return bool
369
     */
370
    public function roleNeedsIdentification($role)
371
    {
372
        if (in_array($role, $this->identificationExempt)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !in_array($role, ...>identificationExempt);.
Loading history...
373
            return false;
374
        }
375
376
        return true;
377
    }
378
}
379