24 namespace MediaWiki\Specials;
40 use Psr\Log\LoggerInterface;
55 private $botPassword =
null;
58 private $operation =
null;
61 private $password =
null;
67 private $passwordFactory;
70 private $centralIdLookup;
76 private $grantsLocalization;
92 parent::__construct(
'BotPasswords',
'editmyprivateinfo' );
94 $this->passwordFactory = $passwordFactory;
95 $this->centralIdLookup = $centralIdLookup;
97 $this->grantsInfo = $grantsInfo;
98 $this->grantsLocalization = $grantsLocalization;
121 if (
$par !==
null ) {
127 'botpasswords',
'botpasswords-bad-appid', [ htmlspecialchars(
$par ) ]
132 parent::execute(
$par );
136 parent::checkExecutePermissions( $user );
139 throw new ErrorPageError(
'botpasswords',
'botpasswords-disabled' );
142 $this->userId = $this->centralIdLookup->centralIdFromLocalUser( $this->
getUser() );
143 if ( !$this->userId ) {
144 throw new ErrorPageError(
'botpasswords',
'botpasswords-no-central-id' );
151 if ( $this->par !==
null ) {
153 if ( !$this->botPassword ) {
155 'centralId' => $this->userId,
156 'appId' => $this->par,
163 'label-message' =>
'username',
167 if ( $this->botPassword->isSaved() ) {
168 $fields[
'resetPassword'] = [
170 'label-message' =>
'botpasswords-label-resetpassword',
172 if ( $this->botPassword->isInvalid() ) {
173 $fields[
'resetPassword'][
'default'] =
true;
178 $showGrants = $this->grantsInfo->getValidGrants();
179 $grantLinks = array_map( [ $this->grantsLocalization,
'getGrantsLink' ], $showGrants );
181 $fields[
'grants'] = [
182 'type' =>
'checkmatrix',
183 'label-message' =>
'botpasswords-label-grants',
184 'help-message' =>
'botpasswords-help-grants',
186 $this->
msg(
'botpasswords-label-grants-column' )->escaped() =>
'grant'
188 'rows' => array_combine(
192 'default' => array_map(
193 static function ( $g ) {
196 $this->botPassword->getGrants()
198 'tooltips' => array_combine(
201 static function ( $rights ) use (
$lang ) {
202 return $lang->semicolonList( array_map( [ User::class,
'getRightDescription' ], $rights ) );
204 array_intersect_key( $this->grantsInfo->getRightsByGrant(),
205 array_fill_keys( $showGrants,
true ) )
208 'force-options-on' => array_map(
209 static function ( $g ) {
212 $this->grantsInfo->getHiddenGrants()
216 $fields[
'restrictions'] = [
217 'class' => HTMLRestrictionsField::class,
219 'default' => $this->botPassword->getRestrictions(),
228 [
'bp_app_id',
'bp_password' ],
229 [
'bp_user' => $this->userId ],
232 foreach (
$res as $row ) {
234 $password = $this->passwordFactory->newFromCiphertext( $row->bp_password );
238 $passwordInvalid =
true;
241 $text = $linkRenderer->makeKnownLink(
245 if ( $passwordInvalid ) {
246 $text .= $this->
msg(
'word-separator' )->escaped()
247 . $this->
msg(
'botpasswords-label-needsreset' )->parse();
251 'section' =>
'existing',
259 'section' =>
'createnew',
260 'type' =>
'textwithbutton',
261 'label-message' =>
'botpasswords-label-appid',
262 'buttondefault' => $this->
msg(
'botpasswords-label-create' )->text(),
263 'buttonflags' => [
'progressive',
'primary' ],
267 'validation-callback' =>
static function ( $v ) {
284 $form->
setId(
'mw-botpasswords-form' );
288 if ( $this->par !==
null ) {
289 if ( $this->botPassword->isSaved() ) {
294 'label-message' =>
'botpasswords-label-update',
295 'flags' => [
'primary',
'progressive' ],
300 'label-message' =>
'botpasswords-label-delete',
301 'flags' => [
'destructive' ],
308 'label-message' =>
'botpasswords-label-create',
309 'flags' => [
'primary',
'progressive' ],
316 'label-message' =>
'botpasswords-label-cancel'
322 $op = $this->
getRequest()->getVal(
'op',
'' );
330 $this->operation =
'insert';
331 return $this->save( $data );
334 $this->operation =
'update';
335 return $this->save( $data );
338 $this->operation =
'delete';
343 "Bot password {op} for {user}@{app_id}",
345 'app_id' => $this->par,
347 'centralId' => $this->userId,
363 private function save( array $data ) {
365 'centralId' => $this->userId,
366 'appId' => $this->par,
367 'restrictions' => $data[
'restrictions'],
368 'grants' => array_merge(
369 $this->grantsInfo->getHiddenGrants(),
372 preg_replace(
'/^grant-/',
'', $data[
'grants'] )
376 if ( $bp ===
null ) {
378 return Status::newFatal(
"botpasswords-{$this->operation}-failed", $this->par );
381 if ( $this->operation ===
'insert' || !empty( $data[
'resetPassword'] ) ) {
383 $password = $this->passwordFactory->newFromPlaintext( $this->password );
388 $res = $bp->save( $this->operation, $password );
393 'Bot password {op} for {user}@{app_id} ' . (
$success ?
'succeeded' :
'failed' ),
395 'op' => $this->operation,
397 'app_id' => $this->par,
398 'centralId' => $this->userId,
399 'restrictions' => $data[
'restrictions'],
400 'grants' => $bp->getGrants(),
401 'client_ip' => $this->getRequest()->getIP(),
412 $username = $this->
getUser()->getName();
413 switch ( $this->operation ) {
415 $out->setPageTitle( $this->
msg(
'botpasswords-created-title' )->text() );
416 $out->addWikiMsg(
'botpasswords-created-body', $this->par, $username );
420 $out->setPageTitle( $this->
msg(
'botpasswords-updated-title' )->text() );
421 $out->addWikiMsg(
'botpasswords-updated-body', $this->par, $username );
425 $out->setPageTitle( $this->
msg(
'botpasswords-deleted-title' )->text() );
426 $out->addWikiMsg(
'botpasswords-deleted-body', $this->par, $username );
427 $this->password =
null;
431 if ( $this->password !==
null ) {
434 'botpasswords-newpassword',
435 htmlspecialchars( $username . $sep . $this->par ),
436 htmlspecialchars( $this->password ),
437 htmlspecialchars( $username ),
438 htmlspecialchars( $this->par . $sep . $this->password )
440 $this->password =
null;
458 class_alias( SpecialBotPasswords::class,
'SpecialBotPasswords' );
Utility class for bot passwords.
static generatePassword( $config)
Returns a (raw, unhashed) random password string.
static getDB( $db)
Get a database connection for the bot passwords database.
static newFromCentralId( $centralId, $appId, $flags=self::READ_NORMAL)
Load a BotPassword from the database.
static newUnsaved(array $data, $flags=self::READ_NORMAL)
Create an unsaved BotPassword.
static getSeparator()
Get the separator for combined user name + app ID.
The CentralIdLookup service allows for connecting local users with cluster-wide IDs.
An error page which can definitely be safely rendered using the OutputPage.
Special page which uses an HTMLForm to handle processing.
string null $par
The sub-page of the special page.
Class for updating an MWRestrictions value (which is, currently, basically just an IP address list).
Represents an invalid password hash.
A class containing constants representing the names of configuration variables.
const EnableBotPasswords
Name constant for the EnableBotPasswords setting, for use with Config::get()
Show an error when any operation involving passwords fails to run.
Factory class for creating and checking Password objects.
getName()
Get the name of this Special Page.
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
requireNamedUser( $reasonMsg='exception-nologin-text', $titleMsg='exception-nologin')
If the user is not logged in or is a temporary user, throws UserNotLoggedIn.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
setAuthManager(AuthManager $authManager)
Set the injected AuthManager from the special page constructor.
getPageTitle( $subpage=false)
Get a self-referential title object.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
if(!isset( $args[0])) $lang