Completed
Branch newinternal (e32466)
by Simon
03:39
created

RequestValidationHelper::validateName()   D

Complexity

Conditions 8
Paths 128

Size

Total Lines 44
Code Lines 18

Duplication

Lines 3
Ratio 6.82 %

Code Coverage

Tests 11
CRAP Score 19.2394

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 3
loc 44
ccs 11
cts 25
cp 0.44
rs 4.6666
cc 8
eloc 18
nc 128
nop 0
crap 19.2394
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\Validation;
10
11
use Exception;
12
use Waca\DataObjects\Request;
13
use Waca\Helpers\HttpHelper;
14
use Waca\Helpers\Interfaces\IBanHelper;
15
use Waca\PdoDatabase;
16
use Waca\Providers\Interfaces\IAntiSpoofProvider;
17
use Waca\Providers\Interfaces\IXffTrustProvider;
18
use Waca\Providers\TorExitProvider;
19
20
/**
21
 * Performs the validation of an incoming request.
22
 */
23
class RequestValidationHelper
24
{
25
	/** @var IBanHelper */
26
	private $banHelper;
27
	/** @var Request */
28
	private $request;
29
	private $emailConfirmation;
30
	/** @var PdoDatabase */
31
	private $database;
32
	/** @var IAntiSpoofProvider */
33
	private $antiSpoofProvider;
34
	/** @var IXffTrustProvider */
35
	private $xffTrustProvider;
36
	/** @var HttpHelper */
37
	private $httpHelper;
38
	/**
39
	 * @var string
40
	 */
41
	private $mediawikiApiEndpoint;
42
	private $titleBlacklistEnabled;
43
	/**
44
	 * @var TorExitProvider
45
	 */
46
	private $torExitProvider;
47
48
	/**
49
	 * Summary of __construct
50
	 *
51
	 * @param IBanHelper         $banHelper
52
	 * @param Request            $request
53
	 * @param string             $emailConfirmation
54
	 * @param PdoDatabase        $database
55
	 * @param IAntiSpoofProvider $antiSpoofProvider
56
	 * @param IXffTrustProvider  $xffTrustProvider
57
	 * @param HttpHelper         $httpHelper
58
	 * @param string             $mediawikiApiEndpoint
59
	 * @param boolean            $titleBlacklistEnabled
60
	 * @param TorExitProvider    $torExitProvider
61
	 */
62 1
	public function __construct(
63
		IBanHelper $banHelper,
64
		Request $request,
65
		$emailConfirmation,
66
		PdoDatabase $database,
67
		IAntiSpoofProvider $antiSpoofProvider,
68
		IXffTrustProvider $xffTrustProvider,
69
		HttpHelper $httpHelper,
70
		$mediawikiApiEndpoint,
71
		$titleBlacklistEnabled,
72
		TorExitProvider $torExitProvider
73
	) {
74 1
		$this->banHelper = $banHelper;
75 1
		$this->request = $request;
76 1
		$this->emailConfirmation = $emailConfirmation;
77 1
		$this->database = $database;
78 1
		$this->antiSpoofProvider = $antiSpoofProvider;
79 1
		$this->xffTrustProvider = $xffTrustProvider;
80 1
		$this->httpHelper = $httpHelper;
81 1
		$this->mediawikiApiEndpoint = $mediawikiApiEndpoint;
82 1
		$this->titleBlacklistEnabled = $titleBlacklistEnabled;
83 1
		$this->torExitProvider = $torExitProvider;
84 1
	}
85
86
	/**
87
	 * Summary of validateName
88
	 * @return ValidationError[]
89
	 */
90 1
	public function validateName()
91
	{
92 1
		$errorList = array();
93
94
		// ERRORS
95
		// name is empty
96 1
		if (trim($this->request->getName()) == "") {
97
			$errorList[ValidationError::NAME_EMPTY] = new ValidationError(ValidationError::NAME_EMPTY);
98
		}
99
100
		// name is banned
101 1
		$ban = $this->banHelper->nameIsBanned($this->request->getName());
102 1
		if ($ban != false) {
103
			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
104
		}
105
106
		// username already exists
107 1
		if ($this->userExists()) {
108
			$errorList[ValidationError::NAME_EXISTS] = new ValidationError(ValidationError::NAME_EXISTS);
109
		}
110
111
		// username part of SUL account
112 1
		if ($this->userSulExists()) {
113
			// using same error slot as name exists - it's the same sort of error, and we probably only want to show one.
114
			$errorList[ValidationError::NAME_EXISTS] = new ValidationError(ValidationError::NAME_EXISTS_SUL);
115
		}
116
117
		// username is numbers
118 1 View Code Duplication
		if (preg_match("/^[0-9]+$/", $this->request->getName()) === 1) {
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...
119
			$errorList[ValidationError::NAME_NUMONLY] = new ValidationError(ValidationError::NAME_NUMONLY);
120
		}
121
122
		// username can't contain #@/<>[]|{}
123 1
		if (preg_match("/[" . preg_quote("#@/<>[]|{}", "/") . "]/", $this->request->getName()) === 1) {
124
			$errorList[ValidationError::NAME_INVALIDCHAR] = new ValidationError(ValidationError::NAME_INVALIDCHAR);
125
		}
126
127
		// existing non-closed request for this name
128 1
		if ($this->nameRequestExists()) {
129
			$errorList[ValidationError::OPEN_REQUEST_NAME] = new ValidationError(ValidationError::OPEN_REQUEST_NAME);
130
		}
131
132 1
		return $errorList;
133
	}
134
135
	/**
136
	 * Summary of validateEmail
137
	 * @return ValidationError[]
138
	 */
139
	public function validateEmail()
140
	{
141
		$errorList = array();
142
143
		// ERRORS
144
145
		// Email is banned
146
		$ban = $this->banHelper->emailIsBanned($this->request->getEmail());
147
		if ($ban != false) {
148
			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
149
		}
150
151
		// email addresses must match
152
		if ($this->request->getEmail() != $this->emailConfirmation) {
153
			$errorList[ValidationError::EMAIL_MISMATCH] = new ValidationError(ValidationError::EMAIL_MISMATCH);
154
		}
155
156
		// email address must be validly formed
157 View Code Duplication
		if (trim($this->request->getEmail()) == "") {
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...
158
			$errorList[ValidationError::EMAIL_EMPTY] = new ValidationError(ValidationError::EMAIL_EMPTY);
159
		}
160
161
		// email address must be validly formed
162
		if (!filter_var($this->request->getEmail(), FILTER_VALIDATE_EMAIL)) {
163 View Code Duplication
			if (trim($this->request->getEmail()) != "") {
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...
164
				$errorList[ValidationError::EMAIL_INVALID] = new ValidationError(ValidationError::EMAIL_INVALID);
165
			}
166
		}
167
168
		// email address can't be wikimedia/wikipedia .com/org
169 View Code Duplication
		if (preg_match('/.*@.*wiki(m.dia|p.dia)\.(org|com)/i', $this->request->getEmail()) === 1) {
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...
170
			$errorList[ValidationError::EMAIL_WIKIMEDIA] = new ValidationError(ValidationError::EMAIL_WIKIMEDIA);
171
		}
172
173
		// WARNINGS
174
175
		return $errorList;
176
	}
177
178
	/**
179
	 * Summary of validateOther
180
	 * @return ValidationError[]
181
	 */
182
	public function validateOther()
183
	{
184
		$errorList = array();
185
186
		$trustedIp = $this->xffTrustProvider->getTrustedClientIp($this->request->getIp(),
187
			$this->request->getForwardedIp());
188
189
		// ERRORS
190
191
		// TOR nodes
192
		if ($this->torExitProvider->isTorExit($trustedIp)) {
193
			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED_TOR);
194
		}
195
196
		// IP banned
197
		$ban = $this->banHelper->ipIsBanned($trustedIp);
198
		if ($ban != false) {
199
			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
200
		}
201
202
		// WARNINGS
203
204
		// Antispoof check
205
		$this->checkAntiSpoof();
206
207
		// Blacklist check
208
		$this->checkTitleBlacklist();
209
210
		return $errorList;
211
	}
