Add AuthManager
[lhc/web/wiklou.git] / tests / phpunit / includes / auth / TemporaryPasswordPrimaryAuthenticationProviderTest.php
1 <?php
2
3 namespace MediaWiki\Auth;
4
5 /**
6 * @group AuthManager
7 * @group Database
8 * @covers MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider
9 */
10 class TemporaryPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
11
12 private $manager = null;
13 private $config = null;
14 private $validity = null;
15
16 protected function setUp() {
17 global $wgDisableAuthManager;
18
19 parent::setUp();
20 if ( $wgDisableAuthManager ) {
21 $this->markTestSkipped( '$wgDisableAuthManager is set' );
22 }
23 }
24
25 /**
26 * Get an instance of the provider
27 *
28 * $provider->checkPasswordValidity is mocked to return $this->validity,
29 * because we don't need to test that here.
30 *
31 * @param array $params
32 * @return TemporaryPasswordPrimaryAuthenticationProvider
33 */
34 protected function getProvider( $params = [] ) {
35 if ( !$this->config ) {
36 $this->config = new \HashConfig( [
37 'EmailEnabled' => true,
38 ] );
39 }
40 $config = new \MultiConfig( [
41 $this->config,
42 \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
43 ] );
44
45 if ( !$this->manager ) {
46 $this->manager = new AuthManager( new \FauxRequest(), $config );
47 }
48 $this->validity = \Status::newGood();
49
50 $mockedMethods[] = 'checkPasswordValidity';
51 $provider = $this->getMock(
52 TemporaryPasswordPrimaryAuthenticationProvider::class,
53 $mockedMethods,
54 [ $params ]
55 );
56 $provider->expects( $this->any() )->method( 'checkPasswordValidity' )
57 ->will( $this->returnCallback( function () {
58 return $this->validity;
59 } ) );
60 $provider->setConfig( $config );
61 $provider->setLogger( new \Psr\Log\NullLogger() );
62 $provider->setManager( $this->manager );
63
64 return $provider;
65 }
66
67 protected function hookMailer( $func = null ) {
68 \Hooks::clear( 'AlternateUserMailer' );
69 if ( $func ) {
70 \Hooks::register( 'AlternateUserMailer', $func );
71 // Safety
72 \Hooks::register( 'AlternateUserMailer', function () {
73 return false;
74 } );
75 } else {
76 \Hooks::register( 'AlternateUserMailer', function () {
77 $this->fail( 'AlternateUserMailer hook called unexpectedly' );
78 return false;
79 } );
80 }
81
82 return new \ScopedCallback( function () {
83 \Hooks::clear( 'AlternateUserMailer' );
84 \Hooks::register( 'AlternateUserMailer', function () {
85 return false;
86 } );
87 } );
88 }
89
90 public function testBasics() {
91 $provider = new TemporaryPasswordPrimaryAuthenticationProvider();
92
93 $this->assertSame(
94 PrimaryAuthenticationProvider::TYPE_CREATE,
95 $provider->accountCreationType()
96 );
97
98 $this->assertTrue( $provider->testUserExists( 'UTSysop' ) );
99 $this->assertTrue( $provider->testUserExists( 'uTSysop' ) );
100 $this->assertFalse( $provider->testUserExists( 'DoesNotExist' ) );
101 $this->assertFalse( $provider->testUserExists( '<invalid>' ) );
102
103 $req = new PasswordAuthenticationRequest;
104 $req->action = AuthManager::ACTION_CHANGE;
105 $req->username = '<invalid>';
106 $provider->providerChangeAuthenticationData( $req );
107 }
108
109 public function testConfig() {
110 $config = new \HashConfig( [
111 'EnableEmail' => false,
112 'NewPasswordExpiry' => 100,
113 'PasswordReminderResendTime' => 101,
114 ] );
115
116 $p = \TestingAccessWrapper::newFromObject( new TemporaryPasswordPrimaryAuthenticationProvider() );
117 $p->setConfig( $config );
118 $this->assertSame( false, $p->emailEnabled );
119 $this->assertSame( 100, $p->newPasswordExpiry );
120 $this->assertSame( 101, $p->passwordReminderResendTime );
121
122 $p = \TestingAccessWrapper::newFromObject( new TemporaryPasswordPrimaryAuthenticationProvider( [
123 'emailEnabled' => true,
124 'newPasswordExpiry' => 42,
125 'passwordReminderResendTime' => 43,
126 ] ) );
127 $p->setConfig( $config );
128 $this->assertSame( true, $p->emailEnabled );
129 $this->assertSame( 42, $p->newPasswordExpiry );
130 $this->assertSame( 43, $p->passwordReminderResendTime );
131 }
132
133 public function testTestUserCanAuthenticate() {
134 $dbw = wfGetDB( DB_MASTER );
135
136 $passwordFactory = new \PasswordFactory();
137 $passwordFactory->init( \RequestContext::getMain()->getConfig() );
138 // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
139 $passwordFactory->setDefaultType( 'A' );
140 $pwhash = $passwordFactory->newFromPlaintext( 'password' )->toString();
141
142 $provider = $this->getProvider();
143 $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
144
145 $this->assertFalse( $provider->testUserCanAuthenticate( '<invalid>' ) );
146 $this->assertFalse( $provider->testUserCanAuthenticate( 'DoesNotExist' ) );
147
148 $dbw->update(
149 'user',
150 [
151 'user_newpassword' => \PasswordFactory::newInvalidPassword()->toString(),
152 'user_newpass_time' => null,
153 ],
154 [ 'user_name' => 'UTSysop' ]
155 );
156 $this->assertFalse( $provider->testUserCanAuthenticate( 'UTSysop' ) );
157
158 $dbw->update(
159 'user',
160 [
161 'user_newpassword' => $pwhash,
162 'user_newpass_time' => null,
163 ],
164 [ 'user_name' => 'UTSysop' ]
165 );
166 $this->assertTrue( $provider->testUserCanAuthenticate( 'UTSysop' ) );
167 $this->assertTrue( $provider->testUserCanAuthenticate( 'uTSysop' ) );
168
169 $dbw->update(
170 'user',
171 [
172 'user_newpassword' => $pwhash,
173 'user_newpass_time' => $dbw->timestamp( time() - 10 ),
174 ],
175 [ 'user_name' => 'UTSysop' ]
176 );
177 $providerPriv->newPasswordExpiry = 100;
178 $this->assertTrue( $provider->testUserCanAuthenticate( 'UTSysop' ) );
179 $providerPriv->newPasswordExpiry = 1;
180 $this->assertFalse( $provider->testUserCanAuthenticate( 'UTSysop' ) );
181
182 $dbw->update(
183 'user',
184 [
185 'user_newpassword' => \PasswordFactory::newInvalidPassword()->toString(),
186 'user_newpass_time' => null,
187 ],
188 [ 'user_name' => 'UTSysop' ]
189 );
190 }
191
192 /**
193 * @dataProvider provideGetAuthenticationRequests
194 * @param string $action
195 * @param array $options
196 * @param array $expected
197 */
198 public function testGetAuthenticationRequests( $action, $options, $expected ) {
199 $actual = $this->getProvider()->getAuthenticationRequests( $action, $options );
200 foreach ( $actual as $req ) {
201 if ( $req instanceof TemporaryPasswordAuthenticationRequest && $req->password !== null ) {
202 $req->password = 'random';
203 }
204 }
205 $this->assertEquals( $expected, $actual );
206 }
207
208 public static function provideGetAuthenticationRequests() {
209 $anon = [ 'username' => null ];
210 $loggedIn = [ 'username' => 'UTSysop' ];
211
212 return [
213 [ AuthManager::ACTION_LOGIN, $anon, [
214 new PasswordAuthenticationRequest
215 ] ],
216 [ AuthManager::ACTION_LOGIN, $loggedIn, [
217 new PasswordAuthenticationRequest
218 ] ],
219 [ AuthManager::ACTION_CREATE, $anon, [] ],
220 [ AuthManager::ACTION_CREATE, $loggedIn, [
221 new TemporaryPasswordAuthenticationRequest( 'random' )
222 ] ],
223 [ AuthManager::ACTION_LINK, $anon, [] ],
224 [ AuthManager::ACTION_LINK, $loggedIn, [] ],
225 [ AuthManager::ACTION_CHANGE, $anon, [
226 new TemporaryPasswordAuthenticationRequest( 'random' )
227 ] ],
228 [ AuthManager::ACTION_CHANGE, $loggedIn, [
229 new TemporaryPasswordAuthenticationRequest( 'random' )
230 ] ],
231 [ AuthManager::ACTION_REMOVE, $anon, [
232 new TemporaryPasswordAuthenticationRequest
233 ] ],
234 [ AuthManager::ACTION_REMOVE, $loggedIn, [
235 new TemporaryPasswordAuthenticationRequest
236 ] ],
237 ];
238 }
239
240 public function testAuthentication() {
241 $password = 'TemporaryPassword';
242 $hash = ':A:' . md5( $password );
243 $dbw = wfGetDB( DB_MASTER );
244 $dbw->update(
245 'user',
246 [ 'user_newpassword' => $hash, 'user_newpass_time' => $dbw->timestamp( time() - 10 ) ],
247 [ 'user_name' => 'UTSysop' ]
248 );
249
250 $req = new PasswordAuthenticationRequest();
251 $req->action = AuthManager::ACTION_LOGIN;
252 $reqs = [ PasswordAuthenticationRequest::class => $req ];
253
254 $provider = $this->getProvider();
255 $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
256
257 $providerPriv->newPasswordExpiry = 100;
258
259 // General failures
260 $this->assertEquals(
261 AuthenticationResponse::newAbstain(),
262 $provider->beginPrimaryAuthentication( [] )
263 );
264
265 $req->username = 'foo';
266 $req->password = null;
267 $this->assertEquals(
268 AuthenticationResponse::newAbstain(),
269 $provider->beginPrimaryAuthentication( $reqs )
270 );
271
272 $req->username = null;
273 $req->password = 'bar';
274 $this->assertEquals(
275 AuthenticationResponse::newAbstain(),
276 $provider->beginPrimaryAuthentication( $reqs )
277 );
278
279 $req->username = '<invalid>';
280 $req->password = 'WhoCares';
281 $ret = $provider->beginPrimaryAuthentication( $reqs );
282 $this->assertEquals(
283 AuthenticationResponse::newAbstain(),
284 $provider->beginPrimaryAuthentication( $reqs )
285 );
286
287 $req->username = 'DoesNotExist';
288 $req->password = 'DoesNotExist';
289 $ret = $provider->beginPrimaryAuthentication( $reqs );
290 $this->assertEquals(
291 AuthenticationResponse::newAbstain(),
292 $provider->beginPrimaryAuthentication( $reqs )
293 );
294
295 // Validation failure
296 $req->username = 'UTSysop';
297 $req->password = $password;
298 $this->validity = \Status::newFatal( 'arbitrary-failure' );
299 $ret = $provider->beginPrimaryAuthentication( $reqs );
300 $this->assertEquals(
301 AuthenticationResponse::FAIL,
302 $ret->status
303 );
304 $this->assertEquals(
305 'arbitrary-failure',
306 $ret->message->getKey()
307 );
308
309 // Successful auth
310 $this->manager->removeAuthenticationSessionData( null );
311 $this->validity = \Status::newGood();
312 $this->assertEquals(
313 AuthenticationResponse::newPass( 'UTSysop' ),
314 $provider->beginPrimaryAuthentication( $reqs )
315 );
316 $this->assertNotNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
317
318 $this->manager->removeAuthenticationSessionData( null );
319 $this->validity = \Status::newGood();
320 $req->username = 'uTSysop';
321 $this->assertEquals(
322 AuthenticationResponse::newPass( 'UTSysop' ),
323 $provider->beginPrimaryAuthentication( $reqs )
324 );
325 $this->assertNotNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
326 $req->username = 'UTSysop';
327
328 // Expired password
329 $providerPriv->newPasswordExpiry = 1;
330 $ret = $provider->beginPrimaryAuthentication( $reqs );
331 $this->assertEquals(
332 AuthenticationResponse::FAIL,
333 $ret->status
334 );
335 $this->assertEquals(
336 'wrongpassword',
337 $ret->message->getKey()
338 );
339
340 // Bad password
341 $providerPriv->newPasswordExpiry = 100;
342 $this->validity = \Status::newGood();
343 $req->password = 'Wrong';
344 $ret = $provider->beginPrimaryAuthentication( $reqs );
345 $this->assertEquals(
346 AuthenticationResponse::FAIL,
347 $ret->status
348 );
349 $this->assertEquals(
350 'wrongpassword',
351 $ret->message->getKey()
352 );
353
354 }
355
356 /**
357 * @dataProvider provideProviderAllowsAuthenticationDataChange
358 * @param string $type
359 * @param string $user
360 * @param \Status $validity Result of the password validity check
361 * @param \StatusValue $expect1 Expected result with $checkData = false
362 * @param \StatusValue $expect2 Expected result with $checkData = true
363 */
364 public function testProviderAllowsAuthenticationDataChange( $type, $user, \Status $validity,
365 \StatusValue $expect1, \StatusValue $expect2
366 ) {
367 if ( $type === PasswordAuthenticationRequest::class ||
368 $type === TemporaryPasswordAuthenticationRequest::class
369 ) {
370 $req = new $type();
371 } else {
372 $req = $this->getMock( $type );
373 }
374 $req->action = AuthManager::ACTION_CHANGE;
375 $req->username = $user;
376 $req->password = 'NewPassword';
377
378 $provider = $this->getProvider();
379 $this->validity = $validity;
380 $this->assertEquals( $expect1, $provider->providerAllowsAuthenticationDataChange( $req, false ) );
381 $this->assertEquals( $expect2, $provider->providerAllowsAuthenticationDataChange( $req, true ) );
382 }
383
384 public static function provideProviderAllowsAuthenticationDataChange() {
385 $err = \StatusValue::newGood();
386 $err->error( 'arbitrary-warning' );
387
388 return [
389 [ AuthenticationRequest::class, 'UTSysop', \Status::newGood(),
390 \StatusValue::newGood( 'ignored' ), \StatusValue::newGood( 'ignored' ) ],
391 [ PasswordAuthenticationRequest::class, 'UTSysop', \Status::newGood(),
392 \StatusValue::newGood( 'ignored' ), \StatusValue::newGood( 'ignored' ) ],
393 [ TemporaryPasswordAuthenticationRequest::class, 'UTSysop', \Status::newGood(),
394 \StatusValue::newGood(), \StatusValue::newGood() ],
395 [ TemporaryPasswordAuthenticationRequest::class, 'uTSysop', \Status::newGood(),
396 \StatusValue::newGood(), \StatusValue::newGood() ],
397 [ TemporaryPasswordAuthenticationRequest::class, 'UTSysop', \Status::wrap( $err ),
398 \StatusValue::newGood(), $err ],
399 [ TemporaryPasswordAuthenticationRequest::class, 'UTSysop',
400 \Status::newFatal( 'arbitrary-error' ), \StatusValue::newGood(),
401 \StatusValue::newFatal( 'arbitrary-error' ) ],
402 [ TemporaryPasswordAuthenticationRequest::class, 'DoesNotExist', \Status::newGood(),
403 \StatusValue::newGood(), \StatusValue::newGood( 'ignored' ) ],
404 [ TemporaryPasswordAuthenticationRequest::class, '<invalid>', \Status::newGood(),
405 \StatusValue::newGood(), \StatusValue::newGood( 'ignored' ) ],
406 ];
407 }
408
409 /**
410 * @dataProvider provideProviderChangeAuthenticationData
411 * @param string $user
412 * @param string $type
413 * @param bool $changed
414 */
415 public function testProviderChangeAuthenticationData( $user, $type, $changed ) {
416 $cuser = ucfirst( $user );
417 $oldpass = 'OldTempPassword';
418 $newpass = 'NewTempPassword';
419
420 $hash = ':A:' . md5( $oldpass );
421 $dbw = wfGetDB( DB_MASTER );
422 $dbw->update(
423 'user',
424 [ 'user_newpassword' => $hash, 'user_newpass_time' => $dbw->timestamp( time() + 10 ) ],
425 [ 'user_name' => 'UTSysop' ]
426 );
427
428 $dbw = wfGetDB( DB_MASTER );
429 $oldHash = $dbw->selectField( 'user', 'user_newpassword', [ 'user_name' => $cuser ] );
430 $cb = new \ScopedCallback( function () use ( $dbw, $cuser, $oldHash ) {
431 $dbw->update( 'user', [ 'user_newpassword' => $oldHash ], [ 'user_name' => $cuser ] );
432 } );
433
434 $provider = $this->getProvider();
435
436 // Sanity check
437 $loginReq = new PasswordAuthenticationRequest();
438 $loginReq->action = AuthManager::ACTION_CHANGE;
439 $loginReq->username = $user;
440 $loginReq->password = $oldpass;
441 $loginReqs = [ PasswordAuthenticationRequest::class => $loginReq ];
442 $this->assertEquals(
443 AuthenticationResponse::newPass( $cuser ),
444 $provider->beginPrimaryAuthentication( $loginReqs ),
445 'Sanity check'
446 );
447
448 if ( $type === PasswordAuthenticationRequest::class ||
449 $type === TemporaryPasswordAuthenticationRequest::class
450 ) {
451 $changeReq = new $type();
452 } else {
453 $changeReq = $this->getMock( $type );
454 }
455 $changeReq->action = AuthManager::ACTION_CHANGE;
456 $changeReq->username = $user;
457 $changeReq->password = $newpass;
458 $resetMailer = $this->hookMailer();
459 $provider->providerChangeAuthenticationData( $changeReq );
460 \ScopedCallback::consume( $resetMailer );
461
462 $loginReq->password = $oldpass;
463 $ret = $provider->beginPrimaryAuthentication( $loginReqs );
464 $this->assertEquals(
465 AuthenticationResponse::FAIL,
466 $ret->status,
467 'old password should fail'
468 );
469 $this->assertEquals(
470 'wrongpassword',
471 $ret->message->getKey(),
472 'old password should fail'
473 );
474
475 $loginReq->password = $newpass;
476 $ret = $provider->beginPrimaryAuthentication( $loginReqs );
477 if ( $changed ) {
478 $this->assertEquals(
479 AuthenticationResponse::newPass( $cuser ),
480 $ret,
481 'new password should pass'
482 );
483 $this->assertNotNull(
484 $dbw->selectField( 'user', 'user_newpass_time', [ 'user_name' => $cuser ] )
485 );
486 } else {
487 $this->assertEquals(
488 AuthenticationResponse::FAIL,
489 $ret->status,
490 'new password should fail'
491 );
492 $this->assertEquals(
493 'wrongpassword',
494 $ret->message->getKey(),
495 'new password should fail'
496 );
497 $this->assertNull(
498 $dbw->selectField( 'user', 'user_newpass_time', [ 'user_name' => $cuser ] )
499 );
500 }
501 }
502
503 public static function provideProviderChangeAuthenticationData() {
504 return [
505 [ 'UTSysop', AuthenticationRequest::class, false ],
506 [ 'UTSysop', PasswordAuthenticationRequest::class, false ],
507 [ 'UTSysop', TemporaryPasswordAuthenticationRequest::class, true ],
508 ];
509 }
510
511 public function testProviderChangeAuthenticationDataEmail() {
512 $dbw = wfGetDB( DB_MASTER );
513 $dbw->update(
514 'user',
515 [ 'user_newpass_time' => $dbw->timestamp( time() - 5 * 3600 ) ],
516 [ 'user_name' => 'UTSysop' ]
517 );
518
519 $user = \User::newFromName( 'UTSysop' );
520 $reset = new \ScopedCallback( function ( $email ) use ( $user ) {
521 $user->setEmail( $email );
522 $user->saveSettings();
523 }, [ $user->getEmail() ] );
524
525 $user->setEmail( 'test@localhost.localdomain' );
526 $user->saveSettings();
527
528 $req = TemporaryPasswordAuthenticationRequest::newRandom();
529 $req->username = $user->getName();
530 $req->mailpassword = true;
531
532 $provider = $this->getProvider( [ 'emailEnabled' => false ] );
533 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
534 $this->assertEquals( \StatusValue::newFatal( 'passwordreset-emaildisabled' ), $status );
535 $req->hasBackchannel = true;
536 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
537 $this->assertFalse( $status->hasMessage( 'passwordreset-emaildisabled' ) );
538 $req->hasBackchannel = false;
539
540 $provider = $this->getProvider( [ 'passwordReminderResendTime' => 10 ] );
541 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
542 $this->assertEquals( \StatusValue::newFatal( 'throttled-mailpassword', 10 ), $status );
543
544 $provider = $this->getProvider( [ 'passwordReminderResendTime' => 3 ] );
545 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
546 $this->assertFalse( $status->hasMessage( 'throttled-mailpassword' ) );
547
548 $dbw->update(
549 'user',
550 [ 'user_newpass_time' => $dbw->timestamp( time() + 5 * 3600 ) ],
551 [ 'user_name' => 'UTSysop' ]
552 );
553 $provider = $this->getProvider( [ 'passwordReminderResendTime' => 0 ] );
554 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
555 $this->assertFalse( $status->hasMessage( 'throttled-mailpassword' ) );
556
557 $req->caller = null;
558 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
559 $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nocaller' ), $status );
560
561 $req->caller = '127.0.0.256';
562 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
563 $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nosuchcaller', '127.0.0.256' ),
564 $status );
565
566 $req->caller = '<Invalid>';
567 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
568 $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nosuchcaller', '<Invalid>' ),
569 $status );
570
571 $req->caller = '127.0.0.1';
572 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
573 $this->assertEquals( \StatusValue::newGood(), $status );
574
575 $req->caller = 'UTSysop';
576 $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
577 $this->assertEquals( \StatusValue::newGood(), $status );
578
579 $mailed = false;
580 $resetMailer = $this->hookMailer( function ( $headers, $to, $from, $subject, $body )
581 use ( &$mailed, $req )
582 {
583 $mailed = true;
584 $this->assertSame( 'test@localhost.localdomain', $to[0]->address );
585 $this->assertContains( $req->password, $body );
586 return false;
587 } );
588 $provider->providerChangeAuthenticationData( $req );
589 \ScopedCallback::consume( $resetMailer );
590 $this->assertTrue( $mailed );
591
592 $priv = \TestingAccessWrapper::newFromObject( $provider );
593 $req->username = '<invalid>';
594 $status = $priv->sendPasswordResetEmail( $req );
595 $this->assertEquals( \Status::newFatal( 'noname' ), $status );
596 }
597
598 public function testTestForAccountCreation() {
599 $user = \User::newFromName( 'foo' );
600 $req = new TemporaryPasswordAuthenticationRequest();
601 $req->username = 'Foo';
602 $req->password = 'Bar';
603 $reqs = [ TemporaryPasswordAuthenticationRequest::class => $req ];
604
605 $provider = $this->getProvider();
606 $this->assertEquals(
607 \StatusValue::newGood(),
608 $provider->testForAccountCreation( $user, $user, [] ),
609 'No password request'
610 );
611
612 $this->assertEquals(
613 \StatusValue::newGood(),
614 $provider->testForAccountCreation( $user, $user, $reqs ),
615 'Password request, validated'
616 );
617
618 $this->validity->error( 'arbitrary warning' );
619 $expect = \StatusValue::newGood();
620 $expect->error( 'arbitrary warning' );
621 $this->assertEquals(
622 $expect,
623 $provider->testForAccountCreation( $user, $user, $reqs ),
624 'Password request, not validated'
625 );
626 }
627
628 public function testAccountCreation() {
629 $resetMailer = $this->hookMailer();
630
631 $user = \User::newFromName( 'Foo' );
632
633 $req = new TemporaryPasswordAuthenticationRequest();
634 $reqs = [ TemporaryPasswordAuthenticationRequest::class => $req ];
635
636 $authreq = new PasswordAuthenticationRequest();
637 $authreq->action = AuthManager::ACTION_CREATE;
638 $authreqs = [ PasswordAuthenticationRequest::class => $authreq ];
639
640 $provider = $this->getProvider();
641
642 $this->assertEquals(
643 AuthenticationResponse::newAbstain(),
644 $provider->beginPrimaryAccountCreation( $user, $user, [] )
645 );
646
647 $req->username = 'foo';
648 $req->password = null;
649 $this->assertEquals(
650 AuthenticationResponse::newAbstain(),
651 $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
652 );
653
654 $req->username = null;
655 $req->password = 'bar';
656 $this->assertEquals(
657 AuthenticationResponse::newAbstain(),
658 $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
659 );
660
661 $req->username = 'foo';
662 $req->password = 'bar';
663
664 $expect = AuthenticationResponse::newPass( 'Foo' );
665 $expect->createRequest = clone( $req );
666 $expect->createRequest->username = 'Foo';
667 $this->assertEquals( $expect, $provider->beginPrimaryAccountCreation( $user, $user, $reqs ) );
668 $this->assertNull( $this->manager->getAuthenticationSessionData( 'no-email' ) );
669
670 // We have to cheat a bit to avoid having to add a new user to
671 // the database to test the actual setting of the password works right
672 $user = \User::newFromName( 'UTSysop' );
673 $req->username = $authreq->username = $user->getName();
674 $req->password = $authreq->password = 'NewPassword';
675 $expect = AuthenticationResponse::newPass( 'UTSysop' );
676 $expect->createRequest = $req;
677
678 $res2 = $provider->beginPrimaryAccountCreation( $user, $user, $reqs );
679 $this->assertEquals( $expect, $res2, 'Sanity check' );
680
681 $ret = $provider->beginPrimaryAuthentication( $authreqs );
682 $this->assertEquals( AuthenticationResponse::FAIL, $ret->status, 'sanity check' );
683
684 $this->assertSame( null, $provider->finishAccountCreation( $user, $user, $res2 ) );
685
686 $ret = $provider->beginPrimaryAuthentication( $authreqs );
687 $this->assertEquals( AuthenticationResponse::PASS, $ret->status, 'new password is set' );
688 }
689
690 public function testAccountCreationEmail() {
691 $creator = \User::newFromName( 'Foo' );
692 $user = \User::newFromName( 'UTSysop' );
693 $reset = new \ScopedCallback( function ( $email ) use ( $user ) {
694 $user->setEmail( $email );
695 $user->saveSettings();
696 }, [ $user->getEmail() ] );
697
698 $user->setEmail( null );
699
700 $req = TemporaryPasswordAuthenticationRequest::newRandom();
701 $req->username = $user->getName();
702 $req->mailpassword = true;
703
704 $provider = $this->getProvider( [ 'emailEnabled' => false ] );
705 $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
706 $this->assertEquals( \StatusValue::newFatal( 'emaildisabled' ), $status );
707 $req->hasBackchannel = true;
708 $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
709 $this->assertFalse( $status->hasMessage( 'emaildisabled' ) );
710 $req->hasBackchannel = false;
711
712 $provider = $this->getProvider( [ 'emailEnabled' => true ] );
713 $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
714 $this->assertEquals( \StatusValue::newFatal( 'noemailcreate' ), $status );
715 $req->hasBackchannel = true;
716 $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
717 $this->assertFalse( $status->hasMessage( 'noemailcreate' ) );
718 $req->hasBackchannel = false;
719
720 $user->setEmail( 'test@localhost.localdomain' );
721 $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
722 $this->assertEquals( \StatusValue::newGood(), $status );
723
724 $mailed = false;
725 $resetMailer = $this->hookMailer( function ( $headers, $to, $from, $subject, $body )
726 use ( &$mailed, $req )
727 {
728 $mailed = true;
729 $this->assertSame( 'test@localhost.localdomain', $to[0]->address );
730 $this->assertContains( $req->password, $body );
731 return false;
732 } );
733
734 $expect = AuthenticationResponse::newPass( 'UTSysop' );
735 $expect->createRequest = clone( $req );
736 $expect->createRequest->username = 'UTSysop';
737 $res = $provider->beginPrimaryAccountCreation( $user, $creator, [ $req ] );
738 $this->assertEquals( $expect, $res );
739 $this->assertTrue( $this->manager->getAuthenticationSessionData( 'no-email' ) );
740 $this->assertFalse( $mailed );
741
742 $this->assertSame( 'byemail', $provider->finishAccountCreation( $user, $creator, $res ) );
743 $this->assertTrue( $mailed );
744
745 \ScopedCallback::consume( $resetMailer );
746 $this->assertTrue( $mailed );
747 }
748
749 }