3 namespace MediaWiki\Session
;
11 * @covers MediaWiki\Session\Session
13 class SessionTest
extends MediaWikiTestCase
{
15 public function testConstructor() {
16 $backend = TestUtils
::getDummySessionBackend();
17 \TestingAccessWrapper
::newFromObject( $backend )->requests
= [ -1 => 'dummy' ];
18 \TestingAccessWrapper
::newFromObject( $backend )->id
= new SessionId( 'abc' );
20 $session = new Session( $backend, 42, new \TestLogger
);
21 $priv = \TestingAccessWrapper
::newFromObject( $session );
22 $this->assertSame( $backend, $priv->backend
);
23 $this->assertSame( 42, $priv->index
);
25 $request = new \
FauxRequest();
26 $priv2 = \TestingAccessWrapper
::newFromObject( $session->sessionWithRequest( $request ) );
27 $this->assertSame( $backend, $priv2->backend
);
28 $this->assertNotSame( $priv->index
, $priv2->index
);
29 $this->assertSame( $request, $priv2->getRequest() );
33 * @dataProvider provideMethods
34 * @param string $m Method to test
35 * @param array $args Arguments to pass to the method
36 * @param bool $index Whether the backend method gets passed the index
37 * @param bool $ret Whether the method returns a value
39 public function testMethods( $m, $args, $index, $ret ) {
40 $mock = $this->getMock( DummySessionBackend
::class,
41 [ $m, 'deregisterSession' ] );
42 $mock->expects( $this->once() )->method( 'deregisterSession' )
43 ->with( $this->identicalTo( 42 ) );
45 $tmp = $mock->expects( $this->once() )->method( $m );
48 $expectArgs[] = $this->identicalTo( 42 );
50 foreach ( $args as $arg ) {
51 $expectArgs[] = $this->identicalTo( $arg );
53 $tmp = call_user_func_array( [ $tmp, 'with' ], $expectArgs );
55 $retval = new \stdClass
;
56 $tmp->will( $this->returnValue( $retval ) );
58 $session = TestUtils
::getDummySession( $mock, 42 );
61 $this->assertSame( $retval, call_user_func_array( [ $session, $m ], $args ) );
63 $this->assertNull( call_user_func_array( [ $session, $m ], $args ) );
66 // Trigger Session destructor
70 public static function provideMethods() {
72 [ 'getId', [], false, true ],
73 [ 'getSessionId', [], false, true ],
74 [ 'resetId', [], false, true ],
75 [ 'getProvider', [], false, true ],
76 [ 'isPersistent', [], false, true ],
77 [ 'persist', [], false, false ],
78 [ 'unpersist', [], false, false ],
79 [ 'shouldRememberUser', [], false, true ],
80 [ 'setRememberUser', [ true ], false, false ],
81 [ 'getRequest', [], true, true ],
82 [ 'getUser', [], false, true ],
83 [ 'getAllowedUserRights', [], false, true ],
84 [ 'canSetUser', [], false, true ],
85 [ 'setUser', [ new \stdClass
], false, false ],
86 [ 'suggestLoginUsername', [], true, true ],
87 [ 'shouldForceHTTPS', [], false, true ],
88 [ 'setForceHTTPS', [ true ], false, false ],
89 [ 'getLoggedOutTimestamp', [], false, true ],
90 [ 'setLoggedOutTimestamp', [ 123 ], false, false ],
91 [ 'getProviderMetadata', [], false, true ],
92 [ 'save', [], false, false ],
93 [ 'delaySave', [], false, true ],
94 [ 'renew', [], false, false ],
98 public function testDataAccess() {
99 $session = TestUtils
::getDummySession();
100 $backend = \TestingAccessWrapper
::newFromObject( $session )->backend
;
102 $this->assertEquals( 1, $session->get( 'foo' ) );
103 $this->assertEquals( 'zero', $session->get( 0 ) );
104 $this->assertFalse( $backend->dirty
);
106 $this->assertEquals( null, $session->get( 'null' ) );
107 $this->assertEquals( 'default', $session->get( 'null', 'default' ) );
108 $this->assertFalse( $backend->dirty
);
110 $session->set( 'foo', 55 );
111 $this->assertEquals( 55, $backend->data
['foo'] );
112 $this->assertTrue( $backend->dirty
);
113 $backend->dirty
= false;
115 $session->set( 1, 'one' );
116 $this->assertEquals( 'one', $backend->data
[1] );
117 $this->assertTrue( $backend->dirty
);
118 $backend->dirty
= false;
120 $session->set( 1, 'one' );
121 $this->assertFalse( $backend->dirty
);
123 $this->assertTrue( $session->exists( 'foo' ) );
124 $this->assertTrue( $session->exists( 1 ) );
125 $this->assertFalse( $session->exists( 'null' ) );
126 $this->assertFalse( $session->exists( 100 ) );
127 $this->assertFalse( $backend->dirty
);
129 $session->remove( 'foo' );
130 $this->assertArrayNotHasKey( 'foo', $backend->data
);
131 $this->assertTrue( $backend->dirty
);
132 $backend->dirty
= false;
133 $session->remove( 1 );
134 $this->assertArrayNotHasKey( 1, $backend->data
);
135 $this->assertTrue( $backend->dirty
);
136 $backend->dirty
= false;
138 $session->remove( 101 );
139 $this->assertFalse( $backend->dirty
);
141 $backend->data
= [ 'a', 'b', '?' => 'c' ];
142 $this->assertSame( 3, $session->count() );
143 $this->assertSame( 3, count( $session ) );
144 $this->assertFalse( $backend->dirty
);
147 foreach ( $session as $key => $value ) {
148 $data[$key] = $value;
150 $this->assertEquals( $backend->data
, $data );
151 $this->assertFalse( $backend->dirty
);
153 $this->assertEquals( $backend->data
, iterator_to_array( $session ) );
154 $this->assertFalse( $backend->dirty
);
157 public function testArrayAccess() {
158 $logger = new \TestLogger
;
159 $session = TestUtils
::getDummySession( null, -1, $logger );
160 $backend = \TestingAccessWrapper
::newFromObject( $session )->backend
;
162 $this->assertEquals( 1, $session['foo'] );
163 $this->assertEquals( 'zero', $session[0] );
164 $this->assertFalse( $backend->dirty
);
166 $logger->setCollect( true );
167 $this->assertEquals( null, $session['null'] );
168 $logger->setCollect( false );
169 $this->assertFalse( $backend->dirty
);
171 [ LogLevel
::DEBUG
, 'Undefined index (auto-adds to session with a null value): null' ]
172 ], $logger->getBuffer() );
173 $logger->clearBuffer();
175 $session['foo'] = 55;
176 $this->assertEquals( 55, $backend->data
['foo'] );
177 $this->assertTrue( $backend->dirty
);
178 $backend->dirty
= false;
181 $this->assertEquals( 'one', $backend->data
[1] );
182 $this->assertTrue( $backend->dirty
);
183 $backend->dirty
= false;
186 $this->assertFalse( $backend->dirty
);
188 $session['bar'] = [ 'baz' => [] ];
189 $session['bar']['baz']['quux'] = 2;
190 $this->assertEquals( [ 'baz' => [ 'quux' => 2 ] ], $backend->data
['bar'] );
192 $logger->setCollect( true );
193 $session['bar2']['baz']['quux'] = 3;
194 $logger->setCollect( false );
195 $this->assertEquals( [ 'baz' => [ 'quux' => 3 ] ], $backend->data
['bar2'] );
197 [ LogLevel
::DEBUG
, 'Undefined index (auto-adds to session with a null value): bar2' ]
198 ], $logger->getBuffer() );
199 $logger->clearBuffer();
201 $backend->dirty
= false;
202 $this->assertTrue( isset( $session['foo'] ) );
203 $this->assertTrue( isset( $session[1] ) );
204 $this->assertFalse( isset( $session['null'] ) );
205 $this->assertFalse( isset( $session['missing'] ) );
206 $this->assertFalse( isset( $session[100] ) );
207 $this->assertFalse( $backend->dirty
);
209 unset( $session['foo'] );
210 $this->assertArrayNotHasKey( 'foo', $backend->data
);
211 $this->assertTrue( $backend->dirty
);
212 $backend->dirty
= false;
213 unset( $session[1] );
214 $this->assertArrayNotHasKey( 1, $backend->data
);
215 $this->assertTrue( $backend->dirty
);
216 $backend->dirty
= false;
218 unset( $session[101] );
219 $this->assertFalse( $backend->dirty
);
222 public function testClear() {
223 $session = TestUtils
::getDummySession();
224 $priv = \TestingAccessWrapper
::newFromObject( $session );
226 $backend = $this->getMock(
227 DummySessionBackend
::class, [ 'canSetUser', 'setUser', 'save' ]
229 $backend->expects( $this->once() )->method( 'canSetUser' )
230 ->will( $this->returnValue( true ) );
231 $backend->expects( $this->once() )->method( 'setUser' )
232 ->with( $this->callback( function ( $user ) {
233 return $user instanceof User
&& $user->isAnon();
235 $backend->expects( $this->once() )->method( 'save' );
236 $priv->backend
= $backend;
238 $this->assertSame( [], $backend->data
);
239 $this->assertTrue( $backend->dirty
);
241 $backend = $this->getMock(
242 DummySessionBackend
::class, [ 'canSetUser', 'setUser', 'save' ]
245 $backend->expects( $this->once() )->method( 'canSetUser' )
246 ->will( $this->returnValue( true ) );
247 $backend->expects( $this->once() )->method( 'setUser' )
248 ->with( $this->callback( function ( $user ) {
249 return $user instanceof User
&& $user->isAnon();
251 $backend->expects( $this->once() )->method( 'save' );
252 $priv->backend
= $backend;
254 $this->assertFalse( $backend->dirty
);
256 $backend = $this->getMock(
257 DummySessionBackend
::class, [ 'canSetUser', 'setUser', 'save' ]
259 $backend->expects( $this->once() )->method( 'canSetUser' )
260 ->will( $this->returnValue( false ) );
261 $backend->expects( $this->never() )->method( 'setUser' );
262 $backend->expects( $this->once() )->method( 'save' );
263 $priv->backend
= $backend;
265 $this->assertSame( [], $backend->data
);
266 $this->assertTrue( $backend->dirty
);
269 public function testTokens() {
270 $session = TestUtils
::getDummySession();
271 $priv = \TestingAccessWrapper
::newFromObject( $session );
272 $backend = $priv->backend
;
274 $token = \TestingAccessWrapper
::newFromObject( $session->getToken() );
275 $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data
);
276 $this->assertArrayHasKey( 'default', $backend->data
['wsTokenSecrets'] );
277 $secret = $backend->data
['wsTokenSecrets']['default'];
278 $this->assertSame( $secret, $token->secret
);
279 $this->assertSame( '', $token->salt
);
280 $this->assertTrue( $token->wasNew() );
282 $token = \TestingAccessWrapper
::newFromObject( $session->getToken( 'foo' ) );
283 $this->assertSame( $secret, $token->secret
);
284 $this->assertSame( 'foo', $token->salt
);
285 $this->assertFalse( $token->wasNew() );
287 $backend->data
['wsTokenSecrets']['secret'] = 'sekret';
288 $token = \TestingAccessWrapper
::newFromObject(
289 $session->getToken( [ 'bar', 'baz' ], 'secret' )
291 $this->assertSame( 'sekret', $token->secret
);
292 $this->assertSame( 'bar|baz', $token->salt
);
293 $this->assertFalse( $token->wasNew() );
295 $session->resetToken( 'secret' );
296 $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data
);
297 $this->assertArrayHasKey( 'default', $backend->data
['wsTokenSecrets'] );
298 $this->assertArrayNotHasKey( 'secret', $backend->data
['wsTokenSecrets'] );
300 $session->resetAllTokens();
301 $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data
);
305 * @dataProvider provideSecretsRoundTripping
308 public function testSecretsRoundTripping( $data ) {
309 $session = TestUtils
::getDummySession();
312 $session->setSecret( 'secret', $data );
313 $this->assertNotEquals( $data, $session->get( 'secret' ) );
314 $this->assertEquals( $data, $session->getSecret( 'secret', 'defaulted' ) );
317 public static function provideSecretsRoundTripping() {
321 [ [ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
322 [ (object)[ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
329 public function testSecrets() {
330 $logger = new \TestLogger
;
331 $session = TestUtils
::getDummySession( null, -1, $logger );
334 $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
336 // Bad encrypted data
337 $session->set( 'test', 'foobar' );
338 $logger->setCollect( true );
339 $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
340 $logger->setCollect( false );
342 [ LogLevel
::WARNING
, 'Invalid sealed-secret format' ]
343 ], $logger->getBuffer() );
344 $logger->clearBuffer();
347 $session->setSecret( 'test', 'foobar' );
348 $encrypted = $session->get( 'test' );
349 $session->set( 'test', $encrypted . 'x' );
350 $logger->setCollect( true );
351 $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
352 $logger->setCollect( false );
354 [ LogLevel
::WARNING
, 'Sealed secret has been tampered with, aborting.' ]
355 ], $logger->getBuffer() );
356 $logger->clearBuffer();
358 // Unserializable data
359 $iv = \MWCryptRand
::generate( 16, true );
360 list( $encKey, $hmacKey ) = \TestingAccessWrapper
::newFromObject( $session )->getSecretKeys();
361 $ciphertext = openssl_encrypt( 'foobar', 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA
, $iv );
362 $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
363 $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
364 $encrypted = base64_encode( $hmac ) . '.' . $sealed;
365 $session->set( 'test', $encrypted );
366 \MediaWiki\
suppressWarnings();
367 $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
368 \MediaWiki\restoreWarnings
();