Merge "Fix sessionfailure i18n message during authentication"
[lhc/web/wiklou.git] / tests / phpunit / includes / session / ImmutableSessionProviderWithCookieTest.php
1 <?php
2
3 namespace MediaWiki\Session;
4
5 use MediaWikiTestCase;
6 use User;
7 use Wikimedia\TestingAccessWrapper;
8
9 /**
10 * @group Session
11 * @group Database
12 * @covers MediaWiki\Session\ImmutableSessionProviderWithCookie
13 */
14 class ImmutableSessionProviderWithCookieTest extends MediaWikiTestCase {
15
16 private function getProvider( $name, $prefix = null ) {
17 $config = new \HashConfig();
18 $config->set( 'CookiePrefix', 'wgCookiePrefix' );
19
20 $params = [
21 'sessionCookieName' => $name,
22 'sessionCookieOptions' => [],
23 ];
24 if ( $prefix !== null ) {
25 $params['sessionCookieOptions']['prefix'] = $prefix;
26 }
27
28 $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class )
29 ->setConstructorArgs( [ $params ] )
30 ->getMockForAbstractClass();
31 $provider->setLogger( new \TestLogger() );
32 $provider->setConfig( $config );
33 $provider->setManager( new SessionManager() );
34
35 return $provider;
36 }
37
38 public function testConstructor() {
39 $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class )
40 ->getMockForAbstractClass();
41 $priv = TestingAccessWrapper::newFromObject( $provider );
42 $this->assertNull( $priv->sessionCookieName );
43 $this->assertSame( [], $priv->sessionCookieOptions );
44
45 $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class )
46 ->setConstructorArgs( [ [
47 'sessionCookieName' => 'Foo',
48 'sessionCookieOptions' => [ 'Bar' ],
49 ] ] )
50 ->getMockForAbstractClass();
51 $priv = TestingAccessWrapper::newFromObject( $provider );
52 $this->assertSame( 'Foo', $priv->sessionCookieName );
53 $this->assertSame( [ 'Bar' ], $priv->sessionCookieOptions );
54
55 try {
56 $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class )
57 ->setConstructorArgs( [ [
58 'sessionCookieName' => false,
59 ] ] )
60 ->getMockForAbstractClass();
61 $this->fail( 'Expected exception not thrown' );
62 } catch ( \InvalidArgumentException $ex ) {
63 $this->assertSame(
64 'sessionCookieName must be a string',
65 $ex->getMessage()
66 );
67 }
68
69 try {
70 $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class )
71 ->setConstructorArgs( [ [
72 'sessionCookieOptions' => 'x',
73 ] ] )
74 ->getMockForAbstractClass();
75 $this->fail( 'Expected exception not thrown' );
76 } catch ( \InvalidArgumentException $ex ) {
77 $this->assertSame(
78 'sessionCookieOptions must be an array',
79 $ex->getMessage()
80 );
81 }
82 }
83
84 public function testBasics() {
85 $provider = $this->getProvider( null );
86 $this->assertFalse( $provider->persistsSessionID() );
87 $this->assertFalse( $provider->canChangeUser() );
88
89 $provider = $this->getProvider( 'Foo' );
90 $this->assertTrue( $provider->persistsSessionID() );
91 $this->assertFalse( $provider->canChangeUser() );
92
93 $msg = $provider->whyNoSession();
94 $this->assertInstanceOf( \Message::class, $msg );
95 $this->assertSame( 'sessionprovider-nocookies', $msg->getKey() );
96 }
97
98 public function testGetVaryCookies() {
99 $provider = $this->getProvider( null );
100 $this->assertSame( [], $provider->getVaryCookies() );
101
102 $provider = $this->getProvider( 'Foo' );
103 $this->assertSame( [ 'wgCookiePrefixFoo' ], $provider->getVaryCookies() );
104
105 $provider = $this->getProvider( 'Foo', 'Bar' );
106 $this->assertSame( [ 'BarFoo' ], $provider->getVaryCookies() );
107
108 $provider = $this->getProvider( 'Foo', '' );
109 $this->assertSame( [ 'Foo' ], $provider->getVaryCookies() );
110 }
111
112 public function testGetSessionIdFromCookie() {
113 $this->setMwGlobals( 'wgCookiePrefix', 'wgCookiePrefix' );
114 $request = new \FauxRequest();
115 $request->setCookies( [
116 '' => 'empty---------------------------',
117 'Foo' => 'foo-----------------------------',
118 'wgCookiePrefixFoo' => 'wgfoo---------------------------',
119 'BarFoo' => 'foobar--------------------------',
120 'bad' => 'bad',
121 ], '' );
122
123 $provider = TestingAccessWrapper::newFromObject( $this->getProvider( null ) );
124 try {
125 $provider->getSessionIdFromCookie( $request );
126 $this->fail( 'Expected exception not thrown' );
127 } catch ( \BadMethodCallException $ex ) {
128 $this->assertSame(
129 'MediaWiki\\Session\\ImmutableSessionProviderWithCookie::getSessionIdFromCookie ' .
130 'may not be called when $this->sessionCookieName === null',
131 $ex->getMessage()
132 );
133 }
134
135 $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'Foo' ) );
136 $this->assertSame(
137 'wgfoo---------------------------',
138 $provider->getSessionIdFromCookie( $request )
139 );
140
141 $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'Foo', 'Bar' ) );
142 $this->assertSame(
143 'foobar--------------------------',
144 $provider->getSessionIdFromCookie( $request )
145 );
146
147 $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'Foo', '' ) );
148 $this->assertSame(
149 'foo-----------------------------',
150 $provider->getSessionIdFromCookie( $request )
151 );
152
153 $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'bad', '' ) );
154 $this->assertSame( null, $provider->getSessionIdFromCookie( $request ) );
155
156 $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'none', '' ) );
157 $this->assertSame( null, $provider->getSessionIdFromCookie( $request ) );
158 }
159
160 protected function getSentRequest() {
161 $sentResponse = $this->getMockBuilder( \FauxResponse::class )
162 ->setMethods( [ 'headersSent', 'setCookie', 'header' ] )
163 ->getMock();
164 $sentResponse->expects( $this->any() )->method( 'headersSent' )
165 ->will( $this->returnValue( true ) );
166 $sentResponse->expects( $this->never() )->method( 'setCookie' );
167 $sentResponse->expects( $this->never() )->method( 'header' );
168
169 $sentRequest = $this->getMockBuilder( \FauxRequest::class )
170 ->setMethods( [ 'response' ] )->getMock();
171 $sentRequest->expects( $this->any() )->method( 'response' )
172 ->will( $this->returnValue( $sentResponse ) );
173 return $sentRequest;
174 }
175
176 /**
177 * @dataProvider providePersistSession
178 * @param bool $secure
179 * @param bool $remember
180 */
181 public function testPersistSession( $secure, $remember ) {
182 $this->setMwGlobals( [
183 'wgCookieExpiration' => 100,
184 'wgSecureLogin' => false,
185 ] );
186
187 $provider = $this->getProvider( 'session' );
188 $provider->setLogger( new \Psr\Log\NullLogger() );
189 $priv = TestingAccessWrapper::newFromObject( $provider );
190 $priv->sessionCookieOptions = [
191 'prefix' => 'x',
192 'path' => 'CookiePath',
193 'domain' => 'CookieDomain',
194 'secure' => false,
195 'httpOnly' => true,
196 ];
197
198 $sessionId = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
199 $user = User::newFromName( 'UTSysop' );
200 $this->assertFalse( $user->requiresHTTPS(), 'sanity check' );
201
202 $backend = new SessionBackend(
203 new SessionId( $sessionId ),
204 new SessionInfo( SessionInfo::MIN_PRIORITY, [
205 'provider' => $provider,
206 'id' => $sessionId,
207 'persisted' => true,
208 'userInfo' => UserInfo::newFromUser( $user, true ),
209 'idIsSafe' => true,
210 ] ),
211 new TestBagOStuff(),
212 new \Psr\Log\NullLogger(),
213 10
214 );
215 TestingAccessWrapper::newFromObject( $backend )->usePhpSessionHandling = false;
216 $backend->setRememberUser( $remember );
217 $backend->setForceHTTPS( $secure );
218
219 // No cookie
220 $priv->sessionCookieName = null;
221 $request = new \FauxRequest();
222 $provider->persistSession( $backend, $request );
223 $this->assertSame( [], $request->response()->getCookies() );
224
225 // Cookie
226 $priv->sessionCookieName = 'session';
227 $request = new \FauxRequest();
228 $time = time();
229 $provider->persistSession( $backend, $request );
230
231 $cookie = $request->response()->getCookieData( 'xsession' );
232 $this->assertInternalType( 'array', $cookie );
233 if ( isset( $cookie['expire'] ) && $cookie['expire'] > 0 ) {
234 // Round expiry so we don't randomly fail if the seconds ticked during the test.
235 $cookie['expire'] = round( $cookie['expire'] - $time, -2 );
236 }
237 $this->assertEquals( [
238 'value' => $sessionId,
239 'expire' => null,
240 'path' => 'CookiePath',
241 'domain' => 'CookieDomain',
242 'secure' => $secure,
243 'httpOnly' => true,
244 'raw' => false,
245 ], $cookie );
246
247 $cookie = $request->response()->getCookieData( 'forceHTTPS' );
248 if ( $secure ) {
249 $this->assertInternalType( 'array', $cookie );
250 if ( isset( $cookie['expire'] ) && $cookie['expire'] > 0 ) {
251 // Round expiry so we don't randomly fail if the seconds ticked during the test.
252 $cookie['expire'] = round( $cookie['expire'] - $time, -2 );
253 }
254 $this->assertEquals( [
255 'value' => 'true',
256 'expire' => null,
257 'path' => 'CookiePath',
258 'domain' => 'CookieDomain',
259 'secure' => false,
260 'httpOnly' => true,
261 'raw' => false,
262 ], $cookie );
263 } else {
264 $this->assertNull( $cookie );
265 }
266
267 // Headers sent
268 $request = $this->getSentRequest();
269 $provider->persistSession( $backend, $request );
270 $this->assertSame( [], $request->response()->getCookies() );
271 }
272
273 public static function providePersistSession() {
274 return [
275 [ false, false ],
276 [ false, true ],
277 [ true, false ],
278 [ true, true ],
279 ];
280 }
281
282 public function testUnpersistSession() {
283 $provider = $this->getProvider( 'session', '' );
284 $provider->setLogger( new \Psr\Log\NullLogger() );
285 $priv = TestingAccessWrapper::newFromObject( $provider );
286
287 // No cookie
288 $priv->sessionCookieName = null;
289 $request = new \FauxRequest();
290 $provider->unpersistSession( $request );
291 $this->assertSame( null, $request->response()->getCookie( 'session', '' ) );
292
293 // Cookie
294 $priv->sessionCookieName = 'session';
295 $request = new \FauxRequest();
296 $provider->unpersistSession( $request );
297 $this->assertSame( '', $request->response()->getCookie( 'session', '' ) );
298
299 // Headers sent
300 $request = $this->getSentRequest();
301 $provider->unpersistSession( $request );
302 $this->assertSame( null, $request->response()->getCookie( 'session', '' ) );
303 }
304
305 }