Completed
Branch newinternal (830cd7)
by Simon
03:46
created

PageViewRequest::setupPrivateData()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 49
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 49
ccs 0
cts 40
cp 0
rs 8.7972
cc 4
eloc 36
nc 2
nop 3
crap 20
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\Pages;
10
11
use Exception;
12
use Waca\DataObjects\Comment;
13
use Waca\DataObjects\EmailTemplate;
14
use Waca\DataObjects\Log;
15
use Waca\DataObjects\Request;
16
use Waca\DataObjects\User;
17
use Waca\Exceptions\ApplicationLogicException;
18
use Waca\Helpers\LogHelper;
19
use Waca\Helpers\SearchHelpers\RequestSearchHelper;
20
use Waca\PdoDatabase;
21
use Waca\Security\SecurityConfiguration;
22
use Waca\SiteConfiguration;
23
use Waca\Tasks\InternalPageBase;
24
use Waca\WebRequest;
25
26
class PageViewRequest extends InternalPageBase
27
{
28
	const STATUS_SYMBOL_OPEN = '&#x2610';
29
	const STATUS_SYMBOL_ACCEPTED = '&#x2611';
30
	const STATUS_SYMBOL_REJECTED = '&#x2612';
31
	/**
32
	 * @var array Array of IP address classed as 'private' by RFC1918.
33
	 */
34
	private 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
	 * Main function for this page, when no specific actions are called.
44
	 * @throws ApplicationLogicException
45
	 */
46
	protected function main()
47
	{
48
		// set up csrf protection
49
		$this->assignCSRFToken();
50
51
		// get some useful objects
52
		$request = $this->getRequest();
53
		$config = $this->getSiteConfiguration();
54
		$database = $this->getDatabase();
55
		$currentUser = User::getCurrent($database);
56
57
		// Test we should be able to look at this request
58
		if ($config->getEmailConfirmationEnabled()) {
59
			if ($request->getEmailConfirm() !== 'Confirmed') {
60
				// Not allowed to look at this yet.
61
				throw new ApplicationLogicException('The email address has not yet been confirmed for this request.');
62
			}
63
		}
64
65
		$this->assign('requestId', $request->getId());
66
		$this->assign('updateVersion', $request->getUpdateVersion());
67
		$this->assign('requestName', $request->getName());
68
		$this->assign('requestDate', $request->getDate());
69
		$this->assign('requestStatus', $request->getStatus());
70
71
		$this->assign('requestIsClosed', !array_key_exists($request->getStatus(), $config->getRequestStates()));
72
73
		$this->setupUsernameData($request);
74
75
		$this->setupTitle($request);
76
77
		$this->setupReservationDetails($request->getReserved(), $database, $currentUser);
78
		$this->setupGeneralData($database);
79
80
		$this->assign('requestDataCleared', false);
81
		if ($request->getEmail() === $this->getSiteConfiguration()->getDataClearEmail()) {
82
			$this->assign('requestDataCleared', true);
83
		}
84
85
		$allowedPrivateData = true && $this->isAllowedPrivateData();
86
87
		$this->setupLogData($request, $database);
88
89
		if ($allowedPrivateData) {
90
			// todo: logging?
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...
91
92
			$this->setupPrivateData($request, $currentUser, $this->getSiteConfiguration());
93
94
			if ($currentUser->isCheckuser()) {
95
				$this->setupCheckUserData($request);
96
			}
97
		}
98
		else {
99
			$this->setTemplate('view-request/main.tpl');
100
		}
101
	}
102
103
	/**
104
	 * Gets a request object
105
	 *
106
	 * @return Request
107
	 * @throws ApplicationLogicException
108
	 */
109 View Code Duplication
	private function getRequest()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
110
	{
111
		$requestId = WebRequest::getInt('id');
112
		if ($requestId === null) {
113
			throw new ApplicationLogicException("No request specified");
114
		}
115
116
		$database = $this->getDatabase();
117
118
		$request = Request::getById($requestId, $database);
119
		if ($request === false || !is_a($request, Request::class)) {
120
			throw new ApplicationLogicException('Could not load the requested request!');
121
		}
122
123
		return $request;
124
	}
125
126
	/**
127
	 * @param Request $request
128
	 */
129
	protected function setupTitle(Request $request)
130
	{
131
		$statusSymbol = self::STATUS_SYMBOL_OPEN;
132
		if ($request->getStatus() === 'Closed') {
133
			if ($request->getWasCreated()) {
134
				$statusSymbol = self::STATUS_SYMBOL_ACCEPTED;
135
			}
136
			else {
137
				$statusSymbol = self::STATUS_SYMBOL_REJECTED;
138
			}
139
		}
140
141
		$this->setHtmlTitle($statusSymbol . ' #' . $request->getId());
142
	}
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 != 0;
152
		$this->assign('requestIsReserved', $requestIsReserved);
153
		$this->assign('requestIsReservedByMe', false);
154
155 View Code Duplication
		if ($requestIsReserved) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
165
	/**
166
	 * Sets up data unrelated to the request, such as the email template information
167
	 *
168
	 * @param PdoDatabase $database
169
	 */
170
	protected function setupGeneralData(PdoDatabase $database)
171
	{
172
		$config = $this->getSiteConfiguration();
173
174
		$this->assign('createAccountReason', 'Requested account at [[WP:ACC]], request #');
175
176
		$this->assign('defaultRequestState', $config->getDefaultRequestStateKey());
177
178
		$this->assign('requestStates', $config->getRequestStates());
179
180
		/** @var EmailTemplate $createdTemplate */
181
		$createdTemplate = EmailTemplate::getById($config->getDefaultCreatedTemplateId(), $database);
182
183
		$this->assign('createdHasJsQuestion', $createdTemplate->getJsquestion() != '');
184
		$this->assign('createdJsQuestion', $createdTemplate->getJsquestion());
185
		$this->assign('createdId', $createdTemplate->getId());
186
		$this->assign('createdName', $createdTemplate->getName());
187
188
		$createReasons = EmailTemplate::getActiveTemplates(EmailTemplate::CREATED, $database);
189
		$this->assign("createReasons", $createReasons);
190
		$declineReasons = EmailTemplate::getActiveTemplates(EmailTemplate::NOT_CREATED, $database);
191
		$this->assign("declineReasons", $declineReasons);
192
193
		$allCreateReasons = EmailTemplate::getAllActiveTemplates(EmailTemplate::CREATED, $database);
194
		$this->assign("allCreateReasons", $allCreateReasons);
195
		$allDeclineReasons = EmailTemplate::getAllActiveTemplates(EmailTemplate::NOT_CREATED, $database);
196
		$this->assign("allDeclineReasons", $allDeclineReasons);
197
		$allOtherReasons = EmailTemplate::getAllActiveTemplates(false, $database);
198
		$this->assign("allOtherReasons", $allOtherReasons);
199
200
		$this->getTypeAheadHelper()->defineTypeAheadSource('username-typeahead', function() use ($database) {
201
			return User::getAllUsernames($database, true);
202
		});
203
	}
204
205
	/**
206
	 * Returns a value stating whether the user is allowed to see private data or not
207
	 *
208
	 * @return bool
209
	 * @category Security-Critical
210
	 * @todo     Implement me!
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
211
	 */
212
	private function isAllowedPrivateData()
213
	{
214
		return true;
215
	}
216
217
	private function setupLogData(Request $request, PdoDatabase $database)
218
	{
219
		$currentUser = User::getCurrent($database);
220
221
		$logs = LogHelper::getRequestLogsWithComments($request->getId(), $database);
222
		$requestLogs = array();
223
224
		if (trim($request->getComment()) !== "") {
225
			$requestLogs[] = array(
226
				'type'     => 'comment',
227
				'security' => 'user',
228
				'userid'   => null,
229
				'user'     => $request->getName(),
230
				'entry'    => null,
231
				'time'     => $request->getDate(),
232
				'canedit'  => false,
233
				'id'       => $request->getId(),
234
				'comment'  => $request->getComment(),
235
			);
236
		}
237
238
		/** @var User[] $nameCache */
239
		$nameCache = array();
240
241
		$editableComments = false;
242
		if ($currentUser->isAdmin() || $currentUser->isCheckuser()) {
243
			$editableComments = true;
244
		}
245
246
		/** @var Log|Comment $entry */
247
		foreach ($logs as $entry) {
248
			if (!($entry instanceof User) && !($entry instanceof Log)) {
249
				// Something weird has happened here.
250
				continue;
251
			}
252
253
			// both log and comment have a 'user' field
254
			if (!array_key_exists($entry->getUser(), $nameCache)) {
255
				$entryUser = User::getById($entry->getUser(), $database);
256
				$nameCache[$entry->getUser()] = $entryUser;
257
			}
258
259
			if ($entry instanceof Comment) {
260
				$requestLogs[] = array(
261
					'type'     => 'comment',
262
					'security' => $entry->getVisibility(),
263
					'user'     => $nameCache[$entry->getUser()]->getUsername(),
264
					'userid'   => $entry->getUser() == -1 ? null : $entry->getUser(),
265
					'entry'    => null,
266
					'time'     => $entry->getTime(),
267
					'canedit'  => ($editableComments || $entry->getUser() == $currentUser->getId()),
268
					'id'       => $entry->getId(),
269
					'comment'  => $entry->getComment(),
270
				);
271
			}
272
273
			if ($entry instanceof Log) {
274
				$requestLogs[] = array(
275
					'type'     => 'log',
276
					'security' => 'user',
277
					'userid'   => $entry->getUser() == -1 ? null : $entry->getUser(),
278
					'user'     => $nameCache[$entry->getUser()]->getUsername(),
279
					'entry'    => LogHelper::getLogDescription($entry),
280
					'time'     => $entry->getTimestamp(),
281
					'canedit'  => false,
282
					'id'       => $entry->getId(),
283
					'comment'  => $entry->getComment(),
284
				);
285
			}
286
		}
287
288
		$this->assign("requestLogs", $requestLogs);
289
	}
290
291
	/**
292
	 * @param Request           $request
293
	 * @param User              $currentUser
294
	 * @param SiteConfiguration $configuration
295
	 *
296
	 * @throws Exception
297
	 */
298
	protected function setupPrivateData($request, User $currentUser, SiteConfiguration $configuration)
299
	{
300
		$xffProvider = $this->getXffTrustProvider();
301
		$this->setTemplate('view-request/main-with-data.tpl');
302
303
		$relatedEmailRequests = RequestSearchHelper::get($this->getDatabase())
304
			->byEmailAddress($request->getEmail())
305
			->withConfirmedEmail()
306
			->excludingPurgedData($configuration)
307
			->excludingRequest($request->getId())
308
			->fetch();
309
310
		$this->assign('requestEmail', $request->getEmail());
311
		$emailDomain = explode("@", $request->getEmail())[1];
312
		$this->assign("emailurl", $emailDomain);
313
		$this->assign('requestRelatedEmailRequestsCount', count($relatedEmailRequests));
314
		$this->assign('requestRelatedEmailRequests', $relatedEmailRequests);
315
316
		$trustedIp = $xffProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
317
		$this->assign('requestTrustedIp', $trustedIp);
318
		$this->assign('requestRealIp', $request->getIp());
319
		$this->assign('requestForwardedIp', $request->getForwardedIp());
320
321
		$trustedIpLocation = $this->getLocationProvider()->getIpLocation($trustedIp);
322
		$this->assign('requestTrustedIpLocation', $trustedIpLocation);
323
324
		$this->assign('requestHasForwardedIp', $request->getForwardedIp() !== null);
325
326
		$relatedIpRequests = RequestSearchHelper::get($this->getDatabase())
327
			->byIp($trustedIp)
328
			->withConfirmedEmail()
329
			->excludingPurgedData($configuration)
330
			->excludingRequest($request->getId())
331
			->fetch();
332
333
		$this->assign('requestRelatedIpRequestsCount', count($relatedIpRequests));
334
		$this->assign('requestRelatedIpRequests', $relatedIpRequests);
335
336
		$this->assign('showRevealLink', false);
337
		if ($request->getReserved() === $currentUser->getId() ||
338
			$currentUser->isAdmin() ||
339
			$currentUser->isCheckuser()
340
		) {
341
			$this->assign('showRevealLink', true);
342
			$this->assign('revealHash', false); // @todo
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
343
		}
344
345
		$this->setupForwardedIpData($request);
346
	}
347
348
	private function setupForwardedIpData(Request $request)
349
	{
350
		if ($request->getForwardedIp() !== null) {
351
			$requestProxyData = array(); // Initialize array to store data to be output in Smarty template.
352
			$proxyIndex = 0;
353
354
			// Assuming [client] <=> [proxy1] <=> [proxy2] <=> [proxy3] <=> [us], we will see an XFF header of [client],
355
			// [proxy1], [proxy2], and our actual IP will be [proxy3]
356
			$proxies = explode(",", $request->getForwardedIp());
357
			$proxies[] = $request->getIp();
358
359
			// Origin is the supposed "client" IP.
360
			$origin = $proxies[0];
361
			$this->assign("forwardedOrigin", $origin);
362
363
			// We step through the servers in reverse order, from closest to furthest
364
			$proxies = array_reverse($proxies);
365
366
			// By default, we have trust, because the first in the chain is now REMOTE_ADDR, which is hardest to spoof.
367
			$trust = true;
368
369
			/**
370
			 * @var int    $index     The zero-based index of the proxy.
371
			 * @var string $proxyData The proxy IP address (although possibly not!)
372
			 */
373
			foreach ($proxies as $index => $proxyData) {
374
				$proxyAddress = trim($proxyData);
375
				$requestProxyData[$proxyIndex]['ip'] = $proxyAddress;
376
377
				// get data on this IP.
378
				$thisProxyIsTrusted = $this->getXffTrustProvider()->isTrusted($proxyAddress);
379
380
				$proxyIsInPrivateRange = $this->getXffTrustProvider()->ipInRange(self::$rfc1918ips, $proxyAddress);
381
382
				if (!$proxyIsInPrivateRange) {
383
					$proxyReverseDns = $this->getRdnsProvider()->getReverseDNS($proxyAddress);
384
					$proxyLocation = $this->getLocationProvider()->getIpLocation($proxyAddress);
385
				}
386
				else {
387
					// this is going to fail, so why bother trying?
388
					$proxyReverseDns = false;
389
					$proxyLocation = false;
390
				}
391
392
				// current trust chain status BEFORE this link
393
				$preLinkTrust = $trust;
394
395
				// is *this* link trusted? Note, this will be true even if there is an untrusted link before this!
396
				$requestProxyData[$proxyIndex]['trustedlink'] = $thisProxyIsTrusted;
397
398
				// set the trust status of the chain to this point
399
				$trust = $trust & $thisProxyIsTrusted;
400
401
				// If this is the origin address, and the chain was trusted before this point, then we can trust
402
				// the origin.
403
				if ($preLinkTrust && $proxyAddress == $origin) {
404
					// if this is the origin, then we are at the last point in the chain.
405
					// @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...
406
					// to see if this is *really* the last in the chain, rather than just the same IP as it.
407
					$trust = true;
408
				}
409
410
				$requestProxyData[$proxyIndex]['trust'] = $trust;
411
412
				$requestProxyData[$proxyIndex]['rdnsfailed'] = $proxyReverseDns === false;
413
				$requestProxyData[$proxyIndex]['rdns'] = $proxyReverseDns;
414
				$requestProxyData[$proxyIndex]['routable'] = !$proxyIsInPrivateRange;
415
416
				$requestProxyData[$proxyIndex]['location'] = $proxyLocation;
417
418
				if ($proxyReverseDns === $proxyAddress && $proxyIsInPrivateRange === false) {
419
					$requestProxyData[$proxyIndex]['rdns'] = null;
420
				}
421
422
				$showLinks = (!$trust || $proxyAddress == $origin) && !$proxyIsInPrivateRange;
423
				$requestProxyData[$proxyIndex]['showlinks'] = $showLinks;
424
425
				$proxyIndex++;
426
			}
427
428
			$this->assign("requestProxyData", $requestProxyData);
429
		}
430
	}
431
432
	/**
433
	 * @param Request $request
434
	 */
435
	protected function setupCheckUserData(Request $request)
436
	{
437
		$this->setTemplate('view-request/main-with-checkuser-data.tpl');
438
		$this->assign('requestUserAgent', $request->getUserAgent());
439
	}
440
441
	/**
442
	 * Sets up the security for this page. If certain actions have different permissions, this should be reflected in
443
	 * the return value from this function.
444
	 *
445
	 * If this page even supports actions, you will need to check the route
446
	 *
447
	 * @return SecurityConfiguration
448
	 * @category Security-Critical
449
	 */
450
	protected function getSecurityConfiguration()
451
	{
452
		return $this->getSecurityManager()->configure()->asInternalPage();
453
	}
454
455
	/**
456
	 * @param Request $request
457
	 */
458
	protected function setupUsernameData(Request $request)
459
	{
460
		$blacklistData = $this->getBlacklistHelper()->isBlacklisted($request->getName());
461
462
		$this->assign('requestIsBlacklisted', $blacklistData !== false);
463
		$this->assign('requestBlacklist', $blacklistData);
464
465
		try {
466
			$spoofs = $this->getAntiSpoofProvider()->getSpoofs($request->getName());
467
		}
468
		catch (Exception $ex) {
469
			$spoofs = $ex->getMessage();
470
		}
471
472
		$this->assign("spoofs", $spoofs);
473
	}
474
}