Completed
Branch newinternal (cdd491)
by Simon
04:39
created

RequestData::setupBasicData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 7
c 1
b 0
f 1
nc 1
nop 2
dl 0
loc 10
rs 9.4285
ccs 0
cts 9
cp 0
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\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
	protected function isAllowedPrivateData(Request $request, User $currentUser)
71
	{
72
		// Test the main security barrier for private data access using SecurityManager
73
		if ($this->barrierTest(PageViewRequest::PRIVATE_DATA_BARRIER)) {
74
			// Tool admins/check-users can always see private data
75
			return true;
76
		}
77
78
		// reserving user is allowed to see the data
79
		if ($currentUser->getId() === $request->getReserved() && $request->getReserved() !== null) {
80
			return true;
81
		}
82
83
		// user has the reveal hash
84
		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
			return true;
86
		}
87
88
		// nope. Not allowed.
89
		return false;
90
	}
91
92
	abstract protected function barrierTest($action);
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...
93
	abstract protected function getRouteName();
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...
94
95
	/** @return SecurityManager */
96
	abstract protected function getSecurityManager();
97
98
	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...
99
	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...
100
101
	/** @return IXffTrustProvider */
102
	abstract protected function getXffTrustProvider();
103
	/** @return ILocationProvider */
104
	abstract protected function getLocationProvider();
105
	/** @return IRDnsProvider */
106
	abstract protected function getRdnsProvider();
107
108
	/**
109
	 * @param int         $requestReservationId
110
	 * @param PdoDatabase $database
111
	 * @param User        $currentUser
112
	 */
113
	protected function setupReservationDetails($requestReservationId, PdoDatabase $database, User $currentUser)
114
	{
115
		$requestIsReserved = $requestReservationId !== null;
116
		$this->assign('requestIsReserved', $requestIsReserved);
117
		$this->assign('requestIsReservedByMe', false);
118
119
		if ($requestIsReserved) {
120
			$this->assign('requestReservedByName', User::getById($requestReservationId, $database)->getUsername());
121
			$this->assign('requestReservedById', $requestReservationId);
122
123
			if ($requestReservationId === $currentUser->getId()) {
124
				$this->assign('requestIsReservedByMe', true);
125
			}
126
		}
127
	}
128
129
	/**
130
	 * Adds private request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
131
	 *
132
	 * @param Request           $request
133
	 * @param User              $currentUser
134
	 * @param SiteConfiguration $configuration
135
	 *
136
	 * @param PdoDatabase       $database
137
	 */
138
	protected function setupPrivateData($request, User $currentUser, SiteConfiguration $configuration, PdoDatabase $database)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
139
	{
140
		$xffProvider = $this->getXffTrustProvider();
141
142
		$relatedEmailRequests = RequestSearchHelper::get($database)
143
			->byEmailAddress($request->getEmail())
144
			->withConfirmedEmail()
145
			->excludingPurgedData($configuration)
146
			->excludingRequest($request->getId())
147
			->fetch();
148
149
		$this->assign('requestEmail', $request->getEmail());
150
		$emailDomain = explode("@", $request->getEmail())[1];
151
		$this->assign("emailurl", $emailDomain);
152
		$this->assign('requestRelatedEmailRequestsCount', count($relatedEmailRequests));
153
		$this->assign('requestRelatedEmailRequests', $relatedEmailRequests);
154
155
		$trustedIp = $xffProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
156
		$this->assign('requestTrustedIp', $trustedIp);
157
		$this->assign('requestRealIp', $request->getIp());
158
		$this->assign('requestForwardedIp', $request->getForwardedIp());
159
160
		$trustedIpLocation = $this->getLocationProvider()->getIpLocation($trustedIp);
161
		$this->assign('requestTrustedIpLocation', $trustedIpLocation);
162
163
		$this->assign('requestHasForwardedIp', $request->getForwardedIp() !== null);
164
165
		$relatedIpRequests = RequestSearchHelper::get($database)
166
			->byIp($trustedIp)
167
			->withConfirmedEmail()
168
			->excludingPurgedData($configuration)
169
			->excludingRequest($request->getId())
170
			->fetch();
171
172
		$this->assign('requestRelatedIpRequestsCount', count($relatedIpRequests));
173
		$this->assign('requestRelatedIpRequests', $relatedIpRequests);
174
175
		$this->assign('showRevealLink', false);
176
		if ($request->getReserved() === $currentUser->getId() ||
177
			$currentUser->isAdmin() ||
178
			$currentUser->isCheckuser()
179
		) {
180
			$this->assign('showRevealLink', true);
181
182
			$this->assign('revealHash', $request->getRevealHash());
183
		}
184
185
		$this->setupForwardedIpData($request);
186
	}
187
188
	/**
189
	 * Adds checkuser request data to Smarty. DO NOT USE WITHOUT FIRST CHECKING THAT THE USER IS AUTHORISED!
190
	 *
191
	 * @param Request $request
192
	 */
193
	protected function setupCheckUserData(Request $request)
194
	{
195
		$this->assign('requestUserAgent', $request->getUserAgent());
196
	}
197
198
	/**
199
	 * @param Request $request
200
	 * @param SiteConfiguration $config
201
	 */
