Completed
Pull Request — newinternal (#285)
by Simon
06:16 queued 03:05
created

RequestData::setupForwardedIpData()   C

Complexity

Conditions 10
Paths 26

Size

Total Lines 84
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 10.0018

Importance

Changes 0
Metric Value
cc 10
eloc 38
nc 26
nop 1
dl 0
loc 84
rs 5.3846
c 0
b 0
f 0
ccs 37
cts 38
cp 0.9737
crap 10.0018

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\PageViewRequest;
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\SecurityConfiguration;
21
use Waca\Security\SecurityManager;
22
use Waca\SiteConfiguration;
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         $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 6
    protected function isAllowedPrivateData(Request $request, User $currentUser)
71
    {
72
        // Test the main security barrier for private data access using SecurityManager
73 6
        if ($this->barrierTest('privateData')) {
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 5
        if ($currentUser->getId() === $request->getReserved() && $request->getReserved() !== null) {
80 1
            return true;
81
        }
82
83
        // user has the reveal hash
84 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:...quest->getRevealHash();.
Loading history...
85 1
            return true;
86
        }
87
88
        // nope. Not allowed.
89 3
        return false;
90
    }
91
92
    /**
93
     * Tests the security barrier for a specified action.
94
     *
95
     * Intended to be used from within templates
96
     *
97
     * @param string $action
98
     *
99
     * @return boolean
100
     * @category Security-Critical
101
     */
102
    abstract protected function barrierTest($action);
103
104
    /**
105
     * Gets the name of the route that has been passed from the request router.
106
     * @return string
107
     */
108
    abstract protected function getRouteName();
109
110
    /** @return SecurityManager */
111
    abstract protected function getSecurityManager();
112
113
    /**
114
     * Sets the name of the template this page should display.
115
     *
116
     * @param string $name
117
     */
118
    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...
119
120
    /** @return IXffTrustProvider */
121
    abstract protected function getXffTrustProvider();
122
123
    /** @return ILocationProvider */
124
    abstract protected function getLocationProvider();
125
126
    /** @return IRDnsProvider */
127
    abstract protected function getRdnsProvider();
128
129
    /**
130
     * Assigns a Smarty variable
131
     *
132
     * @param  array|string $name  the template variable name(s)
133
     * @param  mixed        $value the value to assign
134
     */
135
    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...
136
137
    /**
138
     * @param int         $requestReservationId
139
     * @param PdoDatabase $database
140
     * @param User        $currentUser
141
     */
142
    protected function setupReservationDetails($requestReservationId, PdoDatabase $database, User $currentUser)
143
    {
144
        $requestIsReserved = $requestReservationId !== null;
145
        $this->assign('requestIsReserved', $requestIsReserved);
146
        $this->assign('requestIsReservedByMe', false);
147
148
        if ($requestIsReserved) {
149
            $this->assign('requestReservedByName', User::getById($requestReservationId, $database)->getUsername());
150
            $this->assign('requestReservedById', $requestReservationId);
151
152
            if ($requestReservationId === $currentUser->getId()) {
153
                $this->assign('requestIsReservedByMe', true);
154
            }
155
        }
156
    }
157
158
    /**
159
     * Adds private request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
160
     *
161
     * @param Request           $request
162
     * @param User              $currentUser
163
     * @param SiteConfiguration $configuration
164
     *
165
     * @param PdoDatabase       $database
166
     */
167
    protected function setupPrivateData(
168
        $request,
169
        User $currentUser,
170
        SiteConfiguration $configuration,
171
        PdoDatabase $database
172
    ) {
173
        $xffProvider = $this->getXffTrustProvider();
174
175
        $relatedEmailRequests = RequestSearchHelper::get($database)
176
            ->byEmailAddress($request->getEmail())
177
            ->withConfirmedEmail()
178
            ->excludingPurgedData($configuration)
179
            ->excludingRequest($request->getId())
180
            ->fetch();
181
182
        $this->assign('requestEmail', $request->getEmail());
183
        $emailDomain = explode("@", $request->getEmail())[1];
184
        $this->assign("emailurl", $emailDomain);
185
        $this->assign('requestRelatedEmailRequestsCount', count($relatedEmailRequests));
186
        $this->assign('requestRelatedEmailRequests', $relatedEmailRequests);
187
188
        $trustedIp = $xffProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
189
        $this->assign('requestTrustedIp', $trustedIp);
190
        $this->assign('requestRealIp', $request->getIp());
191
        $this->assign('requestForwardedIp', $request->getForwardedIp());
192
193
        $trustedIpLocation = $this->getLocationProvider()->getIpLocation($trustedIp);
194
        $this->assign('requestTrustedIpLocation', $trustedIpLocation);
195
196
        $this->assign('requestHasForwardedIp', $request->getForwardedIp() !== null);
197
198
        $relatedIpRequests = RequestSearchHelper::get($database)
199
            ->byIp($trustedIp)
200
            ->withConfirmedEmail()
201
            ->excludingPurgedData($configuration)
202
            ->excludingRequest($request->getId())
203
            ->fetch();
204
205
        $this->assign('requestRelatedIpRequestsCount', count($relatedIpRequests));
206
        $this->assign('requestRelatedIpRequests', $relatedIpRequests);
207
208
        $this->assign('showRevealLink', false);
209
        if ($request->getReserved() === $currentUser->getId() ||
210
            $currentUser->isAdmin() ||
211
            $currentUser->isCheckuser()
212
        ) {
213
            $this->assign('showRevealLink', true);
214
215
            $this->assign('revealHash', $request->getRevealHash());
216
        }
217
218
        $this->setupForwardedIpData($request);
219
    }
220
221
    /**
222
     * Adds checkuser request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
223
     *
224
     * @param Request $request
225
     */
226
    protected function setupCheckUserData(Request $request)
227
    {
228
        $this->assign('requestUserAgent', $request->getUserAgent());
229
    }
230
231
    /**
232
     * Sets up the basic data for this request, and adds it to Smarty
233
     *
234
     * @param Request           $request
235
     * @param SiteConfiguration $config
236
     */
237
    protected function setupBasicData(Request $request, SiteConfiguration $config)
238
    {
239
        $this->assign('requestId', $request->getId());
240
        $this->assign('updateVersion', $request->getUpdateVersion());
241
        $this->assign('requestName', $request->getName());
242
        $this->assign('requestDate', $request->getDate());
243
        $this->assign('requestStatus', $request->getStatus());
244
245
        $this->assign('requestIsClosed', !array_key_exists($request->getStatus(), $config->getRequestStates()));
246
    }
247
248
    /**
249
     * Sets up the forwarded IP data for this request and adds it to Smarty
250
     *
251
     * @param Request $request
252
     */
253 6
    protected function setupForwardedIpData(Request $request)
254
    {
255 6
        if ($request->getForwardedIp() !== null) {
256 5
            $requestProxyData = array(); // Initialize array to store data to be output in Smarty template.
257 5
            $proxyIndex = 0;
258
259
            // Assuming [client] <=> [proxy1] <=> [proxy2] <=> [proxy3] <=> [us], we will see an XFF header of [client],
260
            // [proxy1], [proxy2], and our actual IP will be [proxy3]
261 5
            $proxies = explode(",", $request->getForwardedIp());
262 5
            $proxies[] = $request->getIp();
263
264
            // Origin is the supposed "client" IP.
265 5
            $origin = $proxies[0];
266 5
            $this->assign("forwardedOrigin", $origin);
267
268
            // We step through the servers in reverse order, from closest to furthest
269 5
            $proxies = array_reverse($proxies);
270
271
            // By default, we have trust, because the first in the chain is now REMOTE_ADDR, which is hardest to spoof.
272 5
            $trust = true;
273
274
            /**
275
             * @var int    $index     The zero-based index of the proxy.
276
             * @var string $proxyData The proxy IP address (although possibly not!)
277
             */
278 5
            foreach ($proxies as $index => $proxyData) {
279 5
                $proxyAddress = trim($proxyData);
280 5
                $requestProxyData[$proxyIndex]['ip'] = $proxyAddress;
281
282
                // get data on this IP.
283 5
                $thisProxyIsTrusted = $this->getXffTrustProvider()->isTrusted($proxyAddress);
284
285 5
                $proxyIsInPrivateRange = $this->getXffTrustProvider()
286 5
                    ->ipInRange(self::$rfc1918ips, $proxyAddress);
287
288 5
                if (!$proxyIsInPrivateRange) {
289 5
                    $proxyReverseDns = $this->getRdnsProvider()->getReverseDNS($proxyAddress);
290 5
                    $proxyLocation = $this->getLocationProvider()->getIpLocation($proxyAddress);
291
                }
292
                else {
293
                    // this is going to fail, so why bother trying?
294 1
                    $proxyReverseDns = false;
295 1
                    $proxyLocation = false;
296
                }
297
298
                // current trust chain status BEFORE this link
299 5
                $preLinkTrust = $trust;
300
301
                // is *this* link trusted? Note, this will be true even if there is an untrusted link before this!
302 5
                $requestProxyData[$proxyIndex]['trustedlink'] = $thisProxyIsTrusted;
303
304
                // set the trust status of the chain to this point
305 5
                $trust = $trust & $thisProxyIsTrusted;
306
307
                // If this is the origin address, and the chain was trusted before this point, then we can trust
308
                // the origin.
309 5
                if ($preLinkTrust && $proxyAddress == $origin) {
310
                    // if this is the origin, then we are at the last point in the chain.
311
                    // @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...
312
                    // to see if this is *really* the last in the chain, rather than just the same IP as it.
313 3
                    $trust = true;
314
                }
315
316 5
                $requestProxyData[$proxyIndex]['trust'] = $trust;
317
318 5
                $requestProxyData[$proxyIndex]['rdnsfailed'] = $proxyReverseDns === false;
319 5
                $requestProxyData[$proxyIndex]['rdns'] = $proxyReverseDns;
320 5
                $requestProxyData[$proxyIndex]['routable'] = !$proxyIsInPrivateRange;
321
322 5
                $requestProxyData[$proxyIndex]['location'] = $proxyLocation;
323
324 5
                if ($proxyReverseDns === $proxyAddress && $proxyIsInPrivateRange === false) {
325
                    $requestProxyData[$proxyIndex]['rdns'] = null;
326
                }
327
328 5
                $showLinks = (!$trust || $proxyAddress == $origin) && !$proxyIsInPrivateRange;
329 5
                $requestProxyData[$proxyIndex]['showlinks'] = $showLinks;
330
331 5
                $proxyIndex++;
332
            }
333
334 5
            $this->assign("requestProxyData", $requestProxyData);
335
        }
336 6
    }
337
338
    /**
339
     * Sets up the security for this page. If certain actions have different permissions, this should be reflected in
340
     * the return value from this function.
341
     *
342
     * If this page even supports actions, you will need to check the route
343
     *
344
     * @return SecurityConfiguration
345
     * @category Security-Critical
346
     */
347
    protected function getSecurityConfiguration()
348
    {
349
        switch ($this->getRouteName()) {
350
            case PageViewRequest::PRIVATE_DATA_BARRIER:
351
                return $this->getSecurityManager()->configure()->asGeneralPrivateDataAccess();
352
            case PageViewRequest::SET_BAN_BARRIER:
353
                return $this->getSecurityManager()->configure()->asAdminPage();
354
            default:
355
                return $this->getSecurityManager()->configure()->asInternalPage();
356
        }
357
    }
358
}