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

RequestValidationHelper::userSulExists()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2.0017

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 23
ccs 12
cts 13
cp 0.9231
rs 9.0856
cc 2
eloc 13
nc 2
nop 0
crap 2.0017
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
19
/**
20
 * Performs the validation of an incoming request.
21
 */
22
class RequestValidationHelper
23
{
24
	/** @var IBanHelper */
25
	private $banHelper;
26
	/** @var Request */
27
	private $request;
28
	private $emailConfirmation;
29
	/** @var PdoDatabase */
30
	private $database;
31
	/** @var IAntiSpoofProvider */
32
	private $antiSpoofProvider;
33
	/** @var IXffTrustProvider */
34
	private $xffTrustProvider;
35
	/** @var HttpHelper */
36
	private $httpHelper;
37
	/**
38
	 * @var string
39
	 */
40
	private $mediawikiApiEndpoint;
41
	private $titleBlacklistEnabled;
42
43
	/**
44
	 * Summary of __construct
45
	 *
46
	 * @param IBanHelper         $banHelper
47
	 * @param Request            $request
48
	 * @param string             $emailConfirmation
49
	 * @param PdoDatabase        $database
50
	 * @param IAntiSpoofProvider $antiSpoofProvider
51
	 * @param IXffTrustProvider  $xffTrustProvider
52
	 * @param HttpHelper         $httpHelper
53
	 * @param string             $mediawikiApiEndpoint
54
	 * @param boolean            $titleBlacklistEnabled
55
	 */
56 1
	public function __construct(
57
		IBanHelper $banHelper,
58
		Request $request,
59
		$emailConfirmation,
60
		PdoDatabase $database,
61
		IAntiSpoofProvider $antiSpoofProvider,
62
		IXffTrustProvider $xffTrustProvider,
63
		HttpHelper $httpHelper,
64
		$mediawikiApiEndpoint,
65
		$titleBlacklistEnabled
66
	) {
67 1
		$this->banHelper = $banHelper;
68 1
		$this->request = $request;
69 1
		$this->emailConfirmation = $emailConfirmation;
70 1
		$this->database = $database;
71 1
		$this->antiSpoofProvider = $antiSpoofProvider;
72 1
		$this->xffTrustProvider = $xffTrustProvider;
73 1
		$this->httpHelper = $httpHelper;
74 1
		$this->mediawikiApiEndpoint = $mediawikiApiEndpoint;
75 1
		$this->titleBlacklistEnabled = $titleBlacklistEnabled;
76 1
	}
77
78
	/**
79
	 * Summary of validateName
80
	 * @return ValidationError[]
81
	 */
82 1
	public function validateName()
83
	{
84 1
		$errorList = array();
85
86
		// ERRORS
87
		// name is empty
88 1
		if (trim($this->request->getName()) == "") {
89
			$errorList[ValidationError::NAME_EMPTY] = new ValidationError(ValidationError::NAME_EMPTY);
90
		}
91
92
		// name is banned
93 1
		$ban = $this->banHelper->nameIsBanned($this->request->getName());
94 1
		if ($ban != false) {
95
			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
96
		}
97
98
		// username already exists
99
		// TODO: implement
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...
100 1
		if ($this->userExists()) {
101
			$errorList[ValidationError::NAME_EXISTS] = new ValidationError(ValidationError::NAME_EXISTS);
102
		}
103
104
		// username part of SUL account
105
		// TODO: implement
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...
106 1
		if ($this->userSulExists()) {
107
			// using same error slot as name exists - it's the same sort of error, and we probably only want to show one.
108
			$errorList[ValidationError::NAME_EXISTS] = new ValidationError(ValidationError::NAME_EXISTS_SUL);
109
		}
110
111
		// username is numbers
112 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...
113
			$errorList[ValidationError::NAME_NUMONLY] = new ValidationError(ValidationError::NAME_NUMONLY);
114
		}
115
116
		// username can't contain #@/<>[]|{}
117 1
		if (preg_match("/[" . preg_quote("#@/<>[]|{}", "/") . "]/", $this->request->getName()) === 1) {
118
			$errorList[ValidationError::NAME_INVALIDCHAR] = new ValidationError(ValidationError::NAME_INVALIDCHAR);
119
		}
120
121
		// existing non-closed request for this name
122
		// TODO: implement
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...
123 1
		if ($this->nameRequestExists()) {
124
			$errorList[ValidationError::OPEN_REQUEST_NAME] = new ValidationError(ValidationError::OPEN_REQUEST_NAME);
125
		}
126
127
		// WARNINGS
128
		// name has to be sanitised
129
		// TODO: implement
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...
130 1
		if (false) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
131
			$errorList[ValidationError::NAME_SANITISED] = new ValidationError(ValidationError::NAME_SANITISED, false);
132
		}
133
134 1
		return $errorList;
135
	}
136
137
	/**
138
	 * Summary of validateEmail
139
	 * @return ValidationError[]
140
	 */
141
	public function validateEmail()
