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