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 | die('..'.$query); |
||
0 ignored issues
–
show
|
|||
194 | $statement = $this->database->prepare($query); |
||
0 ignored issues
–
show
$statement = $this->database->prepare($query) 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 ![]() |
|||
195 | $trustedIp = $this->xffTrustProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp()); |
||
196 | $isIPv6 = filter_var($trustedIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false; |
||
197 | |||
198 | $statement->execute([ |
||
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 | |||
207 | /** @var Ban[] $result */ |
||
208 | $result = []; |
||
209 | |||
210 | /** @var Ban $v */ |
||
211 | foreach ($statement->fetchAll(PDO::FETCH_CLASS, Ban::class) as $v) { |
||
212 | $v->setDatabase($this->database); |
||
213 | $result[] = $v; |
||
214 | } |
||
215 | |||
216 | return $result; |
||
217 | } |
||
218 | } |
||
219 |
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: