Issues (1401)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

Specials/SpecialSetLabelDescriptionAliases.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Wikibase\Repo\Specials;
4
5
use Html;
6
use HTMLForm;
7
use InvalidArgumentException;
8
use Language;
9
use MediaWiki\Logger\LoggerFactory;
10
use Wikibase\DataModel\Entity\EntityDocument;
11
use Wikibase\DataModel\Term\Fingerprint;
12
use Wikibase\DataModel\Term\FingerprintProvider;
13
use Wikibase\Lib\ContentLanguages;
14
use Wikibase\Lib\Store\EntityTitleLookup;
15
use Wikibase\Lib\Summary;
16
use Wikibase\Lib\UserInputException;
17
use Wikibase\Repo\ChangeOp\ChangeOp;
18
use Wikibase\Repo\ChangeOp\ChangeOpException;
19
use Wikibase\Repo\ChangeOp\ChangeOps;
20
use Wikibase\Repo\ChangeOp\FingerprintChangeOpFactory;
21
use Wikibase\Repo\CopyrightMessageBuilder;
22
use Wikibase\Repo\EditEntity\MediawikiEditEntityFactory;
23
use Wikibase\Repo\Store\EntityPermissionChecker;
24
use Wikibase\Repo\SummaryFormatter;
25
use Wikibase\Repo\WikibaseRepo;
26
27
/**
28
 * Special page for setting label, description and aliases of a Wikibase entity that features
29
 * labels, descriptions and aliases.
30
 *
31
 * @license GPL-2.0-or-later
32
 * @author Thiemo Kreuz
33
 */
