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.

repo/includes/Api/GetEntities.php (1 issue)

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
declare( strict_types = 1 );
4
5
namespace Wikibase\Repo\Api;
6
7
use ApiBase;
8
use ApiMain;
9
use IBufferingStatsdDataFactory;
10
use Wikibase\DataModel\Entity\EntityId;
11
use Wikibase\DataModel\Entity\EntityIdParser;
12
use Wikibase\DataModel\Entity\EntityIdParsingException;
13
use Wikibase\DataModel\Services\Entity\EntityPrefetcher;
14
use Wikibase\Lib\LanguageFallbackChainFactory;
15
use Wikibase\Lib\Store\DivergingEntityIdException;
16
use Wikibase\Lib\Store\EntityRevision;
17
use Wikibase\Lib\Store\EntityRevisionLookup;
18
use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
19
use Wikibase\Lib\StringNormalizer;
20
use Wikibase\Repo\SiteLinkTargetProvider;
21
use Wikibase\Repo\WikibaseRepo;
22
23
/**
24
 * API module to get the data for one or more Wikibase entities.
25
 *
26
 * @license GPL-2.0-or-later
27
 */
28
class GetEntities extends ApiBase {
29
30
	use FederatedPropertyApiValidatorTrait;
31
32
	/**
33
	 * @var StringNormalizer
34
	 */
35
	private $stringNormalizer;
36
37
	/**
38
	 * @var LanguageFallbackChainFactory
39
	 */
40
	private $languageFallbackChainFactory;
41
42
	/**
43
	 * @var SiteLinkTargetProvider
44
	 */
45
	private $siteLinkTargetProvider;
46
47
	/**
48
	 * @var EntityPrefetcher
49
	 */
50
	private $entityPrefetcher;
51
52
	/**
53
	 * @var string[]
54
	 */
55
	private $siteLinkGroups;
56
57
	/**
58
	 * @var ApiErrorReporter
59
	 */
60
	protected $errorReporter;
61
62
	/**
63
	 * @var ResultBuilder
64
	 */
65
	private $resultBuilder;
66
67
	/**
68
	 * @var EntityRevisionLookup
69
	 */
70
	private $entityRevisionLookup;
71
72
	/**
73
	 * @var EntityIdParser
74
	 */
75
	private $idParser;
76
77
	/** @var IBufferingStatsdDataFactory */
78
	private $stats;
79
80
	/**
81
	 * @param ApiMain $mainModule
82
	 * @param string $moduleName
83
	 * @param StringNormalizer $stringNormalizer
84
	 * @param LanguageFallbackChainFactory $languageFallbackChainFactory
85
	 * @param SiteLinkTargetProvider $siteLinkTargetProvider
86
	 * @param EntityPrefetcher $entityPrefetcher
87
	 * @param string[] $siteLinkGroups
88
	 * @param ApiErrorReporter $errorReporter
89
	 * @param ResultBuilder $resultBuilder
90
	 * @param EntityRevisionLookup $entityRevisionLookup
91
	 * @param EntityIdParser $idParser
92
	 * @param IBufferingStatsdDataFactory $stats
93
	 * @param bool $federatedPropertiesEnabled
94
	 *
95
	 * @see ApiBase::__construct
96
	 */
97
	public function __construct(
98
		ApiMain $mainModule,
99
		string $moduleName,
100
		StringNormalizer $stringNormalizer,
101
		LanguageFallbackChainFactory $languageFallbackChainFactory,
102
		SiteLinkTargetProvider $siteLinkTargetProvider,
103
		EntityPrefetcher $entityPrefetcher,
104
		array $siteLinkGroups,
105
		ApiErrorReporter $errorReporter,
106
		ResultBuilder $resultBuilder,
107
		EntityRevisionLookup $entityRevisionLookup,
108
		EntityIdParser $idParser,
109
		IBufferingStatsdDataFactory $stats,
110
		bool $federatedPropertiesEnabled
111
	) {
112
		parent::__construct( $mainModule, $moduleName );
113
114
		$this->stringNormalizer = $stringNormalizer;
115
		$this->languageFallbackChainFactory = $languageFallbackChainFactory;
116
		$this->siteLinkTargetProvider = $siteLinkTargetProvider;
117
		$this->entityPrefetcher = $entityPrefetcher;
118
		$this->siteLinkGroups = $siteLinkGroups;
119
		$this->errorReporter = $errorReporter;
120
		$this->resultBuilder = $resultBuilder;
121
		$this->entityRevisionLookup = $entityRevisionLookup;
122
		$this->idParser = $idParser;
123
		$this->stats = $stats;
124
		$this->federatedPropertiesEnabled = $federatedPropertiesEnabled;
125
	}
126
127
	public static function factory( ApiMain $apiMain, string $moduleName, IBufferingStatsdDataFactory $stats ): self {
128
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
129
		$settings = $wikibaseRepo->getSettings();
130
		$apiHelperFactory = $wikibaseRepo->getApiHelperFactory( $apiMain->getContext() );
131
132
		$siteLinkTargetProvider = new SiteLinkTargetProvider(
133
			$wikibaseRepo->getSiteLookup(),
134
			$settings->getSetting( 'specialSiteLinkGroups' )
135
		);
136
137
		return new self(
138
			$apiMain,
139
			$moduleName,
140
			$wikibaseRepo->getStringNormalizer(),
141
			$wikibaseRepo->getLanguageFallbackChainFactory(),
142
			$siteLinkTargetProvider,
143
			$wikibaseRepo->getStore()->getEntityPrefetcher(),
144
			$settings->getSetting( 'siteLinkGroups' ),
145
			$apiHelperFactory->getErrorReporter( $apiMain ),
146
			$apiHelperFactory->getResultBuilder( $apiMain ),
147
			$wikibaseRepo->getEntityRevisionLookup(),
148
			$wikibaseRepo->getEntityIdParser(),
149
			$stats,
150
			$wikibaseRepo->inFederatedPropertyMode()
151
		);
152
	}
153
154
	/**
155
	 * @inheritDoc
156
	 */
157
	public function execute(): void {
158
		$this->getMain()->setCacheMode( 'public' );
159
160
		$params = $this->extractRequestParams();
161
162
		if ( !isset( $params['ids'] ) && ( empty( $params['sites'] ) || empty( $params['titles'] ) ) ) {
163
			$this->errorReporter->dieWithError(
164
				'wikibase-api-illegal-ids-or-sites-titles-selector',
165
				'param-missing'
166
			);
167
		}
168
169
		$resolveRedirects = $params['redirects'] === 'yes';
170
171
		$entityIds = $this->getEntityIdsFromParams( $params );
172
		foreach ( $entityIds as $entityId ) {
173
			$this->validateAlteringEntityById( $entityId );
174
		}
175
176
		$this->stats->updateCount( 'wikibase.repo.api.getentities.entities', count( $entityIds ) );
177
178
		$entityRevisions = $this->getEntityRevisionsFromEntityIds( $entityIds, $resolveRedirects );
179
180
		foreach ( $entityRevisions as $sourceEntityId => $entityRevision ) {
181
			$this->handleEntity( $sourceEntityId, $entityRevision, $params );
182
		}
183
184
		$this->resultBuilder->markSuccess( 1 );
185
	}
186
187
	/**
188
	 * Get a unique array of EntityIds from api request params
189
	 *
190
	 * @param array $params
191
	 *
192
	 * @return EntityId[]
193
	 */
194
	private function getEntityIdsFromParams( array $params ): array {
195
		$fromIds = $this->getEntityIdsFromIdParam( $params );
196
		$fromSiteTitleCombinations = $this->getEntityIdsFromSiteTitleParams( $params );
197
		$ids = array_merge( $fromIds, $fromSiteTitleCombinations );
198
		return array_unique( $ids );
199
	}
200
201
	/**
202
	 * @param array $params
203
	 *
204
	 * @return EntityId[]
205
	 */
206
	private function getEntityIdsFromIdParam( array $params ): array {
207
		if ( !isset( $params['ids'] ) ) {
208
			return [];
209
		}
210
211
		$ids = [];
212
		foreach ( $params['ids'] as $id ) {
213
			try {
214
				$ids[] = $this->idParser->parse( $id );
215
			} catch ( EntityIdParsingException $e ) {
216
				$this->errorReporter->dieWithError(
217
					[ 'wikibase-api-no-such-entity', $id ],
218
					'no-such-entity',
219
					0,
220
					[ 'id' => $id ]
221
				);
222
			}
223
		}
224
		return $ids;
225
	}
226
227
	/**
228
	 * @param array $params
229
	 * @return EntityId[]
230
	 */
231
	private function getEntityIdsFromSiteTitleParams( array $params ): array {
232
		$ids = [];
233
		if ( !empty( $params['sites'] ) && !empty( $params['titles'] ) ) {
234
			$entityByTitleHelper = $this->getItemByTitleHelper();
235
236
			list( $ids, $missingItems ) = $entityByTitleHelper->getEntityIds(
237
				$params['sites'],
238
				$params['titles'],
239
				$params['normalize']
240
			);
241
242
			$this->addMissingItemsToResult( $missingItems );
243
		}
244
		return $ids;
245
	}
246
247
	private function getItemByTitleHelper(): EntityByTitleHelper {
248
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
249
		$siteLinkStore = $wikibaseRepo->getStore()->getEntityByLinkedTitleLookup();
250
		return new EntityByTitleHelper(
251
			$this,
252
			$this->resultBuilder,
253
			$siteLinkStore,
254
			$wikibaseRepo->getSiteLookup(),
255
			$this->stringNormalizer
256
		);
257
	}
258
259
	/**
260
	 * @param array[] $missingItems Array of arrays, Each internal array has a key 'site' and 'title'
261
	 */
262
	private function addMissingItemsToResult( array $missingItems ): void {
263
		foreach ( $missingItems as $missingItem ) {
264
			$this->resultBuilder->addMissingEntity( null, $missingItem );
265
		}
266
	}
267
268
	/**
269
	 * Returns props based on request parameters
270
	 *
271
	 * @param array $params
272
	 *
273
	 * @return array
274
	 */
275
	private function getPropsFromParams( array $params ): array {
276
		if ( in_array( 'sitelinks/urls', $params['props'] ) ) {
277
			$params['props'][] = 'sitelinks';
278
		}
279
280
		return $params['props'];
281
	}
282
283
	/**
284
	 * @param EntityId[] $entityIds
285
	 * @param bool $resolveRedirects
286
	 *
287
	 * @return EntityRevision[]
288
	 */
289
	private function getEntityRevisionsFromEntityIds( array $entityIds, bool $resolveRedirects = false ): array {
290
		$revisionArray = [];
291
292
		$this->entityPrefetcher->prefetch( $entityIds );
293
294
		foreach ( $entityIds as $entityId ) {
295
			$sourceEntityId = $entityId->getSerialization();
296
			$entityRevision = $this->getEntityRevision( $entityId, $resolveRedirects );
297
298
			$revisionArray[$sourceEntityId] = $entityRevision;
299
		}
300
301
		return $revisionArray;
302
	}
303
304
	private function getEntityRevision( EntityId $entityId, bool $resolveRedirects = false ): ?EntityRevision {
305
		$entityRevision = null;
306
307
		try {
308
			$entityRevision = $this->entityRevisionLookup->getEntityRevision( $entityId );
309
		} catch ( RevisionedUnresolvedRedirectException $ex ) {
310
			if ( $resolveRedirects ) {
311
				$entityId = $ex->getRedirectTargetId();
312
				$entityRevision = $this->getEntityRevision( $entityId, false );
313
			}
314
		} catch ( DivergingEntityIdException $ex ) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
315
			// DivergingEntityIdException is thrown when the repository $entityId is from other
316
			// repository than the entityRevisionLookup was configured to read from.
317
			// Such cases are input errors (e.g. specifying non-existent repository prefix)
318
			// and should be ignored and treated as non-existing entities.
319
		}
320
321
		return $entityRevision;
322
	}
