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.

LinkedData/EntityDataSerializationService.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
namespace Wikibase\Repo\LinkedData;
4
5
use ApiFormatBase;
6
use ApiMain;
7
use ApiResult;
8
use DerivativeContext;
9
use DerivativeRequest;
10
use MWException;
11
use PageProps;
12
use RequestContext;
13
use Serializers\Serializer;
14
use SiteLookup;
15
use Wikibase\DataModel\Entity\EntityId;
16
use Wikibase\DataModel\Entity\EntityRedirect;
17
use Wikibase\DataModel\SerializerFactory;
18
use Wikibase\DataModel\Services\Lookup\EntityLookup;
19
use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup;
20
use Wikibase\Lib\Store\EntityRevision;
21
use Wikibase\Lib\Store\EntityTitleLookup;
22
use Wikibase\Lib\Store\RedirectRevision;
23
use Wikibase\Repo\Api\ResultBuilder;
24
use Wikibase\Repo\Rdf\EntityRdfBuilderFactory;
25
use Wikibase\Repo\Rdf\HashDedupeBag;
26
use Wikibase\Repo\Rdf\RdfBuilder;
27
use Wikibase\Repo\Rdf\RdfProducer;
28
use Wikibase\Repo\Rdf\RdfVocabulary;
29
use Wikibase\Repo\Rdf\ValueSnakRdfBuilderFactory;
30
use Wikimedia\Purtle\RdfWriterFactory;
31
32
/**
33
 * Service for serializing entity data.
34
 *
35
 * Note that we are using the API's serialization facility to ensure a consistent external
36
 * representation of data entities. Using the ContentHandler to serialize the entity would expose
37
 * internal implementation details.
38
 *
39
 * For RDF output, this relies on the RdfBuilder class.
40
 *
41
 * @license GPL-2.0-or-later
42
 * @author Daniel Kinzler
43
 * @author Thomas Pellissier Tanon
44
 * @author Anja Jentzsch < [email protected] >
45
 */
