Completed
Push — rbac ( 1ec5d5...5c33ef )
by Simon
04:39
created

RequestData::isAllowedPrivateData()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 26
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7.1782

Importance

Changes 0
Metric Value
cc 7
eloc 11
nc 4
nop 2
dl 0
loc 26
ccs 11
cts 13
cp 0.8462
crap 7.1782
rs 6.7272
c 0
b 0
f 0
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\Security\SecurityManager;
21
use Waca\SiteConfiguration;
22
use Waca\WebRequest;
23
24
trait RequestData
25
{
26
    /**
27
     * @var array Array of IP address classed as 'private' by RFC1918.
28
     */
29
    protected static $rfc1918ips = array(
30
        "10.0.0.0"    => "10.255.255.255",
31
        "172.16.0.0"  => "172.31.255.255",
32
        "192.168.0.0" => "192.168.255.255",
33
        "169.254.0.0" => "169.254.255.255",
34
        "127.0.0.0"   => "127.255.255.255",
35
    );
36
37
    /**
38
     * Gets a request object
39
     *
40
     * @param PdoDatabase $database  The database connection
41
     * @param int         $requestId The ID of the request to retrieve
42
     *
43
     * @return Request
44
     * @throws ApplicationLogicException
45
     */
46
    protected function getRequest(PdoDatabase $database, $requestId)
47
    {
48
        if ($requestId === null) {
49
            throw new ApplicationLogicException("No request specified");
50
        }
51
52
        $request = Request::getById($requestId, $database);
53
        if ($request === false || !is_a($request, Request::class)) {
54
            throw new ApplicationLogicException('Could not load the requested request!');
55
        }
56
57
        return $request;
58
    }
59
60
    /**
61
     * Returns a value stating whether the user is allowed to see private data or not
62
     *
63
     * @param Request $request
64
     * @param User    $currentUser
65
     *
66
     * @return bool
67
     * @category Security-Critical
68
     */
69 6
    protected function isAllowedPrivateData(Request $request, User $currentUser)
70
    {
71
        // Test the main security barrier for private data access using SecurityManager
72 6
        if ($this->barrierTest('alwaysSeePrivateData', $currentUser, 'RequestData')) {
73
            // Tool admins/check-users can always see private data
74 1
            return true;
75
        }
76
77
        // reserving user is allowed to see the data
78 5
        if ($currentUser->getId() === $request->getReserved()
79 5
            && $request->getReserved() !== null
80 5
            && $this->barrierTest('seePrivateDataWhenReserved', $currentUser, 'RequestData')
81 4
        ) {
82
            return true;
83
        }
84
85
        // user has the reveal hash
86 4
        if (WebRequest::getString('hash') === $request->getRevealHash()
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return \Waca\WebRequest:...ntUser, 'RequestData');.
Loading history...
87 4
            && $this->barrierTest('seePrivateDataWithHash', $currentUser, 'RequestData')
88 3
        ) {
89
            return true;
90
        }
91
92
        // nope. Not allowed.
93 3
        return false;
94
    }
95
96
    /**
97
     * Tests the security barrier for a specified action.
98
     *
99
     * Don't use within templates
100
     *
101
     * @param string      $action
102
     *
103
     * @param User        $user
104
     * @param null|string $pageName
105
     *
106
     * @return bool
107
     * @category Security-Critical
108
     */
109
    abstract protected function barrierTest($action, User $user, $pageName = null);
110
111
    /**
112
     * Gets the name of the route that has been passed from the request router.
113
     * @return string
114
     */
115
    abstract protected function getRouteName();
116
117
    /** @return SecurityManager */
118
    abstract protected function getSecurityManager();
119
120
    /**
121
     * Sets the name of the template this page should display.
122
     *
123
     * @param string $name
124
     */
125
    abstract protected function setTemplate($name);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
126
127
    /** @return IXffTrustProvider */
128
    abstract protected function getXffTrustProvider();
129
130
    /** @return ILocationProvider */
131
    abstract protected function getLocationProvider();
132
133
    /** @return IRDnsProvider */
134
    abstract protected function getRdnsProvider();
135
136
    /**
137
     * Assigns a Smarty variable
138
     *
139
     * @param  array|string $name  the template variable name(s)
140
     * @param  mixed        $value the value to assign
141
     */
142
    abstract protected function assign($name, $value);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
