MediaWiki  master
SpecialBotPasswords.php
Go to the documentation of this file.
1 <?php
25 
32 
34  private $userId = 0;
35 
37  private $botPassword = null;
38 
40  private $operation = null;
41 
43  private $password = null;
44 
46  private $logger = null;
47 
50 
55  parent::__construct( 'BotPasswords', 'editmyprivateinfo' );
56  $this->logger = LoggerFactory::getInstance( 'authentication' );
57  $this->passwordFactory = $passwordFactory;
58  }
59 
63  public function isListed() {
64  return $this->getConfig()->get( 'EnableBotPasswords' );
65  }
66 
67  protected function getLoginSecurityLevel() {
68  return $this->getName();
69  }
70 
75  public function execute( $par ) {
76  $this->getOutput()->disallowUserJs();
77  $this->requireLogin();
78  $this->addHelpLink( 'Manual:Bot_passwords' );
79 
80  $par = trim( $par );
81  if ( strlen( $par ) === 0 ) {
82  $par = null;
83  } elseif ( strlen( $par ) > BotPassword::APPID_MAXLENGTH ) {
84  throw new ErrorPageError( 'botpasswords', 'botpasswords-bad-appid',
85  [ htmlspecialchars( $par ) ] );
86  }
87 
88  parent::execute( $par );
89  }
90 
91  protected function checkExecutePermissions( User $user ) {
92  parent::checkExecutePermissions( $user );
93 
94  if ( !$this->getConfig()->get( 'EnableBotPasswords' ) ) {
95  throw new ErrorPageError( 'botpasswords', 'botpasswords-disabled' );
96  }
97 
98  $this->userId = CentralIdLookup::factory()->centralIdFromLocalUser( $this->getUser() );
99  if ( !$this->userId ) {
100  throw new ErrorPageError( 'botpasswords', 'botpasswords-no-central-id' );
101  }
102  }
103 
104  protected function getFormFields() {
105  $fields = [];
106 
107  if ( $this->par !== null ) {
108  $this->botPassword = BotPassword::newFromCentralId( $this->userId, $this->par );
109  if ( !$this->botPassword ) {
110  $this->botPassword = BotPassword::newUnsaved( [
111  'centralId' => $this->userId,
112  'appId' => $this->par,
113  ] );
114  }
115 
116  $sep = BotPassword::getSeparator();
117  $fields[] = [
118  'type' => 'info',
119  'label-message' => 'username',
120  'default' => $this->getUser()->getName() . $sep . $this->par
121  ];
122 
123  if ( $this->botPassword->isSaved() ) {
124  $fields['resetPassword'] = [
125  'type' => 'check',
126  'label-message' => 'botpasswords-label-resetpassword',
127  ];
128  if ( $this->botPassword->isInvalid() ) {
129  $fields['resetPassword']['default'] = true;
130  }
131  }
132 
133  $lang = $this->getLanguage();
134  $showGrants = MWGrants::getValidGrants();
135  $fields['grants'] = [
136  'type' => 'checkmatrix',
137  'label-message' => 'botpasswords-label-grants',
138  'help-message' => 'botpasswords-help-grants',
139  'columns' => [
140  $this->msg( 'botpasswords-label-grants-column' )->escaped() => 'grant'
141  ],
142  'rows' => array_combine(
143  array_map( 'MWGrants::getGrantsLink', $showGrants ),
144  $showGrants
145  ),
146  'default' => array_map(
147  function ( $g ) {
148  return "grant-$g";
149  },
150  $this->botPassword->getGrants()
151  ),
152  'tooltips' => array_combine(
153  array_map( 'MWGrants::getGrantsLink', $showGrants ),
154  array_map(
155  function ( $rights ) use ( $lang ) {
156  return $lang->semicolonList( array_map( 'User::getRightDescription', $rights ) );
157  },
158  array_intersect_key( MWGrants::getRightsByGrant(), array_flip( $showGrants ) )
159  )
160  ),
161  'force-options-on' => array_map(
162  function ( $g ) {
163  return "grant-$g";
164  },
166  ),
167  ];
168 
169  $fields['restrictions'] = [
170  'class' => HTMLRestrictionsField::class,
171  'required' => true,
172  'default' => $this->botPassword->getRestrictions(),
173  ];
174 
175  } else {
176  $linkRenderer = $this->getLinkRenderer();
177 
179  $res = $dbr->select(
180  'bot_passwords',
181  [ 'bp_app_id', 'bp_password' ],
182  [ 'bp_user' => $this->userId ],
183  __METHOD__
184  );
185  foreach ( $res as $row ) {
186  try {
187  $password = $this->passwordFactory->newFromCiphertext( $row->bp_password );
188  $passwordInvalid = $password instanceof InvalidPassword;
189  unset( $password );
190  } catch ( PasswordError $ex ) {
191  $passwordInvalid = true;
192  }
193 
194  $text = $linkRenderer->makeKnownLink(
195  $this->getPageTitle( $row->bp_app_id ),
196  $row->bp_app_id
197  );
198  if ( $passwordInvalid ) {
199  $text .= $this->msg( 'word-separator' )->escaped()
200  . $this->msg( 'botpasswords-label-needsreset' )->parse();
201  }
202 
203  $fields[] = [
204  'section' => 'existing',
205  'type' => 'info',
206  'raw' => true,
207  'default' => $text,
208  ];
209  }
210 
211  $fields['appId'] = [
212  'section' => 'createnew',
213  'type' => 'textwithbutton',
214  'label-message' => 'botpasswords-label-appid',
215  'buttondefault' => $this->msg( 'botpasswords-label-create' )->text(),
216  'buttonflags' => [ 'progressive', 'primary' ],
217  'required' => true,
219  'maxlength' => BotPassword::APPID_MAXLENGTH,
220  'validation-callback' => function ( $v ) {
221  $v = trim( $v );
222  return $v !== '' && strlen( $v ) <= BotPassword::APPID_MAXLENGTH;
223  },
224  ];
225 
226  $fields[] = [
227  'type' => 'hidden',
228  'default' => 'new',
229  'name' => 'op',
230  ];
231  }
232 
233  return $fields;
234  }
235 
236  protected function alterForm( HTMLForm $form ) {
237  $form->setId( 'mw-botpasswords-form' );
238  $form->setTableId( 'mw-botpasswords-table' );
239  $form->addPreText( $this->msg( 'botpasswords-summary' )->parseAsBlock() );
240  $form->suppressDefaultSubmit();
241 
242  if ( $this->par !== null ) {
243  if ( $this->botPassword->isSaved() ) {
244  $form->setWrapperLegendMsg( 'botpasswords-editexisting' );
245  $form->addButton( [
246  'name' => 'op',
247  'value' => 'update',
248  'label-message' => 'botpasswords-label-update',
249  'flags' => [ 'primary', 'progressive' ],
250  ] );
251  $form->addButton( [
252  'name' => 'op',
253  'value' => 'delete',
254  'label-message' => 'botpasswords-label-delete',
255  'flags' => [ 'destructive' ],
256  ] );
257  } else {
258  $form->setWrapperLegendMsg( 'botpasswords-createnew' );
259  $form->addButton( [
260  'name' => 'op',
261  'value' => 'create',
262  'label-message' => 'botpasswords-label-create',
263  'flags' => [ 'primary', 'progressive' ],
264  ] );
265  }
266 
267  $form->addButton( [
268  'name' => 'op',
269  'value' => 'cancel',
270  'label-message' => 'botpasswords-label-cancel'
271  ] );
272  }
273  }
274 
275  public function onSubmit( array $data ) {
276  $op = $this->getRequest()->getVal( 'op', '' );
277 
278  switch ( $op ) {
279  case 'new':
280  $this->getOutput()->redirect( $this->getPageTitle( $data['appId'] )->getFullURL() );
281  return false;
282 
283  case 'create':
284  $this->operation = 'insert';
285  return $this->save( $data );
286 
287  case 'update':
288  $this->operation = 'update';
289  return $this->save( $data );
290 
291  case 'delete':
292  $this->operation = 'delete';
293  $bp = BotPassword::newFromCentralId( $this->userId, $this->par );
294  if ( $bp ) {
295  $bp->delete();
296  $this->logger->info(
297  "Bot password {op} for {user}@{app_id}",
298  [
299  'app_id' => $this->par,
300  'user' => $this->getUser()->getName(),
301  'centralId' => $this->userId,
302  'op' => 'delete',
303  'client_ip' => $this->getRequest()->getIP()
304  ]
305  );
306  }
307  return Status::newGood();
308 
309  case 'cancel':
310  $this->getOutput()->redirect( $this->getPageTitle()->getFullURL() );
311  return false;
312  }
313 
314  return false;
315  }
316 
317  private function save( array $data ) {
318  $bp = BotPassword::newUnsaved( [
319  'centralId' => $this->userId,
320  'appId' => $this->par,
321  'restrictions' => $data['restrictions'],
322  'grants' => array_merge(
324  // @phan-suppress-next-next-line PhanTypeMismatchArgumentInternal See phan issue #3163,
325  // it's probably failing to infer the type of $data['grants']
326  preg_replace( '/^grant-/', '', $data['grants'] )
327  )
328  ] );
329 
330  if ( $this->operation === 'insert' || !empty( $data['resetPassword'] ) ) {
331  $this->password = BotPassword::generatePassword( $this->getConfig() );
332  $password = $this->passwordFactory->newFromPlaintext( $this->password );
333  } else {
334  $password = null;
335  }
336 
337  if ( $bp && $bp->save( $this->operation, $password ) ) {
338  $this->logger->info(
339  "Bot password {op} for {user}@{app_id}",
340  [
341  'op' => $this->operation,
342  'user' => $this->getUser()->getName(),
343  'app_id' => $this->par,
344  'centralId' => $this->userId,
345  'restrictions' => $data['restrictions'],
346  'grants' => $bp->getGrants(),
347  'client_ip' => $this->getRequest()->getIP()
348  ]
349  );
350  return Status::newGood();
351  }
352 
353  // Messages: botpasswords-insert-failed, botpasswords-update-failed
354  return Status::newFatal( "botpasswords-{$this->operation}-failed", $this->par );
355  }
356 
357  public function onSuccess() {
358  $out = $this->getOutput();
359 
360  $username = $this->getUser()->getName();
361  switch ( $this->operation ) {
362  case 'insert':
363  $out->setPageTitle( $this->msg( 'botpasswords-created-title' )->text() );
364  $out->addWikiMsg( 'botpasswords-created-body', $this->par, $username );
365  break;
366 
367  case 'update':
368  $out->setPageTitle( $this->msg( 'botpasswords-updated-title' )->text() );
369  $out->addWikiMsg( 'botpasswords-updated-body', $this->par, $username );
370  break;
371 
372  case 'delete':
373  $out->setPageTitle( $this->msg( 'botpasswords-deleted-title' )->text() );
374  $out->addWikiMsg( 'botpasswords-deleted-body', $this->par, $username );
375  $this->password = null;
376  break;
377  }
378 
379  if ( $this->password !== null ) {
380  $sep = BotPassword::getSeparator();
381  $out->addWikiMsg(
382  'botpasswords-newpassword',
383  htmlspecialchars( $username . $sep . $this->par ),
384  htmlspecialchars( $this->password ),
385  htmlspecialchars( $username ),
386  htmlspecialchars( $this->par . $sep . $this->password )
387  );
388  $this->password = null;
389  }
390 
391  $out->addReturnTo( $this->getPageTitle() );
392  }
393 
394  protected function getGroupName() {
395  return 'users';
396  }
397 
398  protected function getDisplayFormat() {
399  return 'ooui';
400  }
401 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:75
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:701
SpecialBotPasswords
Let users manage bot passwords.
Definition: SpecialBotPasswords.php:31
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:831
HTMLForm\suppressDefaultSubmit
suppressDefaultSubmit( $suppressSubmit=true)
Stop a default submit button being shown for this form.
Definition: HTMLForm.php:1497
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:747
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
SpecialBotPasswords\checkExecutePermissions
checkExecutePermissions(User $user)
Called from execute() to check if the given user can perform this action.
Definition: SpecialBotPasswords.php:91
BotPassword
Utility class for bot passwords.
Definition: BotPassword.php:32
SpecialBotPasswords\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialBotPasswords.php:394
BotPassword\getSeparator
static getSeparator()
Get the separator for combined user name + app ID.
Definition: BotPassword.php:237
SpecialBotPasswords\save
save(array $data)
Definition: SpecialBotPasswords.php:317
BotPassword\generatePassword
static generatePassword( $config)
Returns a (raw, unhashed) random password string.
Definition: BotPassword.php:420
PasswordError
Show an error when any operation involving passwords fails to run.
Definition: PasswordError.php:29
FormSpecialPage
Special page which uses an HTMLForm to handle processing.
Definition: FormSpecialPage.php:31
SpecialBotPasswords\$passwordFactory
PasswordFactory $passwordFactory
Definition: SpecialBotPasswords.php:49
SpecialBotPasswords\getFormFields
getFormFields()
Get an HTMLForm descriptor array.
Definition: SpecialBotPasswords.php:104
InvalidPassword
Represents an invalid password hash.
Definition: InvalidPassword.php:34
BotPassword\getDB
static getDB( $db)
Get a database connection for the bot passwords database.
Definition: BotPassword.php:83
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:777
BotPassword\APPID_MAXLENGTH
const APPID_MAXLENGTH
Definition: BotPassword.php:34
$res
$res
Definition: testCompression.php:57
SpecialPage\getName
getName()
Get the name of this Special Page.
Definition: SpecialPage.php:168
MWGrants\getHiddenGrants
static getHiddenGrants()
Get the list of grants that are hidden and should always be granted.
Definition: MWGrants.php:159
$dbr
$dbr
Definition: testCompression.php:54
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:867
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:797
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
HTMLForm\addButton
addButton( $data)
Add a button to the form.
Definition: HTMLForm.php:1007
BotPassword\newUnsaved
static newUnsaved(array $data, $flags=self::READ_NORMAL)
Create an unsaved BotPassword.
Definition: BotPassword.php:145
SpecialBotPasswords\onSuccess
onSuccess()
Do something exciting on successful processing of the form, most likely to show a confirmation messag...
Definition: SpecialBotPasswords.php:357
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:757
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
SpecialBotPasswords\$logger
Psr Log LoggerInterface $logger
Definition: SpecialBotPasswords.php:46
MWGrants\getValidGrants
static getValidGrants()
List all known grants.
Definition: MWGrants.php:31
SpecialBotPasswords\getDisplayFormat
getDisplayFormat()
Get display format for the form.
Definition: SpecialBotPasswords.php:398
SpecialBotPasswords\alterForm
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
Definition: SpecialBotPasswords.php:236
SpecialBotPasswords\__construct
__construct(PasswordFactory $passwordFactory)
Definition: SpecialBotPasswords.php:54
FormSpecialPage\$par
string null $par
The sub-page of the special page.
Definition: FormSpecialPage.php:36
SpecialPage\requireLogin
requireLogin( $reasonMsg='exception-nologin-text', $titleMsg='exception-nologin')
If the user is not logged in, throws UserNotLoggedIn error.
Definition: SpecialPage.php:377
SpecialBotPasswords\$password
string $password
New password set, for communication between onSubmit() and onSuccess()
Definition: SpecialBotPasswords.php:43
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
HTMLForm\setId
setId( $id)
Definition: HTMLForm.php:1545
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:737
BotPassword\newFromCentralId
static newFromCentralId( $centralId, $appId, $flags=self::READ_NORMAL)
Load a BotPassword from the database.
Definition: BotPassword.php:114
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:948
SpecialBotPasswords\isListed
isListed()
Definition: SpecialBotPasswords.php:63
SpecialBotPasswords\$userId
int $userId
Central user ID.
Definition: SpecialBotPasswords.php:34
HTMLForm\setWrapperLegendMsg
setWrapperLegendMsg( $msg)
Prompt the whole form to be wrapped in a "<fieldset>", with this message as its "<legend>" element.
Definition: HTMLForm.php:1600
MWGrants\getRightsByGrant
static getRightsByGrant()
Map all grants to corresponding user rights.
Definition: MWGrants.php:41
HTMLForm\setTableId
setTableId( $id)
Set the id of the <table> or outermost <div> element.
Definition: HTMLForm.php:1534
SpecialBotPasswords\onSubmit
onSubmit(array $data)
Process the form on POST submission.
Definition: SpecialBotPasswords.php:275
HTMLForm\addPreText
addPreText( $msg)
Add HTML to introductory message.
Definition: HTMLForm.php:795
PasswordFactory
Factory class for creating and checking Password objects.
Definition: PasswordFactory.php:30
SpecialBotPasswords\$operation
string $operation
Operation being performed: create, update, delete.
Definition: SpecialBotPasswords.php:40
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:30
CentralIdLookup\factory
static factory( $providerId=null)
Fetch a CentralIdLookup.
Definition: CentralIdLookup.php:47
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
SpecialBotPasswords\execute
execute( $par)
Main execution point.
Definition: SpecialBotPasswords.php:75
SpecialBotPasswords\$botPassword
BotPassword null $botPassword
Bot password being edited, if any.
Definition: SpecialBotPasswords.php:37
SpecialBotPasswords\getLoginSecurityLevel
getLoginSecurityLevel()
Tells if the special page does something security-sensitive and needs extra defense against a stolen ...
Definition: SpecialBotPasswords.php:67
HTMLForm
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition: HTMLForm.php:135