Completed
Push — search ( 076ba7...2a5267 )
by Simon
16:36 queued 11:48
created

RequestData::setupRelatedRequests()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 27
ccs 0
cts 19
cp 0
rs 9.6666
cc 1
nc 1
nop 3
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\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;
0 ignored issues
show
Bug introduced by
The type Waca\SiteConfiguration was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use Waca\WebRequest;
24
25
trait RequestData
26
{
27
    /**
28
     * @var array Array of IP address classed as 'private' by RFC1918.
29
     */
30
    protected static $rfc1918ips = array(
31
        "10.0.0.0"    => "10.255.255.255",
32
        "172.16.0.0"  => "172.31.255.255",
33
        "192.168.0.0" => "192.168.255.255",
34
        "169.254.0.0" => "169.254.255.255",
35
        "127.0.0.0"   => "127.255.255.255",
36
    );
37
38
    /**
39
     * Gets a request object
40
     *
41
     * @param PdoDatabase $database  The database connection
42
     * @param int|null    $requestId The ID of the request to retrieve
43
     *
44
     * @return Request
45
     * @throws ApplicationLogicException
46
     */
47
    protected function getRequest(PdoDatabase $database, $requestId)
48
    {
49
        if ($requestId === null) {
50
            throw new ApplicationLogicException("No request specified");
51
        }
52
53
        $request = Request::getById($requestId, $database);
54
        if ($request === false || !is_a($request, Request::class)) {
55
            throw new ApplicationLogicException('Could not load the requested request!');
56
        }
57
58
        return $request;
59
    }
60
61
    /**
62
     * Returns a value stating whether the user is allowed to see private data or not
63
     *
64
     * @param Request $request
65
     * @param User    $currentUser
66
     *
67
     * @return bool
68
     * @category Security-Critical
69
     */
70 8
    protected function isAllowedPrivateData(Request $request, User $currentUser)
71
    {
72
        // Test the main security barrier for private data access using SecurityManager
73 8
        if ($this->barrierTest('alwaysSeePrivateData', $currentUser, 'RequestData')) {
74
            // Tool admins/check-users can always see private data
75 1
            return true;
76
        }
77
78
        // reserving user is allowed to see the data
79 7
        if ($currentUser->getId() === $request->getReserved()
80 7
            && $request->getReserved() !== null
81 7
            && $this->barrierTest('seePrivateDataWhenReserved', $currentUser, 'RequestData')
82
        ) {
83 1
            return true;
84
        }
85
86
        // user has the reveal hash
87 6
        if (WebRequest::getString('hash') === $request->getRevealHash()
88 6
            && $this->barrierTest('seePrivateDataWithHash', $currentUser, 'RequestData')
89
        ) {
90 1
            return true;
91
        }
92
93
        // nope. Not allowed.
94 5
        return false;
95
    }
96
97
    /**
98
     * Tests the security barrier for a specified action.
99
     *
100
     * Don't use within templates
101
     *
102
     * @param string      $action
103
     *
104
     * @param User        $user
105
     * @param null|string $pageName
106
     *
107
     * @return bool
108
     * @category Security-Critical
109
     */
110
    abstract protected function barrierTest($action, User $user, $pageName = null);
111
112
    /**
113
     * Gets the name of the route that has been passed from the request router.
114
     * @return string
115
     */
116
    abstract protected function getRouteName();
117
118
    /** @return SecurityManager */
119
    abstract protected function getSecurityManager();
120
121
    /**
122
     * Sets the name of the template this page should display.
123
     *
124
     * @param string $name
125
     */
126
    abstract protected function setTemplate($name);
127
128
    /** @return IXffTrustProvider */
129
    abstract protected function getXffTrustProvider();
130
131
    /** @return ILocationProvider */
132
    abstract protected function getLocationProvider();
133
134
    /** @return IRDnsProvider */
135
    abstract protected function getRdnsProvider();
136
137
    /**
138
     * Assigns a Smarty variable
139
     *
140
     * @param  array|string $name  the template variable name(s)
141
     * @param  mixed        $value the value to assign
142
     */
143
    abstract protected function assign($name, $value);
144
145
    /**
146
     * @param int|null    $requestReservationId
147
     * @param PdoDatabase $database
148
     * @param User        $currentUser
149
     */
150
    protected function setupReservationDetails($requestReservationId, PdoDatabase $database, User $currentUser)
151
    {
152
        $requestIsReserved = $requestReservationId !== null;
153
        $this->assign('requestIsReserved', $requestIsReserved);
154
        $this->assign('requestIsReservedByMe', false);
155
156
        if ($requestIsReserved) {
157
            $this->assign('requestReservedByName', User::getById($requestReservationId, $database)->getUsername());
158
            $this->assign('requestReservedById', $requestReservationId);
159
160
            if ($requestReservationId === $currentUser->getId()) {
161
                $this->assign('requestIsReservedByMe', true);
162
            }
163
        }
164
165
        $this->assign('canBreakReservation', $this->barrierTest('force', $currentUser, PageBreakReservation::class));
166
    }
167
168
    /**
169
     * Adds private request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
170
     *
171
     * @param Request           $request
172
     */
173
    protected function setupPrivateData(
174
        $request
175
    ) {
176
        $xffProvider = $this->getXffTrustProvider();
177
178
        $this->assign('requestEmail', $request->getEmail());
179
        $emailDomain = explode("@", $request->getEmail())[1];
180
        $this->assign("emailurl", $emailDomain);
181
182
        $trustedIp = $xffProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
183
        $this->assign('requestTrustedIp', $trustedIp);
184
        $this->assign('requestRealIp', $request->getIp());
185
        $this->assign('requestForwardedIp', $request->getForwardedIp());
186
187
        $trustedIpLocation = $this->getLocationProvider()->getIpLocation($trustedIp);
188
        $this->assign('requestTrustedIpLocation', $trustedIpLocation);
189
190
        $this->assign('requestHasForwardedIp', $request->getForwardedIp() !== null);
191
192
        $this->setupForwardedIpData($request);
193
    }
194
195
    /**
196
     * Adds related request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
197
     *
198
     * @param Request           $request
199
     * @param SiteConfiguration $configuration
200
     * @param PdoDatabase       $database
201
     */
202
    protected function setupRelatedRequests(
203
        Request $request,
204
        SiteConfiguration $configuration,
205
        PdoDatabase $database)
206
    {
207
        $this->assign('canSeeRelatedRequests', true);
208
209
        $relatedEmailRequests = RequestSearchHelper::get($database)
210
            ->byEmailAddress($request->getEmail())
211
            ->withConfirmedEmail()
212
            ->excludingPurgedData($configuration)
213
            ->excludingRequest($request->getId())
214
            ->fetch();
215
216
        $this->assign('requestRelatedEmailRequestsCount', count($relatedEmailRequests));
217
        $this->assign('requestRelatedEmailRequests', $relatedEmailRequests);
218
219
        $trustedIp = $this->getXffTrustProvider()->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
220
        $relatedIpRequests = RequestSearchHelper::get($database)
221
            ->byIp($trustedIp)
222
            ->withConfirmedEmail()
223
            ->excludingPurgedData($configuration)
224
            ->excludingRequest($request->getId())
225
            ->fetch();
226
227
        $this->assign('requestRelatedIpRequestsCount', count($relatedIpRequests));
228
        $this->assign('requestRelatedIpRequests', $relatedIpRequests);
229
    }
230
231
    /**
232
     * Adds checkuser request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
233
     *
234
     * @param Request $request
235
     */
236
    protected function setupCheckUserData(Request $request)
237
    {
238
        $this->assign('requestUserAgent', $request->getUserAgent());
239
    }
240
241
    /**
242
     * Sets up the basic data for this request, and adds it to Smarty
243
     *
244
     * @param Request           $request
245
     * @param SiteConfiguration $config
246
     */
247
    protected function setupBasicData(Request $request, SiteConfiguration $config)
248
    {
249
        $this->assign('requestId', $request->getId());
250
        $this->assign('updateVersion', $request->getUpdateVersion());
251
        $this->assign('requestName', $request->getName());
252
        $this->assign('requestDate', $request->getDate());
253
        $this->assign('requestStatus', $request->getStatus());
254
255
        $isClosed = !array_key_exists($request->getStatus(), $config->getRequestStates())
256
            && $request->getStatus() !== RequestStatus::HOSPITAL;
257
        $this->assign('requestIsClosed', $isClosed);
258
    }
259
260
    /**
261
     * Sets up the forwarded IP data for this request and adds it to Smarty
262
     *
263
     * @param Request $request
264
     */
265 6
    protected function setupForwardedIpData(Request $request)
266
    {
267 6
        if ($request->getForwardedIp() !== null) {
268 5
            $requestProxyData = array(); // Initialize array to store data to be output in Smarty template.
269 5
            $proxyIndex = 0;
270
271
            // Assuming [client] <=> [proxy1] <=> [proxy2] <=> [proxy3] <=> [us], we will see an XFF header of [client],
272
            // [proxy1], [proxy2], and our actual IP will be [proxy3]
273 5
            $proxies = explode(",", $request->getForwardedIp());
274 5
            $proxies[] = $request->getIp();
275
276
            // Origin is the supposed "client" IP.
277 5
            $origin = $proxies[0];
278 5
            $this->assign("forwardedOrigin", $origin);
279
280
            // We step through the servers in reverse order, from closest to furthest
281 5
            $proxies = array_reverse($proxies);
282
283
            // By default, we have trust, because the first in the chain is now REMOTE_ADDR, which is hardest to spoof.
284 5
            $trust = true;
285
286
            /**
287
             * @var int    $index     The zero-based index of the proxy.
288
             * @var string $proxyData The proxy IP address (although possibly not!)
289
             */
290 5
            foreach ($proxies as $index => $proxyData) {
291 5
                $proxyAddress = trim($proxyData);
292 5
                $requestProxyData[$proxyIndex]['ip'] = $proxyAddress;
293
294
                // get data on this IP.
295 5
                $thisProxyIsTrusted = $this->getXffTrustProvider()->isTrusted($proxyAddress);
296
297 5
                $proxyIsInPrivateRange = $this->getXffTrustProvider()
298 5
                    ->ipInRange(self::$rfc1918ips, $proxyAddress);
299
300 5
                if (!$proxyIsInPrivateRange) {
301 5
                    $proxyReverseDns = $this->getRdnsProvider()->getReverseDNS($proxyAddress);
302 5
                    $proxyLocation = $this->getLocationProvider()->getIpLocation($proxyAddress);
303
                }
304
                else {
305
                    // this is going to fail, so why bother trying?
306 1
                    $proxyReverseDns = false;
307 1
                    $proxyLocation = false;
308
                }
309
310
                // current trust chain status BEFORE this link
311 5
                $preLinkTrust = $trust;
312
313
                // is *this* link trusted? Note, this will be true even if there is an untrusted link before this!
314 5
                $requestProxyData[$proxyIndex]['trustedlink'] = $thisProxyIsTrusted;
315
316
                // set the trust status of the chain to this point
317 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...
318
319
                // If this is the origin address, and the chain was trusted before this point, then we can trust
320
                // the origin.
321 5
                if ($preLinkTrust && $proxyAddress == $origin) {
322
                    // if this is the origin, then we are at the last point in the chain.
323
                    // @todo: this is probably the cause of some bugs when an IP appears twice - we're missing a check
324
                    // to see if this is *really* the last in the chain, rather than just the same IP as it.
325 3
                    $trust = true;
326
                }
327
328 5
                $requestProxyData[$proxyIndex]['trust'] = $trust;
329
330 5
                $requestProxyData[$proxyIndex]['rdnsfailed'] = $proxyReverseDns === false;
331 5
                $requestProxyData[$proxyIndex]['rdns'] = $proxyReverseDns;
332 5
                $requestProxyData[$proxyIndex]['routable'] = !$proxyIsInPrivateRange;
0 ignored issues
show
introduced by
The condition $proxyIsInPrivateRange is always false.
Loading history...
333
334 5
                $requestProxyData[$proxyIndex]['location'] = $proxyLocation;
335
336 5
                if ($proxyReverseDns === $proxyAddress && $proxyIsInPrivateRange === false) {
337
                    $requestProxyData[$proxyIndex]['rdns'] = null;
338
                }
339
340 5
                $showLinks = (!$trust || $proxyAddress == $origin) && !$proxyIsInPrivateRange;
0 ignored issues
show
introduced by
The condition $trust is always true.
Loading history...
introduced by
Consider adding parentheses for clarity. Current Interpretation: $showLinks = (! $trust |...$proxyIsInPrivateRange), Probably Intended Meaning: $showLinks = ! $trust ||...$proxyIsInPrivateRange)
Loading history...
341 5
                $requestProxyData[$proxyIndex]['showlinks'] = $showLinks;
342
343 5
                $proxyIndex++;
344
            }
345
346 5
            $this->assign("requestProxyData", $requestProxyData);
347
        }
348 6
    }
349
}
350