143
144
    /**
145
     * @param int         $requestReservationId
146
     * @param PdoDatabase $database
147
     * @param User        $currentUser
148
     */
149
    protected function setupReservationDetails($requestReservationId, PdoDatabase $database, User $currentUser)
150
    {
151
        $requestIsReserved = $requestReservationId !== null;
152
        $this->assign('requestIsReserved', $requestIsReserved);
153
        $this->assign('requestIsReservedByMe', false);
154
155
        if ($requestIsReserved) {
156
            $this->assign('requestReservedByName', User::getById($requestReservationId, $database)->getUsername());
157
            $this->assign('requestReservedById', $requestReservationId);
158
159
            if ($requestReservationId === $currentUser->getId()) {
160
                $this->assign('requestIsReservedByMe', true);
161
            }
162
        }
163
164
        $this->assign('canBreakReservation', $this->barrierTest('force', $currentUser, PageBreakReservation::class));
165
    }
166
167
    /**
168
     * Adds private request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
169
     *
170
     * @param Request           $request
171
     * @param User              $currentUser
172
     * @param SiteConfiguration $configuration
173
     *
174
     * @param PdoDatabase       $database
175
     */
176
    protected function setupPrivateData(
177
        $request,
178
        User $currentUser,
179
        SiteConfiguration $configuration,
180
        PdoDatabase $database
181
    ) {
182
        $xffProvider = $this->getXffTrustProvider();
183
184
        $relatedEmailRequests = RequestSearchHelper::get($database)
185
            ->byEmailAddress($request->getEmail())
186
            ->withConfirmedEmail()
187
            ->excludingPurgedData($configuration)
188
            ->excludingRequest($request->getId())
189
            ->fetch();
190
191
        $this->assign('requestEmail', $request->getEmail());
192
        $emailDomain = explode("@", $request->getEmail())[1];
193
        $this->assign("emailurl", $emailDomain);
194
        $this->assign('requestRelatedEmailRequestsCount', count($relatedEmailRequests));
195
        $this->assign('requestRelatedEmailRequests', $relatedEmailRequests);
196
197
        $trustedIp = $xffProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
198
        $this->assign('requestTrustedIp', $trustedIp);
199
        $this->assign('requestRealIp', $request->getIp());
200
        $this->assign('requestForwardedIp', $request->getForwardedIp());
201
202
        $trustedIpLocation = $this->getLocationProvider()->getIpLocation($trustedIp);
203
        $this->assign('requestTrustedIpLocation', $trustedIpLocation);
204
205
        $this->assign('requestHasForwardedIp', $request->getForwardedIp() !== null);
206
207
        $relatedIpRequests = RequestSearchHelper::get($database)
208
            ->byIp($trustedIp)
209
            ->withConfirmedEmail()
210
            ->excludingPurgedData($configuration)
211
            ->excludingRequest($request->getId())
212
            ->fetch();
213
214
        $this->assign('requestRelatedIpRequestsCount', count($relatedIpRequests));
215
        $this->assign('requestRelatedIpRequests', $relatedIpRequests);
216
217
        $this->assign('showRevealLink', false);
218
        if ($request->getReserved() === $currentUser->getId() ||
219
            $this->barrierTest('alwaysSeeHash', $currentUser, 'RequestData')
220
        ) {
221
            $this->assign('showRevealLink', true);
222
            $this->assign('revealHash', $request->getRevealHash());
223
        }
224
225
        $this->setupForwardedIpData($request);
226
    }
227
228
    /**
229
     * Adds checkuser request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
230
     *
231
     * @param Request $request
232
     */
233
    protected function setupCheckUserData(Request $request)
234
    {
235
        $this->assign('requestUserAgent', $request->getUserAgent());
236
    }
237
238
    /**
239
     * Sets up the basic data for this request, and adds it to Smarty
240
     *
241
     * @param Request           $request
242
     * @param SiteConfiguration $config
243
     */
244
    protected function setupBasicData(Request $request, SiteConfiguration $config)
245
    {
246
        $this->assign('requestId', $request->getId());
247
        $this->assign('updateVersion', $request->getUpdateVersion());
248
        $this->assign('requestName', $request->getName());
249
        $this->assign('requestDate', $request->getDate());
250
        $this->assign('requestStatus', $request->getStatus());
251
252
        $this->assign('requestIsClosed', !array_key_exists($request->getStatus(), $config->getRequestStates()));
253
    }
