26use Psr\Log\LoggerInterface;
39 private $botPassword =
null;
42 private $operation =
null;
45 private $password =
null;
47 private LoggerInterface $logger;
60 parent::__construct(
'BotPasswords',
'editmyprivateinfo' );
61 $this->logger = LoggerFactory::getInstance(
'authentication' );
62 $this->passwordFactory = $passwordFactory;
63 $this->centralIdLookup = $centralIdLookup;
65 $this->grantsInfo = $grantsInfo;
66 $this->grantsLocalization = $grantsLocalization;
88 $this->
getOutput()->addModuleStyles(
'mediawiki.special' );
91 if (
$par !==
null ) {
95 } elseif ( strlen(
$par ) > BotPassword::APPID_MAXLENGTH ) {
97 'botpasswords',
'botpasswords-bad-appid', [ htmlspecialchars(
$par ) ]
102 parent::execute(
$par );
107 parent::checkExecutePermissions( $user );
110 throw new ErrorPageError(
'botpasswords',
'botpasswords-disabled' );
113 $this->userId = $this->centralIdLookup->centralIdFromLocalUser( $this->
getUser() );
114 if ( !$this->userId ) {
115 throw new ErrorPageError(
'botpasswords',
'botpasswords-no-central-id' );
123 if ( $this->par !==
null ) {
124 $this->botPassword = BotPassword::newFromCentralId( $this->userId, $this->par );
125 if ( !$this->botPassword ) {
126 $this->botPassword = BotPassword::newUnsaved( [
127 'centralId' => $this->userId,
128 'appId' => $this->par,
132 $sep = BotPassword::getSeparator();
135 'label-message' =>
'username',
139 if ( $this->botPassword->isSaved() ) {
140 $fields[
'resetPassword'] = [
142 'label-message' =>
'botpasswords-label-resetpassword',
144 if ( $this->botPassword->isInvalid() ) {
145 $fields[
'resetPassword'][
'default'] =
true;
149 $showGrants = $this->grantsInfo->getValidGrants();
150 $grantNames = $this->grantsLocalization->getGrantDescriptionsWithClasses(
156 'help-message' =>
'botpasswords-help-grants',
158 $fields[
'grants'] = [
159 'type' =>
'checkmatrix',
160 'label-message' =>
'botpasswords-label-grants',
162 $this->
msg(
'botpasswords-label-grants-column' )->escaped() =>
'grant'
164 'rows' => array_combine(
168 'default' => array_map(
169 static function ( $g ) {
172 $this->botPassword->getGrants()
174 'tooltips-html' => array_combine(
177 fn ( $rights ) => Html::rawElement(
'ul', [], implode(
'', array_map(
178 fn ( $right ) => Html::rawElement(
'li', [], $this->
msg(
"right-$right" )->parse() ),
181 array_intersect_key( $this->grantsInfo->getRightsByGrant(),
182 array_fill_keys( $showGrants,
true ) )
185 'force-options-on' => array_map(
186 static function ( $g ) {
189 $this->grantsInfo->getHiddenGrants()
193 $fields[
'restrictions'] = [
194 'class' => HTMLRestrictionsField::class,
196 'default' => $this->botPassword->getRestrictions(),
202 $dbr = BotPassword::getReplicaDatabase();
203 $res = $dbr->newSelectQueryBuilder()
204 ->select( [
'bp_app_id',
'bp_password' ] )
205 ->from(
'bot_passwords' )
206 ->where( [
'bp_user' => $this->userId ] )
207 ->caller( __METHOD__ )->fetchResultSet();
208 foreach ( $res as $row ) {
210 $password = $this->passwordFactory->newFromCiphertext( $row->bp_password );
214 $passwordInvalid =
true;
217 $text = $linkRenderer->makeKnownLink(
221 if ( $passwordInvalid ) {
222 $text .= $this->
msg(
'word-separator' )->escaped()
223 . $this->
msg(
'botpasswords-label-needsreset' )->parse();
227 'section' =>
'existing',
235 'section' =>
'createnew',
236 'type' =>
'textwithbutton',
237 'label-message' =>
'botpasswords-label-appid',
238 'buttondefault' => $this->
msg(
'botpasswords-label-create' )->text(),
239 'buttonflags' => [
'progressive',
'primary' ],
241 'size' => BotPassword::APPID_MAXLENGTH,
242 'maxlength' => BotPassword::APPID_MAXLENGTH,
243 'validation-callback' =>
static function ( $v ) {
245 return $v !==
'' && strlen( $v ) <= BotPassword::APPID_MAXLENGTH;
260 $form->
setId(
'mw-botpasswords-form' );
264 if ( $this->par !==
null ) {
265 if ( $this->botPassword->isSaved() ) {
270 'label-message' =>
'botpasswords-label-update',
271 'flags' => [
'primary',
'progressive' ],
276 'label-message' =>
'botpasswords-label-delete',
277 'flags' => [
'destructive' ],
284 'label-message' =>
'botpasswords-label-create',
285 'flags' => [
'primary',
'progressive' ],
292 'label-message' =>
'botpasswords-label-cancel'
299 $op = $this->
getRequest()->getVal(
'op',
'' );
307 $this->operation =
'insert';
308 return $this->save( $data );
311 $this->operation =
'update';
312 return $this->save( $data );
315 $this->operation =
'delete';
316 $bp = BotPassword::newFromCentralId( $this->userId, $this->par );
320 "Bot password {op} for {user}@{app_id}",
322 'app_id' => $this->par,
324 'centralId' => $this->userId,
330 return Status::newGood();
340 private function save( array $data ):
Status {
342 'centralId' => $this->userId,
343 'appId' => $this->par,
344 'restrictions' => $data[
'restrictions'],
345 'grants' => array_merge(
346 $this->grantsInfo->getHiddenGrants(),
349 preg_replace(
'/^grant-/',
'', $data[
'grants'] )
353 if ( $bp ===
null ) {
355 return Status::newFatal(
"botpasswords-{$this->operation}-failed", $this->par );
358 if ( $this->operation ===
'insert' || !empty( $data[
'resetPassword'] ) ) {
359 $this->password = BotPassword::generatePassword();
360 $password = $this->passwordFactory->newFromPlaintext( $this->password );
365 $res = $bp->save( $this->operation, $password );
370 'Bot password {op} for {user}@{app_id} ' . (
$success ?
'succeeded' :
'failed' ),
372 'op' => $this->operation,
374 'app_id' => $this->par,
375 'centralId' => $this->userId,
376 'restrictions' => $data[
'restrictions'],
377 'grants' => $bp->getGrants(),
387 $out = $this->getOutput();
389 $username = $this->getUser()->getName();
390 switch ( $this->operation ) {
392 $out->setPageTitleMsg( $this->msg(
'botpasswords-created-title' ) );
393 $out->addWikiMsg(
'botpasswords-created-body', $this->par, $username );
397 $out->setPageTitleMsg( $this->msg(
'botpasswords-updated-title' ) );
398 $out->addWikiMsg(
'botpasswords-updated-body', $this->par, $username );
402 $out->setPageTitleMsg( $this->msg(
'botpasswords-deleted-title' ) );
403 $out->addWikiMsg(
'botpasswords-deleted-body', $this->par, $username );
404 $this->password =
null;
408 if ( $this->password !==
null ) {
409 $sep = BotPassword::getSeparator();
411 'botpasswords-newpassword',
412 htmlspecialchars( $username . $sep . $this->par ),
413 htmlspecialchars( $this->password ),
414 htmlspecialchars( $username ),
415 htmlspecialchars( $this->par . $sep . $this->password )
417 $this->password =
null;
420 $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.