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