Failed Conditions
Pull Request — master (#765)
by Richard
06:01
created

RequestData::setupPrivateData()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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