142
	{
143
		$errorList = array();
144
145
		// ERRORS
146
147
		// Email is banned
148
		$ban = $this->banHelper->emailIsBanned($this->request->getEmail());
149
		if ($ban != false) {
150
			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
151
		}
152
153
		// email addresses must match
154
		if ($this->request->getEmail() != $this->emailConfirmation) {
155
			$errorList[ValidationError::EMAIL_MISMATCH] = new ValidationError(ValidationError::EMAIL_MISMATCH);
156
		}
157
158
		// email address must be validly formed
159 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...
160
			$errorList[ValidationError::EMAIL_EMPTY] = new ValidationError(ValidationError::EMAIL_EMPTY);
161
		}
162
163
		// email address must be validly formed
164
		if (!filter_var($this->request->getEmail(), FILTER_VALIDATE_EMAIL)) {
165 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...
166
				$errorList[ValidationError::EMAIL_INVALID] = new ValidationError(ValidationError::EMAIL_INVALID);
167
			}
168
		}
169
170
		// email address can't be wikimedia/wikipedia .com/org
171 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...
172
			$errorList[ValidationError::EMAIL_WIKIMEDIA] = new ValidationError(ValidationError::EMAIL_WIKIMEDIA);
173
		}
174
175
		// WARNINGS
176
177
		return $errorList;
178
	}
179
180
	/**
181
	 * Summary of validateOther
182
	 * @return ValidationError[]
183
	 */
184
	public function validateOther()
185
	{
186
		$errorList = array();
187
188
		// ERRORS
189
190
		// TOR nodes
191
		// TODO: Implement
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...
192
		if (false) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
193
			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED_TOR);
194
		}
195
196
		// IP banned
197
		$trustedIp = $this->xffTrustProvider->getTrustedClientIp($this->request->getIp(),
198
			$this->request->getForwardedIp());
199
		$ban = $this->banHelper->ipIsBanned($trustedIp);
200
		if ($ban != false) {
201
			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
202
		}
203
204
		// WARNINGS
205
206
		// Antispoof check
207
		$this->checkAntiSpoof();
208
209
		// Blacklist check
210
		$this->checkTitleBlacklist();
211
212
		return $errorList;
213
	}
214
215
	private function checkAntiSpoof()
216
	{
217
		try {
218
			if (count($this->antiSpoofProvider->getSpoofs($this->request->getName())) > 0) {
219
				// If there were spoofs an Admin should handle the request.
220
				$this->request->setStatus("Flagged users");
221
			}
222
		}
223
		catch (Exception $ex) {
224
			// hrm.
225
			// TODO: log this?
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...
226
		}
227
	}
228
229
	private function checkTitleBlacklist()
230
	{
231
		if ($this->titleBlacklistEnabled == 1) {
232
			$apiResult = $this->httpHelper->get(
233
				$this->mediawikiApiEndpoint,
234
				array(
235
					'action'       => 'titleblacklist',
236
					'tbtitle'      => $this->request->getName(),
237
					'tbaction'     => 'new-account',
238
					'tbnooverride' => true,
239
					'format'       => 'php',
240
				)
241
			);
242
243
			$data = unserialize($apiResult);
244
245
			$requestIsOk = $data['titleblacklist']['result'] == "ok";
246
247
			if (!$requestIsOk) {
248
				$this->request->setStatus("Flagged users");
249
			}
250
		}
251
	}
252
253 1
	private function userExists()
254
	{
255 1
		$userexist = $this->httpHelper->get(
256 1
			$this->mediawikiApiEndpoint,
257
			array(
258 1
				'action'  => 'query',
259 1
				'list'    => 'users',
260 1
				'ususers' => $this->request->getName(),
261 1
				'format'  => 'php',
262
			)
263 1
		);
264
265 1
		$ue = unserialize($userexist);
266 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...
267
			return true;
268
		}
269
270 1
		return false;
271
	}
272
273 1
	private function userSulExists()
274
	{
275
		// @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...
276
		// $reqname = 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...
277 1
		$reqname = $this->request->getName();
278
279 1
		$userexist = $this->httpHelper->get(
280 1
			$this->mediawikiApiEndpoint,
281
			array(
282 1
				'action'  => 'query',
283 1
				'meta'    => 'globaluserinfo',
284 1
				'guiuser' => $reqname,
285 1
				'format'  => 'php',
286
			)
287 1
		);
288
289 1
		$ue = unserialize($userexist);
290 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...
291
			return true;
292
		}
293
294 1
		return false;
295
	}
296
297 1
	private function nameRequestExists()
298
	{
299 1
		$query = "SELECT COUNT(id) FROM request WHERE status != 'Closed' AND name = :name;";
300 1
		$statement = $this->database->prepare($query);
301 1
		$statement->execute(array(':name' => $this->request->getName()));
302
303 1
		if (!$statement) {
304
			return false;
305
		}
306
307 1
		return $statement->fetchColumn() > 0;
308
	}
309
}
310