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\Helpers; |
||
11 | |||
12 | use PDO; |
||
13 | use Waca\DataObjects\Ban; |
||
14 | use Waca\DataObjects\Domain; |
||
15 | use Waca\DataObjects\Request; |
||
16 | use Waca\DataObjects\User; |
||
17 | use Waca\Helpers\Interfaces\IBanHelper; |
||
18 | use Waca\PdoDatabase; |
||
19 | use Waca\Providers\Interfaces\IXffTrustProvider; |
||
20 | use Waca\Security\ISecurityManager; |
||
21 | |||
22 | class BanHelper implements IBanHelper |
||
23 | { |
||
24 | /** @var PdoDatabase */ |
||
25 | private $database; |
||
26 | /** @var IXffTrustProvider */ |
||
27 | private $xffTrustProvider; |
||
28 | /** @var Ban[][] */ |
||
29 | private $banCache = []; |
||
30 | |||
31 | private ?ISecurityManager $securityManager; |
||
32 | |||
33 | public function __construct( |
||
34 | PdoDatabase $database, |
||
35 | IXffTrustProvider $xffTrustProvider, |
||
36 | ?ISecurityManager $securityManager |
||
37 | ) { |
||
38 | $this->database = $database; |
||
39 | $this->xffTrustProvider = $xffTrustProvider; |
||
40 | $this->securityManager = $securityManager; |
||
41 | } |
||
42 | |||
43 | public function isBlockBanned(Request $request): bool |
||
44 | { |
||
45 | if (!isset($this->banCache[$request->getId()])) { |
||
46 | $this->banCache[$request->getId()] = $this->getBansForRequestFromDatabase($request); |
||
47 | } |
||
48 | |||
49 | foreach ($this->banCache[$request->getId()] as $ban) { |
||
50 | if ($ban->getAction() === Ban::ACTION_BLOCK) { |
||
51 | return true; |
||
52 | } |
||
53 | } |
||
54 | |||
55 | return false; |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * @param Request $request |
||
60 | * |
||
61 | * @return Ban[] |
||
62 | */ |
||
63 | public function getBans(Request $request): array |
||
64 | { |
||
65 | if (!isset($this->banCache[$request->getId()])) { |
||
66 | $this->banCache[$request->getId()] = $this->getBansForRequestFromDatabase($request); |
||
67 | } |
||
68 | |||
69 | return $this->banCache[$request->getId()]; |
||
70 | } |
||
71 | |||
72 | public function getBansByTarget( |
||
73 | ?string $name, |
||
74 | ?string $email, |
||
75 | ?string $ip, |
||
76 | ?int $mask, |
||
77 | ?string $useragent, |
||
78 | int $domain |
||
79 | ) { |
||
80 | /** @noinspection SqlConstantCondition */ |
||
81 | $query = <<<SQL |
||
82 | SELECT * FROM ban |
||
83 | WHERE 1 = 1 |
||
84 | AND ((name IS NULL AND :nname IS NULL) OR name = :name) |
||
85 | AND ((email IS NULL AND :nemail IS NULL) OR email = :email) |
||
86 | AND ((useragent IS NULL AND :nuseragent IS NULL) OR useragent = :useragent) |
||
87 | AND ((ip IS NULL AND :nip IS NULL) OR ip = INET6_ATON(:ip)) |
||
88 | AND ((ipmask IS NULL AND :nipmask IS NULL) OR ipmask = :ipmask) |
||
89 | AND (duration > UNIX_TIMESTAMP() OR duration IS NULL) |
||
90 | AND active = 1 |
||
91 | AND (domain IS NULL OR domain = :domain); |
||
92 | SQL; |
||
93 | |||
94 | $statement = $this->database->prepare($query); |
||
95 | $statement->execute([ |
||
96 | ':name' => $name, |
||
97 | ':nname' => $name, |
||
98 | ':email' => $email, |
||
99 | ':nemail' => $email, |
||
100 | ':ip' => $ip, |
||
101 | ':nip' => $ip, |
||
102 | ':ipmask' => $mask, |
||
103 | ':nipmask' => $mask, |
||
104 | ':useragent' => $useragent, |
||
105 | ':nuseragent' => $useragent, |
||
106 | ':domain' => $domain, |
||
107 | ]); |
||
108 | |||
109 | $result = array(); |
||
110 | |||
111 | /** @var Ban $v */ |
||
112 | foreach ($statement->fetchAll(PDO::FETCH_CLASS, Ban::class) as $v) { |
||
113 | $v->setDatabase($this->database); |
||
114 | $result[] = $v; |
||
115 | } |
||
116 | |||
117 | return $result; |
||
118 | } |
||
119 | |||
120 | public function isActive(Ban $ban): bool |
||
121 | { |
||
122 | if (!$ban->isActive()) { |
||
123 | return false; |
||
124 | } |
||
125 | |||
126 | if ($ban->getDuration() !== null && $ban->getDuration() < time()) { |
||
127 | return false; |
||
128 | } |
||
129 | |||
130 | return true; |
||
131 | } |
||
132 | |||
133 | public function canUnban(Ban $ban): bool |
||
134 | { |
||
135 | if ($this->securityManager === null) { |
||
136 | return false; |
||
137 | } |
||
138 | |||
139 | if (!$this->isActive($ban)) { |
||
140 | return false; |
||
141 | } |
||
142 | |||
143 | $user = User::getCurrent($this->database); |
||
144 | |||
145 | $allowed = true; |
||
146 | $allowed = $allowed && ($ban->getName() === null || $this->securityManager->allows('BanType', 'name', $user) === ISecurityManager::ALLOWED); |
||
147 | $allowed = $allowed && ($ban->getEmail() === null || $this->securityManager->allows('BanType', 'email', $user) === ISecurityManager::ALLOWED); |
||
148 | $allowed = $allowed && ($ban->getIp() === null || $this->securityManager->allows('BanType', 'ip', $user) === ISecurityManager::ALLOWED); |
||
149 | $allowed = $allowed && ($ban->getUseragent() === null || $this->securityManager->allows('BanType', 'useragent', $user) === ISecurityManager::ALLOWED); |
||
150 | |||
151 | if ($ban->getDomain() === null) { |
||
152 | $allowed &= $this->securityManager->allows('BanType', 'global', $user) === ISecurityManager::ALLOWED; |
||
153 | } |
||
154 | else { |
||
155 | $currentDomain = Domain::getCurrent($this->database); |
||
156 | $allowed &= $currentDomain->getId() === $ban->getDomain(); |
||
157 | } |
||
158 | |||
159 | $allowed = $allowed && $this->securityManager->allows('BanVisibility', $ban->getVisibility(), $user) === ISecurityManager::ALLOWED; |
||
160 | |||
161 | return $allowed; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * @param Request $request |
||
166 | * |
||
167 | * @return Ban[] |
||
168 | */ |
||
169 | private function getBansForRequestFromDatabase(Request $request): array |
||
170 | { |
||
171 | /** @noinspection SqlConstantCondition - included for clarity of code */ |
||
172 | $query = <<<SQL |
||
173 | SELECT b.* FROM ban b |
||
174 | LEFT JOIN netmask n ON 1 = 1 |
||
175 | AND n.cidr = b.ipmask |
||
176 | AND n.protocol = CASE WHEN INET6_ATON(b.ip) IS NOT NULL THEN 6 ELSE 4 END |
||
177 | WHERE 1 = 1 |
||
178 | AND COALESCE(:name RLIKE name, TRUE) |
||
179 | AND COALESCE(:email RLIKE email, TRUE) |
||
180 | AND COALESCE(:useragent RLIKE useragent, TRUE) |
||
181 | AND ( |
||
182 | (INET6_ATON(b.ip) IS NOT NULL AND |
||
183 | (CONV(LEFT(HEX(INET6_ATON(b.ip)), 16), 16, 10) & n.maskh) = (CONV(LEFT(HEX(INET6_ATON(:ip6)), 16), 16, 10) & n.maskh) |
||
184 | AND (CONV(RIGHT(HEX(INET6_ATON(b.ip)), 16), 16, 10) & n.maskl) = (CONV(RIGHT(HEX(INET6_ATON(:ip6)), 16), 16, 10) & n.maskl)) |
||
185 | OR |
||
186 | (INET6_ATON(b.ip) IS NULL AND |
||
187 | (CONV(HEX(INET6_ATON(b.ip)), 16, 10) & n.maskl) = (CONV(HEX(INET6_ATON(:ip4)), 16, 10) & n.maskl)) |
||
188 | ) |
||
189 | AND active = 1 |
||
190 | AND (duration > UNIX_TIMESTAMP() OR duration IS NULL) |
||
191 | AND (b.domain IS NULL OR b.domain = :domain) |
||
192 | SQL; |
||
193 | |||
194 | $statement = $this->database->prepare($query); |
||
195 | $trustedIp = $this->xffTrustProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp()); |
||
196 | $isIPv6 = filter_var($trustedIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false; |
||
197 | |||
198 | $params = [ |
||
199 | ':name' => $request->getName(), |
||
200 | ':email' => $request->getEmail(), |
||
201 | ':useragent' => $request->getUserAgent(), |
||
202 | ':domain' => $request->getDomain(), |
||
203 | ':ip4' => $isIPv6 ? '' : $trustedIp, |
||
204 | ':ip6' => $isIPv6 ? $trustedIp : '', |
||
205 | ]; |
||
206 | die(print_r($params, true)); |
||
0 ignored issues
–
show
In this branch, the function will implicitly return
null which is incompatible with the type-hinted return array . Consider adding a return statement or allowing null as return value.
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: interface ReturnsInt {
public function returnsIntHinted(): int;
}
class MyClass implements ReturnsInt {
public function returnsIntHinted(): int
{
if (foo()) {
return 123;
}
// here: null is implicitly returned
}
}
![]() |
|||
207 | $statement->execute($params); |
||
0 ignored issues
–
show
$statement->execute($params) is not reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||
208 | |||
209 | /** @var Ban[] $result */ |
||
210 | $result = []; |
||
211 | |||
212 | /** @var Ban $v */ |
||
213 | foreach ($statement->fetchAll(PDO::FETCH_CLASS, Ban::class) as $v) { |
||
214 | $v->setDatabase($this->database); |
||
215 | $result[] = $v; |
||
216 | } |
||
217 | |||
218 | return $result; |
||
219 | } |
||
220 | } |
||
221 |
In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.