323
324
	/**
325
	 * Adds the given EntityRevision to the API result.
326
	 *
327
	 * @param string|null $sourceEntityId
328
	 * @param EntityRevision|null $entityRevision
329
	 * @param array $params
330
	 */
331
	private function handleEntity(
332
		?string $sourceEntityId,
333
		EntityRevision $entityRevision = null,
334
		array $params = []
335
	): void {
336
		if ( $entityRevision === null ) {
337
			$this->resultBuilder->addMissingEntity( $sourceEntityId, [ 'id' => $sourceEntityId ] );
338
		} else {
339
			list( $languageCodeFilter, $fallbackChains ) = $this->getLanguageCodesAndFallback( $params );
340
			$this->resultBuilder->addEntityRevision(
341
				$sourceEntityId,
342
				$entityRevision,
343
				$this->getPropsFromParams( $params ),
344
				$params['sitefilter'],
345
				$languageCodeFilter,
346
				$fallbackChains
347
			);
348
		}
349
	}
350
351
	/**
352
	 * @param array $params
353
	 *
354
	 * @return array
355
	 *     0 => string[] languageCodes that the user wants returned
356
	 *     1 => TermLanguageFallbackChain[] Keys are requested lang codes
357
	 */
358
	private function getLanguageCodesAndFallback( array $params ): array {
359
		$languageCodes = ( is_array( $params['languages'] ) ? $params['languages'] : [] );
360
		$fallbackChains = [];
361
362
		if ( $params['languagefallback'] ) {
363
			$fallbackMode = LanguageFallbackChainFactory::FALLBACK_ALL;
364
			foreach ( $languageCodes as $languageCode ) {
365
				$fallbackChains[$languageCode] = $this->languageFallbackChainFactory
366
					->newFromLanguageCode( $languageCode, $fallbackMode );
367
			}
368
		}
369
370
		return [ array_unique( $languageCodes ), $fallbackChains ];
371
	}
