Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
CreateBotPassword
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
3 / 3
16
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
1 / 1
13
 showGrants
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Creates a bot password for an existing user account.
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 Alex Dean <wikimedia@mostlyalex.com>
23 */
24
25use MediaWiki\Maintenance\Maintenance;
26use MediaWiki\User\BotPassword;
27use MediaWiki\User\User;
28
29// @codeCoverageIgnoreStart
30require_once __DIR__ . '/Maintenance.php';
31// @codeCoverageIgnoreEnd
32
33class CreateBotPassword extends Maintenance {
34    /**
35     * Width of initial column of --showgrants output
36     */
37    private const SHOWGRANTS_COLUMN_WIDTH = 20;
38
39    public function __construct() {
40        parent::__construct();
41        $this->addDescription(
42            'Create a bot password for a user. ' .
43            'See https://www.mediawiki.org/wiki/Manual:Bot_passwords for more information.'
44        );
45
46        $this->addOption( "showgrants",
47            "Prints a description of available grants and exits."
48        );
49        $this->addOption( "appid",
50            "App id for the new bot password.", false, true
51        );
52        $this->addOption( "grants",
53            "CSV list of permissions to grant.", false, true
54        );
55        $this->addArg( "user",
56            "The username to create a bot password for.", false
57        );
58        $this->addArg( "password",
59            "A password will be generated if this is omitted." .
60            " If supplied, it must be exactly 32 characters.", false
61        );
62    }
63
64    public function execute() {
65        if ( $this->hasOption( 'showgrants' ) ) {
66            $this->showGrants();
67            return;
68        }
69
70        $username = $this->getArg( 0 );
71        $password = $this->getArg( 1 );
72        $appId = $this->getOption( 'appid' );
73        $grants = explode( ',', $this->getOption( 'grants', '' ) );
74
75        $errors = [];
76        if ( $username === null ) {
77            $errors[] = "Argument <user> required!";
78        }
79        if ( $appId == null ) {
80            $errors[] = "Param appid required!";
81        }
82        if ( $this->getOption( 'grants' ) === null ) {
83            $errors[] = "Param grants required!";
84        }
85        if ( count( $errors ) > 0 ) {
86            $this->fatalError( implode( "\n", $errors ) );
87        }
88
89        $services = $this->getServiceContainer();
90        $grantsInfo = $services->getGrantsInfo();
91        $invalidGrants = array_diff( $grants, $grantsInfo->getValidGrants() );
92        if ( count( $invalidGrants ) > 0 ) {
93            $this->fatalError(
94                "These grants are invalid: " . implode( ', ', $invalidGrants ) . "\n" .
95                "Use the --showgrants option for a full list of valid grant names."
96            );
97        }
98
99        $passwordFactory = $services->getPasswordFactory();
100
101        $userIdentity = $services->getUserIdentityLookup()->getUserIdentityByName( $username );
102        if ( !$userIdentity || !$userIdentity->isRegistered() ) {
103            $this->fatalError( "Cannot create bot password for non-existent user '$username'." );
104        }
105
106        if ( $password === null ) {
107            $password = BotPassword::generatePassword( $this->getConfig() );
108        } else {
109            $passwordLength = strlen( $password );
110            if ( $passwordLength < BotPassword::PASSWORD_MINLENGTH ) {
111                $message = "Bot passwords must have at least " . BotPassword::PASSWORD_MINLENGTH .
112                    " characters. Given password is $passwordLength characters.";
113                $this->fatalError( $message );
114            }
115        }
116
117        $bp = BotPassword::newUnsaved( [
118            'username' => $username,
119            'appId' => $appId,
120            'grants' => $grants
121        ] );
122
123        if ( $bp === null ) {
124            $this->fatalError( "Bot password creation failed." );
125        }
126
127        $passwordInstance = $passwordFactory->newFromPlaintext( $password );
128        $status = $bp->save( 'insert', $passwordInstance );
129
130        if ( $status->isGood() ) {
131            $this->output( "Success.\n" );
132            $this->output( "Log in using username:'{$username}@{$appId}' and password:'{$password}'.\n" );
133        } else {
134            $this->error( "Bot password creation failed. Does this appid already exist for the user perhaps?" );
135            $this->fatalError( $status );
136        }
137    }
138
139    public function showGrants() {
140        $permissions = $this->getServiceContainer()->getGrantsInfo()->getValidGrants();
141        sort( $permissions );
142
143        $this->output( str_pad( 'GRANT', self::SHOWGRANTS_COLUMN_WIDTH ) . " DESCRIPTION\n" );
144        foreach ( $permissions as $permission ) {
145            $this->output(
146                str_pad( $permission, self::SHOWGRANTS_COLUMN_WIDTH ) . " " .
147                User::getRightDescription( $permission ) . "\n"
148            );
149        }
150    }
151}
152
153// @codeCoverageIgnoreStart
154$maintClass = CreateBotPassword::class;
155require_once RUN_MAINTENANCE_IF_MAIN;
156// @codeCoverageIgnoreEnd