Failed Conditions
Pull Request — multiproject/db (#703)
by Simon
26:29 queued 22:11
created

includes/Fragments/RequestData.php (1 issue)

Labels
Severity
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\RequestAction\PageBreakReservation;
17
use Waca\PdoDatabase;
18
use Waca\Providers\Interfaces\ILocationProvider;
19
use Waca\Providers\Interfaces\IRDnsProvider;
20
use Waca\Providers\Interfaces\IXffTrustProvider;
21
use Waca\RequestStatus;
22
use Waca\Security\SecurityManager;
23
use Waca\SiteConfiguration;
24
use Waca\WebRequest;
25
26
trait RequestData
27
{
28
    /** @return SiteConfiguration */
29
    protected abstract function getSiteConfiguration();
30
31
    /**
32
     * @var array Array of IP address classed as 'private' by RFC1918.
33
     */
34
    protected static $rfc1918ips = array(
35
        "10.0.0.0"    => "10.255.255.255",
36
        "172.16.0.0"  => "172.31.255.255",
37
        "192.168.0.0" => "192.168.255.255",
38
        "169.254.0.0" => "169.254.255.255",
39
        "127.0.0.0"   => "127.255.255.255",
40
    );
41
42
    /**
43
     * Gets a request object
44
     *
45
     * @param PdoDatabase $database  The database connection
46
     * @param int|null    $requestId The ID of the request to retrieve
47
     *
48
     * @return Request
49
     * @throws ApplicationLogicException
50
     */
51
    protected function getRequest(PdoDatabase $database, $requestId)
52
    {
53
        if ($requestId === null) {
54
            throw new ApplicationLogicException("No request specified");
55
        }
56
57
        $request = Request::getById($requestId, $database);
58
        if ($request === false || !is_a($request, Request::class)) {
59
            throw new ApplicationLogicException('Could not load the requested request!');
60
        }
61
62
        return $request;
63
    }
64
65
    /**
66
     * Returns a value stating whether the user is allowed to see private data or not
67
     *
68
     * @param Request $request
69
     * @param User    $currentUser
70
     *
71
     * @return bool
72
     * @category Security-Critical
73
     */
74 8
    protected function isAllowedPrivateData(Request $request, User $currentUser)
75
    {
76
        // Test the main security barrier for private data access using SecurityManager
77 8
        if ($this->barrierTest('alwaysSeePrivateData', $currentUser, 'RequestData')) {
78
            // Tool admins/check-users can always see private data
79 1
            return true;
80
        }
81
82
        // reserving user is allowed to see the data
83 7
        if ($currentUser->getId() === $request->getReserved()
84 7
            && $request->getReserved() !== null
85 7
            && $this->barrierTest('seePrivateDataWhenReserved', $currentUser, 'RequestData')
86
        ) {
87 1
            return true;
88
        }
89
90
        // user has the reveal hash
91 6
        if (WebRequest::getString('hash') === $request->getRevealHash()
92 6
            && $this->barrierTest('seePrivateDataWithHash', $currentUser, 'RequestData')
93
        ) {
94 1
            return true;
95
        }
96
97
        // nope. Not allowed.
98 5
        return false;
99
    }
100
101
    /**
102
     * Tests the security barrier for a specified action.
103
     *
104
     * Don't use within templates
105
     *
106
     * @param string      $action
107
     *
108
     * @param User        $user
109
     * @param null|string $pageName
110
     *
111
     * @return bool
112
     * @category Security-Critical
113
     */
114
    abstract protected function barrierTest($action, User $user, $pageName = null);
115
116
    /**
117
     * Gets the name of the route that has been passed from the request router.
118
     * @return string
119
     */
120
    abstract protected function getRouteName();
121
122
    /** @return SecurityManager */
123
    abstract protected function getSecurityManager();
124
125
    /**
126
     * Sets the name of the template this page should display.
127
     *
128
     * @param string $name
129
     */
130
    abstract protected function setTemplate($name);
131
132
    /** @return IXffTrustProvider */
133
    abstract protected function getXffTrustProvider();
134
135
    /** @return ILocationProvider */
136
    abstract protected function getLocationProvider();
137
138
    /** @return IRDnsProvider */
139
    abstract protected function getRdnsProvider();
140
141
    /**
142
     * Assigns a Smarty variable
143
     *
144
     * @param  array|string $name  the template variable name(s)
145
     * @param  mixed        $value the value to assign
146
     */
147
    abstract protected function assign($name, $value);
148
149
    /**
150
     * @param int|null    $requestReservationId
151
     * @param PdoDatabase $database
152
     * @param User        $currentUser
153
     */
154
    protected function setupReservationDetails($requestReservationId, PdoDatabase $database, User $currentUser)
155
    {
156
        $requestIsReserved = $requestReservationId !== null;
157
        $this->assign('requestIsReserved', $requestIsReserved);
158
        $this->assign('requestIsReservedByMe', false);
159
160
        if ($requestIsReserved) {
161
            $this->assign('requestReservedByName', User::getById($requestReservationId, $database)->getUsername());
162
            $this->assign('requestReservedById', $requestReservationId);
163
164
            if ($requestReservationId === $currentUser->getId()) {
165
                $this->assign('requestIsReservedByMe', true);
166
            }
167
        }
168
169
        $this->assign('canBreakReservation', $this->barrierTest('force', $currentUser, PageBreakReservation::class));
170
    }
171
172
    /**
173
     * Adds private request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
174
     *
175
     * @param Request           $request
176
     * @param SiteConfiguration $configuration
177
     */
178
    protected function setupPrivateData(
179
        $request,
180
        SiteConfiguration $configuration
181
    ) {
182
        $xffProvider = $this->getXffTrustProvider();
183
184
        $this->assign('requestEmail', $request->getEmail());
185
        $emailDomain = explode("@", $request->getEmail())[1];
186
        $this->assign("emailurl", $emailDomain);
187
        $this->assign('commonEmailDomain', in_array(strtolower($emailDomain), $configuration->getCommonEmailDomains())
188
            || $request->getEmail() === $this->getSiteConfiguration()->getDataClearEmail());
189
190
        $trustedIp = $xffProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
191
        $this->assign('requestTrustedIp', $trustedIp);
192
        $this->assign('requestRealIp', $request->getIp());
193
        $this->assign('requestForwardedIp', $request->getForwardedIp());
194
195
        $trustedIpLocation = $this->getLocationProvider()->getIpLocation($trustedIp);
196
        $this->assign('requestTrustedIpLocation', $trustedIpLocation);
197
198
        $this->assign('requestHasForwardedIp', $request->getForwardedIp() !== null);
199
200
        $this->setupForwardedIpData($request);
201
    }
202
203
    /**
204
     * Adds related request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
205
     *
206
     * @param Request           $request
207
     * @param SiteConfiguration $configuration
208
     * @param PdoDatabase       $database
209
     */
210
    protected function setupRelatedRequests(
211
        Request $request,
212
        SiteConfiguration $configuration,
213
        PdoDatabase $database)
214
    {
215
        $this->assign('canSeeRelatedRequests', true);
216
217
        $relatedEmailRequests = RequestSearchHelper::get($database)
218
            ->byEmailAddress($request->getEmail())
219
            ->withConfirmedEmail()
220
            ->excludingPurgedData($configuration)
221
            ->excludingRequest($request->getId())
222
            ->fetch();
223
224
        $this->assign('requestRelatedEmailRequestsCount', count($relatedEmailRequests));
225
        $this->assign('requestRelatedEmailRequests', $relatedEmailRequests);
226
227
        $trustedIp = $this->getXffTrustProvider()->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
228
        $relatedIpRequests = RequestSearchHelper::get($database)
229
            ->byIp($trustedIp)
230
            ->withConfirmedEmail()
231
            ->excludingPurgedData($configuration)
232
            ->excludingRequest($request->getId())
233
            ->fetch();
234
235
        $this->assign('requestRelatedIpRequestsCount', count($relatedIpRequests));
236
        $this->assign('requestRelatedIpRequests', $relatedIpRequests);
237
    }
238
239
    /**
240
     * Adds checkuser request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
241
     *
242
     * @param Request $request
243
     */
244
    protected function setupCheckUserData(Request $request)
245
    {
246
        $this->assign('requestUserAgent', $request->getUserAgent());
247
    }
248
249
    /**
250
     * Sets up the basic data for this request, and adds it to Smarty
251
     *
252
     * @param Request           $request
253
     * @param SiteConfiguration $config
254
     */
255
    protected function setupBasicData(Request $request, SiteConfiguration $config)
256
    {
257
        $this->assign('requestId', $request->getId());
258
        $this->assign('updateVersion', $request->getUpdateVersion());
259
        $this->assign('requestName', $request->getName());
260
        $this->assign('requestDate', $request->getDate());
261
        $this->assign('requestStatus', $request->getStatus());
262
263
        $this->assign('requestQueue', null);
264
        if ($request->getQueue() !== null) {
265
            /** @var RequestQueue $queue */
266
            $queue = RequestQueue::getById($request->getQueue(), $this->getDatabase());
0 ignored issues
show
It seems like getDatabase() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

266
            $queue = RequestQueue::getById($request->getQueue(), $this->/** @scrutinizer ignore-call */ getDatabase());
Loading history...
267
            $this->assign('requestQueue', $queue->getHeader());
268
            $this->assign('requestQueueApiName', $queue->getApiName());
269
        }
270
271
        $isClosed = $request->getStatus() === RequestStatus::CLOSED || $request->getStatus() === RequestStatus::JOBQUEUE;
272
        $this->assign('requestIsClosed', $isClosed);
273
    }
274
275
    /**
276
     * Sets up the forwarded IP data for this request and adds it to Smarty
277
     *
278
     * @param Request $request
279
     */
280 6
    protected function setupForwardedIpData(Request $request)
281
    {
282 6
        if ($request->getForwardedIp() !== null) {
283 5
            $requestProxyData = array(); // Initialize array to store data to be output in Smarty template.
284 5
            $proxyIndex = 0;
285
286
            // Assuming [client] <=> [proxy1] <=> [proxy2] <=> [proxy3] <=> [us], we will see an XFF header of [client],
287
            // [proxy1], [proxy2], and our actual IP will be [proxy3]
288 5
            $proxies = explode(",", $request->getForwardedIp());
289 5
            $proxies[] = $request->getIp();
290
291
            // Origin is the supposed "client" IP.
292 5
            $origin = $proxies[0];
293 5
            $this->assign("forwardedOrigin", $origin);
294
295
            // We step through the servers in reverse order, from closest to furthest
296 5
            $proxies = array_reverse($proxies);
297
298
            // By default, we have trust, because the first in the chain is now REMOTE_ADDR, which is hardest to spoof.
299 5
            $trust = true;
300
301
            /**
302
             * @var int    $index     The zero-based index of the proxy.
303
             * @var string $proxyData The proxy IP address (although possibly not!)
304
             */
305 5
            foreach ($proxies as $index => $proxyData) {
306 5
                $proxyAddress = trim($proxyData);
307 5
                $requestProxyData[$proxyIndex]['ip'] = $proxyAddress;
308
309
                // get data on this IP.
310 5
                $thisProxyIsTrusted = $this->getXffTrustProvider()->isTrusted($proxyAddress);
311
312 5
                $proxyIsInPrivateRange = $this->getXffTrustProvider()
313 5
                    ->ipInRange(self::$rfc1918ips, $proxyAddress);
314
315 5
                if (!$proxyIsInPrivateRange) {
316 5
                    $proxyReverseDns = $this->getRdnsProvider()->getReverseDNS($proxyAddress);
317 5
                    $proxyLocation = $this->getLocationProvider()->getIpLocation($proxyAddress);
318
                }
319
                else {
320
                    // this is going to fail, so why bother trying?
321 1
                    $proxyReverseDns = false;
322 1
                    $proxyLocation = false;
323
                }
324
325
                // current trust chain status BEFORE this link
326 5
                $preLinkTrust = $trust;
327
328
                // is *this* link trusted? Note, this will be true even if there is an untrusted link before this!
329 5
                $requestProxyData[$proxyIndex]['trustedlink'] = $thisProxyIsTrusted;
330
331
                // set the trust status of the chain to this point
332 5
                $trust = $trust & $thisProxyIsTrusted;
333
334
                // If this is the origin address, and the chain was trusted before this point, then we can trust
335
                // the origin.
336 5
                if ($preLinkTrust && $proxyAddress == $origin) {
337
                    // if this is the origin, then we are at the last point in the chain.
338
                    // @todo: this is probably the cause of some bugs when an IP appears twice - we're missing a check
339
                    // to see if this is *really* the last in the chain, rather than just the same IP as it.
340 3
                    $trust = true;
341
                }
342
343 5
                $requestProxyData[$proxyIndex]['trust'] = $trust;
344
345 5
                $requestProxyData[$proxyIndex]['rdnsfailed'] = $proxyReverseDns === false;
346 5
                $requestProxyData[$proxyIndex]['rdns'] = $proxyReverseDns;
347 5
                $requestProxyData[$proxyIndex]['routable'] = !$proxyIsInPrivateRange;
348
349 5
                $requestProxyData[$proxyIndex]['location'] = $proxyLocation;
350
351 5
                if ($proxyReverseDns === $proxyAddress && $proxyIsInPrivateRange === false) {
352
                    $requestProxyData[$proxyIndex]['rdns'] = null;
353
                }
354
355 5
                $showLinks = (!$trust || $proxyAddress == $origin) && !$proxyIsInPrivateRange;
356 5
                $requestProxyData[$proxyIndex]['showlinks'] = $showLinks;
357
358 5
                $proxyIndex++;
359
            }
360
361 5
            $this->assign("requestProxyData", $requestProxyData);
362
        }
363 6
    }
364
}
365