SimpleCacheWithBagOStuffTest   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 187
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 3

Importance

Changes 0
Metric Value
wmc 14
lcom 0
cbo 3
dl 0
loc 187
rs 10
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A testGivenPrefixContainsForbiddenCharacters_ConstructorThrowsException() 0 7 1
A testObjectsCanNotBeStored_WhenRetrievedGetIncompleteClass() 0 10 1
A testGet_GivenSignatureIsWrong_ReturnsDefaultValue() 0 11 1
A createSimpleCache() 0 3 1
A testUsesPrefixWhenSetting() 0 10 1
A testUsesPrefixWhenSettingMultiple() 0 10 1
A testGetMultiple_GivenSignatureIsWrong_ReturnsDefaultValue() 0 11 1
A testGet_GivenSignatureIsWrong_LoggsTheEvent() 0 17 1
A testCachedValueCannotBeUnserialized_ReturnsDefaultValue() 0 14 1
A testSecretCanNotBeEmpty() 0 6 1
A spoilTheSignature() 0 5 1
A testSetTtl() 0 14 1
A testSetMultipleTtl() 0 16 1
A testUTF8KeysAreValid() 0 9 1
1
<?php
2
declare( strict_types=1 );
3
4
namespace Wikibase\Lib\Tests;
5
6
use HashBagOStuff;
7
use Prophecy\Argument;
8
use Psr\Log\LoggerInterface;
9
use Psr\SimpleCache\CacheInterface;
10
use Wikibase\Lib\SimpleCacheWithBagOStuff;
11
12
/**
13
 * @group Wikibase
14
 *
15
 * @license GPL-2.0-or-later
16
 */