46
class EntityDataSerializationService {
47
48
	/**
49
	 * @var EntityLookup|null
50
	 */
51
	private $entityLookup = null;
52
53
	/**
54
	 * @var EntityTitleLookup
55
	 */
56
	private $entityTitleLookup;
57
58
	/**
59
	 * @var SerializerFactory
60
	 */
61
	private $serializerFactory;
62
63
	/**
64
	 * @var Serializer
65
	 */
66
	private $entitySerializer;
67
68
	/**
69
	 * @var PropertyDataTypeLookup
70
	 */
71
	private $propertyLookup;
72
73
	/**
74
	 * @var EntityDataFormatProvider
75
	 */
76
	private $entityDataFormatProvider;
77
78
	/**
79
	 * @var RdfWriterFactory
80
	 */
81
	private $rdfWriterFactory;
82
83
	/**
84
	 * @var SiteLookup
85
	 */
86
	private $siteLookup;
87
88
	/**
89
	 * @var RdfVocabulary
90
	 */
91
	private $rdfVocabulary;
92
93
	/**
94
	 * @var ValueSnakRdfBuilderFactory
95
	 */
96
	private $valueSnakRdfBuilderFactory;
97
98
	/**
99
	 * @var EntityRdfBuilderFactory
100
	 */
101
	private $entityRdfBuilderFactory;
102
103
	public function __construct(
104
		EntityLookup $entityLookup,
105
		EntityTitleLookup $entityTitleLookup,
106
		PropertyDataTypeLookup $propertyLookup,
107
		ValueSnakRdfBuilderFactory $valueSnakRdfBuilderFactory,
108
		EntityRdfBuilderFactory $entityRdfBuilderFactory,
109
		EntityDataFormatProvider $entityDataFormatProvider,
110
		SerializerFactory $serializerFactory,
111
		Serializer $entitySerializer,
112
		SiteLookup $siteLookup,
113
		RdfVocabulary $rdfVocabulary
114
	) {
115
		$this->entityLookup = $entityLookup;
116
		$this->entityTitleLookup = $entityTitleLookup;
117
		$this->propertyLookup = $propertyLookup;
118
		$this->valueSnakRdfBuilderFactory = $valueSnakRdfBuilderFactory;
119
		$this->entityRdfBuilderFactory = $entityRdfBuilderFactory;
120
		$this->entityDataFormatProvider = $entityDataFormatProvider;
121
		$this->serializerFactory = $serializerFactory;
122
		$this->entitySerializer = $entitySerializer;
123
		$this->siteLookup = $siteLookup;
124
		$this->rdfVocabulary = $rdfVocabulary;
125
126
		$this->rdfWriterFactory = new RdfWriterFactory();
127
	}
128
129
	/**
130
	 * Output entity data.
131
	 *
132
	 * @param string $format The name (mime type of file extension) of the format to use
133
	 * @param EntityRevision $entityRevision The entity
134
	 * @param RedirectRevision|null $followedRedirect The redirect that led to the entity, or null
135
	 * @param EntityId[] $incomingRedirects Incoming redirects to include in the output
136
	 * @param string|null $flavor The type of the output provided by serializer
137
	 *
138
	 * @return array tuple of ( $data, $contentType )
139
	 * @throws MWException
140
	 */
141
	public function getSerializedData(
142
		$format,
143
		EntityRevision $entityRevision,
144
		RedirectRevision $followedRedirect = null,
145
		array $incomingRedirects = [],
146
		$flavor = null
147
	) {
148
149
		$formatName = $this->entityDataFormatProvider->getFormatName( $format );
150
151
		if ( $formatName === null ) {
152
			throw new MWException( "Unsupported format: $format" );
153
		}
154
155
		$serializer = $this->createApiSerializer( $formatName );
156
157
		if ( $serializer !== null ) {
158
			$data = $this->getApiSerialization( $entityRevision, $serializer );
159
			$contentType = $serializer->getIsHtml() ? 'text/html' : $serializer->getMimeType();
160
		} else {
161
			$rdfBuilder = $this->createRdfBuilder( $formatName, $flavor );
162
163
			if ( $rdfBuilder === null ) {
164
				throw new MWException( "Could not create serializer for $formatName" );
165
			}
166
167
			$data = $this->rdfSerialize( $entityRevision, $followedRedirect, $incomingRedirects, $rdfBuilder, $flavor );
168
169
			$mimeTypes = $this->rdfWriterFactory->getMimeTypes( $formatName );
170
			$contentType = reset( $mimeTypes );
171
		}
172
173
		return [ $data, $contentType ];
174
	}
175
176
	/**
177
	 * @param EntityRevision $entityRevision
178
	 * @param RedirectRevision|null $followedRedirect a redirect leading to the entity for use in the output
179
	 * @param EntityId[] $incomingRedirects Incoming redirects to include in the output
180
	 * @param RdfBuilder $rdfBuilder
181
	 * @param string|null $flavor The type of the output provided by serializer
182
	 *
183
	 * @return string RDF
184
	 */
185
	private function rdfSerialize(
186
		EntityRevision $entityRevision,
187
		?RedirectRevision $followedRedirect,
188
		array $incomingRedirects,
189
		RdfBuilder $rdfBuilder,
190
		$flavor = null
191
	) {
192
		$rdfBuilder->startDocument();
193
		$redir = null;
194
195
		if ( $followedRedirect ) {
196
			$redir = $followedRedirect->getRedirect();
197
			$rdfBuilder->addEntityRedirect( $redir->getEntityId(), $redir->getTargetId() );
198
199
			if ( $followedRedirect->getRevisionId() > 0 ) {
200
				$rdfBuilder->addEntityRevisionInfo(
201
					$redir->getEntityId(),
202
					$followedRedirect->getRevisionId(),
203
					$followedRedirect->getTimestamp()
204
				);
205
			}
206
		}
207
208
		if ( $followedRedirect && $flavor === 'dump' ) {
209
			// For redirects, don't output the target entity data if the "dump" flavor is requested.
210
			// @todo: In this case, avoid loading the Entity all together.
211
			// However we want to output the revisions for redirects
212
		} else {
213
			$rdfBuilder->addEntityRevisionInfo(
214
				$entityRevision->getEntity()->getId(),
215
				$entityRevision->getRevisionId(),
216
				$entityRevision->getTimestamp()
217
			);
218
219
			$rdfBuilder->addEntityPageProps( $entityRevision->getEntity()->getId() );
0 ignored issues
show
It seems like $entityRevision->getEntity()->getId() can be null; however, addEntityPageProps() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
220
221
			$rdfBuilder->addEntity( $entityRevision->getEntity() );
222
			$rdfBuilder->resolveMentionedEntities( $this->entityLookup );
223
		}
224
225
		if ( $flavor !== 'dump' ) {
226
			// For $flavor === 'dump' we don't need to output incoming redirects.
227
228
			$targetId = $entityRevision->getEntity()->getId();
229
			$this->addIncomingRedirects( $targetId, $redir, $incomingRedirects, $rdfBuilder );
230
		}
231
232
		$rdfBuilder->finishDocument();
233
234
		return $rdfBuilder->getRDF();
235
	}
236
237
	/**
238
	 * @param EntityId $targetId
239
	 * @param EntityRedirect|null $followedRedirect The followed redirect, will be omitted from the
240
	 * output.
241
	 * @param EntityId[] $incomingRedirects
242
	 * @param RdfBuilder $rdfBuilder
243
	 */
244
	private function addIncomingRedirects(
245
		EntityId $targetId,
246
		?EntityRedirect $followedRedirect,
247
		array $incomingRedirects,
248
		RdfBuilder $rdfBuilder
249
	) {
250
		foreach ( $incomingRedirects as $rId ) {
251
			// don't add the followed redirect again
252
			if ( !$followedRedirect || !$followedRedirect->getEntityId()->equals( $rId ) ) {
253
				$rdfBuilder->addEntityRedirect( $rId, $targetId );
254
			}
255
		}
256
	}
257
258
	/**
259
	 * Returns an ApiMain module that acts as a context for the formatting and serialization.
260
	 *
261
	 * @param string $format The desired output format, as a format name that ApiBase understands.
262
	 *
263
	 * @return ApiMain
264
	 */
265
	private function newApiMain( $format ) {
266
		// Fake request params to ApiMain, with forced format parameters.
267
		// We can override additional parameters here, as needed.
268
		$params = [
269
			'format' => $format,
270
		];
271
272
		$context = new DerivativeContext( RequestContext::getMain() ); //XXX: ugly
273
274
		$req = new DerivativeRequest( $context->getRequest(), $params );
275
		$context->setRequest( $req );
276
277
		$api = new ApiMain( $context );
278
		return $api;
279
	}
280
281
	/**
282
	 * Creates an API printer that can generate the given output format.
283
	 *
284
	 * @param string $formatName The desired serialization format,
285
	 *           as a format name understood by ApiBase or RdfWriterFactory.
286
	 *
287
	 * @return ApiFormatBase|null A suitable result printer, or null
288
	 *           if the given format is not supported by the API.
289
	 */
290
	private function createApiSerializer( $formatName ) {
291
		//MediaWiki formats
292
		$api = $this->newApiMain( $formatName );
293
		$formatNames = $api->getModuleManager()->getNames( 'format' );
294
		if ( $formatName !== null && in_array( $formatName, $formatNames ) ) {
295
			return $api->createPrinterByName( $formatName );
296
		}
297
298
		return null;
299
	}
300
301
	/**
302
	 * Get the producer setting for current data format
303
	 *
304
	 * @param string|null $flavorName
305
	 *
306
	 * @return int
307
	 * @throws MWException
308
	 */
309
	private function getFlavor( $flavorName ) {
310
		switch ( $flavorName ) {
311
			case 'simple':
312
				return RdfProducer::PRODUCE_TRUTHY_STATEMENTS
313
					| RdfProducer::PRODUCE_SITELINKS
314
					| RdfProducer::PRODUCE_VERSION_INFO;
315
			case 'dump':
316
				return RdfProducer::PRODUCE_ALL_STATEMENTS
317
					| RdfProducer::PRODUCE_TRUTHY_STATEMENTS
318
					| RdfProducer::PRODUCE_QUALIFIERS
319
					| RdfProducer::PRODUCE_REFERENCES
320
					| RdfProducer::PRODUCE_SITELINKS
321
					| RdfProducer::PRODUCE_FULL_VALUES
322
					| RdfProducer::PRODUCE_PAGE_PROPS
323
					| RdfProducer::PRODUCE_NORMALIZED_VALUES
324
					| RdfProducer::PRODUCE_VERSION_INFO;
325
			case 'long':
326
				return RdfProducer::PRODUCE_ALL_STATEMENTS
327
					| RdfProducer::PRODUCE_QUALIFIERS
328
					| RdfProducer::PRODUCE_REFERENCES
329
					| RdfProducer::PRODUCE_SITELINKS
330
					| RdfProducer::PRODUCE_VERSION_INFO;
331
			case 'full':
332
			case null:
333
				return RdfProducer::PRODUCE_ALL;
334
		}
335
336
		throw new MWException( "Unsupported flavor: $flavorName" );
337
	}
338
339
	/**
340
	 * Creates an Rdf Serializer that can generate the given output format.
341
	 *
342
	 * @param string $format The desired serialization format, as a format name understood by ApiBase or RdfWriterFactory
343
	 * @param string|null $flavorName Flavor name (used for RDF output)
344
	 *
345
	 * @return RdfBuilder|null A suitable result printer, or null
346
	 *   if the given format is not supported.
347
	 */
348
	private function createRdfBuilder( $format, $flavorName = null ) {
349
		$canonicalFormat = $this->rdfWriterFactory->getFormatName( $format );
350
351
		if ( !$canonicalFormat ) {
352
			return null;
353
		}
354
355
		$rdfWriter = $this->rdfWriterFactory->getWriter( $format );
356
357
		$rdfBuilder = new RdfBuilder(
358
			$this->rdfVocabulary,
359
			$this->valueSnakRdfBuilderFactory,
360
			$this->propertyLookup,
361
			$this->entityRdfBuilderFactory,
362
			$this->getFlavor( $flavorName ),
363
			$rdfWriter,
364
			new HashDedupeBag(),
365
			$this->entityTitleLookup
366
		);
367
368
		$rdfBuilder->setPageProps( PageProps::getInstance() );
369
370
		return $rdfBuilder;
371
	}
372
373
	/**
374
	 * Pushes the given $entity into the ApiResult held by the ApiMain module
375
	 * returned by newApiMain(). Calling $printer->execute() later will output this
376
	 * result, if $printer was generated from that same ApiMain module, as
377
	 * createApiPrinter() does.
378
	 *
379
	 * @param EntityRevision $entityRevision The entity to convert ot an ApiResult
380
	 * @param ApiFormatBase $printer The output printer that will be used for serialization.
381
	 *   Used to provide context for generating the ApiResult, and may also be manipulated
382
	 *   to fine-tune the output.
383
	 *
384
	 * @return ApiResult
385
	 */
386
	private function generateApiResult( EntityRevision $entityRevision, ApiFormatBase $printer ) {
387
		$res = $printer->getResult();
388
389
		// Make sure result is empty. May still be full if this
390
		// function gets called multiple times during testing, etc.
391
		$res->reset();
392
393
		$resultBuilder = new ResultBuilder(
394
			$res,
395
			$this->entityTitleLookup,
396
			$this->serializerFactory,
397
			$this->entitySerializer,
398
			$this->siteLookup,
399
			$this->propertyLookup,
400
			true // include metadata for the API result printer
401
		);
402
		$resultBuilder->addEntityRevision( null, $entityRevision );
403
404
		return $res;
405
	}
406
407
	/**
408
	 * Serialize the entity data using the provided format.
409
	 *
410
	 * Note that we are using the API's serialization facility to ensure a consistent external
411
	 * representation of data entities. Using the ContentHandler to serialize the entity would
412
	 * expose internal implementation details.
413
	 *
414
	 * @param EntityRevision $entityRevision the entity to output.
415
	 * @param ApiFormatBase $printer the printer to use to generate the output
416
	 *
417
	 * @return string the serialized data
418
	 */
419
	private function getApiSerialization(
420
		EntityRevision $entityRevision,
421
		ApiFormatBase $printer
422
	) {
423
		// NOTE: The way the ApiResult is provided to $printer is somewhat
424
		//       counter-intuitive. Basically, the relevant ApiResult object
425
		//       is owned by the ApiMain module provided by newApiMain().
426
427
		// Pushes $entity into the ApiResult held by the ApiMain module
428
		// TODO: where to put the followed redirect?
429
		// TODO: where to put the incoming redirects? See T98039
430
		$this->generateApiResult( $entityRevision, $printer );
431
432
		$printer->initPrinter();
433
434
		// Outputs the ApiResult held by the ApiMain module, which is hopefully the one we added the entity data to.
435
		//NOTE: this can and will mess with the HTTP response!
436
		$printer->execute();
437
		$data = $printer->getBuffer();
438
439
		$printer->disable();
440
441
		return $data;
442
	}
443
444
}
445