2020-10-30 17:33:33 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* BotPassword interaction with databases
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
*
|
|
|
|
|
* @file
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace MediaWiki\User;
|
|
|
|
|
|
2021-07-15 20:27:29 +00:00
|
|
|
use IDBAccessObject;
|
2020-10-30 17:33:33 +00:00
|
|
|
use MediaWiki\Config\ServiceOptions;
|
2024-05-16 12:28:33 +00:00
|
|
|
use MediaWiki\Json\FormatJson;
|
2022-04-26 15:48:03 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2024-05-16 17:55:24 +00:00
|
|
|
use MediaWiki\Password\Password;
|
|
|
|
|
use MediaWiki\Password\PasswordFactory;
|
2023-09-19 16:42:44 +00:00
|
|
|
use MediaWiki\User\CentralId\CentralIdLookup;
|
2020-10-30 17:33:33 +00:00
|
|
|
use MWCryptRand;
|
2021-07-15 20:27:29 +00:00
|
|
|
use MWRestrictions;
|
2020-10-30 17:33:33 +00:00
|
|
|
use StatusValue;
|
2023-11-22 15:42:02 +00:00
|
|
|
use Wikimedia\Rdbms\IConnectionProvider;
|
2020-10-30 17:33:33 +00:00
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
2023-11-22 15:42:02 +00:00
|
|
|
use Wikimedia\Rdbms\IReadableDatabase;
|
2020-10-30 17:33:33 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @author DannyS712
|
|
|
|
|
* @since 1.37
|
|
|
|
|
*/
|
2024-02-19 08:58:53 +00:00
|
|
|
class BotPasswordStore {
|
2020-10-30 17:33:33 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal For use by ServiceWiring
|
|
|
|
|
*/
|
|
|
|
|
public const CONSTRUCTOR_OPTIONS = [
|
2022-04-26 15:48:03 +00:00
|
|
|
MainConfigNames::EnableBotPasswords,
|
2020-10-30 17:33:33 +00:00
|
|
|
];
|
|
|
|
|
|
2023-09-28 15:25:12 +00:00
|
|
|
private ServiceOptions $options;
|
2023-11-22 15:42:02 +00:00
|
|
|
private IConnectionProvider $dbProvider;
|
2023-09-28 15:25:12 +00:00
|
|
|
private CentralIdLookup $centralIdLookup;
|
2020-10-30 17:33:33 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param ServiceOptions $options
|
|
|
|
|
* @param CentralIdLookup $centralIdLookup
|
2023-11-22 15:42:02 +00:00
|
|
|
* @param IConnectionProvider $dbProvider
|
2020-10-30 17:33:33 +00:00
|
|
|
*/
|
|
|
|
|
public function __construct(
|
|
|
|
|
ServiceOptions $options,
|
|
|
|
|
CentralIdLookup $centralIdLookup,
|
2023-11-22 15:42:02 +00:00
|
|
|
IConnectionProvider $dbProvider
|
2020-10-30 17:33:33 +00:00
|
|
|
) {
|
|
|
|
|
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
|
|
|
|
$this->options = $options;
|
|
|
|
|
$this->centralIdLookup = $centralIdLookup;
|
2023-11-22 15:42:02 +00:00
|
|
|
$this->dbProvider = $dbProvider;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a database connection for the bot passwords database
|
|
|
|
|
* @return IReadableDatabase
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
|
|
|
|
public function getReplicaDatabase(): IReadableDatabase {
|
|
|
|
|
return $this->dbProvider->getReplicaDatabase( 'virtual-botpasswords' );
|
2020-10-30 17:33:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a database connection for the bot passwords database
|
|
|
|
|
* @return IDatabase
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2023-11-22 15:42:02 +00:00
|
|
|
public function getPrimaryDatabase(): IDatabase {
|
|
|
|
|
return $this->dbProvider->getPrimaryDatabase( 'virtual-botpasswords' );
|
2020-10-30 17:33:33 +00:00
|
|
|
}
|
|
|
|
|
|
2021-07-15 20:27:29 +00:00
|
|
|
/**
|
2021-07-21 17:57:10 +00:00
|
|
|
* Load a BotPassword from the database based on a UserIdentity object
|
|
|
|
|
* @param UserIdentity $userIdentity
|
2021-07-15 20:27:29 +00:00
|
|
|
* @param string $appId
|
|
|
|
|
* @param int $flags IDBAccessObject read flags
|
|
|
|
|
* @return BotPassword|null
|
|
|
|
|
*/
|
|
|
|
|
public function getByUser(
|
2021-07-21 17:57:10 +00:00
|
|
|
UserIdentity $userIdentity,
|
2021-07-15 20:27:29 +00:00
|
|
|
string $appId,
|
2024-01-10 21:15:28 +00:00
|
|
|
int $flags = IDBAccessObject::READ_NORMAL
|
2021-07-22 03:11:47 +00:00
|
|
|
): ?BotPassword {
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( !$this->options->get( MainConfigNames::EnableBotPasswords ) ) {
|
2021-07-15 20:27:29 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$centralId = $this->centralIdLookup->centralIdFromLocalUser(
|
2021-07-21 17:57:10 +00:00
|
|
|
$userIdentity,
|
2021-07-15 20:27:29 +00:00
|
|
|
CentralIdLookup::AUDIENCE_RAW,
|
|
|
|
|
$flags
|
|
|
|
|
);
|
|
|
|
|
return $centralId ? $this->getByCentralId( $centralId, $appId, $flags ) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load a BotPassword from the database
|
|
|
|
|
* @param int $centralId from CentralIdLookup
|
|
|
|
|
* @param string $appId
|
|
|
|
|
* @param int $flags IDBAccessObject read flags
|
|
|
|
|
* @return BotPassword|null
|
|
|
|
|
*/
|
|
|
|
|
public function getByCentralId(
|
|
|
|
|
int $centralId,
|
|
|
|
|
string $appId,
|
2024-01-10 21:15:28 +00:00
|
|
|
int $flags = IDBAccessObject::READ_NORMAL
|
2021-07-22 03:11:47 +00:00
|
|
|
): ?BotPassword {
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( !$this->options->get( MainConfigNames::EnableBotPasswords ) ) {
|
2021-07-15 20:27:29 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-15 14:29:26 +00:00
|
|
|
if ( ( $flags & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) {
|
2023-11-22 15:42:02 +00:00
|
|
|
$db = $this->dbProvider->getPrimaryDatabase( 'virtual-botpasswords' );
|
|
|
|
|
} else {
|
|
|
|
|
$db = $this->dbProvider->getReplicaDatabase( 'virtual-botpasswords' );
|
|
|
|
|
}
|
2023-07-25 13:37:41 +00:00
|
|
|
$row = $db->newSelectQueryBuilder()
|
|
|
|
|
->select( [ 'bp_user', 'bp_app_id', 'bp_token', 'bp_restrictions', 'bp_grants' ] )
|
|
|
|
|
->from( 'bot_passwords' )
|
|
|
|
|
->where( [ 'bp_user' => $centralId, 'bp_app_id' => $appId ] )
|
2024-01-10 21:48:50 +00:00
|
|
|
->recency( $flags )
|
2023-07-25 13:37:41 +00:00
|
|
|
->caller( __METHOD__ )->fetchRow();
|
2021-07-15 20:27:29 +00:00
|
|
|
return $row ? new BotPassword( $row, true, $flags ) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create an unsaved BotPassword
|
|
|
|
|
* @param array $data Data to use to create the bot password. Keys are:
|
2021-07-21 17:57:10 +00:00
|
|
|
* - user: (UserIdentity) UserIdentity to create the password for. Overrides username and centralId.
|
2021-07-15 20:27:29 +00:00
|
|
|
* - username: (string) Username to create the password for. Overrides centralId.
|
|
|
|
|
* - centralId: (int) User central ID to create the password for.
|
|
|
|
|
* - appId: (string, required) App ID for the password.
|
|
|
|
|
* - restrictions: (MWRestrictions, optional) Restrictions.
|
|
|
|
|
* - grants: (string[], optional) Grants.
|
|
|
|
|
* @param int $flags IDBAccessObject read flags
|
|
|
|
|
* @return BotPassword|null
|
|
|
|
|
*/
|
|
|
|
|
public function newUnsavedBotPassword(
|
|
|
|
|
array $data,
|
2024-01-10 21:15:28 +00:00
|
|
|
int $flags = IDBAccessObject::READ_NORMAL
|
2021-07-22 03:11:47 +00:00
|
|
|
): ?BotPassword {
|
2021-07-21 17:57:10 +00:00
|
|
|
if ( isset( $data['user'] ) && ( !$data['user'] instanceof UserIdentity ) ) {
|
2021-07-15 20:27:29 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$row = (object)[
|
|
|
|
|
'bp_user' => 0,
|
2021-11-02 09:39:38 +00:00
|
|
|
'bp_app_id' => trim( $data['appId'] ?? '' ),
|
2021-07-15 20:27:29 +00:00
|
|
|
'bp_token' => '**unsaved**',
|
|
|
|
|
'bp_restrictions' => $data['restrictions'] ?? MWRestrictions::newDefault(),
|
|
|
|
|
'bp_grants' => $data['grants'] ?? [],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
$row->bp_app_id === '' ||
|
|
|
|
|
strlen( $row->bp_app_id ) > BotPassword::APPID_MAXLENGTH ||
|
|
|
|
|
!$row->bp_restrictions instanceof MWRestrictions ||
|
|
|
|
|
!is_array( $row->bp_grants )
|
|
|
|
|
) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$row->bp_restrictions = $row->bp_restrictions->toJson();
|
|
|
|
|
$row->bp_grants = FormatJson::encode( $row->bp_grants );
|
|
|
|
|
|
|
|
|
|
if ( isset( $data['user'] ) ) {
|
2021-07-21 17:57:10 +00:00
|
|
|
// Must be a UserIdentity object, already checked above
|
2021-07-15 20:27:29 +00:00
|
|
|
$row->bp_user = $this->centralIdLookup->centralIdFromLocalUser(
|
|
|
|
|
$data['user'],
|
|
|
|
|
CentralIdLookup::AUDIENCE_RAW,
|
|
|
|
|
$flags
|
|
|
|
|
);
|
|
|
|
|
} elseif ( isset( $data['username'] ) ) {
|
|
|
|
|
$row->bp_user = $this->centralIdLookup->centralIdFromName(
|
|
|
|
|
$data['username'],
|
|
|
|
|
CentralIdLookup::AUDIENCE_RAW,
|
|
|
|
|
$flags
|
|
|
|
|
);
|
|
|
|
|
} elseif ( isset( $data['centralId'] ) ) {
|
|
|
|
|
$row->bp_user = $data['centralId'];
|
|
|
|
|
}
|
|
|
|
|
if ( !$row->bp_user ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new BotPassword( $row, false, $flags );
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-30 17:33:33 +00:00
|
|
|
/**
|
|
|
|
|
* Save the new BotPassword to the database
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
*
|
|
|
|
|
* @param BotPassword $botPassword
|
|
|
|
|
* @param Password|null $password Use null for an invalid password
|
|
|
|
|
* @return StatusValue if everything worked, the value of the StatusValue is the new token
|
|
|
|
|
*/
|
|
|
|
|
public function insertBotPassword(
|
|
|
|
|
BotPassword $botPassword,
|
|
|
|
|
Password $password = null
|
2021-07-22 03:11:47 +00:00
|
|
|
): StatusValue {
|
2020-10-30 17:33:33 +00:00
|
|
|
$res = $this->validateBotPassword( $botPassword );
|
|
|
|
|
if ( !$res->isGood() ) {
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $password === null ) {
|
|
|
|
|
$password = PasswordFactory::newInvalidPassword();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 15:42:02 +00:00
|
|
|
$dbw = $this->getPrimaryDatabase();
|
2023-08-03 20:05:22 +00:00
|
|
|
$dbw->newInsertQueryBuilder()
|
In query builders, use insertInto() and deleteFrom() instead of insert() and delete()
The design principle for SelectQueryBuilder was to make the chained
builder calls look as much like SQL as possible, so that developers
could leverage their knowledge of SQL to understand what the query
builder is doing.
That's why SelectQueryBuilder::select() takes a list of fields, and by
the same principle, it makes sense for UpdateQueryBuilder::update() to
take a table. However with "insert" and "delete", the SQL designers
chose to add prepositions "into" and "from", and I think it makes sense
to follow that here.
In terms of natural language, we update a table, but we don't delete a
table, or insert a table. We delete rows from a table, or insert rows
into a table. The table is not the object of the verb.
So, add insertInto() as an alias for insert(), and add deleteFrom() as
an alias for delete(). Use the new methods in MW core callers where
PHPStorm knows the type.
Change-Id: Idb327a54a57a0fb2288ea067472c1e9727016000
2023-09-08 00:06:59 +00:00
|
|
|
->insertInto( 'bot_passwords' )
|
2023-08-03 20:05:22 +00:00
|
|
|
->ignore()
|
|
|
|
|
->row( [
|
|
|
|
|
'bp_user' => $botPassword->getUserCentralId(),
|
|
|
|
|
'bp_app_id' => $botPassword->getAppId(),
|
|
|
|
|
'bp_token' => MWCryptRand::generateHex( User::TOKEN_LENGTH ),
|
|
|
|
|
'bp_restrictions' => $botPassword->getRestrictions()->toJson(),
|
|
|
|
|
'bp_grants' => FormatJson::encode( $botPassword->getGrants() ),
|
|
|
|
|
'bp_password' => $password->toString(),
|
|
|
|
|
] )
|
|
|
|
|
->caller( __METHOD__ )->execute();
|
2020-10-30 17:33:33 +00:00
|
|
|
|
|
|
|
|
$ok = (bool)$dbw->affectedRows();
|
|
|
|
|
if ( $ok ) {
|
2023-07-25 13:37:41 +00:00
|
|
|
$token = $dbw->newSelectQueryBuilder()
|
|
|
|
|
->select( 'bp_token' )
|
|
|
|
|
->from( 'bot_passwords' )
|
|
|
|
|
->where( [ 'bp_user' => $botPassword->getUserCentralId(), 'bp_app_id' => $botPassword->getAppId(), ] )
|
|
|
|
|
->caller( __METHOD__ )->fetchField();
|
2020-10-30 17:33:33 +00:00
|
|
|
return StatusValue::newGood( $token );
|
|
|
|
|
}
|
|
|
|
|
return StatusValue::newFatal( 'botpasswords-insert-failed', $botPassword->getAppId() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update an existing BotPassword in the database
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
*
|
|
|
|
|
* @param BotPassword $botPassword
|
|
|
|
|
* @param Password|null $password Use null for an invalid password
|
|
|
|
|
* @return StatusValue if everything worked, the value of the StatusValue is the new token
|
|
|
|
|
*/
|
|
|
|
|
public function updateBotPassword(
|
|
|
|
|
BotPassword $botPassword,
|
|
|
|
|
Password $password = null
|
2021-07-22 03:11:47 +00:00
|
|
|
): StatusValue {
|
2020-10-30 17:33:33 +00:00
|
|
|
$res = $this->validateBotPassword( $botPassword );
|
|
|
|
|
if ( !$res->isGood() ) {
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conds = [
|
|
|
|
|
'bp_user' => $botPassword->getUserCentralId(),
|
|
|
|
|
'bp_app_id' => $botPassword->getAppId(),
|
|
|
|
|
];
|
|
|
|
|
$fields = [
|
|
|
|
|
'bp_token' => MWCryptRand::generateHex( User::TOKEN_LENGTH ),
|
|
|
|
|
'bp_restrictions' => $botPassword->getRestrictions()->toJson(),
|
|
|
|
|
'bp_grants' => FormatJson::encode( $botPassword->getGrants() ),
|
|
|
|
|
];
|
|
|
|
|
if ( $password !== null ) {
|
|
|
|
|
$fields['bp_password'] = $password->toString();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 15:42:02 +00:00
|
|
|
$dbw = $this->getPrimaryDatabase();
|
2023-06-07 22:07:31 +00:00
|
|
|
$dbw->newUpdateQueryBuilder()
|
|
|
|
|
->update( 'bot_passwords' )
|
|
|
|
|
->set( $fields )
|
|
|
|
|
->where( $conds )
|
|
|
|
|
->caller( __METHOD__ )->execute();
|
2020-10-30 17:33:33 +00:00
|
|
|
|
|
|
|
|
$ok = (bool)$dbw->affectedRows();
|
|
|
|
|
if ( $ok ) {
|
2023-07-25 13:37:41 +00:00
|
|
|
$token = $dbw->newSelectQueryBuilder()
|
|
|
|
|
->select( 'bp_token' )
|
|
|
|
|
->from( 'bot_passwords' )
|
|
|
|
|
->where( $conds )
|
|
|
|
|
->caller( __METHOD__ )->fetchField();
|
2020-10-30 17:33:33 +00:00
|
|
|
return StatusValue::newGood( $token );
|
|
|
|
|
}
|
|
|
|
|
return StatusValue::newFatal( 'botpasswords-update-failed', $botPassword->getAppId() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a BotPassword is valid to save in the database (either inserting a new
|
|
|
|
|
* one or updating an existing one) based on the size of the restrictions and grants
|
|
|
|
|
*
|
|
|
|
|
* @param BotPassword $botPassword
|
|
|
|
|
* @return StatusValue
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
private function validateBotPassword( BotPassword $botPassword ): StatusValue {
|
2020-10-30 17:33:33 +00:00
|
|
|
$res = StatusValue::newGood();
|
|
|
|
|
|
|
|
|
|
$restrictions = $botPassword->getRestrictions()->toJson();
|
|
|
|
|
if ( strlen( $restrictions ) > BotPassword::RESTRICTIONS_MAXLENGTH ) {
|
|
|
|
|
$res->fatal( 'botpasswords-toolong-restrictions' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$grants = FormatJson::encode( $botPassword->getGrants() );
|
|
|
|
|
if ( strlen( $grants ) > BotPassword::GRANTS_MAXLENGTH ) {
|
|
|
|
|
$res->fatal( 'botpasswords-toolong-grants' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete an existing BotPassword in the database
|
|
|
|
|
*
|
|
|
|
|
* @param BotPassword $botPassword
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function deleteBotPassword( BotPassword $botPassword ): bool {
|
2023-11-22 15:42:02 +00:00
|
|
|
$dbw = $this->getPrimaryDatabase();
|
2023-04-29 22:03:51 +00:00
|
|
|
$dbw->newDeleteQueryBuilder()
|
In query builders, use insertInto() and deleteFrom() instead of insert() and delete()
The design principle for SelectQueryBuilder was to make the chained
builder calls look as much like SQL as possible, so that developers
could leverage their knowledge of SQL to understand what the query
builder is doing.
That's why SelectQueryBuilder::select() takes a list of fields, and by
the same principle, it makes sense for UpdateQueryBuilder::update() to
take a table. However with "insert" and "delete", the SQL designers
chose to add prepositions "into" and "from", and I think it makes sense
to follow that here.
In terms of natural language, we update a table, but we don't delete a
table, or insert a table. We delete rows from a table, or insert rows
into a table. The table is not the object of the verb.
So, add insertInto() as an alias for insert(), and add deleteFrom() as
an alias for delete(). Use the new methods in MW core callers where
PHPStorm knows the type.
Change-Id: Idb327a54a57a0fb2288ea067472c1e9727016000
2023-09-08 00:06:59 +00:00
|
|
|
->deleteFrom( 'bot_passwords' )
|
2023-04-29 22:03:51 +00:00
|
|
|
->where( [ 'bp_user' => $botPassword->getUserCentralId() ] )
|
|
|
|
|
->andWhere( [ 'bp_app_id' => $botPassword->getAppId() ] )
|
|
|
|
|
->caller( __METHOD__ )->execute();
|
2020-10-30 17:33:33 +00:00
|
|
|
|
|
|
|
|
return (bool)$dbw->affectedRows();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invalidate all passwords for a user, by name
|
2021-11-26 15:21:17 +00:00
|
|
|
* @param string $username
|
2020-10-30 17:33:33 +00:00
|
|
|
* @return bool Whether any passwords were invalidated
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function invalidateUserPasswords( string $username ): bool {
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( !$this->options->get( MainConfigNames::EnableBotPasswords ) ) {
|
2020-10-30 17:33:33 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$centralId = $this->centralIdLookup->centralIdFromName(
|
|
|
|
|
$username,
|
|
|
|
|
CentralIdLookup::AUDIENCE_RAW,
|
2024-01-23 14:01:06 +00:00
|
|
|
IDBAccessObject::READ_LATEST
|
2020-10-30 17:33:33 +00:00
|
|
|
);
|
|
|
|
|
if ( !$centralId ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 15:42:02 +00:00
|
|
|
$dbw = $this->getPrimaryDatabase();
|
2023-06-07 22:07:31 +00:00
|
|
|
$dbw->newUpdateQueryBuilder()
|
|
|
|
|
->update( 'bot_passwords' )
|
|
|
|
|
->set( [ 'bp_password' => PasswordFactory::newInvalidPassword()->toString() ] )
|
|
|
|
|
->where( [ 'bp_user' => $centralId ] )
|
|
|
|
|
->caller( __METHOD__ )->execute();
|
2020-10-30 17:33:33 +00:00
|
|
|
return (bool)$dbw->affectedRows();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove all passwords for a user, by name
|
2021-11-26 15:21:17 +00:00
|
|
|
* @param string $username
|
2020-10-30 17:33:33 +00:00
|
|
|
* @return bool Whether any passwords were removed
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function removeUserPasswords( string $username ): bool {
|
2022-04-26 15:48:03 +00:00
|
|
|
if ( !$this->options->get( MainConfigNames::EnableBotPasswords ) ) {
|
2020-10-30 17:33:33 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$centralId = $this->centralIdLookup->centralIdFromName(
|
|
|
|
|
$username,
|
|
|
|
|
CentralIdLookup::AUDIENCE_RAW,
|
2024-01-23 14:01:06 +00:00
|
|
|
IDBAccessObject::READ_LATEST
|
2020-10-30 17:33:33 +00:00
|
|
|
);
|
|
|
|
|
if ( !$centralId ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 15:42:02 +00:00
|
|
|
$dbw = $this->getPrimaryDatabase();
|
2023-04-29 22:03:51 +00:00
|
|
|
$dbw->newDeleteQueryBuilder()
|
In query builders, use insertInto() and deleteFrom() instead of insert() and delete()
The design principle for SelectQueryBuilder was to make the chained
builder calls look as much like SQL as possible, so that developers
could leverage their knowledge of SQL to understand what the query
builder is doing.
That's why SelectQueryBuilder::select() takes a list of fields, and by
the same principle, it makes sense for UpdateQueryBuilder::update() to
take a table. However with "insert" and "delete", the SQL designers
chose to add prepositions "into" and "from", and I think it makes sense
to follow that here.
In terms of natural language, we update a table, but we don't delete a
table, or insert a table. We delete rows from a table, or insert rows
into a table. The table is not the object of the verb.
So, add insertInto() as an alias for insert(), and add deleteFrom() as
an alias for delete(). Use the new methods in MW core callers where
PHPStorm knows the type.
Change-Id: Idb327a54a57a0fb2288ea067472c1e9727016000
2023-09-08 00:06:59 +00:00
|
|
|
->deleteFrom( 'bot_passwords' )
|
2023-04-29 22:03:51 +00:00
|
|
|
->where( [ 'bp_user' => $centralId ] )
|
|
|
|
|
->caller( __METHOD__ )->execute();
|
2020-10-30 17:33:33 +00:00
|
|
|
return (bool)$dbw->affectedRows();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|