|
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) { |
|
|
|
|
|
|
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()) == "") { |
|
|
|
|
|
|
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()) != "") { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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'])) { |
|
|
|
|
|
|
264
|
|
|
return true; |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
1 |
|
return false; |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
1 |
|
private function userSulExists() |
|
271
|
|
|
{ |
|
272
|
|
|
// @todo is this really necessary?! |
|
|
|
|
|
|
273
|
|
|
// $requestName = str_replace("_", " ", $this->request->getName()); |
|
|
|
|
|
|
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'])) { |
|
|
|
|
|
|
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() |
|
|
|
|
|
|
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
|
|
|
|
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.