202
	protected function setupBasicData(Request $request, SiteConfiguration $config)
203
	{
204
		$this->assign('requestId', $request->getId());
205
		$this->assign('updateVersion', $request->getUpdateVersion());
206
		$this->assign('requestName', $request->getName());
207
		$this->assign('requestDate', $request->getDate());
208
		$this->assign('requestStatus', $request->getStatus());
209
210
		$this->assign('requestIsClosed', !array_key_exists($request->getStatus(), $config->getRequestStates()));
211
	}
212
213
	protected function setupForwardedIpData(Request $request)
214
	{
215
		if ($request->getForwardedIp() !== null) {
216
			$requestProxyData = array(); // Initialize array to store data to be output in Smarty template.
217
			$proxyIndex = 0;
218
219
			// Assuming [client] <=> [proxy1] <=> [proxy2] <=> [proxy3] <=> [us], we will see an XFF header of [client],
220
			// [proxy1], [proxy2], and our actual IP will be [proxy3]
221
			$proxies = explode(",", $request->getForwardedIp());
222
			$proxies[] = $request->getIp();
223
224
			// Origin is the supposed "client" IP.
225
			$origin = $proxies[0];
226
			$this->assign("forwardedOrigin", $origin);
227
228
			// We step through the servers in reverse order, from closest to furthest
229
			$proxies = array_reverse($proxies);
230
231
			// By default, we have trust, because the first in the chain is now REMOTE_ADDR, which is hardest to spoof.
232
			$trust = true;
233
234
			/**
235
			 * @var int    $index     The zero-based index of the proxy.
236
			 * @var string $proxyData The proxy IP address (although possibly not!)
237
			 */
238
			foreach ($proxies as $index => $proxyData) {
239
				$proxyAddress = trim($proxyData);
240
				$requestProxyData[$proxyIndex]['ip'] = $proxyAddress;
241
242
				// get data on this IP.
243
				$thisProxyIsTrusted = $this->getXffTrustProvider()->isTrusted($proxyAddress);
244
245
				$proxyIsInPrivateRange = $this->getXffTrustProvider()
246
					->ipInRange(self::$rfc1918ips, $proxyAddress);
247
248
				if (!$proxyIsInPrivateRange) {
249
					$proxyReverseDns = $this->getRdnsProvider()->getReverseDNS($proxyAddress);
250
					$proxyLocation = $this->getLocationProvider()->getIpLocation($proxyAddress);
251
				}
252
				else {
253
					// this is going to fail, so why bother trying?
254
					$proxyReverseDns = false;
255
					$proxyLocation = false;
256
				}
257
258
				// current trust chain status BEFORE this link
259
				$preLinkTrust = $trust;
260
261
				// is *this* link trusted? Note, this will be true even if there is an untrusted link before this!
262
				$requestProxyData[$proxyIndex]['trustedlink'] = $thisProxyIsTrusted;
263
264
				// set the trust status of the chain to this point
265
				$trust = $trust & $thisProxyIsTrusted;
266
267
				// If this is the origin address, and the chain was trusted before this point, then we can trust
268
				// the origin.
269
				if ($preLinkTrust && $proxyAddress == $origin) {
270
					// if this is the origin, then we are at the last point in the chain.
271
					// @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...
272
					// to see if this is *really* the last in the chain, rather than just the same IP as it.
273
					$trust = true;
274
				}
275
276
				$requestProxyData[$proxyIndex]['trust'] = $trust;
277
278
				$requestProxyData[$proxyIndex]['rdnsfailed'] = $proxyReverseDns === false;
279
				$requestProxyData[$proxyIndex]['rdns'] = $proxyReverseDns;
280
				$requestProxyData[$proxyIndex]['routable'] = !$proxyIsInPrivateRange;
281
282
				$requestProxyData[$proxyIndex]['location'] = $proxyLocation;
283
284
				if ($proxyReverseDns === $proxyAddress && $proxyIsInPrivateRange === false) {
285
					$requestProxyData[$proxyIndex]['rdns'] = null;
286
				}
287
288
				$showLinks = (!$trust || $proxyAddress == $origin) && !$proxyIsInPrivateRange;
289
				$requestProxyData[$proxyIndex]['showlinks'] = $showLinks;
290
291
				$proxyIndex++;
292
			}
293
294
			$this->assign("requestProxyData", $requestProxyData);
295
		}
296
	}
297
298
	/**
299
	 * Sets up the security for this page. If certain actions have different permissions, this should be reflected in
300
	 * the return value from this function.
301
	 *
302
	 * If this page even supports actions, you will need to check the route
303
	 *
304
	 * @return SecurityConfiguration
305
	 * @category Security-Critical
306
	 */
307
	protected function getSecurityConfiguration()
308
	{
309
		switch ($this->getRouteName()) {
310
			case PageViewRequest::PRIVATE_DATA_BARRIER:
311
				return $this->getSecurityManager()->configure()->asGeneralPrivateDataAccess();
312
			case PageViewRequest::SET_BAN_BARRIER:
313
				return $this->getSecurityManager()->configure()->asAdminPage();
314
			default:
315
				return $this->getSecurityManager()->configure()->asInternalPage();
316
		}
317
	}
318
}