Completed
Push — search ( 076ba7...2a5267 )
by Simon
16:36 queued 11:48
created

RoleConfiguration   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 389
Duplicated Lines 0 %

Test Coverage

Coverage 84.85%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 14
eloc 215
c 1
b 0
f 0
dl 0
loc 389
ccs 28
cts 33
cp 0.8485
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 3
A roleNeedsIdentification() 0 7 2
A getAvailableRoles() 0 16 3
A getApplicableRoles() 0 27 6
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\DataObjects\User;
12
use Waca\Pages\PageBan;
13
use Waca\Pages\PageEditComment;
14
use Waca\Pages\PageEmailManagement;
15
use Waca\Pages\PageExpandedRequestList;
16
use Waca\Pages\PageJobQueue;
17
use Waca\Pages\PageLog;
18
use Waca\Pages\PageMain;
19
use Waca\Pages\PageXffDemo;
20
use Waca\Pages\RequestAction\PageCreateRequest;
21
use Waca\Pages\UserAuth\PageChangePassword;
22
use Waca\Pages\UserAuth\MultiFactor\PageMultiFactor;
23
use Waca\Pages\UserAuth\PageOAuth;
24
use Waca\Pages\UserAuth\PagePreferences;
25
use Waca\Pages\PageSearch;
26
use Waca\Pages\PageSiteNotice;
27
use Waca\Pages\PageTeam;
28
use Waca\Pages\PageUserManagement;
29
use Waca\Pages\PageViewRequest;
30
use Waca\Pages\PageWelcomeTemplateManagement;
31
use Waca\Pages\RequestAction\PageBreakReservation;
32
use Waca\Pages\RequestAction\PageCloseRequest;
33
use Waca\Pages\RequestAction\PageComment;
34
use Waca\Pages\RequestAction\PageCustomClose;
35
use Waca\Pages\RequestAction\PageDeferRequest;
36
use Waca\Pages\RequestAction\PageDropRequest;
37
use Waca\Pages\RequestAction\PageReservation;
38
use Waca\Pages\RequestAction\PageSendToUser;
39
use Waca\Pages\Statistics\StatsFastCloses;
40
use Waca\Pages\Statistics\StatsInactiveUsers;
41
use Waca\Pages\Statistics\StatsMain;
42
use Waca\Pages\Statistics\StatsMonthlyStats;
43
use Waca\Pages\Statistics\StatsReservedRequests;
44
use Waca\Pages\Statistics\StatsTemplateStats;
45
use Waca\Pages\Statistics\StatsTopCreators;
46
use Waca\Pages\Statistics\StatsUsers;
47
48
class RoleConfiguration
49
{
50
    const ACCESS_ALLOW = 1;
51
    const ACCESS_DENY = -1;
52
    const ACCESS_DEFAULT = 0;
53
    const MAIN = 'main';
54
    const ALL = '*';
55
    /**
56
     * A map of roles to rights
57
     *
58
     * For example:
59
     *
60
     * array(
61
     *   'myrole' => array(
62
     *       PageMyPage::class => array(
63
     *           'edit' => self::ACCESS_ALLOW,
64
     *           'create' => self::ACCESS_DENY,
65
     *       )
66
     *   )
67
     * )
68
     *
69
     * Note that DENY takes precedence over everything else when roles are combined, followed by ALLOW, followed by
70
     * DEFAULT. Thus, if you have the following ([A]llow, [D]eny, [-] (default)) grants in different roles, this should
71
     * be the expected result:
72
     *
73
     * - (-,-,-) = - (default because nothing to explicitly say allowed or denied equates to a denial)
74
     * - (A,-,-) = A
75
     * - (D,-,-) = D
76
     * - (A,D,-) = D (deny takes precedence over allow)
77
     * - (A,A,A) = A (repetition has no effect)
78
     *
79
     * The public role is special, and is applied to all users automatically. Avoid using deny on this role.
80
     *
81
     * @var array
82
     */
83
    private $roleConfig = array(
84
        'public'            => array(
85
            /*
86
             * THIS ROLE IS GRANTED TO ALL LOGGED *OUT* USERS IMPLICITLY.
87
             *
88
             * USERS IN THIS ROLE DO NOT HAVE TO BE IDENTIFIED TO GET THE RIGHTS CONFERRED HERE.
89
             * DO NOT ADD ANY SECURITY-SENSITIVE RIGHTS HERE.
90
             */
91
            '_childRoles'   => array(
92
                'publicStats',
93
            ),
94
            PageTeam::class => array(
95
                self::MAIN => self::ACCESS_ALLOW,
96
            ),
97
            PageXffDemo::class        => array(
98
                self::MAIN  => self::ACCESS_ALLOW,
99
            )
100
        ),
101
        'loggedIn'          => array(
102
            /*
103
             * THIS ROLE IS GRANTED TO ALL LOGGED IN USERS IMPLICITLY.
104
             *
105
             * USERS IN THIS ROLE DO NOT HAVE TO BE IDENTIFIED TO GET THE RIGHTS CONFERRED HERE.
106
             * DO NOT ADD ANY SECURITY-SENSITIVE RIGHTS HERE.
107
             */
108
            '_childRoles'             => array(
109
                'public',
110
            ),
111
            PagePreferences::class    => array(
112
                self::MAIN => self::ACCESS_ALLOW,
113
                'refreshOAuth' => self::ACCESS_ALLOW,
114
            ),
115
            PageChangePassword::class => array(
116
                self::MAIN => self::ACCESS_ALLOW,
117
            ),
118
            PageMultiFactor::class    => array(
119
                self::MAIN          => self::ACCESS_ALLOW,
120
                'scratch'           => self::ACCESS_ALLOW,
121
                'enableYubikeyOtp'  => self::ACCESS_ALLOW,
122
                'disableYubikeyOtp' => self::ACCESS_ALLOW,
123
                'enableTotp'        => self::ACCESS_ALLOW,
124
                'disableTotp'       => self::ACCESS_ALLOW,
125
            ),
126
            PageOAuth::class          => array(
127
                'attach' => self::ACCESS_ALLOW,
128
                'detach' => self::ACCESS_ALLOW,
129
            ),
130
        ),
131
        'user'              => array(
132
            '_description'                       => 'A standard tool user.',
133
            '_editableBy'                        => array('admin', 'toolRoot'),
134
            '_childRoles'                        => array(
135
                'internalStats',
136
            ),
137
            PageMain::class                      => array(
138
                self::MAIN => self::ACCESS_ALLOW,
139
            ),
140
            PageBan::class                       => array(
141
                self::MAIN => self::ACCESS_ALLOW,
142
            ),
143
            PageEditComment::class               => array(
144
                self::MAIN => self::ACCESS_ALLOW,
145
            ),
146
            PageEmailManagement::class           => array(
147
                self::MAIN => self::ACCESS_ALLOW,
148
                'view'     => self::ACCESS_ALLOW,
149
            ),
150
            PageExpandedRequestList::class       => array(
151
                self::MAIN => self::ACCESS_ALLOW,
152
            ),
153
            PageLog::class                       => array(
154
                self::MAIN => self::ACCESS_ALLOW,
155
            ),
156
            PageSearch::class                    => array(
157
                self::MAIN => self::ACCESS_ALLOW,
158
            ),
159
            PageWelcomeTemplateManagement::class => array(
160
                self::MAIN => self::ACCESS_ALLOW,
161
                'select'   => self::ACCESS_ALLOW,
162
                'view'     => self::ACCESS_ALLOW,
163
            ),
164
            PageViewRequest::class               => array(
165
                self::MAIN       => self::ACCESS_ALLOW,
166
                'seeAllRequests' => self::ACCESS_ALLOW,
167
            ),
168
            'RequestData'                        => array(
169
                'seePrivateDataWhenReserved' => self::ACCESS_ALLOW,
170
                'seePrivateDataWithHash'     => self::ACCESS_ALLOW,
171
                'seeRelatedRequests'         => self::ACCESS_ALLOW,
172
            ),
173
            PageCustomClose::class               => array(
174
                self::MAIN => self::ACCESS_ALLOW,
175
            ),
176
            PageComment::class                   => array(
177
                self::MAIN => self::ACCESS_ALLOW,
178
            ),
179
            PageCloseRequest::class              => array(
180
                self::MAIN => self::ACCESS_ALLOW,
181
            ),
182
            PageCreateRequest::class             => array(
183
                self::MAIN => self::ACCESS_ALLOW,
184
            ),
185
            PageDeferRequest::class              => array(
186
                self::MAIN => self::ACCESS_ALLOW,
187
            ),
188
            PageDropRequest::class               => array(
189
                self::MAIN => self::ACCESS_ALLOW,
190
            ),
191
            PageReservation::class               => array(
192
                self::MAIN => self::ACCESS_ALLOW,
193
            ),
194
            PageSendToUser::class                => array(
195
                self::MAIN => self::ACCESS_ALLOW,
196
            ),
197
            PageBreakReservation::class          => array(
198
                self::MAIN => self::ACCESS_ALLOW,
199
            ),
200
            PageJobQueue::class                  => array(
201
                self::MAIN => self::ACCESS_ALLOW,
202
                'view'     => self::ACCESS_ALLOW,
203
                'all'      => self::ACCESS_ALLOW,
204
            ),
205
            'RequestCreation'                    => array(
206
                User::CREATION_MANUAL => self::ACCESS_ALLOW,
207
            ),
208
            'GlobalInfo'                         => array(
209
                'viewSiteNotice' => self::ACCESS_ALLOW,
210
                'viewOnlineUsers' => self::ACCESS_ALLOW,
211
            ),
212
        ),
213
        'admin'             => array(
214
            '_description'                       => 'A tool administrator.',
215
            '_editableBy'                        => array('admin', 'toolRoot'),
216
            '_childRoles'                        => array(
217
                'user',
218
                'requestAdminTools',
219
            ),
220
            PageEmailManagement::class           => array(
221
                'edit'   => self::ACCESS_ALLOW,
222
                'create' => self::ACCESS_ALLOW,
223
            ),
224
            PageSiteNotice::class                => array(
225
                self::MAIN => self::ACCESS_ALLOW,
226
            ),
227
            PageUserManagement::class            => array(
228
                self::MAIN  => self::ACCESS_ALLOW,
229
                'approve'   => self::ACCESS_ALLOW,
230
                'decline'   => self::ACCESS_ALLOW,
231
                'rename'    => self::ACCESS_ALLOW,
232
                'editUser'  => self::ACCESS_ALLOW,
233
                'suspend'   => self::ACCESS_ALLOW,
234
                'editRoles' => self::ACCESS_ALLOW,
235
            ),
236
            PageWelcomeTemplateManagement::class => array(
237
                'edit'   => self::ACCESS_ALLOW,
238
                'delete' => self::ACCESS_ALLOW,
239
                'add'    => self::ACCESS_ALLOW,
240
            ),
241
            PageJobQueue::class                  => array(
242
                'acknowledge' => self::ACCESS_ALLOW,
243
                'requeue'     => self::ACCESS_ALLOW,
244
            ),
245
        ),
246
        'checkuser'         => array(
247
            '_description'            => 'A user with CheckUser access',
248
            '_editableBy'             => array('checkuser', 'toolRoot'),
249
            '_childRoles'             => array(
250
                'user',
251
                'requestAdminTools',
252
            ),
253
            PageUserManagement::class => array(
254
                self::MAIN  => self::ACCESS_ALLOW,
255
                'suspend'   => self::ACCESS_ALLOW,
256
                'editRoles' => self::ACCESS_ALLOW,
257
            ),
258
            'RequestData'             => array(
259
                'seeUserAgentData' => self::ACCESS_ALLOW,
260
            ),
261
        ),
262
        'toolRoot'          => array(
263
            '_description' => 'A user with shell access to the servers running the tool',
264
            '_editableBy'  => array('toolRoot'),
265
            '_childRoles'  => array(
266
                'admin',
267
            ),
268
            PageMultiFactor::class => array(
269
                'enableU2F'         => self::ACCESS_ALLOW,
270
                'disableU2F'        => self::ACCESS_ALLOW,
271
            )
272
        ),
273
        'botCreation'       => array(
274
            '_description'    => 'A user allowed to use the bot to perform account creations',
275
            '_editableBy'     => array('admin', 'toolRoot'),
276
            '_childRoles'     => array(),
277
            'RequestCreation' => array(
278
                User::CREATION_BOT => self::ACCESS_ALLOW,
279
            ),
280
        ),
281
        'oauthCreation'       => array(
282
            '_description'    => 'A user allowed to use the OAuth to perform account creations',
283
            '_editableBy'     => array('admin', 'toolRoot'),
284
            '_childRoles'     => array(),
285
            'RequestCreation'                    => array(
286
                User::CREATION_OAUTH  => self::ACCESS_ALLOW,
287
            ),
288
        ),
289
290
291
        // Child roles go below this point
292
        'publicStats'       => array(
293
            '_hidden'               => true,
294
            StatsUsers::class       => array(
295
                self::MAIN => self::ACCESS_ALLOW,
296
                'detail'   => self::ACCESS_ALLOW,
297
            ),
298
            StatsTopCreators::class => array(
299
                self::MAIN => self::ACCESS_ALLOW,
300
            ),
301
        ),
302
        'internalStats'     => array(
303
            '_hidden'                    => true,
304
            StatsMain::class             => array(
305
                self::MAIN => self::ACCESS_ALLOW,
306
            ),
307
            StatsFastCloses::class       => array(
308
                self::MAIN => self::ACCESS_ALLOW,
309
            ),
310
            StatsInactiveUsers::class    => array(
311
                self::MAIN => self::ACCESS_ALLOW,
312
            ),
313
            StatsMonthlyStats::class     => array(
314
                self::MAIN => self::ACCESS_ALLOW,
315
            ),
316
            StatsReservedRequests::class => array(
317
                self::MAIN => self::ACCESS_ALLOW,
318
            ),
319
            StatsTemplateStats::class    => array(
320
                self::MAIN => self::ACCESS_ALLOW,
321
            ),
322
        ),
323
        'requestAdminTools' => array(
324
            '_hidden'                   => true,
325
            PageBan::class              => array(
326
                self::MAIN => self::ACCESS_ALLOW,
327
                'set'      => self::ACCESS_ALLOW,
328
                'remove'   => self::ACCESS_ALLOW,
329
            ),
330
            PageEditComment::class      => array(
331
                'editOthers' => self::ACCESS_ALLOW,
332
            ),
333
            PageBreakReservation::class => array(
334
                'force' => self::ACCESS_ALLOW,
335
            ),
336
            PageCustomClose::class      => array(
337
                'skipCcMailingList' => self::ACCESS_ALLOW,
338
            ),
339
            'RequestData'               => array(
340
                'reopenOldRequest'      => self::ACCESS_ALLOW,
341
                'alwaysSeePrivateData'  => self::ACCESS_ALLOW,
342
                'alwaysSeeHash'         => self::ACCESS_ALLOW,
343
                'seeRestrictedComments' => self::ACCESS_ALLOW,
344
            ),
345
        ),
346
    );
347
    /** @var array
348
     * List of roles which are *exempt* from the identification requirements
349
     *
350
     * Think twice about adding roles to this list.
351
     *
352
     * @category Security-Critical
353
     */
354
    private $identificationExempt = array('public', 'loggedIn');
355
356
    /**
357
     * RoleConfiguration constructor.
358
     *
359
     * @param array $roleConfig           Set to non-null to override the default configuration.
360
     * @param array $identificationExempt Set to non-null to override the default configuration.
361
     */
362 6
    public function __construct(array $roleConfig = null, array $identificationExempt = null)
363
    {
364 6
        if ($roleConfig !== null) {
365 4
            $this->roleConfig = $roleConfig;
366
        }
367
368 6
        if ($identificationExempt !== null) {
369 4
            $this->identificationExempt = $identificationExempt;
370
        }
371 6
    }
372
373
    /**
374
     * @param array $roles The roles to check
375
     *
376
     * @return array
377
     */
378 3
    public function getApplicableRoles(array $roles)
379
    {
380 3
        $available = array();
381
382 3
        foreach ($roles as $role) {
383 3
            if (!isset($this->roleConfig[$role])) {
384
                // wat
385 1
                continue;
386
            }
387
388 3
            $available[$role] = $this->roleConfig[$role];
389
390 3
            if (isset($available[$role]['_childRoles'])) {
391 1
                $childRoles = $this->getApplicableRoles($available[$role]['_childRoles']);
392 1
                $available = array_merge($available, $childRoles);
393
394 1
                unset($available[$role]['_childRoles']);
395
            }
396
397 3
            foreach (array('_hidden', '_editableBy', '_description') as $item) {
398 3
                if (isset($available[$role][$item])) {
399
                    unset($available[$role][$item]);
400
                }
401
            }
402
        }
403
404 3
        return $available;
405
    }
406
407 1
    public function getAvailableRoles()
408
    {
409 1
        $possible = array_diff(array_keys($this->roleConfig), array('public', 'loggedIn'));
410
411 1
        $actual = array();
412
413 1
        foreach ($possible as $role) {
414 1
            if (!isset($this->roleConfig[$role]['_hidden'])) {
415 1
                $actual[$role] = array(
416 1
                    'description' => $this->roleConfig[$role]['_description'],
417 1
                    'editableBy'  => $this->roleConfig[$role]['_editableBy'],
418
                );
419
            }
420
        }
421
422 1
        return $actual;
423
    }
424
425
    /**
426
     * @param string $role
427
     *
428
     * @return bool
429
     */
430
    public function roleNeedsIdentification($role)
431
    {
432
        if (in_array($role, $this->identificationExempt)) {
433
            return false;
434
        }
435
436
        return true;
437
    }
438
}
439