Failed Conditions
Branch newinternal (104de7)
by Simon
09:33
created

PageViewRequest::setupPrivateData()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 47
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 47
rs 8.6845
cc 4
eloc 34
nc 2
nop 3
1
<?php
2
3
namespace Waca\Pages;
4
5
use Exception;
6
use Waca\DataObjects\Comment;
7
use Waca\DataObjects\EmailTemplate;
8
use Waca\DataObjects\Log;
9
use Waca\DataObjects\Request;
10
use Waca\DataObjects\User;
11
use Waca\Exceptions\ApplicationLogicException;
12
use Waca\Helpers\LogHelper;
13
use Waca\Helpers\RequestSearchHelper;
14
use Waca\PdoDatabase;
15
use Waca\Security\SecurityConfiguration;
16
use Waca\SiteConfiguration;
17
use Waca\Tasks\InternalPageBase;
18
use Waca\WebRequest;
19
20
class PageViewRequest extends InternalPageBase
21
{
22
	const STATUS_SYMBOL_OPEN = '&#x2610';
23
	const STATUS_SYMBOL_ACCEPTED = '&#x2611';
24
	const STATUS_SYMBOL_REJECTED = '&#x2612';
25
	/**
26
	 * @var array Array of IP address classed as 'private' by RFC1918.
27
	 */
28
	private static $rfc1918ips = array(
29
		"10.0.0.0"    => "10.255.255.255",
30
		"172.16.0.0"  => "172.31.255.255",
31
		"192.168.0.0" => "192.168.255.255",
32
		"169.254.0.0" => "169.254.255.255",
33
		"127.0.0.0"   => "127.255.255.255",
34
	);
35
36
	/**
37
	 * Main function for this page, when no specific actions are called.
38
	 * @throws ApplicationLogicException
39
	 */
40
	protected function main()
41
	{
42
		// set up csrf protection
43
		$this->assignCSRFToken();
44
45
		// get some useful objects
46
		$request = $this->getRequest();
47
		$config = $this->getSiteConfiguration();
48
		$database = $this->getDatabase();
49
		$currentUser = User::getCurrent($database);
50
51
		// Test we should be able to look at this request
52
		if ($config->getEmailConfirmationEnabled()) {
53
			if ($request->getEmailConfirm() !== 'Confirmed') {
54
				// Not allowed to look at this yet.
55
				throw new ApplicationLogicException('The email address has not yet been confirmed for this request.');
56
			}
57
		}
58
59
		$this->assign('requestId', $request->getId());
60
		$this->assign('updateVersion', $request->getUpdateVersion());
61
		$this->assign('requestName', $request->getName());
62
		$this->assign('requestDate', $request->getDate());
63
		$this->assign('requestStatus', $request->getStatus());
64
65
		$this->assign('requestIsClosed', !array_key_exists($request->getStatus(), $config->getRequestStates()));
66
67
		$this->setupUsernameData($request);
68
69
		$this->setupTitle($request);
70
71
		$this->setupReservationDetails($request->getReserved(), $database, $currentUser);
72
		$this->setupGeneralData($database);
73
74
		$this->assign('requestDataCleared', false);
75
		if ($request->getEmail() === $this->getSiteConfiguration()->getDataClearEmail()) {
76
			$this->assign('requestDataCleared', true);
77
		}
78
79
		$allowedPrivateData = true && $this->isAllowedPrivateData();
80
81
		$this->setupLogData($request, $database);
82
83
		if ($allowedPrivateData) {
84
			// 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...
85
86
			$this->setupPrivateData($request, $currentUser, $this->getSiteConfiguration());
87
88
			if ($currentUser->isCheckuser()) {
89
				$this->setupCheckUserData($request);
90
			}
91
		}
92
		else {
93
			$this->setTemplate('view-request/main.tpl');
94
		}
95
	}
96
97
	/**
98
	 * Gets a request object
99
	 *
100
	 * @return Request
101
	 * @throws ApplicationLogicException
102
	 */
103 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...
104
	{
105
		$requestId = WebRequest::getInt('id');
106
		if ($requestId === null) {
107
			throw new ApplicationLogicException("No request specified");
108
		}
109
110
		$database = $this->getDatabase();
111
112
		$request = Request::getById($requestId, $database);
113
		if ($request === false || !is_a($request, Request::class)) {
114
			throw new ApplicationLogicException('Could not load the requested request!');
115
		}
116
117
		return $request;
118
	}
119
120
	/**
121
	 * @param Request $request
122
	 */
123
	protected function setupTitle(Request $request)
124
	{
125
		$statusSymbol = self::STATUS_SYMBOL_OPEN;
126
		if ($request->getStatus() === 'Closed') {
127
			if ($request->getWasCreated()) {
128
				$statusSymbol = self::STATUS_SYMBOL_ACCEPTED;
129
			}
130
			else {
131
				$statusSymbol = self::STATUS_SYMBOL_REJECTED;
132
			}
133
		}
134
135
		$this->setHtmlTitle($statusSymbol . ' #' . $request->getId());
136
	}
137
138
	/**
139
	 * @param int         $requestReservationId
140
	 * @param PdoDatabase $database
141
	 * @param User        $currentUser
142
	 */
143
	protected function setupReservationDetails($requestReservationId, PdoDatabase $database, User $currentUser)
144
	{
145
		$requestIsReserved = $requestReservationId != 0;
146
		$this->assign('requestIsReserved', $requestIsReserved);
147
		$this->assign('requestIsReservedByMe', false);
148
149 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...
150
			$this->assign('requestReservedByName', User::getById($requestReservationId, $database)->getUsername());
151
			$this->assign('requestReservedById', $requestReservationId);
152
153
			if ($requestReservationId === $currentUser->getId()) {
154
				$this->assign('requestIsReservedByMe', true);
155
			}
156
		}
157
	}
