Completed
Pull Request — master (#571)
by Matthew
03:49 queued 44s
created

RoleConfiguration::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 10
c 1
b 0
f 0
cc 3
nc 4
nop 2
crap 3
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
            ),
172
            PageCustomClose::class               => array(
173
                self::MAIN => self::ACCESS_ALLOW,
174
            ),
175
            PageComment::class                   => array(
176
                self::MAIN => self::ACCESS_ALLOW,
177
            ),
178
            PageCloseRequest::class              => array(
179
                self::MAIN => self::ACCESS_ALLOW,
180
            ),
181
            PageCreateRequest::class             => array(
182
                self::MAIN => self::ACCESS_ALLOW,
183
            ),
184
            PageDeferRequest::class              => array(
185
                self::MAIN => self::ACCESS_ALLOW,
186
            ),
187
            PageDropRequest::class               => array(
188
                self::MAIN => self::ACCESS_ALLOW,
189
            ),
190
            PageReservation::class               => array(
191
                self::MAIN => self::ACCESS_ALLOW,
192
            ),
193
            PageSendToUser::class                => array(
194
                self::MAIN => self::ACCESS_ALLOW,
195
            ),
196
            PageBreakReservation::class          => array(
197
                self::MAIN => self::ACCESS_ALLOW,
198
            ),
199
            PageJobQueue::class                  => array(
200
                self::MAIN => self::ACCESS_ALLOW,
201
                'view'     => self::ACCESS_ALLOW,
202
                'all'      => self::ACCESS_ALLOW,
203
            ),
204
            'RequestCreation'                    => array(
205
                User::CREATION_MANUAL => self::ACCESS_ALLOW,
206
            ),
207
            'GlobalInfo'                         => array(
208
                'viewSiteNotice' => self::ACCESS_ALLOW,
209
                'viewOnlineUsers' => self::ACCESS_ALLOW,
210
            ),
211
        ),
212
        'admin'             => array(
213
            '_description'                       => 'A tool administrator.',
214
            '_editableBy'                        => array('admin', 'toolRoot'),
215
            '_childRoles'                        => array(
216
                'user',
217
                'requestAdminTools',
218
            ),
219
            PageEmailManagement::class           => array(
220
                'edit'   => self::ACCESS_ALLOW,
221
                'create' => self::ACCESS_ALLOW,
222
            ),
223
            PageSiteNotice::class                => array(
224
                self::MAIN => self::ACCESS_ALLOW,
225
            ),
226
            PageUserManagement::class            => array(
227
                self::MAIN  => self::ACCESS_ALLOW,
228
                'approve'   => self::ACCESS_ALLOW,
229
                'decline'   => self::ACCESS_ALLOW,
230
                'rename'    => self::ACCESS_ALLOW,
231
                'editUser'  => self::ACCESS_ALLOW,
232
                'suspend'   => self::ACCESS_ALLOW,
233
                'editRoles' => self::ACCESS_ALLOW,
234
            ),
235
            PageWelcomeTemplateManagement::class => array(
236
                'edit'   => self::ACCESS_ALLOW,
237
                'delete' => self::ACCESS_ALLOW,
238
                'add'    => self::ACCESS_ALLOW,
239
            ),
240
            PageJobQueue::class                  => array(
241
                'acknowledge' => self::ACCESS_ALLOW,
242
                'requeue'     => self::ACCESS_ALLOW,
243
            ),
244
        ),
245
        'checkuser'         => array(
246
            '_description'            => 'A user with CheckUser access',
247
            '_editableBy'             => array('checkuser', 'toolRoot'),
248
            '_childRoles'             => array(
249
                'user',
250
                'requestAdminTools',
251
            ),
252
            PageUserManagement::class => array(
253
                self::MAIN  => self::ACCESS_ALLOW,
254
                'suspend'   => self::ACCESS_ALLOW,
255
                'editRoles' => self::ACCESS_ALLOW,
256
            ),
257
            'RequestData'             => array(
258
                'seeUserAgentData' => self::ACCESS_ALLOW,
259
            ),
260
        ),
261
        'toolRoot'          => array(
262
            '_description' => 'A user with shell access to the servers running the tool',
263
            '_editableBy'  => array('toolRoot'),
264
            '_childRoles'  => array(
265
                'admin',
266
            ),
267
            PageMultiFactor::class => array(
268
                'enableU2F'         => self::ACCESS_ALLOW,
269
                'disableU2F'        => self::ACCESS_ALLOW,
270
            )
271
        ),
272
        'botCreation'       => array(
273
            '_description'    => 'A user allowed to use the bot to perform account creations',
274
            '_editableBy'     => array('admin', 'toolRoot'),
275
            '_childRoles'     => array(),
276
            'RequestCreation' => array(
277
                User::CREATION_BOT => self::ACCESS_ALLOW,
278
            ),
279
        ),
280
        'oauthCreation'       => array(
281
            '_description'    => 'A user allowed to use the OAuth to perform account creations',
282
            '_editableBy'     => array('admin', 'toolRoot'),
283
            '_childRoles'     => array(),
284
            'RequestCreation'                    => array(
285
                User::CREATION_OAUTH  => self::ACCESS_ALLOW,
286
            ),
287
        ),
