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