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