158
159
	/**
160
	 * Sets up data unrelated to the request, such as the email template information
161
	 *
162
	 * @param PdoDatabase $database
163
	 */
164
	protected function setupGeneralData(PdoDatabase $database)
165
	{
166
		$config = $this->getSiteConfiguration();
167
168
		$this->assign('createAccountReason', 'Requested account at [[WP:ACC]], request #');
169
170
		$this->assign('defaultRequestState', $config->getDefaultRequestStateKey());
171
172
		$this->assign('requestStates', $config->getRequestStates());
173
174
		/** @var EmailTemplate $createdTemplate */
175
		$createdTemplate = EmailTemplate::getById($config->getDefaultCreatedTemplateId(), $database);
176
177
		$this->assign('createdHasJsQuestion', $createdTemplate->getJsquestion() != '');
178
		$this->assign('createdJsQuestion', $createdTemplate->getJsquestion());
179
		$this->assign('createdId', $createdTemplate->getId());
180
		$this->assign('createdName', $createdTemplate->getName());
181
182
		$createReasons = EmailTemplate::getActiveTemplates(EmailTemplate::CREATED, $database);
183
		$this->assign("createReasons", $createReasons);
184
		$declineReasons = EmailTemplate::getActiveTemplates(EmailTemplate::NOT_CREATED, $database);
185
		$this->assign("declineReasons", $declineReasons);
186
187
		$allCreateReasons = EmailTemplate::getAllActiveTemplates(EmailTemplate::CREATED, $database);
188
		$this->assign("allCreateReasons", $allCreateReasons);
189
		$allDeclineReasons = EmailTemplate::getAllActiveTemplates(EmailTemplate::NOT_CREATED, $database);
190
		$this->assign("allDeclineReasons", $allDeclineReasons);
191
		$allOtherReasons = EmailTemplate::getAllActiveTemplates(false, $database);
192
		$this->assign("allOtherReasons", $allOtherReasons);
193
194
		$this->getTypeAheadHelper()->defineTypeAheadSource('username-typeahead', function() use ($database) {
195
			return User::getAllUsernames($database, true);
196
		});
197
	}
198
199
	/**
200
	 * Returns a value stating whether the user is allowed to see private data or not
201
	 *
202
	 * @return bool
203
	 * @category Security-Critical
204
	 * @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...
205
	 */
206
	private function isAllowedPrivateData()
207
	{
208
		return true;
209
	}
210
211
	private function setupLogData(Request $request, PdoDatabase $database)
