60 protected function __construct( $row, $isSaved, $flags = self::READ_NORMAL ) {
64 $this->centralId = (int)$row->bp_user;
65 $this->appId = $row->bp_app_id;
66 $this->token = $row->bp_token;
68 $this->grants = FormatJson::decode( $row->bp_grants );
76 public static function getDB( $db ) {
79 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
93 public static function newFromUser(
User $user, $appId, $flags = self::READ_NORMAL ) {
94 $centralId = CentralIdLookup::factory()->centralIdFromLocalUser(
95 $user, CentralIdLookup::AUDIENCE_RAW,
$flags
107 public static function newFromCentralId( $centralId, $appId, $flags = self::READ_NORMAL ) {
114 list( $index, $options ) = DBAccessObjectUtils::getDBOptions(
$flags );
116 $row = $db->selectRow(
118 [
'bp_user',
'bp_app_id',
'bp_token',
'bp_restrictions',
'bp_grants' ],
123 return $row ?
new self( $row,
true,
$flags ) :
null;
138 public static function newUnsaved( array $data, $flags = self::READ_NORMAL ) {
141 'bp_app_id' => isset( $data[
'appId'] ) ? trim( $data[
'appId'] ) :
'',
142 'bp_token' =>
'**unsaved**',
144 'bp_grants' => $data[
'grants'] ?? [],
148 $row->bp_app_id ===
'' || strlen( $row->bp_app_id ) > self::APPID_MAXLENGTH ||
150 !is_array( $row->bp_grants )
155 $row->bp_restrictions = $row->bp_restrictions->toJson();
156 $row->bp_grants = FormatJson::encode( $row->bp_grants );
158 if ( isset( $data[
'user'] ) ) {
159 if ( !$data[
'user'] instanceof
User ) {
162 $row->bp_user = CentralIdLookup::factory()->centralIdFromLocalUser(
163 $data[
'user'], CentralIdLookup::AUDIENCE_RAW,
$flags
165 } elseif ( isset( $data[
'username'] ) ) {
166 $row->bp_user = CentralIdLookup::factory()->centralIdFromName(
167 $data[
'username'], CentralIdLookup::AUDIENCE_RAW,
$flags
169 } elseif ( isset( $data[
'centralId'] ) ) {
170 $row->bp_user = $data[
'centralId'];
172 if ( !$row->bp_user ) {
176 return new self( $row,
false,
$flags );
184 return $this->isSaved;
192 return $this->centralId;
216 return $this->restrictions;
224 return $this->grants;
241 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->flags );
243 $password = $db->selectField(
246 [
'bp_user' => $this->centralId,
'bp_app_id' => $this->appId ],
250 if ( $password ===
false ) {
251 return PasswordFactory::newInvalidPassword();
254 $passwordFactory = MediaWikiServices::getInstance()->getPasswordFactory();
256 return $passwordFactory->newFromCiphertext( $password );
258 return PasswordFactory::newInvalidPassword();
279 'bp_user' => $this->centralId,
280 'bp_app_id' => $this->appId,
284 'bp_restrictions' => $this->restrictions->toJson(),
285 'bp_grants' => FormatJson::encode( $this->grants ),
288 if ( $password !==
null ) {
289 $fields[
'bp_password'] = $password->toString();
290 } elseif ( $operation ===
'insert' ) {
291 $fields[
'bp_password'] = PasswordFactory::newInvalidPassword()->toString();
295 switch ( $operation ) {
297 $dbw->insert(
'bot_passwords', $fields + $conds, __METHOD__, [
'IGNORE' ] );
301 $dbw->update(
'bot_passwords', $fields, $conds, __METHOD__ );
307 $ok = (bool)$dbw->affectedRows();
309 $this->token = $dbw->selectField(
'bot_passwords',
'bp_token', $conds, __METHOD__ );
319 public function delete() {
321 'bp_user' => $this->centralId,
322 'bp_app_id' => $this->appId,
325 $dbw->delete(
'bot_passwords', $conds, __METHOD__ );
326 $ok = (bool)$dbw->affectedRows();
328 $this->token =
'**unsaved**';
340 $centralId = CentralIdLookup::factory()->centralIdFromName(
341 $username, CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST
361 [
'bp_password' => PasswordFactory::newInvalidPassword()->toString() ],
365 return (
bool)$dbw->affectedRows();
374 $centralId = CentralIdLookup::factory()->centralIdFromName(
375 $username, CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST
398 return (
bool)$dbw->affectedRows();
407 return PasswordFactory::generateRandomPasswordString(
408 max( 32, $config->get(
'MinimalPasswordLength' ) ) );
421 $sep = self::getSeparator();
423 if ( strlen( $password ) >= 32 && strpos( $username, $sep ) !==
false ) {
425 if ( preg_match(
'/^[0-9a-w]{32,}$/', $password ) ) {
426 return [ $username, $password ];
428 } elseif ( strlen( $password ) > 32 && strpos( $password, $sep ) !==
false ) {
429 $segments = explode( $sep, $password );
430 $password = array_pop( $segments );
431 $appId = implode( $sep, $segments );
432 if ( preg_match(
'/^[0-9a-w]{32,}$/', $password ) ) {
433 return [ $username . $sep .
$appId, $password ];
450 return Status::newFatal(
'botpasswords-disabled' );
453 $manager = MediaWiki\Session\SessionManager::singleton();
454 $provider = $manager->getProvider( BotPasswordSessionProvider::class );
456 return Status::newFatal(
'botpasswords-no-provider' );
460 $sep = self::getSeparator();
461 if ( strpos( $username, $sep ) ===
false ) {
462 return self::loginHook( $username,
null, Status::newFatal(
'botpasswords-invalid-name', $sep ) );
464 list( $name,
$appId ) = explode( $sep, $username, 2 );
468 if ( !$user || $user->isAnon() ) {
469 return self::loginHook( $user ?: $name,
null, Status::newFatal(
'nosuchuser', $name ) );
472 if ( $user->isLocked() ) {
473 return Status::newFatal(
'botpasswords-locked' );
479 'type' =>
'botpassword',
480 'cache' => ObjectCache::getLocalClusterInstance(),
482 $result = $throttle->increase( $user->getName(), $request->
getIP(), __METHOD__ );
484 $msg =
wfMessage(
'login-throttled' )->durationParams( $result[
'wait'] );
485 return self::loginHook( $user,
null, Status::newFatal( $msg ) );
490 $bp = self::newFromUser( $user,
$appId );
492 return self::loginHook( $user, $bp,
493 Status::newFatal(
'botpasswords-not-exist', $name,
$appId ) );
497 $status = $bp->getRestrictions()->check( $request );
498 if ( !$status->isOK() ) {
499 return self::loginHook( $user, $bp, Status::newFatal(
'botpasswords-restriction-failed' ) );
503 $passwordObj = $bp->getPassword();
505 return self::loginHook( $user, $bp,
506 Status::newFatal(
'botpasswords-needs-reset', $name,
$appId ) );
508 if ( !$passwordObj->verify( $password ) ) {
509 return self::loginHook( $user, $bp, Status::newFatal(
'wrongpassword' ) );
514 $throttle->clear( $user->getName(), $request->
getIP() );
516 return self::loginHook( $user, $bp,
518 Status::newGood( $provider->newSessionForRequest( $user, $bp, $request ) ) );
534 if ( $user instanceof
User ) {
535 $name = $user->getName();
537 $extraData[
'appId'] = $name . self::getSeparator() . $bp->getAppId();
544 if ( $status->
isGood() ) {
545 $response = AuthenticationResponse::newPass( $name );
549 Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$response, $user, $name, $extraData ] );
$wgUserrightsInterwikiDelimiter
Character used as a delimiter when testing for interwiki userrights (In Special:UserRights,...
bool $wgEnableBotPasswords
Whether to enable bot passwords.
$wgPasswordAttemptThrottle
Limit password attempts to X attempts per Y seconds per IP per account.
string bool $wgBotPasswordsDatabase
Database name for the bot_passwords table.
string bool $wgBotPasswordsCluster
Cluster for the bot_passwords table If false, the normal cluster will be used.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Utility class for bot passwords.
static newFromUser(User $user, $appId, $flags=self::READ_NORMAL)
Load a BotPassword from the database.
isInvalid()
Whether the password is currently invalid.
__construct( $row, $isSaved, $flags=self::READ_NORMAL)
static invalidateAllPasswordsForUser( $username)
Invalidate all passwords for a user, by name.
static loginHook( $user, $bp, Status $status)
Call AuthManagerLoginAuthenticateAudit.
getRestrictions()
Get the restrictions.
MWRestrictions $restrictions
static generatePassword( $config)
Returns a (raw, unhashed) random password string.
getPassword()
Get the password.
getUserCentralId()
Get the central user ID.
getGrants()
Get the grants.
static getDB( $db)
Get a database connection for the bot passwords database.
getAppId()
Get the app ID.
static newFromCentralId( $centralId, $appId, $flags=self::READ_NORMAL)
Load a BotPassword from the database.
static login( $username, $password, WebRequest $request)
Try to log the user in.
static invalidateAllPasswordsForCentralId( $centralId)
Invalidate all passwords for a user, by central ID.
isSaved()
Indicate whether this is known to be saved.
static removeAllPasswordsForUser( $username)
Remove all passwords for a user, by name.
static removeAllPasswordsForCentralId( $centralId)
Remove all passwords for a user, by central ID.
static newUnsaved(array $data, $flags=self::READ_NORMAL)
Create an unsaved BotPassword.
static getSeparator()
Get the separator for combined user name + app ID.
save( $operation, Password $password=null)
Save the BotPassword to the database.
static canonicalizeLoginData( $username, $password)
There are two ways to login with a bot password: "username@appId", "password" and "username",...
Represents an invalid password hash.
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
A class to check request restrictions expressed as a JSON object.
static newFromJson( $json)
Show an error when any operation involving passwords fails to run.
Represents a password hash for use in authentication.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
getMessage( $shortContext=false, $longContext=false, $lang=null)
Get a bullet list of the errors as a Message object.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
const TOKEN_LENGTH
Number of characters required for the user_token field.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
Interface for database access objects.