288
289
290
        // Child roles go below this point
291
        'publicStats'       => array(
292
            '_hidden'               => true,
293
            StatsUsers::class       => array(
294
                self::MAIN => self::ACCESS_ALLOW,
295
                'detail'   => self::ACCESS_ALLOW,
296
            ),
297
            StatsTopCreators::class => array(
298
                self::MAIN => self::ACCESS_ALLOW,
299
            ),
300
        ),
301
        'internalStats'     => array(
302
            '_hidden'                    => true,
303
            StatsMain::class             => array(
304
                self::MAIN => self::ACCESS_ALLOW,
305
            ),
306
            StatsFastCloses::class       => array(
307
                self::MAIN => self::ACCESS_ALLOW,
308
            ),
309
            StatsInactiveUsers::class    => array(
310
                self::MAIN => self::ACCESS_ALLOW,
311
            ),
312
            StatsMonthlyStats::class     => array(
313
                self::MAIN => self::ACCESS_ALLOW,
314
            ),
315
            StatsReservedRequests::class => array(
316
                self::MAIN => self::ACCESS_ALLOW,
317
            ),
318
            StatsTemplateStats::class    => array(
319
                self::MAIN => self::ACCESS_ALLOW,
320
            ),
321
        ),
322
        'requestAdminTools' => array(
323
            '_hidden'                   => true,
324
            PageBan::class              => array(
325
                self::MAIN => self::ACCESS_ALLOW,
326
                'set'      => self::ACCESS_ALLOW,
327
                'remove'   => self::ACCESS_ALLOW,
328
            ),
329
            PageEditComment::class      => array(
330
                'editOthers' => self::ACCESS_ALLOW,
331
            ),
332
            PageBreakReservation::class => array(
333
                'force' => self::ACCESS_ALLOW,
334
            ),
335
            PageCustomClose::class      => array(
336
                'skipCcMailingList' => self::ACCESS_ALLOW,
337
            ),
338
            'RequestData'               => array(
339
                'reopenOldRequest'      => self::ACCESS_ALLOW,
340
                'alwaysSeePrivateData'  => self::ACCESS_ALLOW,
341
                'alwaysSeeHash'         => self::ACCESS_ALLOW,
342
                'seeRestrictedComments' => self::ACCESS_ALLOW,
343
            ),
344
        ),
345
    );
346
    /** @var array
347
     * List of roles which are *exempt* from the identification requirements
348
     *
349
     * Think twice about adding roles to this list.
350
     *
351
     * @category Security-Critical
352
     */
353
    private $identificationExempt = array('public', 'loggedIn');
354
355
    /**
356
     * RoleConfiguration constructor.
357
     *
358
     * @param array $roleConfig           Set to non-null to override the default configuration.
359
     * @param array $identificationExempt Set to non-null to override the default configuration.
360
     */
361 6
    public function __construct(array $roleConfig = null, array $identificationExempt = null)
362
    {
363 6
        if ($roleConfig !== null) {
364 4
            $this->roleConfig = $roleConfig;
365
        }
366
367 6
        if ($identificationExempt !== null) {
368 4
            $this->identificationExempt = $identificationExempt;
369
        }
370 6
    }
371
372
    /**
373
     * @param array $roles The roles to check
374
     *
375
     * @return array
376
     */
377 3
    public function getApplicableRoles(array $roles)
378
    {
379 3
        $available = array();
380
381 3
        foreach ($roles as $role) {
382 3
            if (!isset($this->roleConfig[$role])) {
383
                // wat
384 1
                continue;
385
            }
386
387 3
            $available[$role] = $this->roleConfig[$role];
388
389 3
            if (isset($available[$role]['_childRoles'])) {
390 1
                $childRoles = $this->getApplicableRoles($available[$role]['_childRoles']);
391 1
                $available = array_merge($available, $childRoles);
392
393 1
                unset($available[$role]['_childRoles']);
394
            }
395
396 3
            foreach (array('_hidden', '_editableBy', '_description') as $item) {
397 3
                if (isset($available[$role][$item])) {
398
                    unset($available[$role][$item]);
399
                }
400
            }
401
        }
402
403 3
        return $available;
404
    }
405
406 1
    public function getAvailableRoles()
407
    {
408 1
        $possible = array_diff(array_keys($this->roleConfig), array('public', 'loggedIn'));
409
410 1
        $actual = array();
411
412 1
        foreach ($possible as $role) {
413 1
            if (!isset($this->roleConfig[$role]['_hidden'])) {
414 1
                $actual[$role] = array(
415 1
                    'description' => $this->roleConfig[$role]['_description'],
416 1
                    'editableBy'  => $this->roleConfig[$role]['_editableBy'],
417
                );
418
            }
419
        }
420
421 1
        return $actual;
422
    }
423
424
    /**
425
     * @param string $role
426
     *
427
     * @return bool
428
     */
429
    public function roleNeedsIdentification($role)
430
    {
431
        if (in_array($role, $this->identificationExempt)) {
432
            return false;
433
        }
434
435
        return true;
436
    }
437
}
438