212
213
	private function checkAntiSpoof()
214
	{
215
		try {
216
			if (count($this->antiSpoofProvider->getSpoofs($this->request->getName())) > 0) {
217
				// If there were spoofs an Admin should handle the request.
218
				$this->request->setStatus("Flagged users");
219
			}
220
		}
221
		catch (Exception $ex) {
222
			// logme
223
		}
224
	}
225
226
	private function checkTitleBlacklist()
227
	{
228
		if ($this->titleBlacklistEnabled == 1) {
229
			$apiResult = $this->httpHelper->get(
230
				$this->mediawikiApiEndpoint,
231
				array(
232
					'action'       => 'titleblacklist',
233
					'tbtitle'      => $this->request->getName(),
234
					'tbaction'     => 'new-account',
235
					'tbnooverride' => true,
236
					'format'       => 'php',
237
				)
238
			);
239
240
			$data = unserialize($apiResult);
241
242
			$requestIsOk = $data['titleblacklist']['result'] == "ok";
243
244
			if (!$requestIsOk) {
245
				$this->request->setStatus("Flagged users");
246
			}
247
		}
248
	}
249
250 1
	private function userExists()
251
	{
252 1
		$userExists = $this->httpHelper->get(
253 1
			$this->mediawikiApiEndpoint,
254
			array(
255 1
				'action'  => 'query',
256 1
				'list'    => 'users',
257 1
				'ususers' => $this->request->getName(),
258 1
				'format'  => 'php',
259
			)
260 1
		);
261
262 1
		$ue = unserialize($userExists);
263 1
		if (!isset ($ue['query']['users']['0']['missing']) && isset ($ue['query']['users']['0']['userid'])) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !isset($ue['query...sers']['0']['userid']);.
Loading history...
264
			return true;
265
		}
266
267 1
		return false;
268
	}
269
270 1
	private function userSulExists()
271
	{
272
		// @todo is this really necessary?!
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...
273
		// $requestName = str_replace("_", " ", $this->request->getName());
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
274 1
		$requestName = $this->request->getName();
275
276 1
		$userExists = $this->httpHelper->get(
277 1
			$this->mediawikiApiEndpoint,
278
			array(
279 1
				'action'  => 'query',
280 1
				'meta'    => 'globaluserinfo',
281 1
				'guiuser' => $requestName,
282 1
				'format'  => 'php',
283
			)
284 1
		);
285
286 1
		$ue = unserialize($userExists);
287 1
		if (isset ($ue['query']['globaluserinfo']['id'])) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return isset($ue['query'...lobaluserinfo']['id']);.
Loading history...
288
			return true;
289
		}
290
291 1
		return false;
292
	}
293
294
	/**
295
	 * Checks if a request with this name is currently open
296
	 *
297
	 * @return bool
298
	 */
299 1 View Code Duplication
	private function nameRequestExists()
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...
300
	{
301 1
		$query = "SELECT COUNT(id) FROM request WHERE status != 'Closed' AND name = :name;";
302 1
		$statement = $this->database->prepare($query);
303 1
		$statement->execute(array(':name' => $this->request->getName()));
304
305 1
		if (!$statement) {
306
			return false;
307
		}
308
309 1
		return $statement->fetchColumn() > 0;
310
	}
311
}
312