Passed
Push — statistics ( 40505d...a159ce )
by Simon
19:10 queued 15:20
created

RequestData   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 329
Duplicated Lines 0 %

Test Coverage

Coverage 40.87%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 30
eloc 104
c 2
b 0
f 1
dl 0
loc 329
ccs 47
cts 115
cp 0.4087
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A setupRelatedRequests() 0 27 1
B setupForwardedIpData() 0 82 10
A setupBasicData() 0 11 2
A setupCheckUserData() 0 3 1
B isAllowedPrivateData() 0 25 7
A setupReservationDetails() 0 16 3
A getRequest() 0 12 4
A setupPrivateData() 0 23 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\Fragments;
10
11
use Waca\DataObjects\Request;
12
use Waca\DataObjects\User;
13
use Waca\Exceptions\ApplicationLogicException;
14
use Waca\Helpers\SearchHelpers\RequestSearchHelper;
15
use Waca\Pages\RequestAction\PageBreakReservation;
16
use Waca\PdoDatabase;
17
use Waca\Providers\Interfaces\ILocationProvider;
18
use Waca\Providers\Interfaces\IRDnsProvider;
19
use Waca\Providers\Interfaces\IXffTrustProvider;
20
use Waca\RequestStatus;
21
use Waca\Security\SecurityManager;
22
use Waca\SiteConfiguration;
23
use Waca\WebRequest;
24
25
trait RequestData
26
{
27
    /** @return SiteConfiguration */
28
    protected abstract function getSiteConfiguration();
29
30
    /**
31
     * @var array Array of IP address classed as 'private' by RFC1918.
32
     */
33
    protected static $rfc1918ips = array(
34
        "10.0.0.0"    => "10.255.255.255",
35
        "172.16.0.0"  => "172.31.255.255",
36
        "192.168.0.0" => "192.168.255.255",
37
        "169.254.0.0" => "169.254.255.255",
38
        "127.0.0.0"   => "127.255.255.255",
39
    );
40
41
    /**
42
     * Gets a request object
43
     *
44
     * @param PdoDatabase $database  The database connection
45
     * @param int|null    $requestId The ID of the request to retrieve
46
     *
47
     * @return Request
48
     * @throws ApplicationLogicException
49
     */
50
    protected function getRequest(PdoDatabase $database, $requestId)
51
    {
52
        if ($requestId === null) {
53
            throw new ApplicationLogicException("No request specified");
54
        }
55
56
        $request = Request::getById($requestId, $database);
57
        if ($request === false || !is_a($request, Request::class)) {
58
            throw new ApplicationLogicException('Could not load the requested request!');
59
        }
60
61
        return $request;
62
    }
63
64
    /**
65
     * Returns a value stating whether the user is allowed to see private data or not
66
     *
67
     * @param Request $request
68
     * @param User    $currentUser
69
     *
70
     * @return bool
71
     * @category Security-Critical
72
     */
73 8
    protected function isAllowedPrivateData(Request $request, User $currentUser)
74
    {
75
        // Test the main security barrier for private data access using SecurityManager
76 8
        if ($this->barrierTest('alwaysSeePrivateData', $currentUser, 'RequestData')) {
77
            // Tool admins/check-users can always see private data
78 1
            return true;
79
        }
80
81
        // reserving user is allowed to see the data
82 7
        if ($currentUser->getId() === $request->getReserved()
83 7
            && $request->getReserved() !== null
84 7
            && $this->barrierTest('seePrivateDataWhenReserved', $currentUser, 'RequestData')
85
        ) {
86 1
            return true;
87
        }
88
89
        // user has the reveal hash
90 6
        if (WebRequest::getString('hash') === $request->getRevealHash()
91 6
            && $this->barrierTest('seePrivateDataWithHash', $currentUser, 'RequestData')
92
        ) {
93 1
            return true;
94
        }
95
96
        // nope. Not allowed.
97 5
        return false;
98
    }
99
100
    /**
101
     * Tests the security barrier for a specified action.
102
     *
103
     * Don't use within templates
104
     *
105
     * @param string      $action
106
     *
107
     * @param User        $user
108
     * @param null|string $pageName
109
     *
110
     * @return bool
111
     * @category Security-Critical
112
     */
113
    abstract protected function barrierTest($action, User $user, $pageName = null);
114
115
    /**
116
     * Gets the name of the route that has been passed from the request router.
117
     * @return string
118
     */
119
    abstract protected function getRouteName();
120
121
    /** @return SecurityManager */
122
    abstract protected function getSecurityManager();
123
124
    /**
125
     * Sets the name of the template this page should display.
126
     *
127
     * @param string $name
128
     */
129
    abstract protected function setTemplate($name);
130
131
    /** @return IXffTrustProvider */
132
    abstract protected function getXffTrustProvider();
133
134
    /** @return ILocationProvider */
135
    abstract protected function getLocationProvider();
136
137
    /** @return IRDnsProvider */
138
    abstract protected function getRdnsProvider();
139
140
    /**
141
     * Assigns a Smarty variable
142
     *
143
     * @param  array|string $name  the template variable name(s)
144
     * @param  mixed        $value the value to assign
145
     */
146
    abstract protected function assign($name, $value);
147
148
    /**
149
     * @param int|null    $requestReservationId
150
     * @param PdoDatabase $database
151
     * @param User        $currentUser
152
     */
153
    protected function setupReservationDetails($requestReservationId, PdoDatabase $database, User $currentUser)
154
    {
155
        $requestIsReserved = $requestReservationId !== null;
156
        $this->assign('requestIsReserved', $requestIsReserved);
157
        $this->assign('requestIsReservedByMe', false);
158
159
        if ($requestIsReserved) {
160
            $this->assign('requestReservedByName', User::getById($requestReservationId, $database)->getUsername());
161
            $this->assign('requestReservedById', $requestReservationId);
162
163
            if ($requestReservationId === $currentUser->getId()) {
164
                $this->assign('requestIsReservedByMe', true);
165
            }
166
        }
167
168
        $this->assign('canBreakReservation', $this->barrierTest('force', $currentUser, PageBreakReservation::class));
169
    }
170
171
    /**
172
     * Adds private request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
173
     *
174
     * @param Request           $request
175
     * @param SiteConfiguration $configuration
176
     */
177
    protected function setupPrivateData(
178
        $request,
179
        SiteConfiguration $configuration
180
    ) {
181
        $xffProvider = $this->getXffTrustProvider();
182
183
        $this->assign('requestEmail', $request->getEmail());
184
        $emailDomain = explode("@", $request->getEmail())[1];
0 ignored issues
show
Bug introduced by
It seems like $request->getEmail() can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

184
        $emailDomain = explode("@", /** @scrutinizer ignore-type */ $request->getEmail())[1];
Loading history...
185
        $this->assign("emailurl", $emailDomain);
186
        $this->assign('commonEmailDomain', in_array(strtolower($emailDomain), $configuration->getCommonEmailDomains())
187
            || $request->getEmail() === $this->getSiteConfiguration()->getDataClearEmail() );
188
189
        $trustedIp = $xffProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
190
        $this->assign('requestTrustedIp', $trustedIp);
191
        $this->assign('requestRealIp', $request->getIp());
192
        $this->assign('requestForwardedIp', $request->getForwardedIp());
193
194
        $trustedIpLocation = $this->getLocationProvider()->getIpLocation($trustedIp);
195
        $this->assign('requestTrustedIpLocation', $trustedIpLocation);
196
197
        $this->assign('requestHasForwardedIp', $request->getForwardedIp() !== null);
198
199
        $this->setupForwardedIpData($request);
200
    }
201
202
    /**
203
     * Adds related request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
204
     *
205
     * @param Request           $request
206
     * @param SiteConfiguration $configuration
207
     * @param PdoDatabase       $database
208
     */
209
    protected function setupRelatedRequests(
210
        Request $request,
211
        SiteConfiguration $configuration,
212
        PdoDatabase $database)
213
    {
214
        $this->assign('canSeeRelatedRequests', true);
215
216
        $relatedEmailRequests = RequestSearchHelper::get($database)
217
            ->byEmailAddress($request->getEmail())
218
            ->withConfirmedEmail()
219
            ->excludingPurgedData($configuration)
220
            ->excludingRequest($request->getId())
221
            ->fetch();
222
223
        $this->assign('requestRelatedEmailRequestsCount', count($relatedEmailRequests));
224
        $this->assign('requestRelatedEmailRequests', $relatedEmailRequests);
225
226
        $trustedIp = $this->getXffTrustProvider()->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
227
        $relatedIpRequests = RequestSearchHelper::get($database)
228
            ->byIp($trustedIp)
229
            ->withConfirmedEmail()
230
            ->excludingPurgedData($configuration)
231
            ->excludingRequest($request->getId())
232
            ->fetch();
233
234
        $this->assign('requestRelatedIpRequestsCount', count($relatedIpRequests));
235
        $this->assign('requestRelatedIpRequests', $relatedIpRequests);
236
    }
237
238
    /**
239
     * Adds checkuser request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
240
     *
241
     * @param Request $request
242
     */
243
    protected function setupCheckUserData(Request $request)
244
    {
245
        $this->assign('requestUserAgent', $request->getUserAgent());
246
    }
247
248
    /**
249
     * Sets up the basic data for this request, and adds it to Smarty
250
     *
251
     * @param Request           $request
252
     * @param SiteConfiguration $config
253
     */
254
    protected function setupBasicData(Request $request, SiteConfiguration $config)
255
    {
256
        $this->assign('requestId', $request->getId());
257
        $this->assign('updateVersion', $request->getUpdateVersion());
258
        $this->assign('requestName', $request->getName());
259
        $this->assign('requestDate', $request->getDate());
260
        $this->assign('requestStatus', $request->getStatus());
261
262
        $isClosed = !array_key_exists($request->getStatus(), $config->getRequestStates())
263
            && $request->getStatus() !== RequestStatus::HOSPITAL;
264
        $this->assign('requestIsClosed', $isClosed);
265
    }
266
267
    /**
268
     * Sets up the forwarded IP data for this request and adds it to Smarty
269
     *
270
     * @param Request $request
271
     */
272 6
    protected function setupForwardedIpData(Request $request)
273
    {
274 6
        if ($request->getForwardedIp() !== null) {
275 5
            $requestProxyData = array(); // Initialize array to store data to be output in Smarty template.
276 5
            $proxyIndex = 0;
277
278
            // Assuming [client] <=> [proxy1] <=> [proxy2] <=> [proxy3] <=> [us], we will see an XFF header of [client],
279
            // [proxy1], [proxy2], and our actual IP will be [proxy3]
280 5
            $proxies = explode(",", $request->getForwardedIp());
281 5
            $proxies[] = $request->getIp();
282
283
            // Origin is the supposed "client" IP.
284 5
            $origin = $proxies[0];
285 5
            $this->assign("forwardedOrigin", $origin);
286
287
            // We step through the servers in reverse order, from closest to furthest
288 5
            $proxies = array_reverse($proxies);
289
290
            // By default, we have trust, because the first in the chain is now REMOTE_ADDR, which is hardest to spoof.
291 5
            $trust = true;
292
293
            /**
294
             * @var int    $index     The zero-based index of the proxy.
295
             * @var string $proxyData The proxy IP address (although possibly not!)
296
             */
297 5
            foreach ($proxies as $index => $proxyData) {
298 5
                $proxyAddress = trim($proxyData);
299 5
                $requestProxyData[$proxyIndex]['ip'] = $proxyAddress;
300
301
                // get data on this IP.
302 5
                $thisProxyIsTrusted = $this->getXffTrustProvider()->isTrusted($proxyAddress);
303
304 5
                $proxyIsInPrivateRange = $this->getXffTrustProvider()
305 5
                    ->ipInRange(self::$rfc1918ips, $proxyAddress);
306
307 5
                if (!$proxyIsInPrivateRange) {
308 5
                    $proxyReverseDns = $this->getRdnsProvider()->getReverseDNS($proxyAddress);
309 5
                    $proxyLocation = $this->getLocationProvider()->getIpLocation($proxyAddress);
310
                }
311
                else {
312
                    // this is going to fail, so why bother trying?
313 1
                    $proxyReverseDns = false;
314 1
                    $proxyLocation = false;
315
                }
316
317
                // current trust chain status BEFORE this link
318 5
                $preLinkTrust = $trust;
319
320
                // is *this* link trusted? Note, this will be true even if there is an untrusted link before this!
321 5
                $requestProxyData[$proxyIndex]['trustedlink'] = $thisProxyIsTrusted;
322
323
                // set the trust status of the chain to this point
324 5
                $trust = $trust & $thisProxyIsTrusted;
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
325
326
                // If this is the origin address, and the chain was trusted before this point, then we can trust
327
                // the origin.
328 5
                if ($preLinkTrust && $proxyAddress == $origin) {
329
                    // if this is the origin, then we are at the last point in the chain.
330
                    // @todo: this is probably the cause of some bugs when an IP appears twice - we're missing a check
331
                    // to see if this is *really* the last in the chain, rather than just the same IP as it.
332 3
                    $trust = true;
333
                }
334
335 5
                $requestProxyData[$proxyIndex]['trust'] = $trust;
336
337 5
                $requestProxyData[$proxyIndex]['rdnsfailed'] = $proxyReverseDns === false;
338 5
                $requestProxyData[$proxyIndex]['rdns'] = $proxyReverseDns;
339 5
                $requestProxyData[$proxyIndex]['routable'] = !$proxyIsInPrivateRange;
0 ignored issues
show
introduced by
The condition $proxyIsInPrivateRange is always false.
Loading history...
340
341 5
                $requestProxyData[$proxyIndex]['location'] = $proxyLocation;
342
343 5
                if ($proxyReverseDns === $proxyAddress && $proxyIsInPrivateRange === false) {
344
                    $requestProxyData[$proxyIndex]['rdns'] = null;
345
                }
346
347 5
                $showLinks = (!$trust || $proxyAddress == $origin) && !$proxyIsInPrivateRange;
0 ignored issues
show
introduced by
The condition $trust is always true.
Loading history...
348 5
                $requestProxyData[$proxyIndex]['showlinks'] = $showLinks;
349
350 5
                $proxyIndex++;
351
            }
352
353 5
            $this->assign("requestProxyData", $requestProxyData);
354
        }
355 6
    }
356
}
357