372
373
	/**
374
	 * @inheritDoc
375
	 */
376
	protected function getAllowedParams(): array {
377
		$sites = $this->siteLinkTargetProvider->getSiteList( $this->siteLinkGroups );
378
379
		return array_merge( parent::getAllowedParams(), [
380
			'ids' => [
381
				self::PARAM_TYPE => 'string',
382
				self::PARAM_ISMULTI => true,
383
			],
384
			'sites' => [
385
				self::PARAM_TYPE => $sites->getGlobalIdentifiers(),
386
				self::PARAM_ISMULTI => true,
387
				self::PARAM_ALLOW_DUPLICATES => true
388
			],
389
			'titles' => [
390
				self::PARAM_TYPE => 'string',
391
				self::PARAM_ISMULTI => true,
392
				self::PARAM_ALLOW_DUPLICATES => true
393
			],
394
			'redirects' => [
395
				self::PARAM_TYPE => [ 'yes', 'no' ],
396
				self::PARAM_DFLT => 'yes',
397
			],
398
			'props' => [
399
				self::PARAM_TYPE => [ 'info', 'sitelinks', 'sitelinks/urls', 'aliases', 'labels',
400
					'descriptions', 'claims', 'datatype' ],
401
				self::PARAM_DFLT => 'info|sitelinks|aliases|labels|descriptions|claims|datatype',
402
				self::PARAM_ISMULTI => true,
403
			],
404
			'languages' => [
405
				self::PARAM_TYPE => WikibaseRepo::getDefaultInstance()->getTermsLanguages()->getLanguages(),
406
				self::PARAM_ISMULTI => true,
407
			],
408
			'languagefallback' => [
409
				self::PARAM_TYPE => 'boolean',
410
				self::PARAM_DFLT => false
411
			],
412
			'normalize' => [
413
				self::PARAM_TYPE => 'boolean',
414
				self::PARAM_DFLT => false
415
			],
416
			'sitefilter' => [
417
				self::PARAM_TYPE => $sites->getGlobalIdentifiers(),
418
				self::PARAM_ISMULTI => true,
419
				self::PARAM_ALLOW_DUPLICATES => true
420
			],
421
		] );
422
	}
