40use Psr\Log\LoggerInterface;
53 private $botPassword =
null;
56 private $operation =
null;
59 private $password =
null;
61 private LoggerInterface $logger;
81 parent::__construct(
'BotPasswords',
'editmyprivateinfo' );
82 $this->logger = LoggerFactory::getInstance(
'authentication' );
83 $this->passwordFactory = $passwordFactory;
84 $this->centralIdLookup = $centralIdLookup;
86 $this->grantsInfo = $grantsInfo;
87 $this->grantsLocalization = $grantsLocalization;
108 $this->
getOutput()->addModuleStyles(
'mediawiki.special' );
111 if (
$par !==
null ) {
115 } elseif ( strlen(
$par ) > BotPassword::APPID_MAXLENGTH ) {
117 'botpasswords',
'botpasswords-bad-appid', [ htmlspecialchars(
$par ) ]
122 parent::execute(
$par );
126 parent::checkExecutePermissions( $user );
129 throw new ErrorPageError(
'botpasswords',
'botpasswords-disabled' );
132 $this->userId = $this->centralIdLookup->centralIdFromLocalUser( $this->
getUser() );
133 if ( !$this->userId ) {
134 throw new ErrorPageError(
'botpasswords',
'botpasswords-no-central-id' );
141 if ( $this->par !==
null ) {
142 $this->botPassword = BotPassword::newFromCentralId( $this->userId, $this->par );
143 if ( !$this->botPassword ) {
144 $this->botPassword = BotPassword::newUnsaved( [
145 'centralId' => $this->userId,
146 'appId' => $this->par,
150 $sep = BotPassword::getSeparator();
153 'label-message' =>
'username',
157 if ( $this->botPassword->isSaved() ) {
158 $fields[
'resetPassword'] = [
160 'label-message' =>
'botpasswords-label-resetpassword',
162 if ( $this->botPassword->isInvalid() ) {
163 $fields[
'resetPassword'][
'default'] =
true;
167 $showGrants = $this->grantsInfo->getValidGrants();
168 $grantNames = $this->grantsLocalization->getGrantDescriptionsWithClasses(
174 'help-message' =>
'botpasswords-help-grants',
176 $fields[
'grants'] = [
177 'type' =>
'checkmatrix',
178 'label-message' =>
'botpasswords-label-grants',
180 $this->
msg(
'botpasswords-label-grants-column' )->escaped() =>
'grant'
182 'rows' => array_combine(
186 'default' => array_map(
187 static function ( $g ) {
190 $this->botPassword->getGrants()
192 'tooltips-html' => array_combine(
195 fn ( $rights ) => Html::rawElement(
'ul', [], implode(
'', array_map(
196 fn ( $right ) => Html::rawElement(
'li', [], $this->
msg(
"right-$right" )->parse() ),
199 array_intersect_key( $this->grantsInfo->getRightsByGrant(),
200 array_fill_keys( $showGrants,
true ) )
203 'force-options-on' => array_map(
204 static function ( $g ) {
207 $this->grantsInfo->getHiddenGrants()
211 $fields[
'restrictions'] = [
212 'class' => HTMLRestrictionsField::class,
214 'default' => $this->botPassword->getRestrictions(),
220 $dbr = BotPassword::getReplicaDatabase();
221 $res = $dbr->newSelectQueryBuilder()
222 ->select( [
'bp_app_id',
'bp_password' ] )
223 ->from(
'bot_passwords' )
224 ->where( [
'bp_user' => $this->userId ] )
225 ->caller( __METHOD__ )->fetchResultSet();
226 foreach ( $res as $row ) {
228 $password = $this->passwordFactory->newFromCiphertext( $row->bp_password );
232 $passwordInvalid =
true;
235 $text = $linkRenderer->makeKnownLink(
239 if ( $passwordInvalid ) {
240 $text .= $this->
msg(
'word-separator' )->escaped()
241 . $this->
msg(
'botpasswords-label-needsreset' )->parse();
245 'section' =>
'existing',
253 'section' =>
'createnew',
254 'type' =>
'textwithbutton',
255 'label-message' =>
'botpasswords-label-appid',
256 'buttondefault' => $this->
msg(
'botpasswords-label-create' )->text(),
257 'buttonflags' => [
'progressive',
'primary' ],
259 'size' => BotPassword::APPID_MAXLENGTH,
260 'maxlength' => BotPassword::APPID_MAXLENGTH,
261 'validation-callback' =>
static function ( $v ) {
263 return $v !==
'' && strlen( $v ) <= BotPassword::APPID_MAXLENGTH;
278 $form->
setId(
'mw-botpasswords-form' );
282 if ( $this->par !==
null ) {
283 if ( $this->botPassword->isSaved() ) {
288 'label-message' =>
'botpasswords-label-update',
289 'flags' => [
'primary',
'progressive' ],
294 'label-message' =>
'botpasswords-label-delete',
295 'flags' => [
'destructive' ],
302 'label-message' =>
'botpasswords-label-create',
303 'flags' => [
'primary',
'progressive' ],
310 'label-message' =>
'botpasswords-label-cancel'
316 $op = $this->
getRequest()->getVal(
'op',
'' );
324 $this->operation =
'insert';
325 return $this->save( $data );
328 $this->operation =
'update';
329 return $this->save( $data );
332 $this->operation =
'delete';
333 $bp = BotPassword::newFromCentralId( $this->userId, $this->par );
337 "Bot password {op} for {user}@{app_id}",
339 'app_id' => $this->par,
341 'centralId' => $this->userId,
347 return Status::newGood();
357 private function save( array $data ) {
358 $bp = BotPassword::newUnsaved( [
359 'centralId' => $this->userId,
360 'appId' => $this->par,
361 'restrictions' => $data[
'restrictions'],
362 'grants' => array_merge(
363 $this->grantsInfo->getHiddenGrants(),
366 preg_replace(
'/^grant-/',
'', $data[
'grants'] )
370 if ( $bp ===
null ) {
372 return Status::newFatal(
"botpasswords-{$this->operation}-failed", $this->par );
375 if ( $this->operation ===
'insert' || !empty( $data[
'resetPassword'] ) ) {
376 $this->password = BotPassword::generatePassword( $this->
getConfig() );
377 $password = $this->passwordFactory->newFromPlaintext( $this->password );
382 $res = $bp->save( $this->operation, $password );
387 'Bot password {op} for {user}@{app_id} ' . (
$success ?
'succeeded' :
'failed' ),
389 'op' => $this->operation,
391 'app_id' => $this->par,
392 'centralId' => $this->userId,
393 'restrictions' => $data[
'restrictions'],
394 'grants' => $bp->getGrants(),
406 $username = $this->
getUser()->getName();
407 switch ( $this->operation ) {
409 $out->setPageTitleMsg( $this->
msg(
'botpasswords-created-title' ) );
410 $out->addWikiMsg(
'botpasswords-created-body', $this->par, $username );
414 $out->setPageTitleMsg( $this->
msg(
'botpasswords-updated-title' ) );
415 $out->addWikiMsg(
'botpasswords-updated-body', $this->par, $username );
419 $out->setPageTitleMsg( $this->
msg(
'botpasswords-deleted-title' ) );
420 $out->addWikiMsg(
'botpasswords-deleted-body', $this->par, $username );
421 $this->password =
null;
425 if ( $this->password !==
null ) {
426 $sep = BotPassword::getSeparator();
428 'botpasswords-newpassword',
429 htmlspecialchars( $username . $sep . $this->par ),
430 htmlspecialchars( $this->password ),
431 htmlspecialchars( $username ),
432 htmlspecialchars( $this->par . $sep . $this->password )
434 $this->password =
null;
450class_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.