BanHelper::canUnban()   D
last analyzed

Complexity

Conditions 13
Paths 326

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 29
ccs 0
cts 10
cp 0
rs 4.2083
c 0
b 0
f 0
cc 13
nc 326
nop 1
crap 182

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 LENGTH(b.ip) WHEN 4 THEN 4 WHEN 16 THEN 6 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 CASE
182
        WHEN LENGTH(b.ip) = 4 THEN
183
          (CONV(HEX(b.ip), 16, 10) & n.maskl) = (CONV(HEX(INET6_ATON(:ip4)), 16, 10) & n.maskl)
184
        WHEN LENGTH(b.ip) = 16 THEN
185
            (CONV(LEFT(HEX(b.ip), 16), 16, 10) & n.maskh) = (CONV(LEFT(HEX(INET6_ATON(:ip6h)), 16), 16, 10) & n.maskh)
186
            AND (CONV(RIGHT(HEX(b.ip), 16), 16, 10) & n.maskl) = (CONV(RIGHT(HEX(INET6_ATON(:ip6l)), 16), 16, 10) & n.maskl)
187
        WHEN LENGTH(b.ip) IS NULL THEN TRUE
188
    END
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
197
        $statement->execute([
198
            ':name'      => $request->getName(),
199
            ':email'     => $request->getEmail(),
200
            ':useragent' => $request->getUserAgent(),
201
            ':domain'    => $request->getDomain(),
202
            ':ip4'       => $trustedIp,
203
            ':ip6h'      => $trustedIp,
204
            ':ip6l'      => $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