423
424
	/**
425
	 * @inheritDoc
426
	 */
427
	protected function getExamplesMessages(): array {
428
		return [
429
			"action=wbgetentities&ids=Q42"
430
			=> "apihelp-wbgetentities-example-1",
431
			"action=wbgetentities&ids=P17"
432
			=> "apihelp-wbgetentities-example-2",
433
			"action=wbgetentities&ids=Q42|P17"
434
			=> "apihelp-wbgetentities-example-3",
435
			"action=wbgetentities&ids=Q42&languages=en"
436
			=> "apihelp-wbgetentities-example-4",
437
			"action=wbgetentities&ids=Q42&languages=ii&languagefallback="
438
			=> "apihelp-wbgetentities-example-5",
439
			"action=wbgetentities&ids=Q42&props=labels"
440
			=> "apihelp-wbgetentities-example-6",
441
			"action=wbgetentities&ids=P17|P3&props=datatype"
442
			=> "apihelp-wbgetentities-example-7",
443
			"action=wbgetentities&ids=Q42&props=aliases&languages=en"
444
			=> "apihelp-wbgetentities-example-8",
445
			"action=wbgetentities&ids=Q1|Q42&props=descriptions&languages=en|de|fr"
446
			=> "apihelp-wbgetentities-example-9",
447
			'action=wbgetentities&sites=enwiki&titles=Berlin&languages=en'
448
			=> 'apihelp-wbgetentities-example-10',
449
			'action=wbgetentities&sites=enwiki&titles=berlin&normalize='
450
			=> 'apihelp-wbgetentities-example-11',
451
			'action=wbgetentities&ids=Q42&props=sitelinks'
452
			=> 'apihelp-wbgetentities-example-12',
453
			'action=wbgetentities&ids=Q42&sitefilter=enwiki'
454
			=> 'apihelp-wbgetentities-example-13'
455
		];
456
	}
457
458
}
459