17
class SimpleCacheWithBagOStuffTest extends SimpleCacheTestCase {
18
19
	protected $skippedTests = [
20
		'testClear' => 'Not possible to implement for BagOStuff'
21
	];
22
23
	/**
24
	 * @return CacheInterface that is used in the tests
25
	 */
26
	public function createSimpleCache() {
27
		return new SimpleCacheWithBagOStuff( new HashBagOStuff(), 'somePrefix', 'some secret' );
28
	}
29
30
	public function testUsesPrefixWhenSetting() {
31
		$inner = new HashBagOStuff();
32
33
		$prefix = 'somePrefix';
34
		$simpleCache = new SimpleCacheWithBagOStuff( $inner, $prefix, 'some secret' );
35
36
		$simpleCache->set( 'test', 'value' );
37
		$key = $inner->makeKey( $prefix, 'test' );
38
		$this->assertNotFalse( $inner->get( $key ) );
39
	}
40
41
	public function testUsesPrefixWhenSettingMultiple() {
42
		$inner = new HashBagOStuff();
43
44
		$prefix = 'somePrefix';
45
		$simpleCache = new SimpleCacheWithBagOStuff( $inner, $prefix, 'some secret' );
46
47
		$simpleCache->setMultiple( [ 'test' => 'value' ] );
0 ignored issues
show
Documentation introduced by
array('test' => 'value') is of type array<string,string,{"test":"string"}>, but the function expects a object<Wikibase\Lib\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
48
		$key = $inner->makeKey( $prefix, 'test' );
49
		$this->assertNotFalse( $inner->get( $key ) );
50
	}
51
52
	public function testGivenPrefixContainsForbiddenCharacters_ConstructorThrowsException() {
53
		$prefix = '@somePrefix';
54
		$inner = new HashBagOStuff();
55
56
		$this->expectException( \InvalidArgumentException::class );
57
		new SimpleCacheWithBagOStuff( $inner, $prefix, 'some secret' );
58
	}
59
60
	/**
61
	 * This test ensures that we cannot accidentally deserialize arbitrary classes
62
	 * because it is unsecure.
63
	 *
64
	 * @see https://phabricator.wikimedia.org/T161647
65
	 * @see https://secure.php.net/manual/en/function.unserialize.php
66
	 * @see https://www.owasp.org/index.php/PHP_Object_Injection
67
	 */
68
	public function testObjectsCanNotBeStored_WhenRetrievedGetIncompleteClass() {
69
		$initialValue = new \DateTime();
70
71
		$cache = $this->createSimpleCache();
72
		$cache->set( 'key', $initialValue );
73
		$gotValue = $cache->get( 'key' );
74
75
		$this->assertInstanceOf( \__PHP_Incomplete_Class::class, $gotValue );
76
		$this->assertFalse( $initialValue == $gotValue );
77
	}
78
79
	/**
80
	 * This test ensures that if data in cache storage is compromised we won't accidentally
81
	 * use it.
82
	 *
83
	 * @see https://phabricator.wikimedia.org/T161647
84
	 * @see https://secure.php.net/manual/en/function.unserialize.php
85
	 * @see https://www.owasp.org/index.php/PHP_Object_Injection
86
	 */
87
	public function testGet_GivenSignatureIsWrong_ReturnsDefaultValue() {
88
		$inner = new HashBagOStuff();
89
90
		$cache = new SimpleCacheWithBagOStuff( $inner, 'prefix', 'some secret' );
91
		$cache->set( 'key', 'some_string' );
92
		$key = $inner->makeKey( 'prefix', 'key' );
93
		$this->spoilTheSignature( $inner, $key );
94
95
		$got = $cache->get( 'key', 'some default value' );
96
		$this->assertEquals( 'some default value', $got );
97
	}
98
99
	public function testGetMultiple_GivenSignatureIsWrong_ReturnsDefaultValue() {
100
		$inner = new HashBagOStuff();
101
102
		$cache = new SimpleCacheWithBagOStuff( $inner, 'prefix', 'some secret' );
103
		$cache->set( 'key', 'some_string' );
104
		$key = $inner->makeKey( 'prefix', 'key' );
105
		$this->spoilTheSignature( $inner, $key );
106
107
		$got = $cache->getMultiple( [ 'key' ], 'some default value' );
0 ignored issues
show
Documentation introduced by
array('key') is of type array<integer,string,{"0":"string"}>, but the function expects a object<Wikibase\Lib\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
108
		$this->assertEquals( [ 'key' => 'some default value' ], $got );
109
	}
110
111
	public function testGet_GivenSignatureIsWrong_LoggsTheEvent() {
112
		$logger = $this->prophesize( LoggerInterface::class );
113
114
		$inner = new HashBagOStuff();
115
116
		$cache = new SimpleCacheWithBagOStuff( $inner, 'prefix', 'some secret' );
117
		$cache->setLogger( $logger->reveal() );
118
		$cache->set( 'key', 'some_string' );
119
		$key = $inner->makeKey( 'prefix', 'key' );
120
		$value = $inner->get( $key );
121
		list( $signature, $data ) = json_decode( $value );
0 ignored issues
show
Unused Code introduced by
The assignment to $signature is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
122
		$inner->set( $key, json_encode( [ 'wrong signature', $data ] ) );
123
124
		$got = $cache->get( 'key', 'some default value' );
0 ignored issues
show
Unused Code introduced by
$got is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
125
126
		$logger->alert( Argument::any(), Argument::any() )->shouldHaveBeenCalled();
127
	}
128
129
	public function testCachedValueCannotBeUnserialized_ReturnsDefaultValue() {
130
		$inner = new HashBagOStuff();
131
		$brokenData = 'O:1';
132
133
		$correctSignature = hash_hmac( 'sha256', $brokenData, 'secret' );
134
135
		$cache = new SimpleCacheWithBagOStuff( $inner, 'prefix', 'secret' );
136
		$cache->set( 'key', 'some_string' );
137
		$key = $inner->makeKey( 'prefix', 'key' );
138
		$inner->set( $key, json_encode( [ $correctSignature, $brokenData ] ) );
139
140
		$got = $cache->get( 'key', 'some default value' );
141
		$this->assertEquals( 'some default value', $got );
142
	}
143
144
	public function testSecretCanNotBeEmpty() {
145
		$inner = new HashBagOStuff();
146
147
		$this->expectException( \Exception::class );
148
		new SimpleCacheWithBagOStuff( $inner, 'prefix', '' );
149
	}
150
151
	/**
152
	 * @param $inner
153
	 * @param $key
154
	 */
155
	protected function spoilTheSignature( $inner, $key ) {
156
		$value = $inner->get( $key );
157
		list( $signature, $data ) = json_decode( $value );
0 ignored issues
show
Unused Code introduced by
The assignment to $signature is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
158
		$inner->set( $key, json_encode( [ 'wrong signature', $data ] ) );
159
	}
160
161
	public function testSetTtl() {
162
		$inner = new HashBagOStuff();
163
		$now = microtime( true );
164
		$inner->setMockTime( $now );
165
166
		$prefix = 'someprefix';
167
		$cache = new SimpleCacheWithBagOStuff( $inner, $prefix, 'some secret' );
168
169
		$result = $cache->set( 'key1', 'value', 1 );
170
		$this->assertTrue( $result, 'set() must return true if success' );
171
		$this->assertEquals( 'value', $cache->get( 'key1' ) );
172
		$now += 3;
173
		$this->assertNull( $cache->get( 'key1' ), 'Value must expire after ttl.' );
174
	}
175
176
	public function testSetMultipleTtl() {
177
		$inner = new HashBagOStuff();
178
		$now = microtime( true );
179
		$inner->setMockTime( $now );
180
181
		$prefix = 'someprefix';
182
		$cache = new SimpleCacheWithBagOStuff( $inner, $prefix, 'some secret' );
183
184
		$cache->setMultiple( [ 'key2' => 'value2', 'key3' => 'value3' ], 1 );
0 ignored issues
show
Documentation introduced by
array('key2' => 'value2', 'key3' => 'value3') is of type array<string,string,{"ke...ring","key3":"string"}>, but the function expects a object<Wikibase\Lib\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
185
		$this->assertEquals( 'value2', $cache->get( 'key2' ) );
186
		$this->assertEquals( 'value3', $cache->get( 'key3' ) );
187
188
		$now += 3;
189
		$this->assertNull( $cache->get( 'key2' ), 'Value must expire after ttl.' );
190
		$this->assertNull( $cache->get( 'key3' ), 'Value must expire after ttl.' );
191
	}
192
193
	public function testUTF8KeysAreValid() {
194
		$inner = new HashBagOStuff();
195
196
		$prefix = 'someprefix';
197
		$cache = new SimpleCacheWithBagOStuff( $inner, $prefix, 'some secret' );
198
199
		$this->assertTrue( $cache->set( '🏄', 'some value' ) );
200
		$this->assertTrue( $cache->set( '⧼Lang⧽', 'some value' ) );
201
	}
202
203
}
204