212
	{
213
		$currentUser = User::getCurrent($database);
214
215
		$logs = LogHelper::getRequestLogsWithComments($request->getId(), $database);
216
		$requestLogs = array();
217
218
		if (trim($request->getComment()) !== "") {
219
			$requestLogs[] = array(
220
				'type'     => 'comment',
221
				'security' => 'user',
222
				'userid'   => null,
223
				'user'     => $request->getName(),
224
				'entry'    => null,
225
				'time'     => $request->getDate(),
226
				'canedit'  => false,
227
				'id'       => $request->getId(),
228
				'comment'  => $request->getComment(),
229
			);
230
		}
231
232
		/** @var User[] $nameCache */
233
		$nameCache = array();
234
235
		$editableComments = false;
236
		if ($currentUser->isAdmin() || $currentUser->isCheckuser()) {
237
			$editableComments = true;
238
		}
239
240
		/** @var Log|Comment $entry */
241
		foreach ($logs as $entry) {
242
			// both log and comment have a 'user' field
243
			if (!array_key_exists($entry->getUser(), $nameCache)) {
244
				$entryUser = User::getById($entry->getUser(), $database);
245
				$nameCache[$entry->getUser()] = $entryUser;
246
			}
247
248
			if ($entry instanceof Comment) {
249
				$requestLogs[] = array(
250
					'type'     => 'comment',
251
					'security' => $entry->getVisibility(),
252
					'user'     => $nameCache[$entry->getUser()]->getUsername(),
253
					'userid'   => $entry->getUser() == -1 ? null : $entry->getUser(),
254
					'entry'    => null,
255
					'time'     => $entry->getTime(),
256
					'canedit'  => ($editableComments || $entry->getUser() == $currentUser->getId()),
257
					'id'       => $entry->getId(),
258
					'comment'  => $entry->getComment(),
259
				);
260
			}
261
262
			if ($entry instanceof Log) {
263
				$requestLogs[] = array(
264
					'type'     => 'log',
265
					'security' => 'user',
266
					'userid'   => $entry->getUser() == -1 ? null : $entry->getUser(),
267
					'user'     => $nameCache[$entry->getUser()]->getUsername(),
268
					'entry'    => LogHelper::getLogDescription($entry),
269
					'time'     => $entry->getTimestamp(),
270
					'canedit'  => false,
271
					'id'       => $entry->getId(),
272
					'comment'  => $entry->getComment(),
273
				);
274
			}
275
		}
276
277
		$this->assign("requestLogs", $requestLogs);
278
	}
279
280
	/**
281
	 * @param Request           $request
282
	 * @param User              $currentUser
283
	 * @param SiteConfiguration $configuration
284
	 *
285
	 * @throws Exception
286
	 */
287
	protected function setupPrivateData($request, User $currentUser, SiteConfiguration $configuration)
288
	{
289
		$xffProvider = $this->getXffTrustProvider();
290
		$this->setTemplate('view-request/main-with-data.tpl');
291
292
		$relatedEmailRequests = RequestSearchHelper::get($this->getDatabase())
293
			->byEmailAddress($request->getEmail())
294
			->withConfirmedEmail()
295
			->excludingPurgedData($configuration)
296
			->excludingRequest($request->getId())
297
			->fetch();
298
299
		$this->assign('requestEmail', $request->getEmail());
300
		$this->assign('requestRelatedEmailRequestsCount', count($relatedEmailRequests));
301
		$this->assign('requestRelatedEmailRequests', $relatedEmailRequests);
302
303
		$trustedIp = $xffProvider->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
304
		$this->assign('requestTrustedIp', $trustedIp);
305
		$this->assign('requestRealIp', $request->getIp());
306
		$this->assign('requestForwardedIp', $request->getForwardedIp());
307
308
		$trustedIpLocation = $this->getLocationProvider()->getIpLocation($trustedIp);
309
		$this->assign('requestTrustedIpLocation', $trustedIpLocation);
310
311
		$this->assign('requestHasForwardedIp', $request->getForwardedIp() !== null);
312
313
		$relatedIpRequests = RequestSearchHelper::get($this->getDatabase())
314
			->byIp($trustedIp)
315
			->withConfirmedEmail()
316
			->excludingPurgedData($configuration)
317
			->excludingRequest($request->getId())
318
			->fetch();
319
320
		$this->assign('requestRelatedIpRequestsCount', count($relatedIpRequests));
321
		$this->assign('requestRelatedIpRequests', $relatedIpRequests);
322
323
		$this->assign('showRevealLink', false);
324
		if ($request->getReserved() === $currentUser->getId() ||
325
			$currentUser->isAdmin() ||
326
			$currentUser->isCheckuser()
327
		) {
328
			$this->assign('showRevealLink', true);
329
			$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...
330
		}
331
332
		$this->setupForwardedIpData($request);
333
	}
334
335
	private function setupForwardedIpData(Request $request)
