1<?php
2
3use MediaWiki\Auth\AuthManager;
4use MediaWiki\Auth\TemporaryPasswordAuthenticationRequest;
5use MediaWiki\Block\CompositeBlock;
6use MediaWiki\Block\DatabaseBlock;
7use MediaWiki\Block\SystemBlock;
8use MediaWiki\Config\ServiceOptions;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\Permissions\PermissionManager;
11use Psr\Log\NullLogger;
12use Wikimedia\Rdbms\ILoadBalancer;
13
14/**
15 * @covers PasswordReset
16 * @group Database
17 */
18class PasswordResetTest extends MediaWikiIntegrationTestCase {
19	private const VALID_IP = '1.2.3.4';
20	private const VALID_EMAIL = 'foo@bar.baz';
21
22	/**
23	 * @dataProvider provideIsAllowed
24	 */
25	public function testIsAllowed( $passwordResetRoutes, $enableEmail,
26		$allowsAuthenticationDataChange, $canEditPrivate, $block, $globalBlock, $isAllowed
27	) {
28		$config = $this->makeConfig( $enableEmail, $passwordResetRoutes, false );
29
30		$authManager = $this->getMockBuilder( AuthManager::class )->disableOriginalConstructor()
31			->getMock();
32		$authManager->expects( $this->any() )->method( 'allowsAuthenticationDataChange' )
33			->willReturn( $allowsAuthenticationDataChange ? Status::newGood() : Status::newFatal( 'foo' ) );
34
35		$user = $this->getMockBuilder( User::class )->getMock();
36		$user->expects( $this->any() )->method( 'getName' )->willReturn( 'Foo' );
37		$user->expects( $this->any() )->method( 'getBlock' )->willReturn( $block );
38		$user->expects( $this->any() )->method( 'getGlobalBlock' )->willReturn( $globalBlock );
39
40		$permissionManager = $this->getMockBuilder( PermissionManager::class )
41			->disableOriginalConstructor()
42			->getMock();
43		$permissionManager->method( 'userHasRight' )
44			->with( $user, 'editmyprivateinfo' )
45			->willReturn( $canEditPrivate );
46
47		$loadBalancer = $this->createMock( ILoadBalancer::class );
48
49		$hookContainer = $this->createHookContainer();
50
51		$passwordReset = new PasswordReset(
52			$config,
53			$authManager,
54			$permissionManager,
55			$loadBalancer,
56			new NullLogger(),
57			$hookContainer
58		);
59
60		$this->assertSame( $isAllowed, $passwordReset->isAllowed( $user )->isGood() );
61	}
62
63	public function provideIsAllowed() {
64		return [
65			'no routes' => [
66				'passwordResetRoutes' => [],
67				'enableEmail' => true,
68				'allowsAuthenticationDataChange' => true,
69				'canEditPrivate' => true,
70				'block' => null,
71				'globalBlock' => null,
72				'isAllowed' => false,
73			],
74			'email disabled' => [
75				'passwordResetRoutes' => [ 'username' => true ],
76				'enableEmail' => false,
77				'allowsAuthenticationDataChange' => true,
78				'canEditPrivate' => true,
79				'block' => null,
80				'globalBlock' => null,
81				'isAllowed' => false,
82			],
83			'auth data change disabled' => [
84				'passwordResetRoutes' => [ 'username' => true ],
85				'enableEmail' => true,
86				'allowsAuthenticationDataChange' => false,
87				'canEditPrivate' => true,
88				'block' => null,
89				'globalBlock' => null,
90				'isAllowed' => false,
91			],
92			'cannot edit private data' => [
93				'passwordResetRoutes' => [ 'username' => true ],
94				'enableEmail' => true,
95				'allowsAuthenticationDataChange' => true,
96				'canEditPrivate' => false,
97				'block' => null,
98				'globalBlock' => null,
99				'isAllowed' => false,
100			],
101			'blocked with account creation disabled' => [
102				'passwordResetRoutes' => [ 'username' => true ],
103				'enableEmail' => true,
104				'allowsAuthenticationDataChange' => true,
105				'canEditPrivate' => true,
106				'block' => new DatabaseBlock( [ 'createAccount' => true ] ),
107				'globalBlock' => null,
108				'isAllowed' => false,
109			],
110			'blocked w/o account creation disabled' => [
111				'passwordResetRoutes' => [ 'username' => true ],
112				'enableEmail' => true,
113				'allowsAuthenticationDataChange' => true,
114				'canEditPrivate' => true,
115				'block' => new DatabaseBlock( [] ),
116				'globalBlock' => null,
117				'isAllowed' => true,
118			],
119			'using blocked proxy' => [
120				'passwordResetRoutes' => [ 'username' => true ],
121				'enableEmail' => true,
122				'allowsAuthenticationDataChange' => true,
123				'canEditPrivate' => true,
124				'block' => new SystemBlock(
125					[ 'systemBlock' => 'proxy' ]
126				),
127				'globalBlock' => null,
128				'isAllowed' => false,
129			],
130			'globally blocked with account creation not disabled' => [
131				'passwordResetRoutes' => [ 'username' => true ],
132				'enableEmail' => true,
133				'allowsAuthenticationDataChange' => true,
134				'canEditPrivate' => true,
135				'block' => null,
136				'globalBlock' => new SystemBlock(
137					[ 'systemBlock' => 'global-block' ]
138				),
139				'isAllowed' => true,
140			],
141			'blocked via wgSoftBlockRanges' => [
142				'passwordResetRoutes' => [ 'username' => true ],
143				'enableEmail' => true,
144				'allowsAuthenticationDataChange' => true,
145				'canEditPrivate' => true,
146				'block' => new SystemBlock(
147					[ 'systemBlock' => 'wgSoftBlockRanges', 'anonOnly' => true ]
148				),
149				'globalBlock' => null,
150				'isAllowed' => true,
151			],
152			'blocked with an unknown system block type' => [
153				'passwordResetRoutes' => [ 'username' => true ],
154				'enableEmail' => true,
155				'allowsAuthenticationDataChange' => true,
156				'canEditPrivate' => true,
157				'block' => new SystemBlock( [ 'systemBlock' => 'unknown' ] ),
158				'globalBlock' => null,
159				'isAllowed' => false,
160			],
161			'blocked with multiple blocks, all allowing password reset' => [
162				'passwordResetRoutes' => [ 'username' => true ],
163				'enableEmail' => true,
164				'allowsAuthenticationDataChange' => true,
165				'canEditPrivate' => true,
166				'block' => new CompositeBlock( [
167					'originalBlocks' => [
168						new SystemBlock( [ 'systemBlock' => 'wgSoftBlockRanges', 'anonOnly' => true ] ),
169						new Block( [] ),
170					]
171				] ),
172				'globalBlock' => null,
173				'isAllowed' => true,
174			],
175			'blocked with multiple blocks, not all allowing password reset' => [
176				'passwordResetRoutes' => [ 'username' => true ],
177				'enableEmail' => true,
178				'allowsAuthenticationDataChange' => true,
179				'canEditPrivate' => true,
180				'block' => new CompositeBlock( [
181					'originalBlocks' => [
182						new SystemBlock( [ 'systemBlock' => 'wgSoftBlockRanges', 'anonOnly' => true ] ),
183						new SystemBlock( [ 'systemBlock' => 'proxy' ] ),
184					]
185				] ),
186				'globalBlock' => null,
187				'isAllowed' => false,
188			],
189			'all OK' => [
190				'passwordResetRoutes' => [ 'username' => true ],
191				'enableEmail' => true,
192				'allowsAuthenticationDataChange' => true,
193				'canEditPrivate' => true,
194				'block' => null,
195				'globalBlock' => null,
196				'isAllowed' => true,
197			],
198		];
199	}
200
201	public function testExecute_notAllowed() {
202		$user = $this->createMock( User::class );
203		/** @var User $user */
204
205		$passwordReset = $this->getMockBuilder( PasswordReset::class )
206			->disableOriginalConstructor()
207			->setMethods( [ 'isAllowed' ] )
208			->getMock();
209		$passwordReset->expects( $this->any() )
210			->method( 'isAllowed' )
211			->with( $user )
212			->willReturn( Status::newFatal( 'somestatuscode' ) );
213		/** @var PasswordReset $passwordReset */
214
215		$this->expectException( \LogicException::class );
216		$passwordReset->execute( $user );
217	}
218
219	/**
220	 * @dataProvider provideExecute
221	 * @param string|bool $expectedError
222	 * @param ServiceOptions $config
223	 * @param User $performingUser
224	 * @param PermissionManager $permissionManager
225	 * @param AuthManager $authManager
226	 * @param string|null $username
227	 * @param string|null $email
228	 * @param User[] $usersWithEmail
229	 * @covers SendPasswordResetEmailUpdate
230	 */
231	public function testExecute(
232		$expectedError,
233		ServiceOptions $config,
234		User $performingUser,
235		PermissionManager $permissionManager,
236		AuthManager $authManager,
237		$username = '',
238		$email = '',
239		array $usersWithEmail = []
240	) {
241		// Unregister the hooks for proper unit testing
242		$this->mergeMwGlobalArrayValue( 'wgHooks', [
243			'User::mailPasswordInternal' => [],
244			'SpecialPasswordResetOnSubmit' => [],
245		] );
246
247		$loadBalancer = $this->createMock( ILoadBalancer::class );
248
249		$users = $this->makeUsers();
250
251		$lookupUser = function ( $username ) use ( $users ) {
252			return $users[ $username ] ?? false;
253		};
254
255		$passwordReset = $this->getMockBuilder( PasswordReset::class )
256			->setMethods( [ 'getUsersByEmail', 'isAllowed', 'lookupUser' ] )
257			->setConstructorArgs( [
258				$config,
259				$authManager,
260				$permissionManager,
261				$loadBalancer,
262				new NullLogger(),
263				MediaWikiServices::getInstance()->getHookContainer()
264			] )
265			->getMock();
266		$passwordReset->method( 'getUsersByEmail' )->with( $email )
267			->willReturn( array_map( $lookupUser, $usersWithEmail ) );
268		$passwordReset->method( 'isAllowed' )
269			->willReturn( Status::newGood() );
270		$passwordReset->method( 'lookupUser' )
271			->willReturnCallback( $lookupUser );
272
273		/** @var PasswordReset $passwordReset */
274		$status = $passwordReset->execute( $performingUser, $username, $email );
275		$this->assertStatus( $status, $expectedError );
276	}
277
278	public function provideExecute() {
279		$defaultConfig = $this->makeConfig( true, [ 'username' => true, 'email' => true ], false );
280		$emailRequiredConfig = $this->makeConfig( true, [ 'username' => true, 'email' => true ], true );
281		$performingUser = $this->makePerformingUser( self::VALID_IP, false );
282		$throttledUser = $this->makePerformingUser( self::VALID_IP, true );
283		$permissionManager = $this->makePermissionManager( $performingUser, true );
284
285		return [
286			'Throttled, pretend everything is ok' => [
287				'expectedError' => false,
288				'config' => $defaultConfig,
289				'performingUser' => $throttledUser,
290				'permissionManager' => $permissionManager,
291				'authManager' => $this->makeAuthManager(),
292				'username' => 'User1',
293				'email' => '',
294				'usersWithEmail' => [],
295			],
296			'Throttled, email required for resets, is invalid, pretend everything is ok' => [
297				'expectedError' => false,
298				'config' => $emailRequiredConfig,
299				'performingUser' => $throttledUser,
300				'permissionManager' => $permissionManager,
301				'authManager' => $this->makeAuthManager(),
302				'username' => 'User1',
303				'email' => '[invalid email]',
304				'usersWithEmail' => [],
305			],
306			'Invalid email, pretend everything is OK' => [
307				'expectedError' => false,
308				'config' => $defaultConfig,
309				'performingUser' => $performingUser,
310				'permissionManager' => $permissionManager,
311				'authManager' => $this->makeAuthManager(),
312				'username' => '',
313				'email' => '[invalid email]',
314				'usersWithEmail' => [],
315			],
316			'No username, no email' => [
317				'expectedError' => 'passwordreset-nodata',
318				'config' => $defaultConfig,
319				'performingUser' => $performingUser,
320				'permissionManager' => $permissionManager,
321				'authManager' => $this->makeAuthManager(),
322				'username' => '',
323				'email' => '',
324				'usersWithEmail' => [],
325			],
326			'Email route not enabled' => [
327				'expectedError' => 'passwordreset-nodata',
328				'config' => $this->makeConfig( true, [ 'username' => true ], false ),
329				'performingUser' => $performingUser,
330				'permissionManager' => $permissionManager,
331				'authManager' => $this->makeAuthManager(),
332				'username' => '',
333				'email' => self::VALID_EMAIL,
334				'usersWithEmail' => [],
335			],
336			'Username route not enabled' => [
337				'expectedError' => 'passwordreset-nodata',
338				'config' => $this->makeConfig( true, [ 'email' => true ], false ),
339				'performingUser' => $performingUser,
340				'permissionManager' => $permissionManager,
341				'authManager' => $this->makeAuthManager(),
342				'username' => 'User1',
343				'email' => '',
344				'usersWithEmail' => [],
345			],
346			'No routes enabled' => [
347				'expectedError' => 'passwordreset-nodata',
348				'config' => $this->makeConfig( true, [], false ),
349				'performingUser' => $performingUser,
350				'permissionManager' => $permissionManager,
351				'authManager' => $this->makeAuthManager(),
352				'username' => 'User1',
353				'email' => self::VALID_EMAIL,
354				'usersWithEmail' => [],
355			],
356			'Email required for resets but is empty, pretend everything is OK' => [
357				'expectedError' => false,
358				'config' => $emailRequiredConfig,
359				'performingUser' => $performingUser,
360				'permissionManager' => $permissionManager,
361				'authManager' => $this->makeAuthManager(),
362				'username' => 'User1',
363				'email' => '',
364				'usersWithEmail' => [],
365			],
366			'Email required for resets but is invalid, pretend everything is OK' => [
367				'expectedError' => false,
368				'config' => $emailRequiredConfig,
369				'performingUser' => $performingUser,
370				'permissionManager' => $permissionManager,
371				'authManager' => $this->makeAuthManager(),
372				'username' => 'User1',
373				'email' => '[invalid email]',
374				'usersWithEmail' => [],
375			],
376			'Password email already sent within 24 hours, pretend everything is ok' => [
377				'expectedError' => false,
378				'config' => $defaultConfig,
379				'performingUser' => $performingUser,
380				'permissionManager' => $permissionManager,
381				'authManager' => $this->makeAuthManager( [ 'User1' ], 0, [], [ 'User1' ] ),
382				'username' => 'User1',
383				'email' => '',
384				'usersWithEmail' => [ 'User1' ],
385			],
386			'No user by this username, pretend everything is OK' => [
387				'expectedError' => false,
388				'config' => $defaultConfig,
389				'performingUser' => $performingUser,
390				'permissionManager' => $permissionManager,
391				'authManager' => $this->makeAuthManager(),
392				'username' => 'Nonexistent user',
393				'email' => '',
394				'usersWithEmail' => [],
395			],
396			'Username is not valid' => [
397				'expectedError' => 'noname',
398				'config' => $defaultConfig,
399				'performingUser' => $performingUser,
400				'permissionManager' => $permissionManager,
401				'authManager' => $this->makeAuthManager(),
402				'username' => 'Invalid|username',
403				'email' => '',
404				'usersWithEmail' => [],
405			],
406			'If no users with this email found, pretend everything is OK' => [
407				'expectedError' => false,
408				'config' => $defaultConfig,
409				'performingUser' => $performingUser,
410				'permissionManager' => $permissionManager,
411				'authManager' => $this->makeAuthManager(),
412				'username' => '',
413				'email' => 'some@not.found.email',
414				'usersWithEmail' => [],
415			],
416			'No email for the user, pretend everything is OK' => [
417				'expectedError' => false,
418				'config' => $defaultConfig,
419				'performingUser' => $performingUser,
420				'permissionManager' => $permissionManager,
421				'authManager' => $this->makeAuthManager(),
422				'username' => 'BadUser',
423				'email' => '',
424				'usersWithEmail' => [],
425			],
426			'Email required for resets, no match' => [
427				'expectedError' => false,
428				'config' => $emailRequiredConfig,
429				'performingUser' => $performingUser,
430				'permissionManager' => $permissionManager,
431				'authManager' => $this->makeAuthManager(),
432				'username' => 'User1',
433				'email' => 'some@other.email',
434				'usersWithEmail' => [],
435			],
436			"Couldn't determine the performing user's IP" => [
437				'expectedError' => 'badipaddress',
438				'config' => $defaultConfig,
439				'performingUser' => $this->makePerformingUser( null, false ),
440				'permissionManager' => $permissionManager,
441				'authManager' => $this->makeAuthManager(),
442				'username' => 'User1',
443				'email' => '',
444				'usersWithEmail' => [],
445			],
446			'User is allowed, but ignored' => [
447				'expectedError' => 'passwordreset-ignored',
448				'config' => $defaultConfig,
449				'performingUser' => $performingUser,
450				'permissionManager' => $permissionManager,
451				'authManager' => $this->makeAuthManager( [ 'User1' ], 0, [ 'User1' ] ),
452				'username' => 'User1',
453				'email' => '',
454				'usersWithEmail' => [],
455			],
456			'One of users is ignored' => [
457				'expectedError' => 'passwordreset-ignored',
458				'config' => $defaultConfig,
459				'performingUser' => $performingUser,
460				'permissionManager' => $permissionManager,
461				'authManager' => $this->makeAuthManager( [ 'User1', 'User2' ], 0, [ 'User2' ] ),
462				'username' => '',
463				'email' => self::VALID_EMAIL,
464				'usersWithEmail' => [ 'User1', 'User2' ],
465			],
466			'User is rejected' => [
467				'expectedError' => 'rejected by test mock',
468				'config' => $defaultConfig,
469				'performingUser' => $performingUser,
470				'permissionManager' => $permissionManager,
471				'authManager' => $this->makeAuthManager(),
472				'username' => 'User1',
473				'email' => '',
474				'usersWithEmail' => [],
475			],
476			'One of users is rejected' => [
477				'expectedError' => 'rejected by test mock',
478				'config' => $defaultConfig,
479				'performingUser' => $performingUser,
480				'permissionManager' => $permissionManager,
481				'authManager' => $this->makeAuthManager( [ 'User1' ] ),
482				'username' => '',
483				'email' => self::VALID_EMAIL,
484				'usersWithEmail' => [ 'User1', 'User2' ],
485			],
486			'Reset one user via password' => [
487				'expectedError' => false,
488				'config' => $defaultConfig,
489				'performingUser' => $performingUser,
490				'permissionManager' => $permissionManager,
491				'authManager' => $this->makeAuthManager( [ 'User1' ], 1 ),
492				'username' => 'User1',
493				'email' => self::VALID_EMAIL,
494				// Make sure that only the user specified by username is reset
495				'usersWithEmail' => [ 'User1', 'User2' ],
496			],
497			'Reset one user via email' => [
498				'expectedError' => false,
499				'config' => $defaultConfig,
500				'performingUser' => $performingUser,
501				'permissionManager' => $permissionManager,
502				'authManager' => $this->makeAuthManager( [ 'User1' ], 1 ),
503				'username' => '',
504				'email' => self::VALID_EMAIL,
505				'usersWithEmail' => [ 'User1' ],
506			],
507			'Reset multiple users via email' => [
508				'expectedError' => false,
509				'config' => $defaultConfig,
510				'performingUser' => $performingUser,
511				'permissionManager' => $permissionManager,
512				'authManager' => $this->makeAuthManager( [ 'User1', 'User2' ], 2 ),
513				'username' => '',
514				'email' => self::VALID_EMAIL,
515				'usersWithEmail' => [ 'User1', 'User2' ],
516			],
517			"Email is required for resets, user didn't opt in" => [
518				'expectedError' => false,
519				'config' => $emailRequiredConfig,
520				'performingUser' => $performingUser,
521				'permissionManager' => $permissionManager,
522				'authManager' => $this->makeAuthManager( [ 'User2' ], 1 ),
523				'username' => 'User2',
524				'email' => self::VALID_EMAIL,
525				'usersWithEmail' => [ 'User2' ],
526			],
527			'Reset three users via email that did not opt in, multiple users with same email' => [
528				'expectedError' => false,
529				'config' => $emailRequiredConfig,
530				'performingUser' => $performingUser,
531				'permissionManager' => $permissionManager,
532				'authManager' => $this->makeAuthManager( [ 'User2', 'User3', 'User4' ], 3, [ 'User1' ] ),
533				'username' => '',
534				'email' => self::VALID_EMAIL,
535				'usersWithEmail' => [ 'User1', 'User2', 'User3', 'User4' ],
536			],
537		];
538	}
539
540	private function assertStatus( StatusValue $status, $error = false ) {
541		if ( $error === false ) {
542			$this->assertTrue( $status->isGood(), 'Expected status to be good' );
543		} else {
544			$this->assertFalse( $status->isGood(), 'Expected status to not be good' );
545			if ( is_string( $error ) ) {
546				$this->assertNotEmpty( $status->getErrors() );
547				$message = $status->getErrors()[0]['message'];
548				if ( $message instanceof MessageSpecifier ) {
549					$message = $message->getKey();
550				}
551				$this->assertSame( $error, $message );
552			}
553		}
554	}
555
556	private function makeConfig( $enableEmail, array $passwordResetRoutes, $emailForResets ) {
557		$hash = [
558			'AllowRequiringEmailForResets' => $emailForResets,
559			'EnableEmail' => $enableEmail,
560			'PasswordResetRoutes' => $passwordResetRoutes,
561		];
562
563		return new ServiceOptions( PasswordReset::CONSTRUCTOR_OPTIONS, $hash );
564	}
565
566	/**
567	 * @param string|null $ip
568	 * @param bool $pingLimited
569	 * @return User
570	 */
571	private function makePerformingUser( $ip, $pingLimited ) : User {
572		$request = $this->getMockBuilder( WebRequest::class )
573			->getMock();
574		$request->method( 'getIP' )
575			->willReturn( $ip );
576		/** @var WebRequest $request */
577
578		$user = $this->getMockBuilder( User::class )
579			->setMethods( [ 'getName', 'pingLimiter', 'getRequest' ] )
580			->getMock();
581
582		$user->method( 'getName' )
583			->willReturn( 'SomeUser' );
584		$user->method( 'pingLimiter' )
585			->with( 'mailpassword' )
586			->willReturn( $pingLimited );
587		$user->method( 'getRequest' )
588			->willReturn( $request );
589
590		/** @var User $user */
591		return $user;
592	}
593
594	private function makePermissionManager( User $performingUser, $isAllowed ) : PermissionManager {
595		$permissionManager = $this->getMockBuilder( PermissionManager::class )
596			->disableOriginalConstructor()
597			->getMock();
598		$permissionManager->method( 'userHasRight' )
599			->with( $performingUser, 'editmyprivateinfo' )
600			->willReturn( $isAllowed );
601
602		/** @var PermissionManager $permissionManager */
603		return $permissionManager;
604	}
605
606	/**
607	 * @param string[] $allowed Usernames that are allowed to send password reset email
608	 *  by AuthManager's allowsAuthenticationDataChange method.
609	 * @param int $numUsersToAuth Number of users that will receive email
610	 * @param string[] $ignored Usernames that are allowed but ignored by AuthManager's
611	 *  allowsAuthenticationDataChange method and will not receive password reset email.
612	 * @param string[] $mailThrottledLimited Usernames that have already
613	 *  received the password reset email within a given time, and AuthManager
614	 *  changeAuthenticationData method will mark them as 'throttled-mailpassword.'
615	 * @return AuthManager
616	 */
617	private function makeAuthManager(
618		array $allowed = [],
619		$numUsersToAuth = 0,
620		array $ignored = [],
621		array $mailThrottledLimited = []
622	) : AuthManager {
623		$authManager = $this->getMockBuilder( AuthManager::class )
624			->disableOriginalConstructor()
625			->getMock();
626		$authManager->method( 'allowsAuthenticationDataChange' )
627			->willReturnCallback(
628				function ( TemporaryPasswordAuthenticationRequest $req )
629						use ( $allowed, $ignored, $mailThrottledLimited ) {
630					if ( in_array( $req->username, $mailThrottledLimited, true ) ) {
631						return Status::newGood( 'throttled-mailpassword' );
632					}
633
634					$value = in_array( $req->username, $ignored, true )
635						? 'ignored'
636						: 'okie dokie';
637
638					return in_array( $req->username, $allowed, true )
639						? Status::newGood( $value )
640						: Status::newFatal( 'rejected by test mock' );
641				} );
642		// changeAuthenticationData is executed in the deferred update class
643		// SendPasswordResetEmailUpdate
644		$authManager->expects( $this->exactly( $numUsersToAuth ) )
645			->method( 'changeAuthenticationData' );
646
647		/** @var AuthManager $authManager */
648		return $authManager;
649	}
650
651	/**
652	 * @return User[]
653	 */
654	private function makeUsers() {
655		$user1 = $this->getMockBuilder( User::class )->getMock();
656		$user2 = $this->getMockBuilder( User::class )->getMock();
657		$user3 = $this->getMockBuilder( User::class )->getMock();
658		$user4 = $this->getMockBuilder( User::class )->getMock();
659		$user1->method( 'getName' )->willReturn( 'User1' );
660		$user2->method( 'getName' )->willReturn( 'User2' );
661		$user3->method( 'getName' )->willReturn( 'User3' );
662		$user4->method( 'getName' )->willReturn( 'User4' );
663		$user1->method( 'getId' )->willReturn( 1 );
664		$user2->method( 'getId' )->willReturn( 2 );
665		$user3->method( 'getId' )->willReturn( 3 );
666		$user4->method( 'getId' )->willReturn( 4 );
667		$user1->method( 'getEmail' )->willReturn( self::VALID_EMAIL );
668		$user2->method( 'getEmail' )->willReturn( self::VALID_EMAIL );
669		$user3->method( 'getEmail' )->willReturn( self::VALID_EMAIL );
670		$user4->method( 'getEmail' )->willReturn( self::VALID_EMAIL );
671
672		$user1->method( 'getBoolOption' )
673			->with( 'requireemail' )
674			->willReturn( true );
675
676		$badUser = $this->getMockBuilder( User::class )->getMock();
677		$badUser->method( 'getName' )->willReturn( 'BadUser' );
678		$badUser->method( 'getId' )->willReturn( 5 );
679		$badUser->method( 'getEmail' )->willReturn( null );
680
681		return [
682			'User1' => $user1,
683			'User2' => $user2,
684			'User3' => $user3,
685			'User4' => $user4,
686			'BadUser' => $badUser,
687		];
688	}
689}
690