Merge "Fix sessionfailure i18n message during authentication"
[lhc/web/wiklou.git] / tests / phpunit / includes / session / BotPasswordSessionProviderTest.php
1 <?php
2
3 namespace MediaWiki\Session;
4
5 use Psr\Log\LogLevel;
6 use MediaWikiTestCase;
7 use Wikimedia\TestingAccessWrapper;
8
9 /**
10 * @group Session
11 * @group Database
12 * @covers MediaWiki\Session\BotPasswordSessionProvider
13 */
14 class BotPasswordSessionProviderTest extends MediaWikiTestCase {
15
16 private $config;
17
18 private function getProvider( $name = null, $prefix = null ) {
19 global $wgSessionProviders;
20
21 $params = [
22 'priority' => 40,
23 'sessionCookieName' => $name,
24 'sessionCookieOptions' => [],
25 ];
26 if ( $prefix !== null ) {
27 $params['sessionCookieOptions']['prefix'] = $prefix;
28 }
29
30 if ( !$this->config ) {
31 $this->config = new \HashConfig( [
32 'CookiePrefix' => 'wgCookiePrefix',
33 'EnableBotPasswords' => true,
34 'BotPasswordsDatabase' => false,
35 'SessionProviders' => $wgSessionProviders + [
36 BotPasswordSessionProvider::class => [
37 'class' => BotPasswordSessionProvider::class,
38 'args' => [ $params ],
39 ]
40 ],
41 ] );
42 }
43 $manager = new SessionManager( [
44 'config' => new \MultiConfig( [ $this->config, \RequestContext::getMain()->getConfig() ] ),
45 'logger' => new \Psr\Log\NullLogger,
46 'store' => new TestBagOStuff,
47 ] );
48
49 return $manager->getProvider( BotPasswordSessionProvider::class );
50 }
51
52 protected function setUp() {
53 parent::setUp();
54
55 $this->setMwGlobals( [
56 'wgEnableBotPasswords' => true,
57 'wgBotPasswordsDatabase' => false,
58 'wgCentralIdLookupProvider' => 'local',
59 'wgGrantPermissions' => [
60 'test' => [ 'read' => true ],
61 ],
62 ] );
63 }
64
65 public function addDBDataOnce() {
66 $passwordFactory = new \PasswordFactory();
67 $passwordFactory->init( \RequestContext::getMain()->getConfig() );
68 $passwordHash = $passwordFactory->newFromPlaintext( 'foobaz' );
69
70 $sysop = static::getTestSysop()->getUser();
71 $userId = \CentralIdLookup::factory( 'local' )->centralIdFromName( $sysop->getName() );
72
73 $dbw = wfGetDB( DB_MASTER );
74 $dbw->delete(
75 'bot_passwords',
76 [ 'bp_user' => $userId, 'bp_app_id' => 'BotPasswordSessionProvider' ],
77 __METHOD__
78 );
79 $dbw->insert(
80 'bot_passwords',
81 [
82 'bp_user' => $userId,
83 'bp_app_id' => 'BotPasswordSessionProvider',
84 'bp_password' => $passwordHash->toString(),
85 'bp_token' => 'token!',
86 'bp_restrictions' => '{"IPAddresses":["127.0.0.0/8"]}',
87 'bp_grants' => '["test"]',
88 ],
89 __METHOD__
90 );
91 }
92
93 public function testConstructor() {
94 try {
95 $provider = new BotPasswordSessionProvider();
96 $this->fail( 'Expected exception not thrown' );
97 } catch ( \InvalidArgumentException $ex ) {
98 $this->assertSame(
99 'MediaWiki\\Session\\BotPasswordSessionProvider::__construct: priority must be specified',
100 $ex->getMessage()
101 );
102 }
103
104 try {
105 $provider = new BotPasswordSessionProvider( [
106 'priority' => SessionInfo::MIN_PRIORITY - 1
107 ] );
108 $this->fail( 'Expected exception not thrown' );
109 } catch ( \InvalidArgumentException $ex ) {
110 $this->assertSame(
111 'MediaWiki\\Session\\BotPasswordSessionProvider::__construct: Invalid priority',
112 $ex->getMessage()
113 );
114 }
115
116 try {
117 $provider = new BotPasswordSessionProvider( [
118 'priority' => SessionInfo::MAX_PRIORITY + 1
119 ] );
120 $this->fail( 'Expected exception not thrown' );
121 } catch ( \InvalidArgumentException $ex ) {
122 $this->assertSame(
123 'MediaWiki\\Session\\BotPasswordSessionProvider::__construct: Invalid priority',
124 $ex->getMessage()
125 );
126 }
127
128 $provider = new BotPasswordSessionProvider( [
129 'priority' => 40
130 ] );
131 $priv = TestingAccessWrapper::newFromObject( $provider );
132 $this->assertSame( 40, $priv->priority );
133 $this->assertSame( '_BPsession', $priv->sessionCookieName );
134 $this->assertSame( [], $priv->sessionCookieOptions );
135
136 $provider = new BotPasswordSessionProvider( [
137 'priority' => 40,
138 'sessionCookieName' => null,
139 ] );
140 $priv = TestingAccessWrapper::newFromObject( $provider );
141 $this->assertSame( '_BPsession', $priv->sessionCookieName );
142
143 $provider = new BotPasswordSessionProvider( [
144 'priority' => 40,
145 'sessionCookieName' => 'Foo',
146 'sessionCookieOptions' => [ 'Bar' ],
147 ] );
148 $priv = TestingAccessWrapper::newFromObject( $provider );
149 $this->assertSame( 'Foo', $priv->sessionCookieName );
150 $this->assertSame( [ 'Bar' ], $priv->sessionCookieOptions );
151 }
152
153 public function testBasics() {
154 $provider = $this->getProvider();
155
156 $this->assertTrue( $provider->persistsSessionId() );
157 $this->assertFalse( $provider->canChangeUser() );
158
159 $this->assertNull( $provider->newSessionInfo() );
160 $this->assertNull( $provider->newSessionInfo( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) );
161 }
162
163 public function testProvideSessionInfo() {
164 $provider = $this->getProvider();
165 $request = new \FauxRequest;
166 $request->setCookie( '_BPsession', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'wgCookiePrefix' );
167
168 if ( !defined( 'MW_API' ) ) {
169 $this->assertNull( $provider->provideSessionInfo( $request ) );
170 define( 'MW_API', 1 );
171 }
172
173 $info = $provider->provideSessionInfo( $request );
174 $this->assertInstanceOf( SessionInfo::class, $info );
175 $this->assertSame( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', $info->getId() );
176
177 $this->config->set( 'EnableBotPasswords', false );
178 $this->assertNull( $provider->provideSessionInfo( $request ) );
179 $this->config->set( 'EnableBotPasswords', true );
180
181 $this->assertNull( $provider->provideSessionInfo( new \FauxRequest ) );
182 }
183
184 public function testNewSessionInfoForRequest() {
185 $provider = $this->getProvider();
186 $user = static::getTestSysop()->getUser();
187 $request = $this->getMockBuilder( \FauxRequest::class )
188 ->setMethods( [ 'getIP' ] )->getMock();
189 $request->expects( $this->any() )->method( 'getIP' )
190 ->will( $this->returnValue( '127.0.0.1' ) );
191 $bp = \BotPassword::newFromUser( $user, 'BotPasswordSessionProvider' );
192
193 $session = $provider->newSessionForRequest( $user, $bp, $request );
194 $this->assertInstanceOf( Session::class, $session );
195
196 $this->assertEquals( $session->getId(), $request->getSession()->getId() );
197 $this->assertEquals( $user->getName(), $session->getUser()->getName() );
198
199 $this->assertEquals( [
200 'centralId' => $bp->getUserCentralId(),
201 'appId' => $bp->getAppId(),
202 'token' => $bp->getToken(),
203 'rights' => [ 'read' ],
204 ], $session->getProviderMetadata() );
205
206 $this->assertEquals( [ 'read' ], $session->getAllowedUserRights() );
207 }
208
209 public function testCheckSessionInfo() {
210 $logger = new \TestLogger( true );
211 $provider = $this->getProvider();
212 $provider->setLogger( $logger );
213
214 $user = static::getTestSysop()->getUser();
215 $request = $this->getMockBuilder( \FauxRequest::class )
216 ->setMethods( [ 'getIP' ] )->getMock();
217 $request->expects( $this->any() )->method( 'getIP' )
218 ->will( $this->returnValue( '127.0.0.1' ) );
219 $bp = \BotPassword::newFromUser( $user, 'BotPasswordSessionProvider' );
220
221 $data = [
222 'provider' => $provider,
223 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
224 'userInfo' => UserInfo::newFromUser( $user, true ),
225 'persisted' => false,
226 'metadata' => [
227 'centralId' => $bp->getUserCentralId(),
228 'appId' => $bp->getAppId(),
229 'token' => $bp->getToken(),
230 ],
231 ];
232 $dataMD = $data['metadata'];
233
234 foreach ( array_keys( $data['metadata'] ) as $key ) {
235 $data['metadata'] = $dataMD;
236 unset( $data['metadata'][$key] );
237 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, $data );
238 $metadata = $info->getProviderMetadata();
239
240 $this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
241 $this->assertSame( [
242 [ LogLevel::INFO, 'Session "{session}": Missing metadata: {missing}' ]
243 ], $logger->getBuffer() );
244 $logger->clearBuffer();
245 }
246
247 $data['metadata'] = $dataMD;
248 $data['metadata']['appId'] = 'Foobar';
249 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, $data );
250 $metadata = $info->getProviderMetadata();
251 $this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
252 $this->assertSame( [
253 [ LogLevel::INFO, 'Session "{session}": No BotPassword for {centralId} {appId}' ],
254 ], $logger->getBuffer() );
255 $logger->clearBuffer();
256
257 $data['metadata'] = $dataMD;
258 $data['metadata']['token'] = 'Foobar';
259 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, $data );
260 $metadata = $info->getProviderMetadata();
261 $this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
262 $this->assertSame( [
263 [ LogLevel::INFO, 'Session "{session}": BotPassword token check failed' ],
264 ], $logger->getBuffer() );
265 $logger->clearBuffer();
266
267 $request2 = $this->getMockBuilder( \FauxRequest::class )
268 ->setMethods( [ 'getIP' ] )->getMock();
269 $request2->expects( $this->any() )->method( 'getIP' )
270 ->will( $this->returnValue( '10.0.0.1' ) );
271 $data['metadata'] = $dataMD;
272 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, $data );
273 $metadata = $info->getProviderMetadata();
274 $this->assertFalse( $provider->refreshSessionInfo( $info, $request2, $metadata ) );
275 $this->assertSame( [
276 [ LogLevel::INFO, 'Session "{session}": Restrictions check failed' ],
277 ], $logger->getBuffer() );
278 $logger->clearBuffer();
279
280 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, $data );
281 $metadata = $info->getProviderMetadata();
282 $this->assertTrue( $provider->refreshSessionInfo( $info, $request, $metadata ) );
283 $this->assertSame( [], $logger->getBuffer() );
284 $this->assertEquals( $dataMD + [ 'rights' => [ 'read' ] ], $metadata );
285 }
286
287 public function testGetAllowedUserRights() {
288 $logger = new \TestLogger( true );
289 $provider = $this->getProvider();
290 $provider->setLogger( $logger );
291
292 $backend = TestUtils::getDummySessionBackend();
293 $backendPriv = TestingAccessWrapper::newFromObject( $backend );
294
295 try {
296 $provider->getAllowedUserRights( $backend );
297 $this->fail( 'Expected exception not thrown' );
298 } catch ( \InvalidArgumentException $ex ) {
299 $this->assertSame( 'Backend\'s provider isn\'t $this', $ex->getMessage() );
300 }
301
302 $backendPriv->provider = $provider;
303 $backendPriv->providerMetadata = [ 'rights' => [ 'foo', 'bar', 'baz' ] ];
304 $this->assertSame( [ 'foo', 'bar', 'baz' ], $provider->getAllowedUserRights( $backend ) );
305 $this->assertSame( [], $logger->getBuffer() );
306
307 $backendPriv->providerMetadata = [ 'foo' => 'bar' ];
308 $this->assertSame( [], $provider->getAllowedUserRights( $backend ) );
309 $this->assertSame( [
310 [
311 LogLevel::DEBUG,
312 'MediaWiki\\Session\\BotPasswordSessionProvider::getAllowedUserRights: ' .
313 'No provider metadata, returning no rights allowed'
314 ]
315 ], $logger->getBuffer() );
316 $logger->clearBuffer();
317
318 $backendPriv->providerMetadata = [ 'rights' => 'bar' ];
319 $this->assertSame( [], $provider->getAllowedUserRights( $backend ) );
320 $this->assertSame( [
321 [
322 LogLevel::DEBUG,
323 'MediaWiki\\Session\\BotPasswordSessionProvider::getAllowedUserRights: ' .
324 'No provider metadata, returning no rights allowed'
325 ]
326 ], $logger->getBuffer() );
327 $logger->clearBuffer();
328
329 $backendPriv->providerMetadata = null;
330 $this->assertSame( [], $provider->getAllowedUserRights( $backend ) );
331 $this->assertSame( [
332 [
333 LogLevel::DEBUG,
334 'MediaWiki\\Session\\BotPasswordSessionProvider::getAllowedUserRights: ' .
335 'No provider metadata, returning no rights allowed'
336 ]
337 ], $logger->getBuffer() );
338 $logger->clearBuffer();
339 }
340 }