Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 108
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateAndPromote
0.00% covered (danger)
0.00%
0 / 105
0.00% covered (danger)
0.00%
0 / 3
930
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 execute
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 1
756
 addLogEntry
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Creates an account and grants it rights.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Maintenance
22 * @author Rob Church <robchur@gmail.com>
23 * @author Pablo Castellano <pablo@anche.no>
24 */
25
26require_once __DIR__ . '/Maintenance.php';
27
28use MediaWiki\Deferred\SiteStatsUpdate;
29use MediaWiki\User\User;
30use MediaWiki\WikiMap\WikiMap;
31
32/**
33 * Maintenance script to create an account and grant it rights.
34 *
35 * @ingroup Maintenance
36 */
37class CreateAndPromote extends Maintenance {
38    private static $permitRoles = [ 'sysop', 'bureaucrat', 'interface-admin', 'bot' ];
39
40    public function __construct() {
41        parent::__construct();
42        $this->addDescription( 'Create a new user account and/or grant it additional rights' );
43        $this->addOption(
44            'force',
45            'If account exists already, just grant it rights or change password.'
46        );
47        foreach ( self::$permitRoles as $role ) {
48            $this->addOption( $role, "Add the account to the {$role} group" );
49        }
50
51        $this->addOption(
52            'custom-groups',
53            'Comma-separated list of groups to add the user to',
54            false,
55            true
56        );
57
58        $this->addOption(
59            'reason',
60            'Reason for account creation and user rights assignment to log to wiki',
61            false,
62            true
63        );
64
65        $this->addArg( 'username', 'Username of new user' );
66        $this->addArg( 'password', 'Password to set', false );
67    }
68
69    public function execute() {
70        $username = $this->getArg( 0 );
71        $password = $this->getArg( 1 );
72        $force = $this->hasOption( 'force' );
73        $inGroups = [];
74        $services = $this->getServiceContainer();
75
76        $user = $services->getUserFactory()->newFromName( $username );
77        if ( !is_object( $user ) ) {
78            $this->fatalError( 'invalid username.' );
79        }
80
81        $exists = ( $user->idForName() !== 0 );
82
83        if ( $exists && !$force ) {
84            $this->fatalError( 'Account exists. Perhaps you want the --force option?' );
85        } elseif ( !$exists && !$password ) {
86            $this->error( 'Argument <password> required!' );
87            $this->maybeHelp( true );
88        } elseif ( $exists ) {
89            $inGroups = $services->getUserGroupManager()->getUserGroups( $user );
90        }
91
92        $groups = array_filter( self::$permitRoles, [ $this, 'hasOption' ] );
93        if ( $this->hasOption( 'custom-groups' ) ) {
94            $allGroups = array_fill_keys( $services->getUserGroupManager()->listAllGroups(), true );
95            $customGroupsText = $this->getOption( 'custom-groups' );
96            if ( $customGroupsText !== '' ) {
97                $customGroups = explode( ',', $customGroupsText );
98                foreach ( $customGroups as $customGroup ) {
99                    if ( isset( $allGroups[$customGroup] ) ) {
100                        $groups[] = trim( $customGroup );
101                    } else {
102                        $this->output( "$customGroup is not a valid group, ignoring!\n" );
103                    }
104                }
105            }
106        }
107
108        $promotions = array_diff(
109            $groups,
110            $inGroups
111        );
112
113        if ( $exists && !$password && count( $promotions ) === 0 ) {
114            $this->output( "Account exists and nothing to do.\n" );
115
116            return;
117        } elseif ( count( $promotions ) !== 0 ) {
118            $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
119            $promoText = "User:{$username} into " . implode( ', ', $promotions ) . "...\n";
120            if ( $exists ) {
121                $this->output( "$dbDomain: Promoting $promoText" );
122            } else {
123                $this->output( "$dbDomain: Creating and promoting $promoText" );
124            }
125        }
126
127        if ( !$exists ) {
128            // Verify the password meets the password requirements before creating.
129            // This check is repeated below to account for differences between
130            // the password policy for regular users and for users in certain groups.
131            if ( $password ) {
132                $status = $user->checkPasswordValidity( $password );
133
134                if ( !$status->isGood() ) {
135                    $this->fatalError( $status->getMessage( false, false, 'en' )->text() );
136                }
137            }
138
139            // Create the user via AuthManager as there may be various side
140            // effects that are performed by the configured AuthManager chain.
141            $status = $this->getServiceContainer()->getAuthManager()->autoCreateUser(
142                $user,
143                MediaWiki\Auth\AuthManager::AUTOCREATE_SOURCE_MAINT,
144                false
145            );
146            if ( !$status->isGood() ) {
147                $this->fatalError( $status->getMessage( false, false, 'en' )->text() );
148            }
149        }
150
151        if ( $promotions ) {
152            // Add groups before changing password, as the password policy for certain groups has
153            // stricter requirements.
154            $userGroupManager = $services->getUserGroupManager();
155            $userGroupManager->addUserToMultipleGroups( $user, $promotions );
156            $reason = $this->getOption( 'reason' ) ?: '';
157            $this->addLogEntry( $user, $inGroups, array_merge( $inGroups, $promotions ), $reason );
158        }
159
160        if ( $password ) {
161            # Try to set the password
162            try {
163                $status = $user->changeAuthenticationData( [
164                    'username' => $user->getName(),
165                    'password' => $password,
166                    'retype' => $password,
167                ] );
168                if ( !$status->isGood() ) {
169                    throw new PasswordError( $status->getMessage( false, false, 'en' )->text() );
170                }
171                if ( $exists ) {
172                    $this->output( "Password set.\n" );
173                    $user->saveSettings();
174                }
175            } catch ( PasswordError $pwe ) {
176                $this->fatalError( 'Setting the password failed: ' . $pwe->getMessage() );
177            }
178        }
179
180        if ( !$exists ) {
181            # Increment site_stats.ss_users
182            $ssu = SiteStatsUpdate::factory( [ 'users' => 1 ] );
183            $ssu->doUpdate();
184        }
185
186        $this->output( "done.\n" );
187    }
188
189    /**
190     * Add a rights log entry for an action.
191     *
192     * @param User $user
193     * @param array $oldGroups
194     * @param array $newGroups
195     * @param string $reason
196     */
197    private function addLogEntry( $user, array $oldGroups, array $newGroups, string $reason ) {
198        $logEntry = new ManualLogEntry( 'rights', 'rights' );
199        $logEntry->setPerformer( User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] ) );
200        $logEntry->setTarget( $user->getUserPage() );
201        $logEntry->setComment( $reason );
202        $logEntry->setParameters( [
203            '4::oldgroups' => $oldGroups,
204            '5::newgroups' => $newGroups
205        ] );
206        $logid = $logEntry->insert();
207        $logEntry->publish( $logid );
208    }
209}
210
211$maintClass = CreateAndPromote::class;
212require_once RUN_MAINTENANCE_IF_MAIN;