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.

data-access/src/SingleEntitySourceServices.php (2 issues)

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\DataAccess;
4
5
use Deserializers\Deserializer;
6
use Deserializers\DispatchingDeserializer;
7
use MediaWiki\Logger\LoggerFactory;
8
use MediaWiki\MediaWikiServices;
9
use MediaWiki\Storage\NameTableStore;
10
use Wikibase\DataAccess\Serializer\ForbiddenSerializer;
11
use Wikibase\DataModel\DeserializerFactory;
12
use Wikibase\DataModel\Entity\EntityId;
13
use Wikibase\DataModel\Entity\EntityIdParser;
14
use Wikibase\DataModel\Entity\EntityRedirect;
15
use Wikibase\DataModel\Entity\Property;
16
use Wikibase\DataModel\Services\EntityId\EntityIdComposer;
17
use Wikibase\InternalSerialization\DeserializerFactory as InternalDeserializerFactory;
18
use Wikibase\Lib\Interactors\MatchingTermsSearchInteractorFactory;
19
use Wikibase\Lib\Interactors\TermSearchInteractorFactory;
20
use Wikibase\Lib\Store\EntityContentDataCodec;
21
use Wikibase\Lib\Store\EntityNamespaceLookup;
22
use Wikibase\Lib\Store\EntityRevision;
23
use Wikibase\Lib\Store\EntityStoreWatcher;
24
use Wikibase\Lib\Store\MatchingTermsLookup;
25
use Wikibase\Lib\Store\Sql\EntityIdLocalPartPageTableEntityQuery;
26
use Wikibase\Lib\Store\Sql\PrefetchingWikiPageEntityMetaDataAccessor;
27
use Wikibase\Lib\Store\Sql\PropertyInfoTable;
28
use Wikibase\Lib\Store\Sql\Terms\DatabaseMatchingTermsLookup;
29
use Wikibase\Lib\Store\Sql\Terms\DatabaseTermInLangIdsResolver;
30
use Wikibase\Lib\Store\Sql\Terms\DatabaseTypeIdsStore;
31
use Wikibase\Lib\Store\Sql\Terms\PrefetchingItemTermLookup;
32
use Wikibase\Lib\Store\Sql\TypeDispatchingWikiPageEntityMetaDataAccessor;
33
use Wikibase\Lib\Store\Sql\WikiPageEntityDataLoader;
34
use Wikibase\Lib\Store\Sql\WikiPageEntityMetaDataAccessor;
35
use Wikibase\Lib\Store\Sql\WikiPageEntityMetaDataLookup;
36
use Wikibase\Lib\Store\Sql\WikiPageEntityRevisionLookup;
37
use Wikibase\Lib\Store\TypeDispatchingEntityRevisionLookup;
38
use Wikibase\Lib\WikibaseSettings;
39
use Wikimedia\Assert\Assert;
40
41
/**
42
 * Collection of services for a single EntitySource.
43
 * Some GenericServices are injected alongside some more specific services for the EntitySource.
44
 * Various logic then pulls these services together into more composed services.
45
 *
46
 * TODO fixme, lots of things in this class bind to wikibase lib and mediawiki directly.
47
 *
48
 * @license GPL-2.0-or-later
49
 */
