MediaWiki  master
BotPasswordStore.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\User;
24 
25 use BotPassword;
26 use CentralIdLookup;
28 use FormatJson;
29 use IDBAccessObject;
31 use MWCryptRand;
32 use MWRestrictions;
33 use Password;
34 use PasswordFactory;
35 use StatusValue;
36 use User;
39 
45 
49  public const CONSTRUCTOR_OPTIONS = [
50  'EnableBotPasswords',
51  'BotPasswordsCluster',
52  'BotPasswordsDatabase',
53  ];
54 
56  private $options;
57 
59  private $lbFactory;
60 
63 
69  public function __construct(
73  ) {
74  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
75  $this->options = $options;
76  $this->centralIdLookup = $centralIdLookup;
77  $this->lbFactory = $lbFactory;
78  }
79 
86  public function getDatabase( int $db ): IDatabase {
87  if ( $this->options->get( 'BotPasswordsCluster' ) ) {
88  $loadBalancer = $this->lbFactory->getExternalLB(
89  $this->options->get( 'BotPasswordsCluster' )
90  );
91  } else {
92  $loadBalancer = $this->lbFactory->getMainLB(
93  $this->options->get( 'BotPasswordsDatabase' )
94  );
95  }
96  return $loadBalancer->getConnectionRef(
97  $db,
98  [],
99  $this->options->get( 'BotPasswordsDatabase' )
100  );
101  }
102 
110  public function getByUser(
111  UserIdentity $userIdentity,
112  string $appId,
113  int $flags = self::READ_NORMAL
114  ): ?BotPassword {
115  if ( !$this->options->get( 'EnableBotPasswords' ) ) {
116  return null;
117  }
118 
119  $centralId = $this->centralIdLookup->centralIdFromLocalUser(
120  $userIdentity,
122  $flags
123  );
124  return $centralId ? $this->getByCentralId( $centralId, $appId, $flags ) : null;
125  }
126 
134  public function getByCentralId(
135  int $centralId,
136  string $appId,
137  int $flags = self::READ_NORMAL
138  ): ?BotPassword {
139  if ( !$this->options->get( 'EnableBotPasswords' ) ) {
140  return null;
141  }
142 
143  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
144  $db = $this->getDatabase( $index );
145  $row = $db->selectRow(
146  'bot_passwords',
147  [ 'bp_user', 'bp_app_id', 'bp_token', 'bp_restrictions', 'bp_grants' ],
148  [ 'bp_user' => $centralId, 'bp_app_id' => $appId ],
149  __METHOD__,
150  $options
151  );
152  return $row ? new BotPassword( $row, true, $flags ) : null;
153  }
154 
167  public function newUnsavedBotPassword(
168  array $data,
169  int $flags = self::READ_NORMAL
170  ): ?BotPassword {
171  if ( isset( $data['user'] ) && ( !$data['user'] instanceof UserIdentity ) ) {
172  return null;
173  }
174 
175  $row = (object)[
176  'bp_user' => 0,
177  'bp_app_id' => trim( $data['appId'] ?? '' ),
178  'bp_token' => '**unsaved**',
179  'bp_restrictions' => $data['restrictions'] ?? MWRestrictions::newDefault(),
180  'bp_grants' => $data['grants'] ?? [],
181  ];
182 
183  if (
184  $row->bp_app_id === '' ||
185  strlen( $row->bp_app_id ) > BotPassword::APPID_MAXLENGTH ||
186  !$row->bp_restrictions instanceof MWRestrictions ||
187  !is_array( $row->bp_grants )
188  ) {
189  return null;
190  }
191 
192  $row->bp_restrictions = $row->bp_restrictions->toJson();
193  $row->bp_grants = FormatJson::encode( $row->bp_grants );
194 
195  if ( isset( $data['user'] ) ) {
196  // Must be a UserIdentity object, already checked above
197  $row->bp_user = $this->centralIdLookup->centralIdFromLocalUser(
198  $data['user'],
200  $flags
201  );
202  } elseif ( isset( $data['username'] ) ) {
203  $row->bp_user = $this->centralIdLookup->centralIdFromName(
204  $data['username'],
206  $flags
207  );
208  } elseif ( isset( $data['centralId'] ) ) {
209  $row->bp_user = $data['centralId'];
210  }
211  if ( !$row->bp_user ) {
212  return null;
213  }
214 
215  return new BotPassword( $row, false, $flags );
216  }
217 
227  public function insertBotPassword(
228  BotPassword $botPassword,
229  Password $password = null
230  ): StatusValue {
231  $res = $this->validateBotPassword( $botPassword );
232  if ( !$res->isGood() ) {
233  return $res;
234  }
235 
236  if ( $password === null ) {
238  }
239  $fields = [
240  'bp_user' => $botPassword->getUserCentralId(),
241  'bp_app_id' => $botPassword->getAppId(),
243  'bp_restrictions' => $botPassword->getRestrictions()->toJson(),
244  'bp_grants' => FormatJson::encode( $botPassword->getGrants() ),
245  'bp_password' => $password->toString(),
246  ];
247 
248  $dbw = $this->getDatabase( DB_PRIMARY );
249  $dbw->insert(
250  'bot_passwords',
251  $fields,
252  __METHOD__,
253  [ 'IGNORE' ]
254  );
255 
256  $ok = (bool)$dbw->affectedRows();
257  if ( $ok ) {
258  $token = $dbw->selectField(
259  'bot_passwords',
260  'bp_token',
261  [
262  'bp_user' => $botPassword->getUserCentralId(),
263  'bp_app_id' => $botPassword->getAppId(),
264  ],
265  __METHOD__
266  );
267  return StatusValue::newGood( $token );
268  }
269  return StatusValue::newFatal( 'botpasswords-insert-failed', $botPassword->getAppId() );
270  }
271 
281  public function updateBotPassword(
282  BotPassword $botPassword,
283  Password $password = null
284  ): StatusValue {
285  $res = $this->validateBotPassword( $botPassword );
286  if ( !$res->isGood() ) {
287  return $res;
288  }
289 
290  $conds = [
291  'bp_user' => $botPassword->getUserCentralId(),
292  'bp_app_id' => $botPassword->getAppId(),
293  ];
294  $fields = [
296  'bp_restrictions' => $botPassword->getRestrictions()->toJson(),
297  'bp_grants' => FormatJson::encode( $botPassword->getGrants() ),
298  ];
299  if ( $password !== null ) {
300  $fields['bp_password'] = $password->toString();
301  }
302 
303  $dbw = $this->getDatabase( DB_PRIMARY );
304  $dbw->update(
305  'bot_passwords',
306  $fields,
307  $conds,
308  __METHOD__
309  );
310 
311  $ok = (bool)$dbw->affectedRows();
312  if ( $ok ) {
313  $token = $dbw->selectField(
314  'bot_passwords',
315  'bp_token',
316  $conds,
317  __METHOD__
318  );
319  return StatusValue::newGood( $token );
320  }
321  return StatusValue::newFatal( 'botpasswords-update-failed', $botPassword->getAppId() );
322  }
323 
331  private function validateBotPassword( BotPassword $botPassword ): StatusValue {
333 
334  $restrictions = $botPassword->getRestrictions()->toJson();
335  if ( strlen( $restrictions ) > BotPassword::RESTRICTIONS_MAXLENGTH ) {
336  $res->fatal( 'botpasswords-toolong-restrictions' );
337  }
338 
339  $grants = FormatJson::encode( $botPassword->getGrants() );
340  if ( strlen( $grants ) > BotPassword::GRANTS_MAXLENGTH ) {
341  $res->fatal( 'botpasswords-toolong-grants' );
342  }
343 
344  return $res;
345  }
346 
353  public function deleteBotPassword( BotPassword $botPassword ): bool {
354  $dbw = $this->getDatabase( DB_PRIMARY );
355  $dbw->delete(
356  'bot_passwords',
357  [
358  'bp_user' => $botPassword->getUserCentralId(),
359  'bp_app_id' => $botPassword->getAppId(),
360  ],
361  __METHOD__
362  );
363 
364  return (bool)$dbw->affectedRows();
365  }
366 
372  public function invalidateUserPasswords( string $username ): bool {
373  if ( !$this->options->get( 'EnableBotPasswords' ) ) {
374  return false;
375  }
376 
377  $centralId = $this->centralIdLookup->centralIdFromName(
378  $username,
380  CentralIdLookup::READ_LATEST
381  );
382  if ( !$centralId ) {
383  return false;
384  }
385 
386  $dbw = $this->getDatabase( DB_PRIMARY );
387  $dbw->update(
388  'bot_passwords',
389  [ 'bp_password' => PasswordFactory::newInvalidPassword()->toString() ],
390  [ 'bp_user' => $centralId ],
391  __METHOD__
392  );
393  return (bool)$dbw->affectedRows();
394  }
395 
401  public function removeUserPasswords( string $username ): bool {
402  if ( !$this->options->get( 'EnableBotPasswords' ) ) {
403  return false;
404  }
405 
406  $centralId = $this->centralIdLookup->centralIdFromName(
407  $username,
409  CentralIdLookup::READ_LATEST
410  );
411  if ( !$centralId ) {
412  return false;
413  }
414 
415  $dbw = $this->getDatabase( DB_PRIMARY );
416  $dbw->delete(
417  'bot_passwords',
418  [ 'bp_user' => $centralId ],
419  __METHOD__
420  );
421  return (bool)$dbw->affectedRows();
422  }
423 
424 }
MWRestrictions
A class to check request restrictions expressed as a JSON object.
Definition: MWRestrictions.php:27
BotPassword\getRestrictions
getRestrictions()
Definition: BotPassword.php:181
StatusValue
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:43
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
BotPassword\getUserCentralId
getUserCentralId()
Get the central user ID.
Definition: BotPassword.php:160
BotPassword
Utility class for bot passwords.
Definition: BotPassword.php:33
MediaWiki\User\BotPasswordStore\__construct
__construct(ServiceOptions $options, CentralIdLookup $centralIdLookup, LBFactory $lbFactory)
Definition: BotPasswordStore.php:69
MediaWiki\User\BotPasswordStore\invalidateUserPasswords
invalidateUserPasswords(string $username)
Invalidate all passwords for a user, by name.
Definition: BotPasswordStore.php:372
BotPassword\getAppId
getAppId()
Definition: BotPassword.php:167
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
BotPassword\APPID_MAXLENGTH
const APPID_MAXLENGTH
Definition: BotPassword.php:35
$res
$res
Definition: testCompression.php:57
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:57
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
MediaWiki\User\BotPasswordStore\$options
ServiceOptions $options
Definition: BotPasswordStore.php:56
FormatJson\encode
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:96
FormatJson
JSON formatter wrapper class.
Definition: FormatJson.php:26
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\User\BotPasswordStore\getDatabase
getDatabase(int $db)
Get a database connection for the bot passwords database.
Definition: BotPasswordStore.php:86
BotPassword\RESTRICTIONS_MAXLENGTH
const RESTRICTIONS_MAXLENGTH
Maximum length of the json representation of restrictions.
Definition: BotPassword.php:46
MediaWiki\User\BotPasswordStore\deleteBotPassword
deleteBotPassword(BotPassword $botPassword)
Delete an existing BotPassword in the database.
Definition: BotPasswordStore.php:353
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:86
MediaWiki\User\BotPasswordStore\getByCentralId
getByCentralId(int $centralId, string $appId, int $flags=self::READ_NORMAL)
Load a BotPassword from the database.
Definition: BotPasswordStore.php:134
MediaWiki\User\BotPasswordStore\getByUser
getByUser(UserIdentity $userIdentity, string $appId, int $flags=self::READ_NORMAL)
Load a BotPassword from the database based on a UserIdentity object.
Definition: BotPasswordStore.php:110
MWRestrictions\newDefault
static newDefault()
Definition: MWRestrictions.php:44
DBAccessObjectUtils
Helper class for DAO classes.
Definition: DBAccessObjectUtils.php:29
MediaWiki\User\BotPasswordStore\validateBotPassword
validateBotPassword(BotPassword $botPassword)
Check if a BotPassword is valid to save in the database (either inserting a new one or updating an ex...
Definition: BotPasswordStore.php:331
MediaWiki\User\BotPasswordStore
Definition: BotPasswordStore.php:44
MediaWiki\User\BotPasswordStore\insertBotPassword
insertBotPassword(BotPassword $botPassword, Password $password=null)
Save the new BotPassword to the database.
Definition: BotPasswordStore.php:227
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
MediaWiki\User\BotPasswordStore\removeUserPasswords
removeUserPasswords(string $username)
Remove all passwords for a user, by name.
Definition: BotPasswordStore.php:401
MediaWiki\User
Definition: ActorCache.php:21
MediaWiki\User\BotPasswordStore\newUnsavedBotPassword
newUnsavedBotPassword(array $data, int $flags=self::READ_NORMAL)
Create an unsaved BotPassword.
Definition: BotPasswordStore.php:167
MWCryptRand
Definition: MWCryptRand.php:27
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
MediaWiki\User\BotPasswordStore\$centralIdLookup
CentralIdLookup $centralIdLookup
Definition: BotPasswordStore.php:62
BotPassword\getGrants
getGrants()
Definition: BotPassword.php:188
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:242
CentralIdLookup
The CentralIdLookup service allows for connecting local users with cluster-wide IDs.
Definition: CentralIdLookup.php:35
BotPassword\GRANTS_MAXLENGTH
const GRANTS_MAXLENGTH
Maximum length of the json representation of grants.
Definition: BotPassword.php:52
Wikimedia\Rdbms\LBFactory
An interface for generating database load balancers.
Definition: LBFactory.php:42
MediaWiki\User\BotPasswordStore\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: BotPasswordStore.php:49
PasswordFactory
Factory class for creating and checking Password objects.
Definition: PasswordFactory.php:30
CentralIdLookup\AUDIENCE_RAW
const AUDIENCE_RAW
Definition: CentralIdLookup.php:38
Password
Represents a password hash for use in authentication.
Definition: Password.php:61
MediaWiki\User\BotPasswordStore\updateBotPassword
updateBotPassword(BotPassword $botPassword, Password $password=null)
Update an existing BotPassword in the database.
Definition: BotPasswordStore.php:281
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:67
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:71
MediaWiki\User\BotPasswordStore\$lbFactory
LBFactory $lbFactory
Definition: BotPasswordStore.php:59