Passed
Push — multiproject/db ( 16c0c4...9dd649 )
by Simon
15:28 queued 11:05
created

RequestData::getRequest()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 6
dl 0
loc 12
ccs 0
cts 7
cp 0
rs 10
c 1
b 0
f 1
cc 4
nc 3
nop 2
crap 20
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())
0 ignored issues
show
Deprecated Code introduced by
The function Waca\SiteConfiguration::getRequestStates() has been deprecated: To be removed after dynamic queues hit production. This will need to be major point release. ( Ignorable by Annotation )

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

262
        $isClosed = !array_key_exists($request->getStatus(), /** @scrutinizer ignore-deprecated */ $config->getRequestStates())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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