254
255
    /**
256
     * Sets up the forwarded IP data for this request and adds it to Smarty
257
     *
258
     * @param Request $request
259
     */
260 6
    protected function setupForwardedIpData(Request $request)
261
    {
262 6
        if ($request->getForwardedIp() !== null) {
263 5
            $requestProxyData = array(); // Initialize array to store data to be output in Smarty template.
264 5
            $proxyIndex = 0;
265
266
            // Assuming [client] <=> [proxy1] <=> [proxy2] <=> [proxy3] <=> [us], we will see an XFF header of [client],
267
            // [proxy1], [proxy2], and our actual IP will be [proxy3]
268 5
            $proxies = explode(",", $request->getForwardedIp());
269 5
            $proxies[] = $request->getIp();
270
271
            // Origin is the supposed "client" IP.
272 5
            $origin = $proxies[0];
273 5
            $this->assign("forwardedOrigin", $origin);
274
275
            // We step through the servers in reverse order, from closest to furthest
276 5
            $proxies = array_reverse($proxies);
277
278
            // By default, we have trust, because the first in the chain is now REMOTE_ADDR, which is hardest to spoof.
279 5
            $trust = true;
280
281
            /**
282
             * @var int    $index     The zero-based index of the proxy.
283
             * @var string $proxyData The proxy IP address (although possibly not!)
284
             */
285 5
            foreach ($proxies as $index => $proxyData) {
286 5
                $proxyAddress = trim($proxyData);
287 5
                $requestProxyData[$proxyIndex]['ip'] = $proxyAddress;
288
289
                // get data on this IP.
290 5
                $thisProxyIsTrusted = $this->getXffTrustProvider()->isTrusted($proxyAddress);
291
292 5
                $proxyIsInPrivateRange = $this->getXffTrustProvider()
293 5
                    ->ipInRange(self::$rfc1918ips, $proxyAddress);
294
295 5
                if (!$proxyIsInPrivateRange) {
296 5
                    $proxyReverseDns = $this->getRdnsProvider()->getReverseDNS($proxyAddress);
297 5
                    $proxyLocation = $this->getLocationProvider()->getIpLocation($proxyAddress);
298 5
                }
299
                else {
300
                    // this is going to fail, so why bother trying?
301 1
                    $proxyReverseDns = false;
302 1
                    $proxyLocation = false;
303
                }
304
305
                // current trust chain status BEFORE this link
306 5
                $preLinkTrust = $trust;
307
308
                // is *this* link trusted? Note, this will be true even if there is an untrusted link before this!
309 5
                $requestProxyData[$proxyIndex]['trustedlink'] = $thisProxyIsTrusted;
310
311
                // set the trust status of the chain to this point
312 5
                $trust = $trust & $thisProxyIsTrusted;
313
314
                // If this is the origin address, and the chain was trusted before this point, then we can trust
315
                // the origin.
316 5
                if ($preLinkTrust && $proxyAddress == $origin) {
317
                    // if this is the origin, then we are at the last point in the chain.
318
                    // @todo: this is probably the cause of some bugs when an IP appears twice - we're missing a check
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
319
                    // to see if this is *really* the last in the chain, rather than just the same IP as it.
320 3
                    $trust = true;
321 3
                }
322
323 5
                $requestProxyData[$proxyIndex]['trust'] = $trust;
324
325 5
                $requestProxyData[$proxyIndex]['rdnsfailed'] = $proxyReverseDns === false;
326 5
                $requestProxyData[$proxyIndex]['rdns'] = $proxyReverseDns;
327 5
                $requestProxyData[$proxyIndex]['routable'] = !$proxyIsInPrivateRange;
328
329 5
                $requestProxyData[$proxyIndex]['location'] = $proxyLocation;
330
331 5
                if ($proxyReverseDns === $proxyAddress && $proxyIsInPrivateRange === false) {
332
                    $requestProxyData[$proxyIndex]['rdns'] = null;
333
                }
334
335 5
                $showLinks = (!$trust || $proxyAddress == $origin) && !$proxyIsInPrivateRange;
336 5
                $requestProxyData[$proxyIndex]['showlinks'] = $showLinks;
337
338 5
                $proxyIndex++;
339 5
            }
340
341 5
            $this->assign("requestProxyData", $requestProxyData);
342 5
        }
343 6
    }
344
}
345