Failed Conditions
Pull Request — bugsquish (#573)
by Simon
11:01 queued 08:44
created

BanHelper::nameIsBanned()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
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\Helpers;
10
11
use PDO;
12
use Waca\DataObjects\Ban;
13
use Waca\DataObjects\Request;
14
use Waca\DataObjects\User;
15
use Waca\Helpers\Interfaces\IBanHelper;
16
use Waca\PdoDatabase;
17
use Waca\Providers\Interfaces\IXffTrustProvider;
18
use Waca\Security\SecurityManager;
19
20
class BanHelper implements IBanHelper
21
{
22
    /** @var PdoDatabase */
23
    private $database;
24
    /** @var IXffTrustProvider */
25
    private $xffTrustProvider;
26
    /** @var Ban[][] */
27
    private $banCache = [];
28
    /**
29
     * @var null|SecurityManager
30
     */
31
    private $securityManager;
32
33
    public function __construct(
34
        PdoDatabase $database,
35
        IXffTrustProvider $xffTrustProvider,
36
        ?SecurityManager $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(?string $name, ?string $email, ?string $ip, ?int $mask, ?string $useragent)
73
    {
74
        /** @noinspection SqlConstantCondition */
75
        $query = <<<SQL
76
SELECT * FROM ban 
77
WHERE 1 = 1
78
  AND ((name is null and :nname is null) OR name = :name)
79
  AND ((email is null and :nemail is null) OR email = :email)
80
  AND ((useragent is null and :nuseragent is null) OR useragent = :useragent)
81
  AND ((ip is null and :nip is null) OR ip = inet6_aton(:ip))
82
  AND ((ipmask is null and :nipmask is null) OR ipmask = :ipmask)
83
  AND (duration > UNIX_TIMESTAMP() OR duration is null) 
84
  AND active = 1;
85
SQL;
86
87
        $statement = $this->database->prepare($query);
88
        $statement->execute([
89
            ':name'       => $name,
90
            ':nname'      => $name,
91
            ':email'      => $email,
92
            ':nemail'     => $email,
93
            ':ip'         => $ip,
94
            ':nip'        => $ip,
95
            ':ipmask'     => $mask,
96
            ':nipmask'    => $mask,
97
            ':useragent'  => $useragent,
98
            ':nuseragent' => $useragent,
99
        ]);
100
101
        $result = array();
102
103
        /** @var Ban $v */
104
        foreach ($statement->fetchAll(PDO::FETCH_CLASS, Ban::class) as $v) {
105
            $v->setDatabase($this->database);
106
            $result[] = $v;
107
        }
108
109
        return $result;
110
    }
111
112
    public function isActive(Ban $ban): bool
113
    {
114
        if (!$ban->isActive()) {
115
            return false;
116
        }
117
118
        if ($ban->getDuration() !== null && $ban->getDuration() < time()) {
119
            return false;
120
        }
121
122
        return true;
123
    }
124
125
    public function canUnban(Ban $ban): bool
126
    {
127
        if ($this->securityManager === null) {
128
            return false;
129
        }
130
131
        if (!$this->isActive($ban)) {
132
            return false;
133
        }
134
135
        $user = User::getCurrent($this->database);
136
137
        $allowed = true;
138
        $allowed &= ($ban->getName() === null || $this->securityManager->allows('BanType', 'name',
139
                $user) === SecurityManager::ALLOWED);
140
        $allowed &= ($ban->getEmail() === null || $this->securityManager->allows('BanType', 'email',
141
                $user) === SecurityManager::ALLOWED);
142
        $allowed &= ($ban->getIp() === null || $this->securityManager->allows('BanType', 'ip',
143
                $user) === SecurityManager::ALLOWED);
144
        $allowed &= ($ban->getUseragent() === null || $this->securityManager->allows('BanType', 'useragent',
145
                $user) === SecurityManager::ALLOWED);
146
147
        return $allowed;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $allowed returns the type integer which is incompatible with the type-hinted return boolean.
Loading history...
148
    }
149
150
    /**
151
     * @param Request $request
152
     *
153
     * @return Ban[]
154
     */
155
    private function getBansForRequestFromDatabase(Request $request): array
156
    {
157
        /** @noinspection SqlConstantCondition - included for clarity of code */
158
        $query = <<<SQL
159
select b.* from ban b
160
left join netmask n on 1 = 1
161
    and n.cidr = b.ipmask
162
    and n.protocol = case length(b.ip) when 4 then 4 when 16 then 6 end
163
where 1 = 1
164
    and coalesce(:name rlike name, true)
165
    and coalesce(:email rlike email, true)
166
    and coalesce(:useragent rlike useragent, true)
167
    and case
168
        when length(b.ip) = 4 then
169
          (conv(hex(b.ip), 16, 10) & n.maskl) = (conv(hex(inet6_aton(:ip4)), 16, 10) & n.maskl)
170
        when length(b.ip) = 16 then
171
            (conv(left(hex(b.ip), 16), 16, 10) & n.maskh) = (conv(left(hex(inet6_aton(:ip6h)), 16), 16, 10) & n.maskh)
172
            and (conv(right(hex(b.ip), 16), 16, 10) & n.maskl) = (conv(right(hex(inet6_aton(:ip6l)), 16), 16, 10) & n.maskl)
173
        when length(b.ip) is null then true
174
    end
175
    and active = 1
176
    and (duration > UNIX_TIMESTAMP() or duration is null)
177
SQL;
178
179
        $statement = $this->database->prepare($query);
180
        $trustedIp = $this->xffTrustProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
181
182
        $statement->execute([
183
            ':name'      => $request->getName(),
184
            ':email'     => $request->getEmail(),
185
            ':useragent' => $request->getUserAgent(),
186
            ':ip4'       => $trustedIp,
187
            ':ip6h'      => $trustedIp,
188
            ':ip6l'      => $trustedIp,
189
        ]);
190
191
        /** @var Ban[] $result */
192
        $result = [];
193
194
        /** @var Ban $v */
195
        foreach ($statement->fetchAll(PDO::FETCH_CLASS, Ban::class) as $v) {
196
            $v->setDatabase($this->database);
197
            $result[] = $v;
198
        }
199
200
        return $result;
201
    }
202
}
203