34
class SpecialSetLabelDescriptionAliases extends SpecialModifyEntity {
0 ignored issues
show
There is one abstract method getName in this class; you could implement it, or declare this class as abstract.
Loading history...
35
36
	use ParameterizedDescriptionTrait;
37
38
	/**
39
	 * @var FingerprintChangeOpFactory
40
	 */
41
	private $changeOpFactory;
42
43
	/**
44
	 * @var ContentLanguages
45
	 */
46
	private $termsLanguages;
47
48
	/**
49
	 * @var EntityPermissionChecker
50
	 */
51
	private $permissionChecker;
52
53
	/**
54
	 * @var string
55
	 */
56
	private $languageCode;
57
58
	/**
59
	 * @var string
60
	 */
61
	private $label = '';
62
63
	/**
64
	 * @var string
65
	 */
66
	private $description = '';
67
68
	/**
69
	 * @var string[]
70
	 */
71
	private $aliases = [];
72
73
	public function __construct(
74
		SpecialPageCopyrightView $copyrightView,
75
		SummaryFormatter $summaryFormatter,
76
		EntityTitleLookup $entityTitleLookup,
77
		MediawikiEditEntityFactory $editEntityFactory,
78
		FingerprintChangeOpFactory $changeOpFactory,
79
		ContentLanguages $termsLanguages,
80
		EntityPermissionChecker $permissionChecker
81
	) {
82
		parent::__construct(
83
			'SetLabelDescriptionAliases',
84
			$copyrightView,
85
			$summaryFormatter,
86
			$entityTitleLookup,
87
			$editEntityFactory
88
		);
89
90
		$this->changeOpFactory = $changeOpFactory;
91
		$this->termsLanguages = $termsLanguages;
92
		$this->permissionChecker = $permissionChecker;
93
	}
94
95
	public static function factory(): self {
96
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
97
98
		$settings = $wikibaseRepo->getSettings();
99
		$copyrightView = new SpecialPageCopyrightView(
100
			new CopyrightMessageBuilder(),
101
			$settings->getSetting( 'dataRightsUrl' ),
102
			$settings->getSetting( 'dataRightsText' )
103
		);
104
105
		return new self(
106
			$copyrightView,
107
			$wikibaseRepo->getSummaryFormatter(),
108
			$wikibaseRepo->getEntityTitleLookup(),
109
			$wikibaseRepo->newEditEntityFactory(),
110
			$wikibaseRepo->getChangeOpFactoryProvider()->getFingerprintChangeOpFactory(),
111
			$wikibaseRepo->getTermsLanguages(),
112
			$wikibaseRepo->getEntityPermissionChecker()
113
		);
114
	}
115
116
	public function doesWrites() {
117
		return true;
118
	}
119
120
	/**
121
	 * @see SpecialModifyEntity::validateInput
122
	 *
123
	 * @return bool
124
	 */
125
	protected function validateInput() {
126
		return parent::validateInput()
127
			&& $this->getBaseRevision()->getEntity() instanceof FingerprintProvider
128
			&& $this->isValidLanguageCode( $this->languageCode )
129
			&& $this->wasPostedWithLabelDescriptionOrAliases()
130
			&& $this->isAllowedToChangeTerms( $this->getBaseRevision()->getEntity() );
131
	}
132
133
	/**
134
	 * @return bool
135
	 */
136
	private function wasPostedWithLabelDescriptionOrAliases() {
137
		$request = $this->getRequest();
138
139
		return $request->wasPosted() && (
140
			$request->getCheck( 'label' )
141
			|| $request->getCheck( 'description' )
142
			|| $request->getCheck( 'aliases' )
143
		);
144
	}
145
146
	/**
147
	 * @param EntityDocument $entity
148
	 *
149
	 * @return bool
150
	 */
151
	private function isAllowedToChangeTerms( EntityDocument $entity ) {
152
		$status = $this->permissionChecker->getPermissionStatusForEntity(
153
			$this->getUser(),
154
			EntityPermissionChecker::ACTION_EDIT_TERMS,
155
			$entity
156
		);
157
158
		if ( !$status->isOK() ) {
159
			$this->showErrorHTML( $this->msg( 'permissionserrors' )->parse() );
160
			return false;
161
		}
162
163
		return true;
164
	}
165
166
	/**
167
	 * @see SpecialModifyEntity::getForm
168
	 *
169
	 * @param EntityDocument|null $entity
170
	 *
171
	 * @return HTMLForm
172
	 */
173
	protected function getForm( EntityDocument $entity = null ) {
174
		if ( $entity !== null && $this->languageCode !== null ) {
175
176
			$languageName = Language::fetchLanguageName(
177
				$this->languageCode, $this->getLanguage()->getCode()
178
			);
179
			$intro = $this->msg(
180
				'wikibase-setlabeldescriptionaliases-introfull',
181
				$this->getEntityTitle( $entity->getId() )->getPrefixedText(),
182
				$languageName
183
			)->parse();
184
185
			$formDescriptor = [
186
				'id' => [
187
					'name' => 'id',
188
					'type' => 'hidden',
189
					'default' => $entity->getId()->getSerialization()
190
				],
191
				'language' => [
192
					'name' => 'language',
193
					'type' => 'hidden',
194
					'default' => $this->languageCode
195
				],
196
				'revid' => [
197
					'name' => 'revid',
198
					'type' => 'hidden',
199
					'default' => $this->getBaseRevision()->getRevisionId(),
200
				]
201
			];
202
			$formDescriptor = array_merge(
203
				$formDescriptor,
204
				$this->getLabeledInputField( 'label', $this->label ),
205
				$this->getLabeledInputField( 'description', $this->description ),
206
				$this->getLabeledInputField( 'aliases', implode( '|', $this->aliases ) )
207
			);
208
		} else {
209
			$intro = $this->msg( 'wikibase-setlabeldescriptionaliases-intro' )->parse();
210
			$fieldId = 'wikibase-setlabeldescriptionaliases-language';
211
			$languageCode = $this->languageCode ?: $this->getLanguage()->getCode();
212
213
			$formDescriptor = $this->getFormElements( $entity );
214
			$formDescriptor['language'] = [
215
				'name' => 'language',
216
				'default' => $languageCode,
217
				'type' => 'text',
218
				'id' => $fieldId,
219
				'label-message' => 'wikibase-modifyterm-language'
220
			];
221
		}
222
223
		return HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
224
			->setHeaderText( Html::rawElement( 'p', [], $intro ) );
225
	}
226
227
	/**
228
	 * Returns an HTML label and text input element for a specific term.
229
	 *
230
	 * @param string $termType Either 'label', 'description' or 'aliases'.
231
	 * @param string $value Text to fill the input element with
232
	 *
233
	 * @return array[]
234
	 */
235
	private function getLabeledInputField( $termType, $value ) {
236
		$fieldId = 'wikibase-setlabeldescriptionaliases-' . $termType;
237
238
		// Messages:
239
		// wikibase-setlabeldescriptionaliases-label-label
240
		// wikibase-setlabeldescriptionaliases-description-label
241
		// wikibase-setlabeldescriptionaliases-aliases-label
242
		return [
243
			$termType => [
244
				'name' => $termType,
245
				'default' => $value,
246
				'type' => 'text',
247
				'id' => $fieldId,
248
				'placeholder' => $value,
249
				'label-message' => $fieldId . '-label'
250
			]
251
		];
252
	}
253
254
	/**
255
	 * @see SpecialModifyEntity::processArguments
256
	 *
257
	 * @param string|null $subPage
258
	 */
259
	protected function processArguments( $subPage ) {
260
		$this->extractInput( $subPage );
261
262
		// Parse the 'id' parameter and throw an exception if the entity cannot be loaded
263
		parent::processArguments( $subPage );
264
265
		if ( $this->languageCode === '' ) {
266
			$this->languageCode = $this->getLanguage()->getCode();
267
		} elseif ( !$this->isValidLanguageCode( $this->languageCode ) ) {
268
			$msg = $this->msg( 'wikibase-wikibaserepopage-invalid-langcode' )
269
				->plaintextParams( $this->languageCode );
270
271
			$this->showErrorHTML( $msg->parse() );
272
			$this->languageCode = null;
273
		}
274
275
		$entity = $this->getEntityForDisplay();
276
		if ( $this->languageCode !== null && $entity ) {
277
			if ( $entity instanceof FingerprintProvider ) {
278
				$this->setFingerprintFields( $entity->getFingerprint() );
279
			}
280
		}
281
	}
282
283
	/**
284
	 * @param string|null $subPage
285
	 */
286
	private function extractInput( $subPage ) {
287
		$request = $this->getRequest();
288
289
		$parts = $subPage === '' ? [] : explode( '/', $subPage, 2 );
290
		$this->languageCode = $request->getRawVal( 'language', $parts[1] ?? '' );
291
292
		$label = $request->getVal( 'label', '' );
293
		$this->label = $this->stringNormalizer->trimToNFC( $label );
294
295
		$description = $request->getVal( 'description', '' );
296
		$this->description = $this->stringNormalizer->trimToNFC( $description );
297
298
		$aliases = $request->getVal( 'aliases', '' );
299
		$aliases = $this->stringNormalizer->trimToNFC( $aliases );
300
		$this->aliases = $aliases === '' ? [] : explode( '|', $aliases );
301
		foreach ( $this->aliases as &$alias ) {
302
			$alias = $this->stringNormalizer->trimToNFC( $alias );
303
		}
304
	}
305
306
	private function setFingerprintFields( Fingerprint $fingerprint ) {
307
		if ( !$this->getRequest()->getCheck( 'label' )
308
			&& $fingerprint->hasLabel( $this->languageCode )
309
		) {
310
			$this->label = $fingerprint->getLabel( $this->languageCode )->getText();
311
		}
312
313
		if ( !$this->getRequest()->getCheck( 'description' )
314
			&& $fingerprint->hasDescription( $this->languageCode )
315
		) {
316
			$this->description = $fingerprint->getDescription( $this->languageCode )->getText();
317
		}
318
319
		if ( !$this->getRequest()->getCheck( 'aliases' )
320
			&& $fingerprint->hasAliasGroup( $this->languageCode )
321
		) {
322
			$this->aliases = $fingerprint->getAliasGroup( $this->languageCode )->getAliases();
323
		}
324
	}
325
326
	/**
327
	 * @param string|null $languageCode
328
	 *
329
	 * @return bool
330
	 */
331
	private function isValidLanguageCode( $languageCode ) {
332
		return $languageCode !== null && $this->termsLanguages->hasLanguage( $languageCode );
333
	}
334
335
	/**
336
	 * @see SpecialModifyEntity::modifyEntity
337
	 *
338
	 * @param EntityDocument $entity
339
	 *
340
	 * @throws InvalidArgumentException
341
	 * @return Summary|bool
342
	 */
343
	protected function modifyEntity( EntityDocument $entity ) {
344
		if ( !( $entity instanceof FingerprintProvider ) ) {
345
			throw new InvalidArgumentException( '$entity must be a FingerprintProvider' );
346
		}
347
348
		if ( $this->assertNoPipeCharacterInAliases( $entity->getFingerprint() ) ) {
349
			$logger = LoggerFactory::getInstance( 'Wikibase' );
350
			$logger->error( 'Special:SpecialSetLabelDescriptionAliases attempt to save pipes in aliases' );
351
			$this->showErrorHTML( $this->msg( 'wikibase-wikibaserepopage-pipe-in-alias' )->parse() );
352
			return false;
353
		}
354
		$changeOps = $this->getChangeOps( $entity->getFingerprint() );
355
356
		if ( empty( $changeOps ) ) {
357
			return false;
358
		}
359
360
		try {
361
			// @phan-suppress-next-line PhanTypeMismatchArgument
362
			return $this->applyChangeOpList( $changeOps, $entity );
363
		} catch ( ChangeOpException $ex ) {
364
			$this->showErrorHTML( $ex->getMessage() );
365
			return false;
366
		}
367
	}
368
369
	/**
370
	 * @param Fingerprint $fingerprint
371
	 *
372
	 * @throws UserInputException
373
	 * @return bool
374
	 */
375
	private function assertNoPipeCharacterInAliases( Fingerprint $fingerprint ) {
376
		if ( !empty( $this->aliases ) ) {
377
			if ( $fingerprint->hasAliasGroup( $this->languageCode )
378
			) {
379
				$aliasesInLang = $fingerprint->getAliasGroup( $this->languageCode )->getAliases();
380
				foreach ( $aliasesInLang as $alias ) {
381
					if ( strpos( $alias, '|' ) !== false ) {
382
						return true;
383
384
					}
385
				}
386
			}
387
		}
388
389
		return false;
390
	}
391
392
	/**
393
	 * @throws ChangeOpException
394
	 */
395
	private function applyChangeOpList( array $changeOps, EntityDocument $entity ): Summary {
396
		$changeOp = $this->changeOpFactory->newFingerprintChangeOp( new ChangeOps( $changeOps ) );
397
		/**
398
		 * XXX: The $changeOps array is still used below as it is indexed with the
399
		 * module name to pass to the Summary object.
400
		 */
401
		if ( count( $changeOps ) === 1 ) {
402
			$module = key( $changeOps );
403
			$summary = new Summary( $module );
404
			$this->applyChangeOp( $changeOp, $entity, $summary );
405
			return $summary;
406
		} else {
407
			$this->applyChangeOp( $changeOp, $entity, new Summary() );
408
			return $this->getSummaryForLabelDescriptionAliases();
409
		}
410
	}
411
412
	/**
413
	 * @param Fingerprint $fingerprint
414
	 *
415
	 * @return ChangeOp[]
416
	 */
417
	private function getChangeOps( Fingerprint $fingerprint ) {
418
		$changeOpFactory = $this->changeOpFactory;
419
		$changeOps = [];
420
421
		if ( $this->label !== '' ) {
422
			if ( !$fingerprint->hasLabel( $this->languageCode )
423
				|| $fingerprint->getLabel( $this->languageCode )->getText() !== $this->label
424
			) {
425
				$changeOps['wbsetlabel'] = $changeOpFactory->newSetLabelOp(
426
					$this->languageCode,
427
					$this->label
428
				);
429
			}
430
		} elseif ( $fingerprint->hasLabel( $this->languageCode ) ) {
431
			$changeOps['wbsetlabel'] = $changeOpFactory->newRemoveLabelOp(
432
				$this->languageCode
433
			);
434
		}
435
436
		if ( $this->description !== '' ) {
437
			if ( !$fingerprint->hasDescription( $this->languageCode )
438
				|| $fingerprint->getDescription( $this->languageCode )->getText() !== $this->description
439
			) {
440
				$changeOps['wbsetdescription'] = $changeOpFactory->newSetDescriptionOp(
441
					$this->languageCode,
442
					$this->description
443
				);
444
			}
445
		} elseif ( $fingerprint->hasDescription( $this->languageCode ) ) {
446
			$changeOps['wbsetdescription'] = $changeOpFactory->newRemoveDescriptionOp(
447
				$this->languageCode
448
			);
449
		}
450
451
		if ( !empty( $this->aliases ) ) {
452
			if ( !$fingerprint->hasAliasGroup( $this->languageCode )
453
				|| $fingerprint->getAliasGroup( $this->languageCode )->getAliases() !== $this->aliases
454
			) {
455
				$changeOps['wbsetaliases'] = $changeOpFactory->newSetAliasesOp(
456
					$this->languageCode,
457
					$this->aliases
458
				);
459
			}
460
		} elseif ( $fingerprint->hasAliasGroup( $this->languageCode ) ) {
461
			$changeOps['wbsetaliases'] = $changeOpFactory->newRemoveAliasesOp(
462
				$this->languageCode,
463
				$fingerprint->getAliasGroup( $this->languageCode )->getAliases()
464
			);
465
		}
466
467
		return $changeOps;
468
	}
469
470
	/**
471
	 * @return Summary
472
	 */
473
	private function getSummaryForLabelDescriptionAliases() {
474
		// FIXME: Introduce more specific messages if only 2 of the 3 fields changed.
475
		$summary = new Summary( 'wbsetlabeldescriptionaliases' );
476
		$summary->addAutoSummaryArgs( $this->label, $this->description, $this->aliases );
477
478
		$summary->setLanguage( $this->languageCode );
479
		return $summary;
480
	}
481
482
}
483