[SECURITY] Password Reset Updates
[lhc/web/wiklou.git] / tests / phpunit / includes / user / PasswordResetTest.php
index 351ef54..d16244b 100644 (file)
@@ -2,8 +2,8 @@
 
 use MediaWiki\Auth\AuthManager;
 use MediaWiki\Auth\TemporaryPasswordAuthenticationRequest;
-use MediaWiki\Block\DatabaseBlock;
 use MediaWiki\Block\CompositeBlock;
+use MediaWiki\Block\DatabaseBlock;
 use MediaWiki\Block\SystemBlock;
 use MediaWiki\Config\ServiceOptions;
 use MediaWiki\Permissions\PermissionManager;
@@ -43,7 +43,7 @@ class PasswordResetTest extends MediaWikiTestCase {
                        ->with( $user, 'editmyprivateinfo' )
                        ->willReturn( $canEditPrivate );
 
-               $loadBalancer = $this->getMockBuilder( ILoadBalancer::class )->getMock();
+               $loadBalancer = $this->createMock( ILoadBalancer::class );
 
                $passwordReset = new PasswordReset(
                        $config,
@@ -194,11 +194,8 @@ class PasswordResetTest extends MediaWikiTestCase {
                ];
        }
 
-       /**
-        * @expectedException \LogicException
-        */
        public function testExecute_notAllowed() {
-               $user = $this->getMock( User::class );
+               $user = $this->createMock( User::class );
                /** @var User $user */
 
                $passwordReset = $this->getMockBuilder( PasswordReset::class )
@@ -211,6 +208,7 @@ class PasswordResetTest extends MediaWikiTestCase {
                        ->willReturn( Status::newFatal( 'somestatuscode' ) );
                /** @var PasswordReset $passwordReset */
 
+               $this->expectException( \LogicException::class );
                $passwordReset->execute( $user );
        }
 
@@ -242,8 +240,7 @@ class PasswordResetTest extends MediaWikiTestCase {
                        'SpecialPasswordResetOnSubmit' => [],
                ] );
 
-               $loadBalancer = $this->getMockBuilder( ILoadBalancer::class )
-                       ->getMock();
+               $loadBalancer = $this->createMock( ILoadBalancer::class );
 
                $users = $this->makeUsers();
 
@@ -281,12 +278,32 @@ class PasswordResetTest extends MediaWikiTestCase {
                $permissionManager = $this->makePermissionManager( $performingUser, true );
 
                return [
-                       'Invalid email' => [
-                               'expectedError' => 'passwordreset-invalidemail',
+                       'Throttled, pretend everything is ok' => [
+                               'expectedError' => false,
                                'config' => $defaultConfig,
                                'performingUser' => $throttledUser,
                                'permissionManager' => $permissionManager,
                                'authManager' => $this->makeAuthManager(),
+                               'username' => 'User1',
+                               'email' => '',
+                               'usersWithEmail' => [],
+                       ],
+                       'Throttled, email required for resets, is invalid, pretend everything is ok' => [
+                               'expectedError' => false,
+                               'config' => $emailRequiredConfig,
+                               'performingUser' => $throttledUser,
+                               'permissionManager' => $permissionManager,
+                               'authManager' => $this->makeAuthManager(),
+                               'username' => 'User1',
+                               'email' => '[invalid email]',
+                               'usersWithEmail' => [],
+                       ],
+                       'Invalid email, pretend everything is OK' => [
+                               'expectedError' => false,
+                               'config' => $defaultConfig,
+                               'performingUser' => $performingUser,
+                               'permissionManager' => $permissionManager,
+                               'authManager' => $this->makeAuthManager(),
                                'username' => '',
                                'email' => '[invalid email]',
                                'usersWithEmail' => [],
@@ -294,7 +311,7 @@ class PasswordResetTest extends MediaWikiTestCase {
                        'No username, no email' => [
                                'expectedError' => 'passwordreset-nodata',
                                'config' => $defaultConfig,
-                               'performingUser' => $throttledUser,
+                               'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
                                'authManager' => $this->makeAuthManager(),
                                'username' => '',
@@ -304,7 +321,7 @@ class PasswordResetTest extends MediaWikiTestCase {
                        'Email route not enabled' => [
                                'expectedError' => 'passwordreset-nodata',
                                'config' => $this->makeConfig( true, [ 'username' => true ], false ),
-                               'performingUser' => $throttledUser,
+                               'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
                                'authManager' => $this->makeAuthManager(),
                                'username' => '',
@@ -314,7 +331,7 @@ class PasswordResetTest extends MediaWikiTestCase {
                        'Username route not enabled' => [
                                'expectedError' => 'passwordreset-nodata',
                                'config' => $this->makeConfig( true, [ 'email' => true ], false ),
-                               'performingUser' => $throttledUser,
+                               'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
                                'authManager' => $this->makeAuthManager(),
                                'username' => 'User1',
@@ -324,45 +341,45 @@ class PasswordResetTest extends MediaWikiTestCase {
                        'No routes enabled' => [
                                'expectedError' => 'passwordreset-nodata',
                                'config' => $this->makeConfig( true, [], false ),
-                               'performingUser' => $throttledUser,
+                               'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
                                'authManager' => $this->makeAuthManager(),
                                'username' => 'User1',
                                'email' => self::VALID_EMAIL,
                                'usersWithEmail' => [],
                        ],
-                       'Email reqiured for resets, but is empty' => [
-                               'expectedError' => 'passwordreset-username-email-required',
+                       'Email required for resets but is empty, pretend everything is OK' => [
+                               'expectedError' => false,
                                'config' => $emailRequiredConfig,
-                               'performingUser' => $throttledUser,
+                               'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
                                'authManager' => $this->makeAuthManager(),
                                'username' => 'User1',
                                'email' => '',
                                'usersWithEmail' => [],
                        ],
-                       'Email reqiured for resets, is invalid' => [
-                               'expectedError' => 'passwordreset-invalidemail',
+                       'Email required for resets but is invalid, pretend everything is OK' => [
+                               'expectedError' => false,
                                'config' => $emailRequiredConfig,
-                               'performingUser' => $throttledUser,
+                               'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
                                'authManager' => $this->makeAuthManager(),
                                'username' => 'User1',
                                'email' => '[invalid email]',
                                'usersWithEmail' => [],
                        ],
-                       'Throttled' => [
-                               'expectedError' => 'actionthrottledtext',
+                       'Password email already sent within 24 hours, pretend everything is ok' => [
+                               'expectedError' => false,
                                'config' => $defaultConfig,
-                               'performingUser' => $throttledUser,
+                               'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
-                               'authManager' => $this->makeAuthManager(),
+                               'authManager' => $this->makeAuthManager( [ 'User1' ], 0, [], [ 'User1' ] ),
                                'username' => 'User1',
                                'email' => '',
-                               'usersWithEmail' => [],
+                               'usersWithEmail' => [ 'User1' ],
                        ],
-                       'No user by this username' => [
-                               'expectedError' => 'nosuchuser',
+                       'No user by this username, pretend everything is OK' => [
+                               'expectedError' => false,
                                'config' => $defaultConfig,
                                'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
@@ -371,6 +388,16 @@ class PasswordResetTest extends MediaWikiTestCase {
                                'email' => '',
                                'usersWithEmail' => [],
                        ],
+                       'Username is not valid' => [
+                               'expectedError' => 'noname',
+                               'config' => $defaultConfig,
+                               'performingUser' => $performingUser,
+                               'permissionManager' => $permissionManager,
+                               'authManager' => $this->makeAuthManager(),
+                               'username' => 'Invalid|username',
+                               'email' => '',
+                               'usersWithEmail' => [],
+                       ],
                        'If no users with this email found, pretend everything is OK' => [
                                'expectedError' => false,
                                'config' => $defaultConfig,
@@ -381,8 +408,8 @@ class PasswordResetTest extends MediaWikiTestCase {
                                'email' => 'some@not.found.email',
                                'usersWithEmail' => [],
                        ],
-                       'No email for the user' => [
-                               'expectedError' => 'noemail',
+                       'No email for the user, pretend everything is OK' => [
+                               'expectedError' => false,
                                'config' => $defaultConfig,
                                'performingUser' => $performingUser,
                                'permissionManager' => $permissionManager,
@@ -391,7 +418,7 @@ class PasswordResetTest extends MediaWikiTestCase {
                                'email' => '',
                                'usersWithEmail' => [],
                        ],
-                       'Email reqiured for resets, no match' => [
+                       'Email required for resets, no match' => [
                                'expectedError' => false,
                                'config' => $emailRequiredConfig,
                                'performingUser' => $performingUser,
@@ -492,6 +519,16 @@ class PasswordResetTest extends MediaWikiTestCase {
                                'email' => self::VALID_EMAIL,
                                'usersWithEmail' => [ 'User2' ],
                        ],
+                       'Reset three users via email that did not opt in, multiple users with same email' => [
+                               'expectedError' => false,
+                               'config' => $emailRequiredConfig,
+                               'performingUser' => $performingUser,
+                               'permissionManager' => $permissionManager,
+                               'authManager' => $this->makeAuthManager( [ 'User2', 'User3', 'User4' ], 3, [ 'User1' ] ),
+                               'username' => '',
+                               'email' => self::VALID_EMAIL,
+                               'usersWithEmail' => [ 'User1', 'User2', 'User3', 'User4' ],
+                       ],
                ];
        }
 
@@ -562,25 +599,37 @@ class PasswordResetTest extends MediaWikiTestCase {
        }
 
        /**
-        * @param string[] $allowed
-        * @param int $numUsersToAuth
-        * @param string[] $ignored
+        * @param string[] $allowed Usernames that are allowed to send password reset email
+        *  by AuthManager's allowsAuthenticationDataChange method.
+        * @param int $numUsersToAuth Number of users that will receive email
+        * @param string[] $ignored Usernames that are allowed but ignored by AuthManager's
+        *  allowsAuthenticationDataChange method and will not receive password reset email.
+        * @param string[] $mailThrottledLimited Usernames that have already
+        *  received the password reset email within a given time, and AuthManager
+        *  changeAuthenticationData method will mark them as 'throttled-mailpassword.'
         * @return AuthManager
         */
        private function makeAuthManager(
                array $allowed = [],
                $numUsersToAuth = 0,
-               array $ignored = []
+               array $ignored = [],
+               array $mailThrottledLimited = []
        ) : AuthManager {
                $authManager = $this->getMockBuilder( AuthManager::class )
                        ->disableOriginalConstructor()
                        ->getMock();
                $authManager->method( 'allowsAuthenticationDataChange' )
                        ->willReturnCallback(
-                               function ( TemporaryPasswordAuthenticationRequest $req ) use ( $allowed, $ignored ) {
+                               function ( TemporaryPasswordAuthenticationRequest $req )
+                                               use ( $allowed, $ignored, $mailThrottledLimited ) {
+                                       if ( in_array( $req->username, $mailThrottledLimited, true ) ) {
+                                               return Status::newGood( 'throttled-mailpassword' );
+                                       }
+
                                        $value = in_array( $req->username, $ignored, true )
                                                ? 'ignored'
                                                : 'okie dokie';
+
                                        return in_array( $req->username, $allowed, true )
                                                ? Status::newGood( $value )
                                                : Status::newFatal( 'rejected by test mock' );
@@ -600,12 +649,20 @@ class PasswordResetTest extends MediaWikiTestCase {
        private function makeUsers() {
                $user1 = $this->getMockBuilder( User::class )->getMock();
                $user2 = $this->getMockBuilder( User::class )->getMock();
+               $user3 = $this->getMockBuilder( User::class )->getMock();
+               $user4 = $this->getMockBuilder( User::class )->getMock();
                $user1->method( 'getName' )->willReturn( 'User1' );
                $user2->method( 'getName' )->willReturn( 'User2' );
+               $user3->method( 'getName' )->willReturn( 'User3' );
+               $user4->method( 'getName' )->willReturn( 'User4' );
                $user1->method( 'getId' )->willReturn( 1 );
                $user2->method( 'getId' )->willReturn( 2 );
+               $user3->method( 'getId' )->willReturn( 3 );
+               $user4->method( 'getId' )->willReturn( 4 );
                $user1->method( 'getEmail' )->willReturn( self::VALID_EMAIL );
                $user2->method( 'getEmail' )->willReturn( self::VALID_EMAIL );
+               $user3->method( 'getEmail' )->willReturn( self::VALID_EMAIL );
+               $user4->method( 'getEmail' )->willReturn( self::VALID_EMAIL );
 
                $user1->method( 'getBoolOption' )
                        ->with( 'requireemail' )
@@ -613,12 +670,14 @@ class PasswordResetTest extends MediaWikiTestCase {
 
                $badUser = $this->getMockBuilder( User::class )->getMock();
                $badUser->method( 'getName' )->willReturn( 'BadUser' );
-               $badUser->method( 'getId' )->willReturn( 3 );
+               $badUser->method( 'getId' )->willReturn( 5 );
                $badUser->method( 'getEmail' )->willReturn( null );
 
                return [
                        'User1' => $user1,
                        'User2' => $user2,
+                       'User3' => $user3,
+                       'User4' => $user4,
                        'BadUser' => $badUser,
                ];
        }