Failed Conditions
Pull Request — bugsquish (#573)
by Simon
03:02 queued 49s
created

BanHelper::getBansByTarget()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 38
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 28
dl 0
loc 38
ccs 0
cts 32
cp 0
rs 9.472
c 0
b 0
f 0
cc 2
nc 2
nop 5
crap 6
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 isBanned(Request $request): bool
44
    {
45
        if (!isset($this->banCache[$request->getId()])) {
46
            $this->banCache[$request->getId()] = $this->getBansForRequestFromDatabase($request);
47
        }
48
49
        return count($this->banCache[$request->getId()]) >= 1;
50
    }
51
52
    /**
53
     * @param Request $request
54
     *
55
     * @return Ban[]
56
     */
57
    public function getBans(Request $request): array
58
    {
59
        if (!isset($this->banCache[$request->getId()])) {
60
            $this->banCache[$request->getId()] = $this->getBansForRequestFromDatabase($request);
61
        }
62
63
        return $this->banCache[$request->getId()];
64
    }
65
66
    public function getBansByTarget(?string $name, ?string $email, ?string $ip, ?int $mask, ?string $useragent)
67
    {
68
        /** @noinspection SqlConstantCondition */
69
        $query = <<<SQL
70
SELECT * FROM ban 
71
WHERE 1 = 1
72
  AND ((name is null and :nname is null) OR name = :name)
73
  AND ((email is null and :nemail is null) OR email = :email)
74
  AND ((useragent is null and :nuseragent is null) OR useragent = :useragent)
75
  AND ((ip is null and :nip is null) OR ip = inet6_aton(:ip))
76
  AND ((ipmask is null and :nipmask is null) OR ipmask = :ipmask)
77
  AND (duration > UNIX_TIMESTAMP() OR duration is null) 
78
  AND active = 1;
79
SQL;
80
81
        $statement = $this->database->prepare($query);
82
        $statement->execute([
83
            ':name'       => $name,
84
            ':nname'      => $name,
85
            ':email'      => $email,
86
            ':nemail'     => $email,
87
            ':ip'         => $ip,
88
            ':nip'        => $ip,
89
            ':ipmask'     => $mask,
90
            ':nipmask'    => $mask,
91
            ':useragent'  => $useragent,
92
            ':nuseragent' => $useragent,
93
        ]);
94
95
        $result = array();
96
97
        /** @var Ban $v */
98
        foreach ($statement->fetchAll(PDO::FETCH_CLASS, Ban::class) as $v) {
99
            $v->setDatabase($this->database);
100
            $result[] = $v;
101
        }
102
103
        return $result;
104
    }
105
106
    public function isActive(Ban $ban): bool
107
    {
108
        if (!$ban->isActive()) {
109
            return false;
110
        }
111
112
        if ($ban->getDuration() !== null && $ban->getDuration() < time()) {
113
            return false;
114
        }
115
116
        return true;
117
    }
118
119
    public function canUnban(Ban $ban): bool
120
    {
121
        if ($this->securityManager === null) {
122
            return false;
123
        }
124
125
        if (!$this->isActive($ban)) {
126
            return false;
127
        }
128
129
        $user = User::getCurrent($this->database);
130
131
        $allowed = true;
132
        $allowed &= ($ban->getName() === null || $this->securityManager->allows('BanType', 'name',
133
                $user) === SecurityManager::ALLOWED);
134
        $allowed &= ($ban->getEmail() === null || $this->securityManager->allows('BanType', 'email',
135
                $user) === SecurityManager::ALLOWED);
136
        $allowed &= ($ban->getIp() === null || $this->securityManager->allows('BanType', 'ip',
137
                $user) === SecurityManager::ALLOWED);
138
        $allowed &= ($ban->getUseragent() === null || $this->securityManager->allows('BanType', 'useragent',
139
                $user) === SecurityManager::ALLOWED);
140
141
        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...
142
    }
143
144
    /**
145
     * @param Request $request
146
     *
147
     * @return Ban[]
148
     */
149
    private function getBansForRequestFromDatabase(Request $request): array
150
    {
151
        /** @noinspection SqlConstantCondition - included for clarity of code */
152
        $query = <<<SQL
153
select b.* from ban b
154
left join netmask n on 1 = 1
155
    and n.cidr = b.ipmask
156
    and n.protocol = case length(b.ip) when 4 then 4 when 16 then 6 end
157
where 1 = 1
158
    and coalesce(:name rlike name, true)
159
    and coalesce(:email rlike email, true)
160
    and coalesce(:useragent rlike useragent, true)
161
    and case
162
        when length(b.ip) = 4 then
163
          (conv(hex(b.ip), 16, 10) & n.maskl) = (conv(hex(inet6_aton(:ip4)), 16, 10) & n.maskl)
164
        when length(b.ip) = 16 then
165
            (conv(left(hex(b.ip), 16), 16, 10) & n.maskh) = (conv(left(hex(inet6_aton(:ip6h)), 16), 16, 10) & n.maskh)
166
            and (conv(right(hex(b.ip), 16), 16, 10) & n.maskl) = (conv(right(hex(inet6_aton(:ip6l)), 16), 16, 10) & n.maskl)
167
        when length(b.ip) is null then true
168
    end
169
    and active = 1
170
    and (duration > UNIX_TIMESTAMP() or duration is null)
171
SQL;
172
173
        $statement = $this->database->prepare($query);
174
        $trustedIp = $this->xffTrustProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
175
176
        $statement->execute([
177
            ':name'      => $request->getName(),
178
            ':email'     => $request->getEmail(),
179
            ':useragent' => $request->getUserAgent(),
180
            ':ip4'       => $trustedIp,
181
            ':ip6h'      => $trustedIp,
182
            ':ip6l'      => $trustedIp,
183
        ]);
184
185
        /** @var Ban[] $result */
186
        $result = [];
187
188
        /** @var Ban $v */
189
        foreach ($statement->fetchAll(PDO::FETCH_CLASS, Ban::class) as $v) {
190
            $v->setDatabase($this->database);
191
            $result[] = $v;
192
        }
193
194
        return $result;
195
    }
196
}
197