50
class SingleEntitySourceServices implements EntityStoreWatcher {
51
52
	/**
53
	 * @var GenericServices
54
	 */
55
	private $genericServices;
56
57
	/**
58
	 * @var EntityIdParser
59
	 */
60
	private $entityIdParser;
61
62
	private $entityIdComposer;
63
64
	private $dataValueDeserializer;
65
66
	/**
67
	 * @var DataAccessSettings
68
	 */
69
	private $dataAccessSettings;
70
71
	/**
72
	 * @var EntitySource
73
	 */
74
	private $entitySource;
75
	private $deserializerFactoryCallbacks;
76
	private $entityMetaDataAccessorCallbacks;
77
78
	/**
79
	 * @var callable[]
80
	 */
81
	private $prefetchingTermLookupCallbacks;
82
83
	private $slotRoleStore;
84
	private $entityRevisionLookup = null;
85
86
	private $termSearchInteractorFactory = null;
87
88
	private $prefetchingTermLookup = null;
89
90
	/**
91
	 * @var PrefetchingWikiPageEntityMetaDataAccessor|null
92
	 */
93
	private $entityMetaDataAccessor = null;
94
95
	private $propertyInfoLookup = null;
96
97
	private $entityRevisionLookupFactoryCallbacks;
98
	private $entityNamespaceLookup;
99
100
	public function __construct(
101
		GenericServices $genericServices,
102
		EntityIdParser $entityIdParser,
103
		EntityIdComposer $entityIdComposer,
104
		Deserializer $dataValueDeserializer,
105
		NameTableStore $slotRoleStore,
106
		DataAccessSettings $dataAccessSettings,
107
		EntitySource $entitySource,
108
		array $deserializerFactoryCallbacks,
109
		array $entityMetaDataAccessorCallbacks,
110
		array $prefetchingTermLookupCallbacks,
111
		array $entityRevisionFactoryLookupCallbacks
112
	) {
113
		$this->assertCallbackArrayTypes(
114
			$deserializerFactoryCallbacks,
115
			$entityMetaDataAccessorCallbacks,
116
			$prefetchingTermLookupCallbacks,
117
			$entityRevisionFactoryLookupCallbacks
118
		);
119
120
		$this->genericServices = $genericServices;
121
		$this->entityIdParser = $entityIdParser;
122
		$this->entityIdComposer = $entityIdComposer;
123
		$this->dataValueDeserializer = $dataValueDeserializer;
124
		$this->slotRoleStore = $slotRoleStore;
125
		$this->dataAccessSettings = $dataAccessSettings;
126
		$this->entitySource = $entitySource;
127
		$this->deserializerFactoryCallbacks = $deserializerFactoryCallbacks;
128
		$this->entityMetaDataAccessorCallbacks = $entityMetaDataAccessorCallbacks;
129
		$this->prefetchingTermLookupCallbacks = $prefetchingTermLookupCallbacks;
130
		$this->entityRevisionLookupFactoryCallbacks = $entityRevisionFactoryLookupCallbacks;
131
	}
132
133
	private function assertCallbackArrayTypes(
134
		array $deserializerFactoryCallbacks,
135
		array $entityMetaDataAccessorCallbacks,
136
		array $prefetchingTermLookupCallbacks,
137
		array $entityRevisionFactoryLookupCallbacks
138
	) {
139
		Assert::parameterElementType(
140
			'callable',
141
			$deserializerFactoryCallbacks,
142
			'$deserializerFactoryCallbacks'
143
		);
144
		Assert::parameterElementType(
145
			'callable',
146
			$entityMetaDataAccessorCallbacks,
147
			'$entityMetaDataAccessorCallbacks'
148
		);
149
		Assert::parameterElementType(
150
			'callable',
151
			$prefetchingTermLookupCallbacks,
152
			'$prefetchingTermLookupCallbacks'
153
		);
154
		Assert::parameterElementType(
155
			'callable',
156
			$entityRevisionFactoryLookupCallbacks,
157
			'$entityRevisionFactoryLookupCallbacks'
158
		);
159
	}
160
161
	/**
162
	 * @return EntitySource The EntitySource object for this set of services
163
	 */
164
	public function getEntitySource() : EntitySource {
165
		return $this->entitySource;
166
	}
167
168
	/**
169
	 * @return EntityNamespaceLookup The EntityNamespaceLookup object for this EntitySource
170
	 */
171
	public function getEntityNamespaceLookup() : EntityNamespaceLookup {
172
		if ( $this->entityNamespaceLookup === null ) {
173
			$this->entityNamespaceLookup = new EntityNamespaceLookup(
174
				$this->entitySource->getEntityNamespaceIds(),
175
				$this->entitySource->getEntitySlotNames()
176
			);
177
		}
178
179
		return $this->entityNamespaceLookup;
180
	}
181
182
	/**
183
	 * It would be nice to only return hint against the TermInLangIdsResolver interface here,
184
	 * but current users need a method only provided by DatabaseTermInLangIdsResolver
185
	 * @return DatabaseTermInLangIdsResolver
186
	 */
187
	public function getTermInLangIdsResolver() : DatabaseTermInLangIdsResolver {
188
		$mediaWikiServices = MediaWikiServices::getInstance();
189
		$logger = LoggerFactory::getInstance( 'Wikibase' );
190
191
		$databaseName = $this->entitySource->getDatabaseName();
192
		$loadBalancer = $mediaWikiServices->getDBLoadBalancerFactory()
193
			->getMainLB( $databaseName );
194
195
		$databaseTypeIdsStore = new DatabaseTypeIdsStore(
196
			$loadBalancer,
197
			$mediaWikiServices->getMainWANObjectCache(),
198
			$databaseName,
0 ignored issues
show
It seems like $databaseName defined by $this->entitySource->getDatabaseName() on line 191 can also be of type string; however, Wikibase\Lib\Store\Sql\T...IdsStore::__construct() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
199
			$logger
200
		);
201
		return new DatabaseTermInLangIdsResolver(
202
			$databaseTypeIdsStore,
203
			$databaseTypeIdsStore,
204
			$loadBalancer,
205
			$databaseName,
206
			$logger
207
		);
208
	}
209
210
	public function getEntityRevisionLookup() {
211
		if ( $this->entityRevisionLookup === null ) {
212
			if ( !WikibaseSettings::isRepoEnabled() ) {
213
				$serializer = new ForbiddenSerializer( 'Entity serialization is not supported on the client!' );
214
			} else {
215
				$serializer = $this->genericServices->getStorageEntitySerializer();
216
			}
217
218
			$codec = new EntityContentDataCodec(
219
				$this->entityIdParser,
220
				$serializer,
221
				$this->getEntityDeserializer(),
222
				$this->dataAccessSettings->maxSerializedEntitySizeInBytes()
223
			);
224
225
			/** @var WikiPageEntityMetaDataAccessor $metaDataAccessor */
226
			$metaDataAccessor = $this->getEntityMetaDataAccessor();
227
228
			// TODO: instead calling static getInstance randomly here, inject two db-specific services
229
			$revisionStoreFactory = MediaWikiServices::getInstance()->getRevisionStoreFactory();
230
			$blobStoreFactory = MediaWikiServices::getInstance()->getBlobStoreFactory();
231
232
			$databaseName = $this->entitySource->getDatabaseName();
233
234
			// TODO: This wikiPageEntityRevisionStoreLookup should probably instead be built by a factory
235
			// that is returned by a method somewhere in data-access and then instead of being used here
236
			// as a default it should go in the wiring files for each entity type. See: T246451
237
			$wikiPageEntityRevisionStoreLookup = new WikiPageEntityRevisionLookup(
238
				$metaDataAccessor,
239
				new WikiPageEntityDataLoader( $codec, $blobStoreFactory->newBlobStore( $databaseName ) ),
240
				$revisionStoreFactory->getRevisionStore( $databaseName ),
241
				$databaseName
242
			);
243
244
			$this->entityRevisionLookup = new TypeDispatchingEntityRevisionLookup(
245
				$this->entityRevisionLookupFactoryCallbacks,
246
				$wikiPageEntityRevisionStoreLookup
247
			);
248
		}
249
250
		return $this->entityRevisionLookup;
251
	}
252
253
	private function getEntityDeserializer() {
254
		$deserializerFactory = new DeserializerFactory(
255
			$this->dataValueDeserializer,
256
			$this->entityIdParser
257
		);
258
259
		$deserializers = [];
260
		foreach ( $this->deserializerFactoryCallbacks as $callback ) {
261
			$deserializers[] = call_user_func( $callback, $deserializerFactory );
262
		}
263
264
		$internalDeserializerFactory = new InternalDeserializerFactory(
265
			$this->dataValueDeserializer,
266
			$this->entityIdParser,
267
			new DispatchingDeserializer( $deserializers )
268
		);
269
270
		return $internalDeserializerFactory->newEntityDeserializer();
271
	}
272
273
	private function getEntityMetaDataAccessor() {
274
		if ( $this->entityMetaDataAccessor === null ) {
275
			$entityNamespaceLookup = $this->getEntityNamespaceLookup();
276
			$repositoryName = '';
277
			$databaseName = $this->entitySource->getDatabaseName();
278
			$this->entityMetaDataAccessor = new PrefetchingWikiPageEntityMetaDataAccessor(
279
				new TypeDispatchingWikiPageEntityMetaDataAccessor(
280
					$this->entityMetaDataAccessorCallbacks,
281
					new WikiPageEntityMetaDataLookup(
282
						$entityNamespaceLookup,
283
						new EntityIdLocalPartPageTableEntityQuery(
284
							$entityNamespaceLookup,
285
							$this->slotRoleStore
286
						),
287
						$this->entitySource
288
					),
289
					$databaseName,
290
					$repositoryName
291
				),
292
				// TODO: inject?
293
				LoggerFactory::getInstance( 'Wikibase' )
294
			);
295
		}
296
297
		return $this->entityMetaDataAccessor;
298
	}
299
300
	public function getTermSearchInteractorFactory(): TermSearchInteractorFactory {
301
		if ( $this->termSearchInteractorFactory === null ) {
302
			$this->termSearchInteractorFactory = new MatchingTermsSearchInteractorFactory(
303
				$this->getMatchingTermsLookup(),
304
				$this->genericServices->getLanguageFallbackChainFactory(),
305
				$this->getPrefetchingTermLookup()
306
			);
307
		}
308
309
		return $this->termSearchInteractorFactory;
310
	}
311
312
	private function getMatchingTermsLookup(): MatchingTermsLookup {
313
		$mediaWikiServices = MediaWikiServices::getInstance();
314
		$logger = LoggerFactory::getInstance( 'Wikibase' );
315
		$repoDbDomain = $this->entitySource->getDatabaseName();
316
		$loadBalancer = $mediaWikiServices->getDBLoadBalancerFactory()->getMainLB( $repoDbDomain );
317
		$databaseTypeIdsStore = new DatabaseTypeIdsStore(
318
			$loadBalancer,
319
			$mediaWikiServices->getMainWANObjectCache(),
320
			$repoDbDomain,
0 ignored issues
show
It seems like $repoDbDomain defined by $this->entitySource->getDatabaseName() on line 315 can also be of type string; however, Wikibase\Lib\Store\Sql\T...IdsStore::__construct() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
321
			$logger
322
		);
323
		return new DatabaseMatchingTermsLookup(
324
			$loadBalancer,
325
			$databaseTypeIdsStore,
326
			$databaseTypeIdsStore,
327
			$this->entityIdComposer,
328
			$logger
329
		);
330
	}
331
332
	public function getPrefetchingTermLookup() {
333
		if ( $this->prefetchingTermLookup === null ) {
334
			$this->prefetchingTermLookup = new ByTypeDispatchingPrefetchingTermLookup(
335
				$this->getPrefetchingTermLookups(),
336
				new NullPrefetchingTermLookup()
337
			);
338
		}
339
340
		return $this->prefetchingTermLookup;
341
	}
342
343
	/**
344
	 * @return PrefetchingItemTermLookup[] indexed by entity type
345
	 */
346
	private function getPrefetchingTermLookups(): array {
347
		$typesWithCustomLookups = array_keys( $this->prefetchingTermLookupCallbacks );
348
349
		$lookupConstructorsByType = array_intersect( $typesWithCustomLookups, $this->entitySource->getEntityTypes() );
350
		$customLookups = [];
351
		foreach ( $lookupConstructorsByType as $type ) {
352
			$callback = $this->prefetchingTermLookupCallbacks[$type];
353
			$lookup = call_user_func( $callback, $this );
354
355
			Assert::postcondition(
356
				$lookup instanceof PrefetchingTermLookup,
357
				"Callback creating a lookup for $type must create an instance of PrefetchingTermLookup"
358
			);
359
360
			$customLookups[$type] = $lookup;
361
		}
362
		return $customLookups;
363
	}
364
365
	public function getEntityPrefetcher() {
366
		return $this->getEntityMetaDataAccessor();
367
	}
368
369
	public function getPropertyInfoLookup() {
370
		if ( !in_array( Property::ENTITY_TYPE, $this->entitySource->getEntityTypes() ) ) {
371
			throw new \LogicException( 'Entity source: ' . $this->entitySource->getSourceName() . ' does no provide properties' );
372
		}
373
		if ( $this->propertyInfoLookup === null ) {
374
			$this->propertyInfoLookup = new PropertyInfoTable(
375
				$this->entityIdComposer,
376
				$this->entitySource->getDatabaseName(),
377
				false
378
			);
379
		}
380
		return $this->propertyInfoLookup;
381
	}
382
383
	public function entityUpdated( EntityRevision $entityRevision ) {
384
		// TODO: should this become more "generic" and somehow enumerate all services and
385
		// update all of these which are instances of EntityStoreWatcher?
386
387
		// Only notify entityMetaDataAccessor if the service is created, as the EntityStoreWatcher
388
		// is only used for purging of an in process cache.
389
		if ( $this->entityMetaDataAccessor !== null ) {
390
			$this->entityMetaDataAccessor->entityUpdated( $entityRevision );
391
		}
392
	}
393
394
	public function redirectUpdated( EntityRedirect $entityRedirect, $revisionId ) {
395
		// TODO: should this become more "generic" and somehow enumerate all services and
396
		// update all of these which are instances of EntityStoreWatcher?
397
398
		// Only notify entityMetaDataAccessor if the service is created, as the EntityStoreWatcher
399
		// is only used for purging of an in process cache.
400
		if ( $this->entityMetaDataAccessor !== null ) {
401
			$this->entityMetaDataAccessor->redirectUpdated( $entityRedirect, $revisionId );
402
		}
403
	}
404
405
	public function entityDeleted( EntityId $entityId ) {
406
		// TODO: should this become more "generic" and somehow enumerate all services and
407
		// update all of these which are instances of EntityStoreWatcher?
408
409
		// Only notify entityMetaDataAccessor if the service is created, as the EntityStoreWatcher
410
		// is only used for purging of an in process cache.
411
		if ( $this->entityMetaDataAccessor !== null ) {
412
			$this->entityMetaDataAccessor->entityDeleted( $entityId );
413
		}
414
	}
415
416
}
417