]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - tests/phpunit/includes/session/SessionTest.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / tests / phpunit / includes / session / SessionTest.php
1 <?php
2
3 namespace MediaWiki\Session;
4
5 use Psr\Log\LogLevel;
6 use MediaWikiTestCase;
7 use User;
8 use Wikimedia\TestingAccessWrapper;
9
10 /**
11  * @group Session
12  * @covers MediaWiki\Session\Session
13  */
14 class SessionTest extends MediaWikiTestCase {
15
16         public function testConstructor() {
17                 $backend = TestUtils::getDummySessionBackend();
18                 TestingAccessWrapper::newFromObject( $backend )->requests = [ -1 => 'dummy' ];
19                 TestingAccessWrapper::newFromObject( $backend )->id = new SessionId( 'abc' );
20
21                 $session = new Session( $backend, 42, new \TestLogger );
22                 $priv = TestingAccessWrapper::newFromObject( $session );
23                 $this->assertSame( $backend, $priv->backend );
24                 $this->assertSame( 42, $priv->index );
25
26                 $request = new \FauxRequest();
27                 $priv2 = TestingAccessWrapper::newFromObject( $session->sessionWithRequest( $request ) );
28                 $this->assertSame( $backend, $priv2->backend );
29                 $this->assertNotSame( $priv->index, $priv2->index );
30                 $this->assertSame( $request, $priv2->getRequest() );
31         }
32
33         /**
34          * @dataProvider provideMethods
35          * @param string $m Method to test
36          * @param array $args Arguments to pass to the method
37          * @param bool $index Whether the backend method gets passed the index
38          * @param bool $ret Whether the method returns a value
39          */
40         public function testMethods( $m, $args, $index, $ret ) {
41                 $mock = $this->getMockBuilder( DummySessionBackend::class )
42                         ->setMethods( [ $m, 'deregisterSession' ] )
43                         ->getMock();
44                 $mock->expects( $this->once() )->method( 'deregisterSession' )
45                         ->with( $this->identicalTo( 42 ) );
46
47                 $tmp = $mock->expects( $this->once() )->method( $m );
48                 $expectArgs = [];
49                 if ( $index ) {
50                         $expectArgs[] = $this->identicalTo( 42 );
51                 }
52                 foreach ( $args as $arg ) {
53                         $expectArgs[] = $this->identicalTo( $arg );
54                 }
55                 $tmp = call_user_func_array( [ $tmp, 'with' ], $expectArgs );
56
57                 $retval = new \stdClass;
58                 $tmp->will( $this->returnValue( $retval ) );
59
60                 $session = TestUtils::getDummySession( $mock, 42 );
61
62                 if ( $ret ) {
63                         $this->assertSame( $retval, call_user_func_array( [ $session, $m ], $args ) );
64                 } else {
65                         $this->assertNull( call_user_func_array( [ $session, $m ], $args ) );
66                 }
67
68                 // Trigger Session destructor
69                 $session = null;
70         }
71
72         public static function provideMethods() {
73                 return [
74                         [ 'getId', [], false, true ],
75                         [ 'getSessionId', [], false, true ],
76                         [ 'resetId', [], false, true ],
77                         [ 'getProvider', [], false, true ],
78                         [ 'isPersistent', [], false, true ],
79                         [ 'persist', [], false, false ],
80                         [ 'unpersist', [], false, false ],
81                         [ 'shouldRememberUser', [], false, true ],
82                         [ 'setRememberUser', [ true ], false, false ],
83                         [ 'getRequest', [], true, true ],
84                         [ 'getUser', [], false, true ],
85                         [ 'getAllowedUserRights', [], false, true ],
86                         [ 'canSetUser', [], false, true ],
87                         [ 'setUser', [ new \stdClass ], false, false ],
88                         [ 'suggestLoginUsername', [], true, true ],
89                         [ 'shouldForceHTTPS', [], false, true ],
90                         [ 'setForceHTTPS', [ true ], false, false ],
91                         [ 'getLoggedOutTimestamp', [], false, true ],
92                         [ 'setLoggedOutTimestamp', [ 123 ], false, false ],
93                         [ 'getProviderMetadata', [], false, true ],
94                         [ 'save', [], false, false ],
95                         [ 'delaySave', [], false, true ],
96                         [ 'renew', [], false, false ],
97                 ];
98         }
99
100         public function testDataAccess() {
101                 $session = TestUtils::getDummySession();
102                 $backend = TestingAccessWrapper::newFromObject( $session )->backend;
103
104                 $this->assertEquals( 1, $session->get( 'foo' ) );
105                 $this->assertEquals( 'zero', $session->get( 0 ) );
106                 $this->assertFalse( $backend->dirty );
107
108                 $this->assertEquals( null, $session->get( 'null' ) );
109                 $this->assertEquals( 'default', $session->get( 'null', 'default' ) );
110                 $this->assertFalse( $backend->dirty );
111
112                 $session->set( 'foo', 55 );
113                 $this->assertEquals( 55, $backend->data['foo'] );
114                 $this->assertTrue( $backend->dirty );
115                 $backend->dirty = false;
116
117                 $session->set( 1, 'one' );
118                 $this->assertEquals( 'one', $backend->data[1] );
119                 $this->assertTrue( $backend->dirty );
120                 $backend->dirty = false;
121
122                 $session->set( 1, 'one' );
123                 $this->assertFalse( $backend->dirty );
124
125                 $this->assertTrue( $session->exists( 'foo' ) );
126                 $this->assertTrue( $session->exists( 1 ) );
127                 $this->assertFalse( $session->exists( 'null' ) );
128                 $this->assertFalse( $session->exists( 100 ) );
129                 $this->assertFalse( $backend->dirty );
130
131                 $session->remove( 'foo' );
132                 $this->assertArrayNotHasKey( 'foo', $backend->data );
133                 $this->assertTrue( $backend->dirty );
134                 $backend->dirty = false;
135                 $session->remove( 1 );
136                 $this->assertArrayNotHasKey( 1, $backend->data );
137                 $this->assertTrue( $backend->dirty );
138                 $backend->dirty = false;
139
140                 $session->remove( 101 );
141                 $this->assertFalse( $backend->dirty );
142
143                 $backend->data = [ 'a', 'b', '?' => 'c' ];
144                 $this->assertSame( 3, $session->count() );
145                 $this->assertSame( 3, count( $session ) );
146                 $this->assertFalse( $backend->dirty );
147
148                 $data = [];
149                 foreach ( $session as $key => $value ) {
150                         $data[$key] = $value;
151                 }
152                 $this->assertEquals( $backend->data, $data );
153                 $this->assertFalse( $backend->dirty );
154
155                 $this->assertEquals( $backend->data, iterator_to_array( $session ) );
156                 $this->assertFalse( $backend->dirty );
157         }
158
159         public function testArrayAccess() {
160                 $logger = new \TestLogger;
161                 $session = TestUtils::getDummySession( null, -1, $logger );
162                 $backend = TestingAccessWrapper::newFromObject( $session )->backend;
163
164                 $this->assertEquals( 1, $session['foo'] );
165                 $this->assertEquals( 'zero', $session[0] );
166                 $this->assertFalse( $backend->dirty );
167
168                 $logger->setCollect( true );
169                 $this->assertEquals( null, $session['null'] );
170                 $logger->setCollect( false );
171                 $this->assertFalse( $backend->dirty );
172                 $this->assertSame( [
173                         [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): null' ]
174                 ], $logger->getBuffer() );
175                 $logger->clearBuffer();
176
177                 $session['foo'] = 55;
178                 $this->assertEquals( 55, $backend->data['foo'] );
179                 $this->assertTrue( $backend->dirty );
180                 $backend->dirty = false;
181
182                 $session[1] = 'one';
183                 $this->assertEquals( 'one', $backend->data[1] );
184                 $this->assertTrue( $backend->dirty );
185                 $backend->dirty = false;
186
187                 $session[1] = 'one';
188                 $this->assertFalse( $backend->dirty );
189
190                 $session['bar'] = [ 'baz' => [] ];
191                 $session['bar']['baz']['quux'] = 2;
192                 $this->assertEquals( [ 'baz' => [ 'quux' => 2 ] ], $backend->data['bar'] );
193
194                 $logger->setCollect( true );
195                 $session['bar2']['baz']['quux'] = 3;
196                 $logger->setCollect( false );
197                 $this->assertEquals( [ 'baz' => [ 'quux' => 3 ] ], $backend->data['bar2'] );
198                 $this->assertSame( [
199                         [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): bar2' ]
200                 ], $logger->getBuffer() );
201                 $logger->clearBuffer();
202
203                 $backend->dirty = false;
204                 $this->assertTrue( isset( $session['foo'] ) );
205                 $this->assertTrue( isset( $session[1] ) );
206                 $this->assertFalse( isset( $session['null'] ) );
207                 $this->assertFalse( isset( $session['missing'] ) );
208                 $this->assertFalse( isset( $session[100] ) );
209                 $this->assertFalse( $backend->dirty );
210
211                 unset( $session['foo'] );
212                 $this->assertArrayNotHasKey( 'foo', $backend->data );
213                 $this->assertTrue( $backend->dirty );
214                 $backend->dirty = false;
215                 unset( $session[1] );
216                 $this->assertArrayNotHasKey( 1, $backend->data );
217                 $this->assertTrue( $backend->dirty );
218                 $backend->dirty = false;
219
220                 unset( $session[101] );
221                 $this->assertFalse( $backend->dirty );
222         }
223
224         public function testClear() {
225                 $session = TestUtils::getDummySession();
226                 $priv = TestingAccessWrapper::newFromObject( $session );
227
228                 $backend = $this->getMockBuilder( DummySessionBackend::class )
229                         ->setMethods( [ 'canSetUser', 'setUser', 'save' ] )
230                         ->getMock();
231                 $backend->expects( $this->once() )->method( 'canSetUser' )
232                         ->will( $this->returnValue( true ) );
233                 $backend->expects( $this->once() )->method( 'setUser' )
234                         ->with( $this->callback( function ( $user ) {
235                                 return $user instanceof User && $user->isAnon();
236                         } ) );
237                 $backend->expects( $this->once() )->method( 'save' );
238                 $priv->backend = $backend;
239                 $session->clear();
240                 $this->assertSame( [], $backend->data );
241                 $this->assertTrue( $backend->dirty );
242
243                 $backend = $this->getMockBuilder( DummySessionBackend::class )
244                         ->setMethods( [ 'canSetUser', 'setUser', 'save' ] )
245                         ->getMock();
246                 $backend->data = [];
247                 $backend->expects( $this->once() )->method( 'canSetUser' )
248                         ->will( $this->returnValue( true ) );
249                 $backend->expects( $this->once() )->method( 'setUser' )
250                         ->with( $this->callback( function ( $user ) {
251                                 return $user instanceof User && $user->isAnon();
252                         } ) );
253                 $backend->expects( $this->once() )->method( 'save' );
254                 $priv->backend = $backend;
255                 $session->clear();
256                 $this->assertFalse( $backend->dirty );
257
258                 $backend = $this->getMockBuilder( DummySessionBackend::class )
259                         ->setMethods( [ 'canSetUser', 'setUser', 'save' ] )
260                         ->getMock();
261                 $backend->expects( $this->once() )->method( 'canSetUser' )
262                         ->will( $this->returnValue( false ) );
263                 $backend->expects( $this->never() )->method( 'setUser' );
264                 $backend->expects( $this->once() )->method( 'save' );
265                 $priv->backend = $backend;
266                 $session->clear();
267                 $this->assertSame( [], $backend->data );
268                 $this->assertTrue( $backend->dirty );
269         }
270
271         public function testTokens() {
272                 $session = TestUtils::getDummySession();
273                 $priv = TestingAccessWrapper::newFromObject( $session );
274                 $backend = $priv->backend;
275
276                 $token = TestingAccessWrapper::newFromObject( $session->getToken() );
277                 $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
278                 $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
279                 $secret = $backend->data['wsTokenSecrets']['default'];
280                 $this->assertSame( $secret, $token->secret );
281                 $this->assertSame( '', $token->salt );
282                 $this->assertTrue( $token->wasNew() );
283
284                 $token = TestingAccessWrapper::newFromObject( $session->getToken( 'foo' ) );
285                 $this->assertSame( $secret, $token->secret );
286                 $this->assertSame( 'foo', $token->salt );
287                 $this->assertFalse( $token->wasNew() );
288
289                 $backend->data['wsTokenSecrets']['secret'] = 'sekret';
290                 $token = TestingAccessWrapper::newFromObject(
291                         $session->getToken( [ 'bar', 'baz' ], 'secret' )
292                 );
293                 $this->assertSame( 'sekret', $token->secret );
294                 $this->assertSame( 'bar|baz', $token->salt );
295                 $this->assertFalse( $token->wasNew() );
296
297                 $session->resetToken( 'secret' );
298                 $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
299                 $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
300                 $this->assertArrayNotHasKey( 'secret', $backend->data['wsTokenSecrets'] );
301
302                 $session->resetAllTokens();
303                 $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data );
304         }
305
306         /**
307          * @dataProvider provideSecretsRoundTripping
308          * @param mixed $data
309          */
310         public function testSecretsRoundTripping( $data ) {
311                 $session = TestUtils::getDummySession();
312
313                 // Simple round-trip
314                 $session->setSecret( 'secret', $data );
315                 $this->assertNotEquals( $data, $session->get( 'secret' ) );
316                 $this->assertEquals( $data, $session->getSecret( 'secret', 'defaulted' ) );
317         }
318
319         public static function provideSecretsRoundTripping() {
320                 return [
321                         [ 'Foobar' ],
322                         [ 42 ],
323                         [ [ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
324                         [ (object)[ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
325                         [ true ],
326                         [ false ],
327                         [ null ],
328                 ];
329         }
330
331         public function testSecrets() {
332                 $logger = new \TestLogger;
333                 $session = TestUtils::getDummySession( null, -1, $logger );
334
335                 // Simple defaulting
336                 $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
337
338                 // Bad encrypted data
339                 $session->set( 'test', 'foobar' );
340                 $logger->setCollect( true );
341                 $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
342                 $logger->setCollect( false );
343                 $this->assertSame( [
344                         [ LogLevel::WARNING, 'Invalid sealed-secret format' ]
345                 ], $logger->getBuffer() );
346                 $logger->clearBuffer();
347
348                 // Tampered data
349                 $session->setSecret( 'test', 'foobar' );
350                 $encrypted = $session->get( 'test' );
351                 $session->set( 'test', $encrypted . 'x' );
352                 $logger->setCollect( true );
353                 $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
354                 $logger->setCollect( false );
355                 $this->assertSame( [
356                         [ LogLevel::WARNING, 'Sealed secret has been tampered with, aborting.' ]
357                 ], $logger->getBuffer() );
358                 $logger->clearBuffer();
359
360                 // Unserializable data
361                 $iv = \MWCryptRand::generate( 16, true );
362                 list( $encKey, $hmacKey ) = TestingAccessWrapper::newFromObject( $session )->getSecretKeys();
363                 $ciphertext = openssl_encrypt( 'foobar', 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, $iv );
364                 $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
365                 $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
366                 $encrypted = base64_encode( $hmac ) . '.' . $sealed;
367                 $session->set( 'test', $encrypted );
368                 \MediaWiki\suppressWarnings();
369                 $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
370                 \MediaWiki\restoreWarnings();
371         }
372
373 }