27use HTMLRestrictionsField;
43use Psr\Log\LoggerInterface;
56 private $botPassword =
null;
59 private $operation =
null;
62 private $password =
null;
64 private LoggerInterface $logger;
84 parent::__construct(
'BotPasswords',
'editmyprivateinfo' );
85 $this->logger = LoggerFactory::getInstance(
'authentication' );
86 $this->passwordFactory = $passwordFactory;
87 $this->centralIdLookup = $centralIdLookup;
89 $this->grantsInfo = $grantsInfo;
90 $this->grantsLocalization = $grantsLocalization;
111 $this->
getOutput()->addModuleStyles(
'mediawiki.special' );
114 if (
$par !==
null ) {
118 } elseif ( strlen(
$par ) > BotPassword::APPID_MAXLENGTH ) {
120 'botpasswords',
'botpasswords-bad-appid', [ htmlspecialchars(
$par ) ]
125 parent::execute(
$par );
129 parent::checkExecutePermissions( $user );
132 throw new ErrorPageError(
'botpasswords',
'botpasswords-disabled' );
135 $this->userId = $this->centralIdLookup->centralIdFromLocalUser( $this->
getUser() );
136 if ( !$this->userId ) {
137 throw new ErrorPageError(
'botpasswords',
'botpasswords-no-central-id' );
144 if ( $this->par !==
null ) {
145 $this->botPassword = BotPassword::newFromCentralId( $this->userId, $this->par );
146 if ( !$this->botPassword ) {
147 $this->botPassword = BotPassword::newUnsaved( [
148 'centralId' => $this->userId,
149 'appId' => $this->par,
153 $sep = BotPassword::getSeparator();
156 'label-message' =>
'username',
160 if ( $this->botPassword->isSaved() ) {
161 $fields[
'resetPassword'] = [
163 'label-message' =>
'botpasswords-label-resetpassword',
165 if ( $this->botPassword->isInvalid() ) {
166 $fields[
'resetPassword'][
'default'] =
true;
170 $showGrants = $this->grantsInfo->getValidGrants();
171 $grantNames = $this->grantsLocalization->getGrantDescriptionsWithClasses(
177 'help-message' =>
'botpasswords-help-grants',
179 $fields[
'grants'] = [
180 'type' =>
'checkmatrix',
181 'label-message' =>
'botpasswords-label-grants',
183 $this->
msg(
'botpasswords-label-grants-column' )->escaped() =>
'grant'
185 'rows' => array_combine(
189 'default' => array_map(
190 static function ( $g ) {
193 $this->botPassword->getGrants()
195 'tooltips-html' => array_combine(
198 fn ( $rights ) => Html::rawElement(
'ul', [], implode(
'', array_map(
199 fn ( $right ) => Html::rawElement(
'li', [], $this->
msg(
"right-$right" )->parse() ),
202 array_intersect_key( $this->grantsInfo->getRightsByGrant(),
203 array_fill_keys( $showGrants,
true ) )
206 'force-options-on' => array_map(
207 static function ( $g ) {
210 $this->grantsInfo->getHiddenGrants()
214 $fields[
'restrictions'] = [
215 'class' => HTMLRestrictionsField::class,
217 'default' => $this->botPassword->getRestrictions(),
223 $dbr = BotPassword::getReplicaDatabase();
224 $res = $dbr->newSelectQueryBuilder()
225 ->select( [
'bp_app_id',
'bp_password' ] )
226 ->from(
'bot_passwords' )
227 ->where( [
'bp_user' => $this->userId ] )
228 ->caller( __METHOD__ )->fetchResultSet();
229 foreach ( $res as $row ) {
231 $password = $this->passwordFactory->newFromCiphertext( $row->bp_password );
235 $passwordInvalid =
true;
238 $text = $linkRenderer->makeKnownLink(
242 if ( $passwordInvalid ) {
243 $text .= $this->
msg(
'word-separator' )->escaped()
244 . $this->
msg(
'botpasswords-label-needsreset' )->parse();
248 'section' =>
'existing',
256 'section' =>
'createnew',
257 'type' =>
'textwithbutton',
258 'label-message' =>
'botpasswords-label-appid',
259 'buttondefault' => $this->
msg(
'botpasswords-label-create' )->text(),
260 'buttonflags' => [
'progressive',
'primary' ],
262 'size' => BotPassword::APPID_MAXLENGTH,
263 'maxlength' => BotPassword::APPID_MAXLENGTH,
264 'validation-callback' =>
static function ( $v ) {
266 return $v !==
'' && strlen( $v ) <= BotPassword::APPID_MAXLENGTH;
281 $form->
setId(
'mw-botpasswords-form' );
285 if ( $this->par !==
null ) {
286 if ( $this->botPassword->isSaved() ) {
291 'label-message' =>
'botpasswords-label-update',
292 'flags' => [
'primary',
'progressive' ],
297 'label-message' =>
'botpasswords-label-delete',
298 'flags' => [
'destructive' ],
305 'label-message' =>
'botpasswords-label-create',
306 'flags' => [
'primary',
'progressive' ],
313 'label-message' =>
'botpasswords-label-cancel'
319 $op = $this->
getRequest()->getVal(
'op',
'' );
327 $this->operation =
'insert';
328 return $this->save( $data );
331 $this->operation =
'update';
332 return $this->save( $data );
335 $this->operation =
'delete';
336 $bp = BotPassword::newFromCentralId( $this->userId, $this->par );
340 "Bot password {op} for {user}@{app_id}",
342 'app_id' => $this->par,
344 'centralId' => $this->userId,
350 return Status::newGood();
360 private function save( array $data ) {
361 $bp = BotPassword::newUnsaved( [
362 'centralId' => $this->userId,
363 'appId' => $this->par,
364 'restrictions' => $data[
'restrictions'],
365 'grants' => array_merge(
366 $this->grantsInfo->getHiddenGrants(),
369 preg_replace(
'/^grant-/',
'', $data[
'grants'] )
373 if ( $bp ===
null ) {
375 return Status::newFatal(
"botpasswords-{$this->operation}-failed", $this->par );
378 if ( $this->operation ===
'insert' || !empty( $data[
'resetPassword'] ) ) {
379 $this->password = BotPassword::generatePassword( $this->
getConfig() );
380 $password = $this->passwordFactory->newFromPlaintext( $this->password );
385 $res = $bp->save( $this->operation, $password );
390 'Bot password {op} for {user}@{app_id} ' . (
$success ?
'succeeded' :
'failed' ),
392 'op' => $this->operation,
394 'app_id' => $this->par,
395 'centralId' => $this->userId,
396 'restrictions' => $data[
'restrictions'],
397 'grants' => $bp->getGrants(),
409 $username = $this->
getUser()->getName();
410 switch ( $this->operation ) {
412 $out->setPageTitleMsg( $this->
msg(
'botpasswords-created-title' ) );
413 $out->addWikiMsg(
'botpasswords-created-body', $this->par, $username );
417 $out->setPageTitleMsg( $this->
msg(
'botpasswords-updated-title' ) );
418 $out->addWikiMsg(
'botpasswords-updated-body', $this->par, $username );
422 $out->setPageTitleMsg( $this->
msg(
'botpasswords-deleted-title' ) );
423 $out->addWikiMsg(
'botpasswords-deleted-body', $this->par, $username );
424 $this->password =
null;
428 if ( $this->password !==
null ) {
429 $sep = BotPassword::getSeparator();
431 'botpasswords-newpassword',
432 htmlspecialchars( $username . $sep . $this->par ),
433 htmlspecialchars( $this->password ),
434 htmlspecialchars( $username ),
435 htmlspecialchars( $this->par . $sep . $this->password )
437 $this->password =
null;
453class_alias( SpecialBotPasswords::class,
'SpecialBotPasswords' );
An error page which can definitely be safely rendered using the OutputPage.
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 canonical, unlocalized name of this special page without namespace.
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.