42use Psr\Log\LoggerInterface;
55 private $botPassword =
null;
58 private $operation =
null;
61 private $password =
null;
63 private LoggerInterface $logger;
83 parent::__construct(
'BotPasswords',
'editmyprivateinfo' );
84 $this->logger = LoggerFactory::getInstance(
'authentication' );
85 $this->passwordFactory = $passwordFactory;
86 $this->centralIdLookup = $centralIdLookup;
88 $this->grantsInfo = $grantsInfo;
89 $this->grantsLocalization = $grantsLocalization;
112 if (
$par !==
null ) {
116 } elseif ( strlen(
$par ) > BotPassword::APPID_MAXLENGTH ) {
118 'botpasswords',
'botpasswords-bad-appid', [ htmlspecialchars(
$par ) ]
123 parent::execute(
$par );
127 parent::checkExecutePermissions( $user );
130 throw new ErrorPageError(
'botpasswords',
'botpasswords-disabled' );
133 $this->userId = $this->centralIdLookup->centralIdFromLocalUser( $this->
getUser() );
134 if ( !$this->userId ) {
135 throw new ErrorPageError(
'botpasswords',
'botpasswords-no-central-id' );
142 if ( $this->par !==
null ) {
143 $this->botPassword = BotPassword::newFromCentralId( $this->userId, $this->par );
144 if ( !$this->botPassword ) {
145 $this->botPassword = BotPassword::newUnsaved( [
146 'centralId' => $this->userId,
147 'appId' => $this->par,
151 $sep = BotPassword::getSeparator();
154 'label-message' =>
'username',
158 if ( $this->botPassword->isSaved() ) {
159 $fields[
'resetPassword'] = [
161 'label-message' =>
'botpasswords-label-resetpassword',
163 if ( $this->botPassword->isInvalid() ) {
164 $fields[
'resetPassword'][
'default'] =
true;
169 $showGrants = $this->grantsInfo->getValidGrants();
170 $grantLinks = array_map( [ $this->grantsLocalization,
'getGrantsLink' ], $showGrants );
175 'help-message' =>
'botpasswords-help-grants',
177 $fields[
'grants'] = [
178 'type' =>
'checkmatrix',
179 'label-message' =>
'botpasswords-label-grants',
181 $this->
msg(
'botpasswords-label-grants-column' )->escaped() =>
'grant'
183 'rows' => array_combine(
187 'default' => array_map(
188 static function ( $g ) {
191 $this->botPassword->getGrants()
193 'tooltips-html' => array_combine(
196 function ( $rights ) use ( $lang ) {
197 return $lang->semicolonList(
199 fn ( $right ) => $this->
msg(
"right-$right" )->parse(),
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(),
226 $res = $dbr->newSelectQueryBuilder()
227 ->select( [
'bp_app_id',
'bp_password' ] )
228 ->from(
'bot_passwords' )
229 ->where( [
'bp_user' => $this->userId ] )
230 ->caller( __METHOD__ )->fetchResultSet();
231 foreach ( $res as $row ) {
233 $password = $this->passwordFactory->newFromCiphertext( $row->bp_password );
237 $passwordInvalid =
true;
240 $text = $linkRenderer->makeKnownLink(
244 if ( $passwordInvalid ) {
245 $text .= $this->
msg(
'word-separator' )->escaped()
246 . $this->
msg(
'botpasswords-label-needsreset' )->parse();
250 'section' =>
'existing',
258 'section' =>
'createnew',
259 'type' =>
'textwithbutton',
260 'label-message' =>
'botpasswords-label-appid',
261 'buttondefault' => $this->
msg(
'botpasswords-label-create' )->text(),
262 'buttonflags' => [
'progressive',
'primary' ],
264 'size' => BotPassword::APPID_MAXLENGTH,
265 'maxlength' => BotPassword::APPID_MAXLENGTH,
266 'validation-callback' =>
static function ( $v ) {
268 return $v !==
'' && strlen( $v ) <= BotPassword::APPID_MAXLENGTH;
283 $form->
setId(
'mw-botpasswords-form' );
287 if ( $this->par !==
null ) {
288 if ( $this->botPassword->isSaved() ) {
293 'label-message' =>
'botpasswords-label-update',
294 'flags' => [
'primary',
'progressive' ],
299 'label-message' =>
'botpasswords-label-delete',
300 'flags' => [
'destructive' ],
307 'label-message' =>
'botpasswords-label-create',
308 'flags' => [
'primary',
'progressive' ],
315 'label-message' =>
'botpasswords-label-cancel'
321 $op = $this->
getRequest()->getVal(
'op',
'' );
329 $this->operation =
'insert';
330 return $this->save( $data );
333 $this->operation =
'update';
334 return $this->save( $data );
337 $this->operation =
'delete';
338 $bp = BotPassword::newFromCentralId( $this->userId, $this->par );
342 "Bot password {op} for {user}@{app_id}",
344 'app_id' => $this->par,
346 'centralId' => $this->userId,
352 return Status::newGood();
362 private function save( array $data ) {
363 $bp = BotPassword::newUnsaved( [
364 'centralId' => $this->userId,
365 'appId' => $this->par,
366 'restrictions' => $data[
'restrictions'],
367 'grants' => array_merge(
368 $this->grantsInfo->getHiddenGrants(),
371 preg_replace(
'/^grant-/',
'', $data[
'grants'] )
375 if ( $bp ===
null ) {
377 return Status::newFatal(
"botpasswords-{$this->operation}-failed", $this->par );
380 if ( $this->operation ===
'insert' || !empty( $data[
'resetPassword'] ) ) {
381 $this->password = BotPassword::generatePassword( $this->
getConfig() );
382 $password = $this->passwordFactory->newFromPlaintext( $this->password );
387 $res = $bp->save( $this->operation, $password );
392 'Bot password {op} for {user}@{app_id} ' . (
$success ?
'succeeded' :
'failed' ),
394 'op' => $this->operation,
396 'app_id' => $this->par,
397 'centralId' => $this->userId,
398 'restrictions' => $data[
'restrictions'],
399 'grants' => $bp->getGrants(),
411 $username = $this->
getUser()->getName();
412 switch ( $this->operation ) {
414 $out->setPageTitleMsg( $this->
msg(
'botpasswords-created-title' ) );
415 $out->addWikiMsg(
'botpasswords-created-body', $this->par, $username );
419 $out->setPageTitleMsg( $this->
msg(
'botpasswords-updated-title' ) );
420 $out->addWikiMsg(
'botpasswords-updated-body', $this->par, $username );
424 $out->setPageTitleMsg( $this->
msg(
'botpasswords-deleted-title' ) );
425 $out->addWikiMsg(
'botpasswords-deleted-body', $this->par, $username );
426 $this->password =
null;
430 if ( $this->password !==
null ) {
431 $sep = BotPassword::getSeparator();
433 'botpasswords-newpassword',
434 htmlspecialchars( $username . $sep . $this->par ),
435 htmlspecialchars( $this->password ),
436 htmlspecialchars( $username ),
437 htmlspecialchars( $this->par . $sep . $this->password )
439 $this->password =
null;
457class_alias( SpecialBotPasswords::class,
'SpecialBotPasswords' );
An error page which can definitely be safely rendered using the OutputPage.
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()
Special page which uses an HTMLForm to handle processing.
string null $par
The sub-page of the special page.
getUser()
Shortcut to get the User executing this instance.
setAuthManager(AuthManager $authManager)
Set the injected AuthManager from the special page constructor.
getPageTitle( $subpage=false)
Get a self-referential title object.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
requireNamedUser( $reasonMsg='exception-nologin-text', $titleMsg='exception-nologin')
If the user is not logged in or is a temporary user, throws UserNotLoggedIn.
getOutput()
Get the OutputPage being used for this instance.
getLanguage()
Shortcut to get user's language.
getName()
Get the name of this Special Page.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Show an error when any operation involving passwords fails to run.
Factory class for creating and checking Password objects.