40use Psr\Log\LoggerInterface;
53 private $botPassword =
null;
56 private $operation =
null;
59 private $password =
null;
61 private LoggerInterface $logger;
74 parent::__construct(
'BotPasswords',
'editmyprivateinfo' );
75 $this->logger = LoggerFactory::getInstance(
'authentication' );
76 $this->passwordFactory = $passwordFactory;
77 $this->centralIdLookup = $centralIdLookup;
79 $this->grantsInfo = $grantsInfo;
80 $this->grantsLocalization = $grantsLocalization;
101 $this->
getOutput()->addModuleStyles(
'mediawiki.special' );
104 if (
$par !==
null ) {
108 } elseif ( strlen(
$par ) > BotPassword::APPID_MAXLENGTH ) {
110 'botpasswords',
'botpasswords-bad-appid', [ htmlspecialchars(
$par ) ]
115 parent::execute(
$par );
119 parent::checkExecutePermissions( $user );
122 throw new ErrorPageError(
'botpasswords',
'botpasswords-disabled' );
125 $this->userId = $this->centralIdLookup->centralIdFromLocalUser( $this->
getUser() );
126 if ( !$this->userId ) {
127 throw new ErrorPageError(
'botpasswords',
'botpasswords-no-central-id' );
134 if ( $this->par !==
null ) {
135 $this->botPassword = BotPassword::newFromCentralId( $this->userId, $this->par );
136 if ( !$this->botPassword ) {
137 $this->botPassword = BotPassword::newUnsaved( [
138 'centralId' => $this->userId,
139 'appId' => $this->par,
143 $sep = BotPassword::getSeparator();
146 'label-message' =>
'username',
150 if ( $this->botPassword->isSaved() ) {
151 $fields[
'resetPassword'] = [
153 'label-message' =>
'botpasswords-label-resetpassword',
155 if ( $this->botPassword->isInvalid() ) {
156 $fields[
'resetPassword'][
'default'] =
true;
160 $showGrants = $this->grantsInfo->getValidGrants();
161 $grantNames = $this->grantsLocalization->getGrantDescriptionsWithClasses(
167 'help-message' =>
'botpasswords-help-grants',
169 $fields[
'grants'] = [
170 'type' =>
'checkmatrix',
171 'label-message' =>
'botpasswords-label-grants',
173 $this->
msg(
'botpasswords-label-grants-column' )->escaped() =>
'grant'
175 'rows' => array_combine(
179 'default' => array_map(
180 static function ( $g ) {
183 $this->botPassword->getGrants()
185 'tooltips-html' => array_combine(
188 fn ( $rights ) => Html::rawElement(
'ul', [], implode(
'', array_map(
189 fn ( $right ) => Html::rawElement(
'li', [], $this->
msg(
"right-$right" )->parse() ),
192 array_intersect_key( $this->grantsInfo->getRightsByGrant(),
193 array_fill_keys( $showGrants,
true ) )
196 'force-options-on' => array_map(
197 static function ( $g ) {
200 $this->grantsInfo->getHiddenGrants()
204 $fields[
'restrictions'] = [
205 'class' => HTMLRestrictionsField::class,
207 'default' => $this->botPassword->getRestrictions(),
213 $dbr = BotPassword::getReplicaDatabase();
214 $res = $dbr->newSelectQueryBuilder()
215 ->select( [
'bp_app_id',
'bp_password' ] )
216 ->from(
'bot_passwords' )
217 ->where( [
'bp_user' => $this->userId ] )
218 ->caller( __METHOD__ )->fetchResultSet();
219 foreach ( $res as $row ) {
221 $password = $this->passwordFactory->newFromCiphertext( $row->bp_password );
225 $passwordInvalid =
true;
228 $text = $linkRenderer->makeKnownLink(
232 if ( $passwordInvalid ) {
233 $text .= $this->
msg(
'word-separator' )->escaped()
234 . $this->
msg(
'botpasswords-label-needsreset' )->parse();
238 'section' =>
'existing',
246 'section' =>
'createnew',
247 'type' =>
'textwithbutton',
248 'label-message' =>
'botpasswords-label-appid',
249 'buttondefault' => $this->
msg(
'botpasswords-label-create' )->text(),
250 'buttonflags' => [
'progressive',
'primary' ],
252 'size' => BotPassword::APPID_MAXLENGTH,
253 'maxlength' => BotPassword::APPID_MAXLENGTH,
254 'validation-callback' =>
static function ( $v ) {
256 return $v !==
'' && strlen( $v ) <= BotPassword::APPID_MAXLENGTH;
271 $form->
setId(
'mw-botpasswords-form' );
275 if ( $this->par !==
null ) {
276 if ( $this->botPassword->isSaved() ) {
281 'label-message' =>
'botpasswords-label-update',
282 'flags' => [
'primary',
'progressive' ],
287 'label-message' =>
'botpasswords-label-delete',
288 'flags' => [
'destructive' ],
295 'label-message' =>
'botpasswords-label-create',
296 'flags' => [
'primary',
'progressive' ],
303 'label-message' =>
'botpasswords-label-cancel'
309 $op = $this->
getRequest()->getVal(
'op',
'' );
317 $this->operation =
'insert';
318 return $this->save( $data );
321 $this->operation =
'update';
322 return $this->save( $data );
325 $this->operation =
'delete';
326 $bp = BotPassword::newFromCentralId( $this->userId, $this->par );
330 "Bot password {op} for {user}@{app_id}",
332 'app_id' => $this->par,
334 'centralId' => $this->userId,
340 return Status::newGood();
350 private function save( array $data ):
Status {
352 'centralId' => $this->userId,
353 'appId' => $this->par,
354 'restrictions' => $data[
'restrictions'],
355 'grants' => array_merge(
356 $this->grantsInfo->getHiddenGrants(),
359 preg_replace(
'/^grant-/',
'', $data[
'grants'] )
363 if ( $bp ===
null ) {
365 return Status::newFatal(
"botpasswords-{$this->operation}-failed", $this->par );
368 if ( $this->operation ===
'insert' || !empty( $data[
'resetPassword'] ) ) {
369 $this->password = BotPassword::generatePassword( $this->
getConfig() );
370 $password = $this->passwordFactory->newFromPlaintext( $this->password );
375 $res = $bp->save( $this->operation, $password );
380 'Bot password {op} for {user}@{app_id} ' . (
$success ?
'succeeded' :
'failed' ),
382 'op' => $this->operation,
384 'app_id' => $this->par,
385 'centralId' => $this->userId,
386 'restrictions' => $data[
'restrictions'],
387 'grants' => $bp->getGrants(),
397 $out = $this->getOutput();
399 $username = $this->getUser()->getName();
400 switch ( $this->operation ) {
402 $out->setPageTitleMsg( $this->msg(
'botpasswords-created-title' ) );
403 $out->addWikiMsg(
'botpasswords-created-body', $this->par, $username );
407 $out->setPageTitleMsg( $this->msg(
'botpasswords-updated-title' ) );
408 $out->addWikiMsg(
'botpasswords-updated-body', $this->par, $username );
412 $out->setPageTitleMsg( $this->msg(
'botpasswords-deleted-title' ) );
413 $out->addWikiMsg(
'botpasswords-deleted-body', $this->par, $username );
414 $this->password =
null;
418 if ( $this->password !==
null ) {
419 $sep = BotPassword::getSeparator();
421 'botpasswords-newpassword',
422 htmlspecialchars( $username . $sep . $this->par ),
423 htmlspecialchars( $this->password ),
424 htmlspecialchars( $username ),
425 htmlspecialchars( $this->par . $sep . $this->password )
427 $this->password =
null;
430 $out->addReturnTo( $this->getPageTitle() );
443class_alias( SpecialBotPasswords::class,
'SpecialBotPasswords' );
An error page which can definitely be safely rendered using the OutputPage.
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 subpage of the special page.
getUser()
Shortcut to get the User executing this instance.
requireNamedUser( $reasonMsg='exception-nologin-text', $titleMsg='exception-nologin', bool $alwaysRedirectToLoginPage=false)
If the user is not logged in or is a temporary user, throws UserNotLoggedIn.
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.
getOutput()
Get the OutputPage being used for this instance.
getLanguage()
Shortcut to get user's language.
getName()
Get the canonical, unlocalized name of this special page without namespace.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.