336
	{
337
		if ($request->getForwardedIp() !== null) {
338
			$requestProxyData = array(); // Initialize array to store data to be output in Smarty template.
339
			$proxyIndex = 0;
340
341
			// Assuming [client] <=> [proxy1] <=> [proxy2] <=> [proxy3] <=> [us], we will see an XFF header of [client],
342
			// [proxy1], [proxy2], and our actual IP will be [proxy3]
343
			$proxies = explode(",", $request->getForwardedIp());
344
			$proxies[] = $request->getIp();
345
346
			// Origin is the supposed "client" IP.
347
			$origin = $proxies[0];
348
			$this->assign("forwardedOrigin", $origin);
349
350
			// We step through the servers in reverse order, from closest to furthest
351
			$proxies = array_reverse($proxies);
352
353
			// By default, we have trust, because the first in the chain is now REMOTE_ADDR, which is hardest to spoof.
354
			$trust = true;
355
356
			/**
357
			 * @var int    $index     The zero-based index of the proxy.
358
			 * @var string $proxyData The proxy IP address (although possibly not!)
359
			 */
360
			foreach ($proxies as $index => $proxyData) {
361
				$proxyAddress = trim($proxyData);
362
				$requestProxyData[$proxyIndex]['ip'] = $proxyAddress;
363
364
				// get data on this IP.
365
				$thisProxyIsTrusted = $this->getXffTrustProvider()->isTrusted($proxyAddress);
366
367
				$proxyIsInPrivateRange = $this->getXffTrustProvider()->ipInRange(self::$rfc1918ips, $proxyAddress);
368
369
				if (!$proxyIsInPrivateRange) {
370
					$proxyReverseDns = $this->getRdnsProvider()->getReverseDNS($proxyAddress);
371
					$proxyLocation = $this->getLocationProvider()->getIpLocation($proxyAddress);
372
				}
373
				else {
374
					// this is going to fail, so why bother trying?
375
					$proxyReverseDns = false;
376
					$proxyLocation = false;
377
				}
378
379
				// current trust chain status BEFORE this link
380
				$preLinkTrust = $trust;
381
382
				// is *this* link trusted? Note, this will be true even if there is an untrusted link before this!
383
				$requestProxyData[$proxyIndex]['trustedlink'] = $thisProxyIsTrusted;
384
385
				// set the trust status of the chain to this point
386
				$trust = $trust & $thisProxyIsTrusted;
387
388
				// If this is the origin address, and the chain was trusted before this point, then we can trust
389
				// the origin.
390
				if ($preLinkTrust && $proxyAddress == $origin) {
391
					// if this is the origin, then we are at the last point in the chain.
392
					// @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...
393
					// to see if this is *really* the last in the chain, rather than just the same IP as it.
394
					$trust = true;
395
				}
396
397
				$requestProxyData[$proxyIndex]['trust'] = $trust;
398
399
				$requestProxyData[$proxyIndex]['rdnsfailed'] = $proxyReverseDns === false;
400
				$requestProxyData[$proxyIndex]['rdns'] = $proxyReverseDns;
401
				$requestProxyData[$proxyIndex]['routable'] = !$proxyIsInPrivateRange;
402
403
				$requestProxyData[$proxyIndex]['location'] = $proxyLocation;
404
405
				if ($proxyReverseDns === $proxyAddress && $proxyIsInPrivateRange === false) {
406
					$requestProxyData[$proxyIndex]['rdns'] = null;
407
				}
408
409
				$showLinks = (!$trust || $proxyAddress == $origin) && !$proxyIsInPrivateRange;
410
				$requestProxyData[$proxyIndex]['showlinks'] = $showLinks;
411
412
				$proxyIndex++;
413
			}
414
415
			$this->assign("requestProxyData", $requestProxyData);
416
		}
417
	}
418
419
	/**
420
	 * @param Request $request
421
	 */
422
	protected function setupCheckUserData(Request $request)
423
	{
424
		$this->setTemplate('view-request/main-with-checkuser-data.tpl');
425
		$this->assign('requestUserAgent', $request->getUserAgent());
426
	}
427
428
	/**
429
	 * Sets up the security for this page. If certain actions have different permissions, this should be reflected in
430
	 * the return value from this function.
431
	 *
432
	 * If this page even supports actions, you will need to check the route
433
	 *
434
	 * @return SecurityConfiguration
435
	 * @category Security-Critical
436
	 */
437
	protected function getSecurityConfiguration()
438
	{
439
		// @todo review me!
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...
440
		return $this->getSecurityManager()->configure()->asInternalPage();
441
	}
442
443
	/**
444
	 * @param Request $request
445
	 */
446
	protected function setupUsernameData(Request $request)
447
	{
448
		$blacklistData = $this->getBlacklistHelper()->isBlacklisted($request->getName());
449
450
		$this->assign('requestIsBlacklisted', $blacklistData !== false);
451
		$this->assign('requestBlacklist', $blacklistData);
452
453
		try {
454
			$spoofs = $this->getAntiSpoofProvider()->getSpoofs($request->getName());
455
		}
456
		catch (Exception $ex) {
457
			$spoofs = $ex->getMessage();
458
		}
459
460
		$this->assign("spoofs", $spoofs);
461
	}
462
}