1 | <?php |
||||
2 | /****************************************************************************** |
||||
3 | * Wikipedia Account Creation Assistance tool * |
||||
4 | * ACC Development Team. Please see team.json for a list of contributors. * |
||||
5 | * * |
||||
6 | * This is free and unencumbered software released into the public domain. * |
||||
7 | * Please see LICENSE.md for the full licencing statement. * |
||||
8 | ******************************************************************************/ |
||||
9 | |||||
10 | namespace Waca\Pages; |
||||
11 | |||||
12 | use Exception; |
||||
13 | use Smarty\Exception as SmartyException; |
||||
14 | use Waca\DataObjects\Ban; |
||||
15 | use Waca\DataObjects\Domain; |
||||
16 | use Waca\DataObjects\Request; |
||||
17 | use Waca\DataObjects\RequestQueue; |
||||
18 | use Waca\DataObjects\User; |
||||
19 | use Waca\Exceptions\AccessDeniedException; |
||||
20 | use Waca\Exceptions\ApplicationLogicException; |
||||
21 | use Waca\Helpers\BanHelper; |
||||
22 | use Waca\Helpers\Logger; |
||||
23 | use Waca\Helpers\SearchHelpers\UserSearchHelper; |
||||
24 | use Waca\SessionAlert; |
||||
25 | use Waca\Tasks\InternalPageBase; |
||||
26 | use Waca\WebRequest; |
||||
27 | |||||
28 | class PageBan extends InternalPageBase |
||||
29 | { |
||||
30 | /** |
||||
31 | * Main function for this page, when no specific actions are called. |
||||
32 | */ |
||||
33 | protected function main(): void |
||||
34 | { |
||||
35 | $this->assignCSRFToken(); |
||||
36 | $this->setHtmlTitle('Bans'); |
||||
37 | |||||
38 | $database = $this->getDatabase(); |
||||
39 | $currentDomain = Domain::getCurrent($database); |
||||
40 | $bans = Ban::getActiveBans($database, $currentDomain->getId()); |
||||
41 | |||||
42 | $this->setupBanList($bans); |
||||
43 | |||||
44 | $this->assign('isFiltered', false); |
||||
45 | $this->assign('currentUnixTime', time()); |
||||
46 | $this->setTemplate('bans/main.tpl'); |
||||
47 | } |
||||
48 | |||||
49 | protected function show(): void |
||||
50 | { |
||||
51 | $this->assignCSRFToken(); |
||||
52 | $this->setHtmlTitle('Bans'); |
||||
53 | |||||
54 | $rawIdList = WebRequest::getString('id'); |
||||
55 | if ($rawIdList === null) { |
||||
56 | $this->redirect('bans'); |
||||
57 | |||||
58 | return; |
||||
59 | } |
||||
60 | |||||
61 | $idList = explode(',', $rawIdList); |
||||
62 | |||||
63 | $database = $this->getDatabase(); |
||||
64 | $currentDomain = Domain::getCurrent($database); |
||||
65 | $bans = Ban::getByIdList($idList, $database, $currentDomain->getId()); |
||||
66 | |||||
67 | $this->setupBanList($bans); |
||||
68 | $this->assign('isFiltered', true); |
||||
69 | $this->assign('currentUnixTime', time()); |
||||
70 | $this->setTemplate('bans/main.tpl'); |
||||
71 | } |
||||
72 | |||||
73 | /** |
||||
74 | * Entry point for the ban set action |
||||
75 | * @throws SmartyException |
||||
76 | * @throws Exception |
||||
77 | */ |
||||
78 | protected function set(): void |
||||
79 | { |
||||
80 | $this->setHtmlTitle('Bans'); |
||||
81 | |||||
82 | // dual-mode action |
||||
83 | if (WebRequest::wasPosted()) { |
||||
84 | try { |
||||
85 | $this->handlePostMethodForSetBan(); |
||||
86 | } |
||||
87 | catch (ApplicationLogicException $ex) { |
||||
88 | SessionAlert::error($ex->getMessage()); |
||||
89 | $this->redirect("bans", "set"); |
||||
90 | } |
||||
91 | } |
||||
92 | else { |
||||
93 | $this->handleGetMethodForSetBan(); |
||||
94 | |||||
95 | $user = User::getCurrent($this->getDatabase()); |
||||
96 | $banType = WebRequest::getString('type'); |
||||
97 | $banRequest = WebRequest::getInt('request'); |
||||
98 | |||||
99 | // if the parameters are null, skip loading a request. |
||||
100 | if ($banType !== null && $banRequest !== null && $banRequest !== 0) { |
||||
101 | $this->preloadFormForRequest($banRequest, $banType, $user); |
||||
102 | } |
||||
103 | } |
||||
104 | } |
||||
105 | |||||
106 | protected function replace(): void |
||||
107 | { |
||||
108 | $this->setHtmlTitle('Bans'); |
||||
109 | |||||
110 | $database = $this->getDatabase(); |
||||
111 | $domain = Domain::getCurrent($database); |
||||
112 | |||||
113 | // dual-mode action |
||||
114 | if (WebRequest::wasPosted()) { |
||||
115 | try { |
||||
116 | $originalBanId = WebRequest::postInt('replaceBanId'); |
||||
117 | $originalBanUpdateVersion = WebRequest::postInt('replaceBanUpdateVersion'); |
||||
118 | |||||
119 | $originalBan = Ban::getActiveId($originalBanId, $database, $domain->getId()); |
||||
120 | |||||
121 | if ($originalBan === false) { |
||||
122 | throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist."); |
||||
123 | } |
||||
124 | |||||
125 | // Discard original ban; we're replacing it. |
||||
126 | $originalBan->setUpdateVersion($originalBanUpdateVersion); |
||||
127 | $originalBan->setActive(false); |
||||
128 | $originalBan->save(); |
||||
129 | |||||
130 | Logger::banReplaced($database, $originalBan); |
||||
131 | |||||
132 | // Proceed as normal to save the new ban. |
||||
133 | $this->handlePostMethodForSetBan(); |
||||
134 | } |
||||
135 | catch (ApplicationLogicException $ex) { |
||||
136 | $database->rollback(); |
||||
137 | SessionAlert::error($ex->getMessage()); |
||||
138 | $this->redirect("bans", "set"); |
||||
139 | } |
||||
140 | } |
||||
141 | else { |
||||
142 | $this->handleGetMethodForSetBan(); |
||||
143 | |||||
144 | $user = User::getCurrent($database); |
||||
145 | $originalBanId = WebRequest::getString('id'); |
||||
146 | |||||
147 | $originalBan = Ban::getActiveId($originalBanId, $database, $domain->getId()); |
||||
148 | |||||
149 | if ($originalBan === false) { |
||||
150 | throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist."); |
||||
151 | } |
||||
152 | |||||
153 | if ($originalBan->getName() !== null) { |
||||
154 | if (!$this->barrierTest('name', $user, 'BanType')) { |
||||
155 | SessionAlert::error("You are not allowed to set this type of ban."); |
||||
156 | $this->redirect("bans", "set"); |
||||
157 | return; |
||||
158 | } |
||||
159 | |||||
160 | $this->assign('banName', $originalBan->getName()); |
||||
161 | } |
||||
162 | |||||
163 | if ($originalBan->getEmail() !== null) { |
||||
164 | if (!$this->barrierTest('email', $user, 'BanType')) { |
||||
165 | SessionAlert::error("You are not allowed to set this type of ban."); |
||||
166 | $this->redirect("bans", "set"); |
||||
167 | return; |
||||
168 | } |
||||
169 | |||||
170 | $this->assign('banEmail', $originalBan->getEmail()); |
||||
171 | } |
||||
172 | |||||
173 | if ($originalBan->getUseragent() !== null) { |
||||
174 | if (!$this->barrierTest('useragent', $user, 'BanType')) { |
||||
175 | SessionAlert::error("You are not allowed to set this type of ban."); |
||||
176 | $this->redirect("bans", "set"); |
||||
177 | return; |
||||
178 | } |
||||
179 | |||||
180 | $this->assign('banUseragent', $originalBan->getUseragent()); |
||||
181 | } |
||||
182 | |||||
183 | if ($originalBan->getIp() !== null) { |
||||
184 | if (!$this->barrierTest('ip', $user, 'BanType')) { |
||||
185 | SessionAlert::error("You are not allowed to set this type of ban."); |
||||
186 | $this->redirect("bans", "set"); |
||||
187 | return; |
||||
188 | } |
||||
189 | |||||
190 | $this->assign('banIP', $originalBan->getIp() . '/' . $originalBan->getIpMask()); |
||||
191 | } |
||||
192 | |||||
193 | $banIsGlobal = $originalBan->getDomain() === null; |
||||
194 | if ($banIsGlobal) { |
||||
195 | if (!$this->barrierTest('global', $user, 'BanType')) { |
||||
196 | SessionAlert::error("You are not allowed to set this type of ban."); |
||||
197 | $this->redirect("bans", "set"); |
||||
198 | return; |
||||
199 | } |
||||
200 | } |
||||
201 | |||||
202 | if (!$this->barrierTest($originalBan->getVisibility(), $user, 'BanVisibility')) { |
||||
203 | SessionAlert::error("You are not allowed to set this type of ban."); |
||||
204 | $this->redirect("bans", "set"); |
||||
205 | return; |
||||
206 | } |
||||
207 | |||||
208 | $this->assign('banGlobal', $banIsGlobal); |
||||
209 | $this->assign('banVisibility', $originalBan->getVisibility()); |
||||
210 | |||||
211 | if ($originalBan->getDuration() !== null) { |
||||
212 | $this->assign('banDuration', date('c', $originalBan->getDuration())); |
||||
213 | } |
||||
214 | |||||
215 | $this->assign('banReason', $originalBan->getReason()); |
||||
216 | $this->assign('banAction', $originalBan->getAction()); |
||||
217 | $this->assign('banQueue', $originalBan->getTargetQueue()); |
||||
218 | |||||
219 | $this->assign('replaceBanId', $originalBan->getId()); |
||||
220 | $this->assign('replaceBanUpdateVersion', $originalBan->getUpdateVersion()); |
||||
221 | } |
||||
222 | } |
||||
223 | |||||
224 | /** |
||||
225 | * Entry point for the ban remove action |
||||
226 | * |
||||
227 | * @throws AccessDeniedException |
||||
228 | * @throws ApplicationLogicException |
||||
229 | * @throws SmartyException |
||||
230 | */ |
||||
231 | protected function remove(): void |
||||
232 | { |
||||
233 | $this->setHtmlTitle('Bans'); |
||||
234 | |||||
235 | $ban = $this->getBanForUnban(); |
||||
236 | |||||
237 | $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager()); |
||||
238 | if (!$banHelper->canUnban($ban)) { |
||||
239 | // triggered when a user tries to unban a ban they can't see the entirety of. |
||||
240 | // there's no UI way to get to this, so a raw exception is fine. |
||||
241 | throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager()); |
||||
242 | } |
||||
243 | |||||
244 | // dual mode |
||||
245 | if (WebRequest::wasPosted()) { |
||||
246 | $this->validateCSRFToken(); |
||||
247 | $unbanReason = WebRequest::postString('unbanreason'); |
||||
248 | |||||
249 | if ($unbanReason === null || trim($unbanReason) === "") { |
||||
250 | SessionAlert::error('No unban reason specified'); |
||||
251 | $this->redirect("bans", "remove", array('id' => $ban->getId())); |
||||
252 | } |
||||
253 | |||||
254 | // set optimistic locking from delete form page load |
||||
255 | $updateVersion = WebRequest::postInt('updateversion'); |
||||
256 | $ban->setUpdateVersion($updateVersion); |
||||
257 | |||||
258 | $database = $this->getDatabase(); |
||||
259 | $ban->setActive(false); |
||||
260 | $ban->save(); |
||||
261 | |||||
262 | Logger::unbanned($database, $ban, $unbanReason); |
||||
263 | |||||
264 | SessionAlert::quick('Disabled ban.'); |
||||
265 | $this->getNotificationHelper()->unbanned($ban, $unbanReason); |
||||
266 | |||||
267 | $this->redirect('bans'); |
||||
268 | } |
||||
269 | else { |
||||
270 | $this->assignCSRFToken(); |
||||
271 | $this->assign('ban', $ban); |
||||
272 | $this->setTemplate('bans/unban.tpl'); |
||||
273 | } |
||||
274 | } |
||||
275 | |||||
276 | /** |
||||
277 | * Retrieves the requested ban duration from the WebRequest |
||||
278 | * |
||||
279 | * @throws ApplicationLogicException |
||||
280 | */ |
||||
281 | private function getBanDuration(): ?int |
||||
282 | { |
||||
283 | $duration = WebRequest::postString('duration'); |
||||
284 | if ($duration === "other") { |
||||
285 | $duration = strtotime(WebRequest::postString('otherduration')); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
286 | |||||
287 | if (!$duration) { |
||||
288 | throw new ApplicationLogicException('Invalid ban time'); |
||||
289 | } |
||||
290 | elseif (time() > $duration) { |
||||
291 | throw new ApplicationLogicException('Ban time has already expired!'); |
||||
292 | } |
||||
293 | |||||
294 | return $duration; |
||||
295 | } |
||||
296 | elseif ($duration === "-1") { |
||||
297 | return null; |
||||
298 | } |
||||
299 | else { |
||||
300 | return WebRequest::postInt('duration') + time(); |
||||
301 | } |
||||
302 | } |
||||
303 | |||||
304 | /** |
||||
305 | * Handles the POST method on the set action |
||||
306 | * |
||||
307 | * @throws ApplicationLogicException |
||||
308 | * @throws Exception |
||||
309 | */ |
||||
310 | private function handlePostMethodForSetBan() |
||||
311 | { |
||||
312 | $this->validateCSRFToken(); |
||||
313 | $database = $this->getDatabase(); |
||||
314 | $user = User::getCurrent($database); |
||||
315 | $currentDomain = Domain::getCurrent($database); |
||||
316 | |||||
317 | // Checks whether there is a reason entered for ban. |
||||
318 | $reason = WebRequest::postString('banreason'); |
||||
319 | if ($reason === null || trim($reason) === "") { |
||||
320 | throw new ApplicationLogicException('You must specify a ban reason'); |
||||
321 | } |
||||
322 | |||||
323 | // ban targets |
||||
324 | list($targetName, $targetIp, $targetEmail, $targetUseragent) = $this->getRawBanTargets($user); |
||||
325 | |||||
326 | $visibility = $this->getBanVisibility(); |
||||
327 | |||||
328 | // Validate ban duration |
||||
329 | $duration = $this->getBanDuration(); |
||||
330 | |||||
331 | $action = WebRequest::postString('banAction') ?? Ban::ACTION_NONE; |
||||
332 | |||||
333 | $global = WebRequest::postBoolean('banGlobal'); |
||||
334 | if (!$this->barrierTest('global', $user, 'BanType')) { |
||||
335 | $global = false; |
||||
336 | } |
||||
337 | |||||
338 | if ($action === Ban::ACTION_DEFER && $global) { |
||||
339 | throw new ApplicationLogicException("Cannot set a global ban in defer-to-queue mode."); |
||||
340 | } |
||||
341 | |||||
342 | // handle CIDR ranges |
||||
343 | $targetMask = null; |
||||
344 | if ($targetIp !== null) { |
||||
345 | list($targetIp, $targetMask) = $this->splitCidrRange($targetIp); |
||||
346 | $this->validateIpBan($targetIp, $targetMask, $user, $action); |
||||
347 | } |
||||
348 | |||||
349 | $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager()); |
||||
350 | |||||
351 | $bansByTarget = $banHelper->getBansByTarget( |
||||
352 | $targetName, |
||||
353 | $targetEmail, |
||||
354 | $targetIp, |
||||
355 | $targetMask, |
||||
356 | $targetUseragent, |
||||
357 | $currentDomain->getId()); |
||||
358 | |||||
359 | if (count($bansByTarget) > 0) { |
||||
360 | throw new ApplicationLogicException('This target is already banned!'); |
||||
361 | } |
||||
362 | |||||
363 | $ban = new Ban(); |
||||
364 | $ban->setDatabase($database); |
||||
365 | $ban->setActive(true); |
||||
366 | |||||
367 | $ban->setName($targetName); |
||||
368 | $ban->setIp($targetIp, $targetMask); |
||||
369 | $ban->setEmail($targetEmail); |
||||
370 | $ban->setUseragent($targetUseragent); |
||||
371 | |||||
372 | $ban->setUser($user->getId()); |
||||
373 | $ban->setReason($reason); |
||||
374 | $ban->setDuration($duration); |
||||
375 | $ban->setVisibility($visibility); |
||||
376 | |||||
377 | $ban->setDomain($global ? null : $currentDomain->getId()); |
||||
378 | |||||
379 | $ban->setAction($action); |
||||
380 | if ($ban->getAction() === Ban::ACTION_DEFER) { |
||||
381 | //FIXME: domains |
||||
382 | $queue = RequestQueue::getByApiName($database, WebRequest::postString('banActionTarget'), 1); |
||||
0 ignored issues
–
show
It seems like
Waca\WebRequest::postString('banActionTarget') can also be of type null ; however, parameter $apiName of Waca\DataObjects\RequestQueue::getByApiName() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
383 | if ($queue === false) { |
||||
384 | throw new ApplicationLogicException("Unknown target queue"); |
||||
385 | } |
||||
386 | |||||
387 | if (!$queue->isEnabled()) { |
||||
388 | throw new ApplicationLogicException("Target queue is not enabled"); |
||||
389 | } |
||||
390 | |||||
391 | $ban->setTargetQueue($queue->getId()); |
||||
392 | } |
||||
393 | |||||
394 | $ban->save(); |
||||
395 | |||||
396 | Logger::banned($database, $ban, $reason); |
||||
397 | |||||
398 | $this->getNotificationHelper()->banned($ban); |
||||
399 | SessionAlert::quick('Ban has been set.'); |
||||
400 | |||||
401 | $this->redirect('bans'); |
||||
402 | } |
||||
403 | |||||
404 | /** |
||||
405 | * Handles the GET method on the set action |
||||
406 | * @throws Exception |
||||
407 | */ |
||||
408 | private function handleGetMethodForSetBan() |
||||
409 | { |
||||
410 | $this->setTemplate('bans/banform.tpl'); |
||||
411 | $this->assignCSRFToken(); |
||||
412 | |||||
413 | $this->assign('maxIpRange', $this->getSiteConfiguration()->getBanMaxIpRange()); |
||||
414 | $this->assign('maxIpBlockRange', $this->getSiteConfiguration()->getBanMaxIpBlockRange()); |
||||
415 | |||||
416 | $this->assign('banVisibility', 'user'); |
||||
417 | $this->assign('banGlobal', false); |
||||
418 | $this->assign('banQueue', false); |
||||
419 | $this->assign('banAction', Ban::ACTION_BLOCK); |
||||
420 | $this->assign('banDuration', ''); |
||||
421 | $this->assign('banReason', ''); |
||||
422 | |||||
423 | $this->assign('banEmail', ''); |
||||
424 | $this->assign('banIP', ''); |
||||
425 | $this->assign('banName', ''); |
||||
426 | $this->assign('banUseragent', ''); |
||||
427 | |||||
428 | $this->assign('replaceBanId', null); |
||||
429 | |||||
430 | |||||
431 | |||||
432 | $database = $this->getDatabase(); |
||||
433 | |||||
434 | $user = User::getCurrent($database); |
||||
435 | $this->setupSecurity($user); |
||||
436 | |||||
437 | $queues = RequestQueue::getEnabledQueues($database); |
||||
438 | |||||
439 | $this->assign('requestQueues', $queues); |
||||
440 | } |
||||
441 | |||||
442 | /** |
||||
443 | * Finds the Ban object referenced in the WebRequest if it is valid |
||||
444 | * |
||||
445 | * @return Ban |
||||
446 | * @throws ApplicationLogicException |
||||
447 | */ |
||||
448 | private function getBanForUnban(): Ban |
||||
449 | { |
||||
450 | $banId = WebRequest::getInt('id'); |
||||
451 | if ($banId === null || $banId === 0) { |
||||
452 | throw new ApplicationLogicException("The ban ID appears to be missing. This is probably a bug."); |
||||
453 | } |
||||
454 | |||||
455 | $database = $this->getDatabase(); |
||||
456 | $this->setupSecurity(User::getCurrent($database)); |
||||
457 | $currentDomain = Domain::getCurrent($database); |
||||
458 | $ban = Ban::getActiveId($banId, $database, $currentDomain->getId()); |
||||
459 | |||||
460 | if ($ban === false) { |
||||
461 | throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist."); |
||||
462 | } |
||||
463 | |||||
464 | return $ban; |
||||
465 | } |
||||
466 | |||||
467 | /** |
||||
468 | * Sets up Smarty variables for access control |
||||
469 | */ |
||||
470 | private function setupSecurity(User $user): void |
||||
471 | { |
||||
472 | $this->assign('canSeeIpBan', $this->barrierTest('ip', $user, 'BanType')); |
||||
473 | $this->assign('canSeeNameBan', $this->barrierTest('name', $user, 'BanType')); |
||||
474 | $this->assign('canSeeEmailBan', $this->barrierTest('email', $user, 'BanType')); |
||||
475 | $this->assign('canSeeUseragentBan', $this->barrierTest('useragent', $user, 'BanType')); |
||||
476 | |||||
477 | $this->assign('canGlobalBan', $this->barrierTest('global', $user, 'BanType')); |
||||
478 | |||||
479 | $this->assign('canSeeUserVisibility', $this->barrierTest('user', $user, 'BanVisibility')); |
||||
480 | $this->assign('canSeeAdminVisibility', $this->barrierTest('admin', $user, 'BanVisibility')); |
||||
481 | $this->assign('canSeeCheckuserVisibility', $this->barrierTest('checkuser', $user, 'BanVisibility')); |
||||
482 | } |
||||
483 | |||||
484 | /** |
||||
485 | * Validates that the provided IP is acceptable for a ban of this type |
||||
486 | * |
||||
487 | * @param string $targetIp IP address |
||||
488 | * @param int $targetMask CIDR prefix length |
||||
489 | * @param User $user User performing the ban |
||||
490 | * @param string $action Ban action to take |
||||
491 | * |
||||
492 | * @throws ApplicationLogicException |
||||
493 | */ |
||||
494 | private function validateIpBan(string $targetIp, int $targetMask, User $user, string $action): void |
||||
495 | { |
||||
496 | // validate this is an IP |
||||
497 | if (!filter_var($targetIp, FILTER_VALIDATE_IP)) { |
||||
498 | throw new ApplicationLogicException("Not a valid IP address"); |
||||
499 | } |
||||
500 | |||||
501 | $canLargeIpBan = $this->barrierTest('ip-largerange', $user, 'BanType'); |
||||
502 | $maxIpBlockRange = $this->getSiteConfiguration()->getBanMaxIpBlockRange(); |
||||
503 | $maxIpRange = $this->getSiteConfiguration()->getBanMaxIpRange(); |
||||
504 | |||||
505 | // validate CIDR ranges |
||||
506 | if (filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { |
||||
507 | if ($targetMask < 0 || $targetMask > 128) { |
||||
508 | throw new ApplicationLogicException("CIDR mask out of range for IPv6"); |
||||
509 | } |
||||
510 | |||||
511 | // prevent setting the ban if: |
||||
512 | // * the user isn't allowed to set large bans, AND |
||||
513 | // * the ban is a drop or a block (preventing human review of the request), AND |
||||
514 | // * the mask is too wide-reaching |
||||
515 | if (!$canLargeIpBan && ($action == Ban::ACTION_BLOCK || $action == Ban::ACTION_DROP) && $targetMask < $maxIpBlockRange[6]) { |
||||
516 | throw new ApplicationLogicException("The requested IP range for this ban is too wide for the block/drop action."); |
||||
517 | } |
||||
518 | |||||
519 | if (!$canLargeIpBan && $targetMask < $maxIpRange[6]) { |
||||
520 | throw new ApplicationLogicException("The requested IP range for this ban is too wide."); |
||||
521 | } |
||||
522 | } |
||||
523 | |||||
524 | if (filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { |
||||
525 | if ($targetMask < 0 || $targetMask > 32) { |
||||
526 | throw new ApplicationLogicException("CIDR mask out of range for IPv4"); |
||||
527 | } |
||||
528 | |||||
529 | if (!$canLargeIpBan && ($action == Ban::ACTION_BLOCK || $action == Ban::ACTION_DROP) && $targetMask < $maxIpBlockRange[4]) { |
||||
530 | throw new ApplicationLogicException("The IP range for this ban is too wide for the block/drop action."); |
||||
531 | } |
||||
532 | |||||
533 | if (!$canLargeIpBan && $targetMask < $maxIpRange[4]) { |
||||
534 | throw new ApplicationLogicException("The requested IP range for this ban is too wide."); |
||||
535 | } |
||||
536 | } |
||||
537 | |||||
538 | $squidIpList = $this->getSiteConfiguration()->getSquidList(); |
||||
539 | if (in_array($targetIp, $squidIpList)) { |
||||
540 | throw new ApplicationLogicException("This IP address is on the protected list of proxies, and cannot be banned."); |
||||
541 | } |
||||
542 | } |
||||
543 | |||||
544 | /** |
||||
545 | * Configures a ban list template for display |
||||
546 | * |
||||
547 | * @param Ban[] $bans |
||||
548 | */ |
||||
549 | private function setupBanList(array $bans): void |
||||
550 | { |
||||
551 | $userIds = array_map(fn(Ban $entry) => $entry->getUser(), $bans); |
||||
552 | $userList = UserSearchHelper::get($this->getDatabase())->inIds($userIds)->fetchMap('username'); |
||||
553 | |||||
554 | $domainIds = array_filter(array_unique(array_map(fn(Ban $entry) => $entry->getDomain(), $bans))); |
||||
555 | $domains = []; |
||||
556 | foreach ($domainIds as $d) { |
||||
557 | if ($d === null) { |
||||
558 | continue; |
||||
559 | } |
||||
560 | $domains[$d] = Domain::getById($d, $this->getDatabase()); |
||||
561 | } |
||||
562 | |||||
563 | $this->assign('domains', $domains); |
||||
564 | |||||
565 | $user = User::getCurrent($this->getDatabase()); |
||||
566 | $this->assign('canSet', $this->barrierTest('set', $user)); |
||||
567 | $this->assign('canRemove', $this->barrierTest('remove', $user)); |
||||
568 | |||||
569 | $this->setupSecurity($user); |
||||
570 | |||||
571 | $this->assign('usernames', $userList); |
||||
572 | $this->assign('activebans', $bans); |
||||
573 | |||||
574 | $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager()); |
||||
575 | $this->assign('banHelper', $banHelper); |
||||
576 | } |
||||
577 | |||||
578 | /** |
||||
579 | * Converts a plain IP or CIDR mask into an IP and a CIDR suffix |
||||
580 | * |
||||
581 | * @param string $targetIp IP or CIDR range |
||||
582 | * |
||||
583 | * @return array |
||||
584 | */ |
||||
585 | private function splitCidrRange(string $targetIp): array |
||||
586 | { |
||||
587 | if (strpos($targetIp, '/') !== false) { |
||||
588 | $ipParts = explode('/', $targetIp, 2); |
||||
589 | $targetIp = $ipParts[0]; |
||||
590 | $targetMask = (int)$ipParts[1]; |
||||
591 | } |
||||
592 | else { |
||||
593 | // Default the CIDR range based on the IP type |
||||
594 | $targetMask = filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? 128 : 32; |
||||
595 | } |
||||
596 | |||||
597 | return array($targetIp, $targetMask); |
||||
598 | } |
||||
599 | |||||
600 | /** |
||||
601 | * Returns the validated ban visibility from WebRequest |
||||
602 | * |
||||
603 | * @throws ApplicationLogicException |
||||
604 | */ |
||||
605 | private function getBanVisibility(): string |
||||
606 | { |
||||
607 | $visibility = WebRequest::postString('banVisibility'); |
||||
608 | if ($visibility !== 'user' && $visibility !== 'admin' && $visibility !== 'checkuser') { |
||||
609 | throw new ApplicationLogicException('Invalid ban visibility'); |
||||
610 | } |
||||
611 | |||||
612 | return $visibility; |
||||
613 | } |
||||
614 | |||||
615 | /** |
||||
616 | * Returns array of [username, ip, email, ua] as ban targets from WebRequest, |
||||
617 | * filtered for whether the user is allowed to set bans including those types. |
||||
618 | * |
||||
619 | * @return string[] |
||||
620 | * @throws ApplicationLogicException |
||||
621 | */ |
||||
622 | private function getRawBanTargets(User $user): array |
||||
623 | { |
||||
624 | $targetName = WebRequest::postString('banName'); |
||||
625 | $targetIp = WebRequest::postString('banIP'); |
||||
626 | $targetEmail = WebRequest::postString('banEmail'); |
||||
627 | $targetUseragent = WebRequest::postString('banUseragent'); |
||||
628 | |||||
629 | // check the user is allowed to use provided targets |
||||
630 | if (!$this->barrierTest('name', $user, 'BanType')) { |
||||
631 | $targetName = null; |
||||
632 | } |
||||
633 | if (!$this->barrierTest('ip', $user, 'BanType')) { |
||||
634 | $targetIp = null; |
||||
635 | } |
||||
636 | if (!$this->barrierTest('email', $user, 'BanType')) { |
||||
637 | $targetEmail = null; |
||||
638 | } |
||||
639 | if (!$this->barrierTest('useragent', $user, 'BanType')) { |
||||
640 | $targetUseragent = null; |
||||
641 | } |
||||
642 | |||||
643 | // Checks whether there is a target entered to ban. |
||||
644 | if ($targetName === null && $targetIp === null && $targetEmail === null && $targetUseragent === null) { |
||||
645 | throw new ApplicationLogicException('You must specify a target to be banned'); |
||||
646 | } |
||||
647 | |||||
648 | return array($targetName, $targetIp, $targetEmail, $targetUseragent); |
||||
649 | } |
||||
650 | |||||
651 | private function preloadFormForRequest(int $banRequest, string $banType, User $user): void |
||||
652 | { |
||||
653 | $database = $this->getDatabase(); |
||||
654 | |||||
655 | // Attempt to resolve the correct target |
||||
656 | /** @var Request|false $request */ |
||||
657 | $request = Request::getById($banRequest, $database); |
||||
658 | if ($request === false) { |
||||
0 ignored issues
–
show
|
|||||
659 | $this->assign('bantarget', ''); |
||||
660 | |||||
661 | return; |
||||
662 | } |
||||
663 | |||||
664 | switch ($banType) { |
||||
665 | case 'EMail': |
||||
666 | if ($this->barrierTest('email', $user, 'BanType')) { |
||||
667 | $this->assign('banEmail', $request->getEmail()); |
||||
668 | } |
||||
669 | break; |
||||
670 | case 'IP': |
||||
671 | if ($this->barrierTest('ip', $user, 'BanType')) { |
||||
672 | $trustedIp = $this->getXffTrustProvider()->getTrustedClientIp( |
||||
673 | $request->getIp(), |
||||
674 | $request->getForwardedIp()); |
||||
675 | |||||
676 | $this->assign('banIP', $trustedIp); |
||||
677 | } |
||||
678 | break; |
||||
679 | case 'Name': |
||||
680 | if ($this->barrierTest('name', $user, 'BanType')) { |
||||
681 | $this->assign('banName', $request->getName()); |
||||
682 | } |
||||
683 | break; |
||||
684 | case 'UA': |
||||
685 | if ($this->barrierTest('useragent', $user, 'BanType')) { |
||||
686 | $this->assign('banUseragent', $request->getEmail()); |
||||
687 | } |
||||
688 | break; |
||||
689 | } |
||||
690 | } |
||||
691 | } |
||||
692 |