Skip to content

Commit 1b250c4

Browse files
lucaswerkmeisterWMDE bot
authored andcommitted
EntityId: Assert valid serialization in unserialize()
In change I4850feb0b5, we removed some tests that called unserialize() with a serialization that wasn’t actually valid (non-ID strings, ints, etc.), which noted that “All these cases are kind of an injection vector and allow constructing invalid ids.” It’s unclear why these cases were allowed and tested to begin with; my best guess is that unserialize() skipped validation for performance reasons, but as far as I can tell, it’s rarely if ever called from production code, so I think it’s better to add the validation and reject invalid serializations. We delegate most of the validation to the constructor (which then calls assertValidIdFormat(); however, we afterwards validate that the resulting serialization is the same string as in the data, i.e. that the strtoupper() in the constructor didn’t have an effect. FederatedPropertyId is not touched because it already calls assertValidSerialization() in its __unserialize() method. Change-Id: Id8239ce28ac448ab65a1b911cd25315960fa881e
1 parent d109c4c commit 1b250c4

4 files changed

Lines changed: 34 additions & 2 deletions

File tree

src/Entity/ItemId.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ public function __serialize(): array {
6464
}
6565

6666
public function __unserialize( array $data ): void {
67-
$this->serialization = $data['serialization'];
67+
$this->__construct( $data['serialization'] );
68+
if ( $this->serialization !== $data['serialization'] ) {
69+
throw new InvalidArgumentException( '$data contained invalid serialization' );
70+
}
6871
}
6972

7073
/**

src/Entity/NumericPropertyId.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ public function __serialize(): array {
6060
}
6161

6262
public function __unserialize( array $data ): void {
63-
$this->serialization = $data['serialization'];
63+
$this->__construct( $data['serialization'] );
64+
if ( $this->serialization !== $data['serialization'] ) {
65+
throw new InvalidArgumentException( '$data contained invalid serialization' );
66+
}
6467
}
6568

6669
/**

tests/unit/Entity/ItemIdTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,19 @@ public function testUnserialize() {
9696
$this->assertSame( 'Q2', $id->getSerialization() );
9797
}
9898

99+
public function testUnserializeInvalid(): void {
100+
$id = new ItemId( 'Q1' );
101+
$this->expectException( InvalidArgumentException::class );
102+
$id->__unserialize( [ 'serialization' => 'q' ] );
103+
}
104+
105+
public function testUnserializeNotNormalized(): void {
106+
$id = new ItemId( 'Q1' );
107+
$this->expectException( InvalidArgumentException::class );
108+
$id->__unserialize( [ 'serialization' => 'q2' ] );
109+
// 'q2' is allowed in the constructor (silently uppercased) but not in unserialize()
110+
}
111+
99112
/**
100113
* @dataProvider numericIdProvider
101114
*/

tests/unit/Entity/NumericPropertyIdTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,19 @@ public function testUnserialize() {
9595
$this->assertSame( 'P2', $id->getSerialization() );
9696
}
9797

98+
public function testUnserializeInvalid(): void {
99+
$id = new NumericPropertyId( 'P1' );
100+
$this->expectException( InvalidArgumentException::class );
101+
$id->__unserialize( [ 'serialization' => 'p' ] );
102+
}
103+
104+
public function testUnserializeNotNormalized(): void {
105+
$id = new NumericPropertyId( 'P1' );
106+
$this->expectException( InvalidArgumentException::class );
107+
$id->__unserialize( [ 'serialization' => 'p2' ] );
108+
// 'p2' is allowed in the constructor (silently uppercased) but not in unserialize()
109+
}
110+
98111
/**
99112
* @dataProvider numericIdProvider
100113
*/

0 commit comments

Comments
 (0)