Move CSRF token handling into MediaWiki\Session\Session
[lhc/web/wiklou.git] / tests / phpunit / includes / session / SessionTest.php
1 <?php
2
3 namespace MediaWiki\Session;
4
5 use MediaWikiTestCase;
6 use User;
7
8 /**
9 * @group Session
10 * @covers MediaWiki\Session\Session
11 */
12 class SessionTest extends MediaWikiTestCase {
13
14 public function testConstructor() {
15 $backend = TestUtils::getDummySessionBackend();
16 \TestingAccessWrapper::newFromObject( $backend )->requests = array( -1 => 'dummy' );
17 \TestingAccessWrapper::newFromObject( $backend )->id = new SessionId( 'abc' );
18
19 $session = new Session( $backend, 42 );
20 $priv = \TestingAccessWrapper::newFromObject( $session );
21 $this->assertSame( $backend, $priv->backend );
22 $this->assertSame( 42, $priv->index );
23
24 $request = new \FauxRequest();
25 $priv2 = \TestingAccessWrapper::newFromObject( $session->sessionWithRequest( $request ) );
26 $this->assertSame( $backend, $priv2->backend );
27 $this->assertNotSame( $priv->index, $priv2->index );
28 $this->assertSame( $request, $priv2->getRequest() );
29 }
30
31 /**
32 * @dataProvider provideMethods
33 * @param string $m Method to test
34 * @param array $args Arguments to pass to the method
35 * @param bool $index Whether the backend method gets passed the index
36 * @param bool $ret Whether the method returns a value
37 */
38 public function testMethods( $m, $args, $index, $ret ) {
39 $mock = $this->getMock( 'MediaWiki\\Session\\DummySessionBackend',
40 array( $m, 'deregisterSession' ) );
41 $mock->expects( $this->once() )->method( 'deregisterSession' )
42 ->with( $this->identicalTo( 42 ) );
43
44 $tmp = $mock->expects( $this->once() )->method( $m );
45 $expectArgs = array();
46 if ( $index ) {
47 $expectArgs[] = $this->identicalTo( 42 );
48 }
49 foreach ( $args as $arg ) {
50 $expectArgs[] = $this->identicalTo( $arg );
51 }
52 $tmp = call_user_func_array( array( $tmp, 'with' ), $expectArgs );
53
54 $retval = new \stdClass;
55 $tmp->will( $this->returnValue( $retval ) );
56
57 $session = TestUtils::getDummySession( $mock, 42 );
58
59 if ( $ret ) {
60 $this->assertSame( $retval, call_user_func_array( array( $session, $m ), $args ) );
61 } else {
62 $this->assertNull( call_user_func_array( array( $session, $m ), $args ) );
63 }
64
65 // Trigger Session destructor
66 $session = null;
67 }
68
69 public static function provideMethods() {
70 return array(
71 array( 'getId', array(), false, true ),
72 array( 'getSessionId', array(), false, true ),
73 array( 'resetId', array(), false, true ),
74 array( 'getProvider', array(), false, true ),
75 array( 'isPersistent', array(), false, true ),
76 array( 'persist', array(), false, false ),
77 array( 'shouldRememberUser', array(), false, true ),
78 array( 'setRememberUser', array( true ), false, false ),
79 array( 'getRequest', array(), true, true ),
80 array( 'getUser', array(), false, true ),
81 array( 'getAllowedUserRights', array(), false, true ),
82 array( 'canSetUser', array(), false, true ),
83 array( 'setUser', array( new \stdClass ), false, false ),
84 array( 'suggestLoginUsername', array(), true, true ),
85 array( 'shouldForceHTTPS', array(), false, true ),
86 array( 'setForceHTTPS', array( true ), false, false ),
87 array( 'getLoggedOutTimestamp', array(), false, true ),
88 array( 'setLoggedOutTimestamp', array( 123 ), false, false ),
89 array( 'getProviderMetadata', array(), false, true ),
90 array( 'save', array(), false, false ),
91 array( 'delaySave', array(), false, true ),
92 array( 'renew', array(), false, false ),
93 );
94 }
95
96 public function testDataAccess() {
97 $session = TestUtils::getDummySession();
98 $backend = \TestingAccessWrapper::newFromObject( $session )->backend;
99
100 $this->assertEquals( 1, $session->get( 'foo' ) );
101 $this->assertEquals( 'zero', $session->get( 0 ) );
102 $this->assertFalse( $backend->dirty );
103
104 $this->assertEquals( null, $session->get( 'null' ) );
105 $this->assertEquals( 'default', $session->get( 'null', 'default' ) );
106 $this->assertFalse( $backend->dirty );
107
108 $session->set( 'foo', 55 );
109 $this->assertEquals( 55, $backend->data['foo'] );
110 $this->assertTrue( $backend->dirty );
111 $backend->dirty = false;
112
113 $session->set( 1, 'one' );
114 $this->assertEquals( 'one', $backend->data[1] );
115 $this->assertTrue( $backend->dirty );
116 $backend->dirty = false;
117
118 $session->set( 1, 'one' );
119 $this->assertFalse( $backend->dirty );
120
121 $this->assertTrue( $session->exists( 'foo' ) );
122 $this->assertTrue( $session->exists( 1 ) );
123 $this->assertFalse( $session->exists( 'null' ) );
124 $this->assertFalse( $session->exists( 100 ) );
125 $this->assertFalse( $backend->dirty );
126
127 $session->remove( 'foo' );
128 $this->assertArrayNotHasKey( 'foo', $backend->data );
129 $this->assertTrue( $backend->dirty );
130 $backend->dirty = false;
131 $session->remove( 1 );
132 $this->assertArrayNotHasKey( 1, $backend->data );
133 $this->assertTrue( $backend->dirty );
134 $backend->dirty = false;
135
136 $session->remove( 101 );
137 $this->assertFalse( $backend->dirty );
138
139 $backend->data = array( 'a', 'b', '?' => 'c' );
140 $this->assertSame( 3, $session->count() );
141 $this->assertSame( 3, count( $session ) );
142 $this->assertFalse( $backend->dirty );
143
144 $data = array();
145 foreach ( $session as $key => $value ) {
146 $data[$key] = $value;
147 }
148 $this->assertEquals( $backend->data, $data );
149 $this->assertFalse( $backend->dirty );
150
151 $this->assertEquals( $backend->data, iterator_to_array( $session ) );
152 $this->assertFalse( $backend->dirty );
153 }
154
155 public function testClear() {
156 $session = TestUtils::getDummySession();
157 $priv = \TestingAccessWrapper::newFromObject( $session );
158
159 $backend = $this->getMock(
160 'MediaWiki\\Session\\DummySessionBackend', array( 'canSetUser', 'setUser', 'save' )
161 );
162 $backend->expects( $this->once() )->method( 'canSetUser' )
163 ->will( $this->returnValue( true ) );
164 $backend->expects( $this->once() )->method( 'setUser' )
165 ->with( $this->callback( function ( $user ) {
166 return $user instanceof User && $user->isAnon();
167 } ) );
168 $backend->expects( $this->once() )->method( 'save' );
169 $priv->backend = $backend;
170 $session->clear();
171 $this->assertSame( array(), $backend->data );
172 $this->assertTrue( $backend->dirty );
173
174 $backend = $this->getMock(
175 'MediaWiki\\Session\\DummySessionBackend', array( 'canSetUser', 'setUser', 'save' )
176 );
177 $backend->data = array();
178 $backend->expects( $this->once() )->method( 'canSetUser' )
179 ->will( $this->returnValue( true ) );
180 $backend->expects( $this->once() )->method( 'setUser' )
181 ->with( $this->callback( function ( $user ) {
182 return $user instanceof User && $user->isAnon();
183 } ) );
184 $backend->expects( $this->once() )->method( 'save' );
185 $priv->backend = $backend;
186 $session->clear();
187 $this->assertFalse( $backend->dirty );
188
189 $backend = $this->getMock(
190 'MediaWiki\\Session\\DummySessionBackend', array( 'canSetUser', 'setUser', 'save' )
191 );
192 $backend->expects( $this->once() )->method( 'canSetUser' )
193 ->will( $this->returnValue( false ) );
194 $backend->expects( $this->never() )->method( 'setUser' );
195 $backend->expects( $this->once() )->method( 'save' );
196 $priv->backend = $backend;
197 $session->clear();
198 $this->assertSame( array(), $backend->data );
199 $this->assertTrue( $backend->dirty );
200 }
201
202 public function testTokens() {
203 $rc = new \ReflectionClass( 'MediaWiki\\Session\\Session' );
204 if ( !method_exists( $rc, 'newInstanceWithoutConstructor' ) ) {
205 $this->markTestSkipped(
206 'ReflectionClass::newInstanceWithoutConstructor isn\'t available'
207 );
208 }
209
210 // Instead of actually constructing the Session, we use reflection to
211 // bypass the constructor and plug a mock SessionBackend into the
212 // private fields to avoid having to actually create a SessionBackend.
213 $backend = new DummySessionBackend;
214 $session = $rc->newInstanceWithoutConstructor();
215 $priv = \TestingAccessWrapper::newFromObject( $session );
216 $priv->backend = $backend;
217 $priv->index = 42;
218
219 $token = \TestingAccessWrapper::newFromObject( $session->getToken() );
220 $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
221 $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
222 $secret = $backend->data['wsTokenSecrets']['default'];
223 $this->assertSame( $secret, $token->secret );
224 $this->assertSame( '', $token->salt );
225 $this->assertTrue( $token->wasNew() );
226
227 $token = \TestingAccessWrapper::newFromObject( $session->getToken( 'foo' ) );
228 $this->assertSame( $secret, $token->secret );
229 $this->assertSame( 'foo', $token->salt );
230 $this->assertFalse( $token->wasNew() );
231
232 $backend->data['wsTokenSecrets']['secret'] = 'sekret';
233 $token = \TestingAccessWrapper::newFromObject(
234 $session->getToken( array( 'bar', 'baz' ), 'secret' )
235 );
236 $this->assertSame( 'sekret', $token->secret );
237 $this->assertSame( 'bar|baz', $token->salt );
238 $this->assertFalse( $token->wasNew() );
239
240 $session->resetToken( 'secret' );
241 $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
242 $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
243 $this->assertArrayNotHasKey( 'secret', $backend->data['wsTokenSecrets'] );
244
245 $session->resetAllTokens();
246 $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data );
247
248 }
249 }