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 );
172 $fields[
'grants'] = [
173 'type' =>
'checkmatrix',
174 'label-message' =>
'botpasswords-label-grants',
175 'help-message' =>
'botpasswords-help-grants',
177 $this->
msg(
'botpasswords-label-grants-column' )->escaped() =>
'grant'
179 'rows' => array_combine(
183 'default' => array_map(
184 static function ( $g ) {
187 $this->botPassword->getGrants()
189 'tooltips' => array_combine(
192 static function ( $rights ) use ( $lang ) {
193 return $lang->semicolonList( array_map( [ User::class,
'getRightDescription' ], $rights ) );
195 array_intersect_key( $this->grantsInfo->getRightsByGrant(),
196 array_fill_keys( $showGrants,
true ) )
199 'force-options-on' => array_map(
200 static function ( $g ) {
203 $this->grantsInfo->getHiddenGrants()
207 $fields[
'restrictions'] = [
208 'class' => HTMLRestrictionsField::class,
210 'default' => $this->botPassword->getRestrictions(),
217 $res = $dbr->newSelectQueryBuilder()
218 ->select( [
'bp_app_id',
'bp_password' ] )
219 ->from(
'bot_passwords' )
220 ->where( [
'bp_user' => $this->userId ] )
221 ->caller( __METHOD__ )->fetchResultSet();
222 foreach ( $res as $row ) {
224 $password = $this->passwordFactory->newFromCiphertext( $row->bp_password );
228 $passwordInvalid =
true;
231 $text = $linkRenderer->makeKnownLink(
235 if ( $passwordInvalid ) {
236 $text .= $this->
msg(
'word-separator' )->escaped()
237 . $this->
msg(
'botpasswords-label-needsreset' )->parse();
241 'section' =>
'existing',
249 'section' =>
'createnew',
250 'type' =>
'textwithbutton',
251 'label-message' =>
'botpasswords-label-appid',
252 'buttondefault' => $this->
msg(
'botpasswords-label-create' )->text(),
253 'buttonflags' => [
'progressive',
'primary' ],
255 'size' => BotPassword::APPID_MAXLENGTH,
256 'maxlength' => BotPassword::APPID_MAXLENGTH,
257 'validation-callback' =>
static function ( $v ) {
259 return $v !==
'' && strlen( $v ) <= BotPassword::APPID_MAXLENGTH;
274 $form->
setId(
'mw-botpasswords-form' );
278 if ( $this->par !==
null ) {
279 if ( $this->botPassword->isSaved() ) {
284 'label-message' =>
'botpasswords-label-update',
285 'flags' => [
'primary',
'progressive' ],
290 'label-message' =>
'botpasswords-label-delete',
291 'flags' => [
'destructive' ],
298 'label-message' =>
'botpasswords-label-create',
299 'flags' => [
'primary',
'progressive' ],
306 'label-message' =>
'botpasswords-label-cancel'
312 $op = $this->
getRequest()->getVal(
'op',
'' );
320 $this->operation =
'insert';
321 return $this->save( $data );
324 $this->operation =
'update';
325 return $this->save( $data );
328 $this->operation =
'delete';
329 $bp = BotPassword::newFromCentralId( $this->userId, $this->par );
333 "Bot password {op} for {user}@{app_id}",
335 'app_id' => $this->par,
337 'centralId' => $this->userId,
343 return Status::newGood();
353 private function save( array $data ) {
354 $bp = BotPassword::newUnsaved( [
355 'centralId' => $this->userId,
356 'appId' => $this->par,
357 'restrictions' => $data[
'restrictions'],
358 'grants' => array_merge(
359 $this->grantsInfo->getHiddenGrants(),
362 preg_replace(
'/^grant-/',
'', $data[
'grants'] )
366 if ( $bp ===
null ) {
368 return Status::newFatal(
"botpasswords-{$this->operation}-failed", $this->par );
371 if ( $this->operation ===
'insert' || !empty( $data[
'resetPassword'] ) ) {
372 $this->password = BotPassword::generatePassword( $this->
getConfig() );
373 $password = $this->passwordFactory->newFromPlaintext( $this->password );
378 $res = $bp->save( $this->operation, $password );
383 'Bot password {op} for {user}@{app_id} ' . (
$success ?
'succeeded' :
'failed' ),
385 'op' => $this->operation,
387 'app_id' => $this->par,
388 'centralId' => $this->userId,
389 'restrictions' => $data[
'restrictions'],
390 'grants' => $bp->getGrants(),
402 $username = $this->
getUser()->getName();
403 switch ( $this->operation ) {
405 $out->setPageTitleMsg( $this->
msg(
'botpasswords-created-title' ) );
406 $out->addWikiMsg(
'botpasswords-created-body', $this->par, $username );
410 $out->setPageTitleMsg( $this->
msg(
'botpasswords-updated-title' ) );
411 $out->addWikiMsg(
'botpasswords-updated-body', $this->par, $username );
415 $out->setPageTitleMsg( $this->
msg(
'botpasswords-deleted-title' ) );
416 $out->addWikiMsg(
'botpasswords-deleted-body', $this->par, $username );
417 $this->password =
null;
421 if ( $this->password !==
null ) {
422 $sep = BotPassword::getSeparator();
424 'botpasswords-newpassword',
425 htmlspecialchars( $username . $sep . $this->par ),
426 htmlspecialchars( $this->password ),
427 htmlspecialchars( $username ),
428 htmlspecialchars( $this->par . $sep . $this->password )
430 $this->password =
null;
448class_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.