Failed Conditions
Pull Request — bugsquish (#574)
by Simon
05:54 queued 03:23
created

PageBan   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 421
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 66
eloc 193
dl 0
loc 421
ccs 0
cts 273
cp 0
rs 3.12
c 5
b 1
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A main() 0 9 1
A remove() 0 42 5
A getBanDuration() 0 22 5
A show() 0 18 2
A set() 0 16 3
B validateIpBan() 0 19 9
B handlePostMethodForSetBan() 0 59 6
A splitCidrRange() 0 12 3
A getBanVisibility() 0 8 4
A setupSecurity() 0 10 1
A getBanForUnban() 0 16 4
C handleGetMethodForSetBan() 0 51 13
B getRawBanTargets() 0 27 9
A setupBanList() 0 20 1

How to fix   Complexity   

Complex Class

Complex classes like PageBan often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PageBan, and based on these observations, apply Extract Interface, too.

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\Pages;
10
11
use Exception;
12
use SmartyException;
13
use Waca\DataObjects\Ban;
14
use Waca\DataObjects\Request;
15
use Waca\DataObjects\User;
16
use Waca\Exceptions\AccessDeniedException;
17
use Waca\Exceptions\ApplicationLogicException;
18
use Waca\Helpers\BanHelper;
19
use Waca\Helpers\Logger;
20
use Waca\Helpers\SearchHelpers\UserSearchHelper;
21
use Waca\SessionAlert;
22
use Waca\Tasks\InternalPageBase;
23
use Waca\WebRequest;
24
25
class PageBan extends InternalPageBase
26
{
27
    /**
28
     * Main function for this page, when no specific actions are called.
29
     */
30
    protected function main()
31
    {
32
        $this->assignCSRFToken();
33
        $this->setHtmlTitle('Bans');
34
35
        $bans = Ban::getActiveBans($this->getDatabase());
36
37
        $this->setupBanList($bans);
38
        $this->setTemplate('bans/main.tpl');
39
    }
40
41
    protected function show()
42
    {
43
        $this->assignCSRFToken();
44
        $this->setHtmlTitle('Bans');
45
46
        $rawIdList = WebRequest::getString('id');
47
        if ($rawIdList === null) {
48
            $this->redirect('bans');
49
50
            return;
51
        }
52
53
        $idList = explode(',', $rawIdList);
54
55
        $bans = Ban::getByIdList($idList, $this->getDatabase());
56
57
        $this->setupBanList($bans);
58
        $this->setTemplate('bans/showbans.tpl');
59
    }
60
61
    /**
62
     * Entry point for the ban set action
63
     * @throws SmartyException
64
     * @throws Exception
65
     */
66
    protected function set()
67
    {
68
        $this->setHtmlTitle('Bans');
69
70
        // dual-mode action
71
        if (WebRequest::wasPosted()) {
72
            try {
73
                $this->handlePostMethodForSetBan();
74
            }
75
            catch (ApplicationLogicException $ex) {
76
                SessionAlert::error($ex->getMessage());
77
                $this->redirect("bans", "set");
78
            }
79
        }
80
        else {
81
            $this->handleGetMethodForSetBan();
82
        }
83
    }
84
85
    /**
86
     * Entry point for the ban remove action
87
     *
88
     * @throws AccessDeniedException
89
     * @throws ApplicationLogicException
90
     * @throws SmartyException
91
     */
92
    protected function remove()
93
    {
94
        $this->setHtmlTitle('Bans');
95
96
        $ban = $this->getBanForUnban();
97
98
        $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
99
        if (!$banHelper->canUnban($ban)) {
100
            // triggered when a user tries to unban a ban they can't see the entirety of.
101
            // there's no UI way to get to this, so a raw exception is fine.
102
            throw new AccessDeniedException($this->getSecurityManager());
103
        }
104
105
        // dual mode
106
        if (WebRequest::wasPosted()) {
107
            $this->validateCSRFToken();
108
            $unbanReason = WebRequest::postString('unbanreason');
109
110
            if ($unbanReason === null || trim($unbanReason) === "") {
111
                SessionAlert::error('No unban reason specified');
112
                $this->redirect("bans", "remove", array('id' => $ban->getId()));
113
            }
114
115
            // set optimistic locking from delete form page load
116
            $updateVersion = WebRequest::postInt('updateversion');
117
            $ban->setUpdateVersion($updateVersion);
118
119
            $database = $this->getDatabase();
120
            $ban->setActive(false);
121
            $ban->save();
122
123
            Logger::unbanned($database, $ban, $unbanReason);
124
125
            SessionAlert::quick('Disabled ban.');
126
            $this->getNotificationHelper()->unbanned($ban, $unbanReason);
127
128
            $this->redirect('bans');
129
        }
130
        else {
131
            $this->assignCSRFToken();
132
            $this->assign('ban', $ban);
133
            $this->setTemplate('bans/unban.tpl');
134
        }
135
    }
136
137
    /**
138
     * @throws ApplicationLogicException
139
     */
140
    private function getBanDuration()
141
    {
142
        $duration = WebRequest::postString('duration');
143
        if ($duration === "other") {
144
            $duration = strtotime(WebRequest::postString('otherduration'));
145
146
            if (!$duration) {
147
                throw new ApplicationLogicException('Invalid ban time');
148
            }
149
            elseif (time() > $duration) {
150
                throw new ApplicationLogicException('Ban time has already expired!');
151
            }
152
153
            return $duration;
154
        }
155
        elseif ($duration === "-1") {
156
            return null;
157
        }
158
        else {
159
            $duration = WebRequest::postInt('duration') + time();
160
161
            return $duration;
162
        }
163
    }
164
165
    /**
166
     * Handles the POST method on the set action
167
     *
168
     * @throws ApplicationLogicException
169
     * @throws Exception
170
     */
171
    private function handlePostMethodForSetBan()
172
    {
173
        $this->validateCSRFToken();
174
        $database = $this->getDatabase();
175
        $user = User::getCurrent($database);
176
177
        // Checks whether there is a reason entered for ban.
178
        $reason = WebRequest::postString('banreason');
179
        if ($reason === null || trim($reason) === "") {
180
            throw new ApplicationLogicException('You must specify a ban reason');
181
        }
182
183
        // ban targets
184
        list($targetName, $targetIp, $targetEmail, $targetUseragent) = $this->getRawBanTargets($user);
185
186
        $visibility = $this->getBanVisibility();
187
188
        // Validate ban duration
189
        $duration = $this->getBanDuration();
190
191
        // handle CIDR ranges
192
        $targetMask = null;
193
        if ($targetIp !== null) {
194
            list($targetIp, $targetMask) = $this->splitCidrRange($targetIp);
195
            $this->validateIpBan($targetIp, $targetMask);
196
        }
197
198
        $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
199
        if (count($banHelper->getBansByTarget($targetName, $targetEmail, $targetIp, $targetMask, $targetUseragent)) > 0) {
200
            throw new ApplicationLogicException('This target is already banned!');
201
        }
202
203
        $ban = new Ban();
204
        $ban->setDatabase($database);
205
        $ban->setActive(true);
206
207
        $ban->setName($targetName);
208
        $ban->setIp($targetIp, $targetMask);
209
        $ban->setEmail($targetEmail);
210
        $ban->setUseragent($targetUseragent);
211
212
        $ban->setUser($user->getId());
213
        $ban->setReason($reason);
214
        $ban->setDuration($duration);
215
        $ban->setVisibility($visibility);
216
217
        $ban->setAction(WebRequest::postString('banAction') ?? Ban::ACTION_NONE);
218
        if ($ban->getAction() === Ban::ACTION_DEFER) {
219
            $ban->setActionTarget(WebRequest::postString('banActionTarget'));
220
        }
221
222
        $ban->save();
223
224
        Logger::banned($database, $ban, $reason);
225
226
        $this->getNotificationHelper()->banned($ban);
227
        SessionAlert::quick('Ban has been set.');
228
229
        $this->redirect('bans');
230
    }
231
232
    /**
233
     * Handles the GET method on the set action
234
     * @throws Exception
235
     */
236
    protected function handleGetMethodForSetBan()
237
    {
238
        $this->setTemplate('bans/banform.tpl');
239
        $this->assignCSRFToken();
240
241
        $database = $this->getDatabase();
242
243
        $user = User::getCurrent($database);
244
        $this->setupSecurity($user);
245
246
        $this->assign('requestStates', $this->getSiteConfiguration()->getRequestStates());
247
248
        $banType = WebRequest::getString('type');
249
        $banRequest = WebRequest::getInt('request');
250
251
        // if the parameters are null, skip loading a request.
252
        if ($banType === null || $banRequest === null || $banRequest === 0) {
253
            return;
254
        }
255
256
        // Attempt to resolve the correct target
257
        /** @var Request|false $request */
258
        $request = Request::getById($banRequest, $database);
259
        if ($request === false) {
0 ignored issues
show
introduced by
The condition $request === false is always false.
Loading history...
260
            $this->assign('bantarget', '');
261
262
            return;
263
        }
264
265
        switch ($banType) {
266
            case 'EMail':
267
                if ($this->barrierTest('email', $user, 'BanType')) {
268
                    $this->assign('banEmail', $request->getEmail());
269
                }
270
                break;
271
            case 'IP':
272
                if ($this->barrierTest('ip', $user, 'BanType')) {
273
                    $this->assign('banIP', $this->getXffTrustProvider()
274
                        ->getTrustedClientIp($request->getIp(), $request->getForwardedIp()));
275
                }
276
                break;
277
            case 'Name':
278
                if ($this->barrierTest('username', $user, 'BanType')) {
279
                    $this->assign('banName', $request->getEmail());
280
                }
281
                break;
282
            case 'UA':
283
                if ($this->barrierTest('useragent', $user, 'BanType')) {
284
                    $this->assign('banUseragent', $request->getEmail());
285
                }
286
                break;
287
        }
288
    }
289
290
    /**
291
     * @return Ban
292
     * @throws ApplicationLogicException
293
     */
294
    private function getBanForUnban()
295
    {
296
        $banId = WebRequest::getInt('id');
297
        if ($banId === null || $banId === 0) {
298
            throw new ApplicationLogicException("The ban ID appears to be missing. This is probably a bug.");
299
        }
300
301
        $database = $this->getDatabase();
302
        $this->setupSecurity(User::getCurrent($database));
303
        $ban = Ban::getActiveId($banId, $database);
304
305
        if ($ban === false) {
306
            throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist.");
307
        }
308
309
        return $ban;
310
    }
311
312
    /**
313
     * @param $user
314
     */
315
    protected function setupSecurity($user): void
316
    {
317
        $this->assign('canSeeIpBan', $this->barrierTest('ip', $user, 'BanType'));
318
        $this->assign('canSeeNameBan', $this->barrierTest('name', $user, 'BanType'));
319
        $this->assign('canSeeEmailBan', $this->barrierTest('email', $user, 'BanType'));
320
        $this->assign('canSeeUseragentBan', $this->barrierTest('useragent', $user, 'BanType'));
321
322
        $this->assign('canSeeUserVisibility', $this->barrierTest('user', $user, 'BanVisibility'));
323
        $this->assign('canSeeAdminVisibility', $this->barrierTest('admin', $user, 'BanVisibility'));
324
        $this->assign('canSeeCheckuserVisibility', $this->barrierTest('checkuser', $user, 'BanVisibility'));
325
    }
326
327
    /**
328
     * @param string $targetIp
329
     * @param        $targetMask
330
     *
331
     * @throws ApplicationLogicException
332
     */
333
    private function validateIpBan(string $targetIp, $targetMask): void
334
    {
335
        // validate this is an IP
336
        if (!filter_var($targetIp, FILTER_VALIDATE_IP)) {
337
            throw new ApplicationLogicException("Not a valid IP address");
338
        }
339
340
        // validate CIDR ranges
341
        if (filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && ($targetMask < 0 || $targetMask > 128)) {
342
            throw new ApplicationLogicException("CIDR mask out of range for IPv6");
343
        }
344
345
        if (filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && ($targetMask < 0 || $targetMask > 32)) {
346
            throw new ApplicationLogicException("CIDR mask out of range for IPv4");
347
        }
348
349
        $squidIpList = $this->getSiteConfiguration()->getSquidList();
350
        if (in_array($targetIp, $squidIpList)) {
351
            throw new ApplicationLogicException("This IP address is on the protected list of proxies, and cannot be banned.");
352
        }
353
    }
354
355
    /**
356
     * @param array $bans
357
     */
358
    protected function setupBanList(array $bans): void
359
    {
360
        $userIds = array_map(
361
            function(Ban $entry) {
362
                return $entry->getUser();
363
            },
364
            $bans);
365
        $userList = UserSearchHelper::get($this->getDatabase())->inIds($userIds)->fetchMap('username');
366
367
        $user = User::getCurrent($this->getDatabase());
368
        $this->assign('canSet', $this->barrierTest('set', $user));
369
        $this->assign('canRemove', $this->barrierTest('remove', $user));
370
371
        $this->setupSecurity($user);
372
373
        $this->assign('usernames', $userList);
374
        $this->assign('activebans', $bans);
375
376
        $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
377
        $this->assign('banHelper', $banHelper);
378
    }
379
380
    /**
381
     * @param string $targetIp
382
     *
383
     * @return array
384
     */
385
    private function splitCidrRange(string $targetIp): array
386
    {
387
        if (strpos($targetIp, '/') !== false) {
388
            $ipParts = explode('/', $targetIp, 2);
389
            $targetIp = $ipParts[0];
390
            $targetMask = (int)$ipParts[1];
391
        }
392
        else {
393
            $targetMask = filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? 128 : 32;
394
        }
395
396
        return array($targetIp, $targetMask);
397
}
398
399
    /**
400
     * @return string|null
401
     * @throws ApplicationLogicException
402
     */
403
    private function getBanVisibility()
404
    {
405
        $visibility = WebRequest::postString('banVisibility');
406
        if ($visibility !== 'user' && $visibility !== 'admin' && $visibility !== 'checkuser') {
407
            throw new ApplicationLogicException('Invalid ban visibility');
408
        }
409
410
        return $visibility;
411
    }
412
413
    /**
414
     * @param $user
415
     *
416
     * @return array
417
     * @throws ApplicationLogicException
418
     */
419
    private function getRawBanTargets($user): array
420
    {
421
        $targetName = WebRequest::postString('banName');
422
        $targetIp = WebRequest::postString('banIP');
423
        $targetEmail = WebRequest::postString('banEmail');
424
        $targetUseragent = WebRequest::postString('banUseragent');
425
426
        // check the user is allowed to use provided targets
427
        if (!$this->barrierTest('name', $user, 'BanType')) {
428
            $targetName = null;
429
        }
430
        if (!$this->barrierTest('ip', $user, 'BanType')) {
431
            $targetIp = null;
432
        }
433
        if (!$this->barrierTest('email', $user, 'BanType')) {
434
            $targetEmail = null;
435
        }
436
        if (!$this->barrierTest('useragent', $user, 'BanType')) {
437
            $targetUseragent = null;
438
        }
439
440
        // Checks whether there is a target entered to ban.
441
        if ($targetName === null && $targetIp === null && $targetEmail === null && $targetUseragent === null) {
442
            throw new ApplicationLogicException('You must specify a target to be banned');
443
        }
444
445
        return array($targetName, $targetIp, $targetEmail, $targetUseragent);
446
}
447
}
448