MediaWiki REL1_37
BotPasswordStore.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\User;
24
25use BotPassword;
28use FormatJson;
31use MWCryptRand;
33use Password;
35use StatusValue;
36use 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' => isset( $data['appId'] ) ? 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'],
199 CentralIdLookup::AUDIENCE_RAW,
200 $flags
201 );
202 } elseif ( isset( $data['username'] ) ) {
203 $row->bp_user = $this->centralIdLookup->centralIdFromName(
204 $data['username'],
205 CentralIdLookup::AUDIENCE_RAW,
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 ) {
237 $password = PasswordFactory::newInvalidPassword();
238 }
239 $fields = [
240 'bp_user' => $botPassword->getUserCentralId(),
241 'bp_app_id' => $botPassword->getAppId(),
242 'bp_token' => MWCryptRand::generateHex( User::TOKEN_LENGTH ),
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 = [
295 'bp_token' => MWCryptRand::generateHex( User::TOKEN_LENGTH ),
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 {
332 $res = StatusValue::newGood();
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,
379 CentralIdLookup::AUDIENCE_RAW,
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,
408 CentralIdLookup::AUDIENCE_RAW,
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}
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition Setup.php:88
Utility class for bot passwords.
getUserCentralId()
Get the central user ID.
The CentralIdLookup service allows for connecting local users with cluster-wide IDs.
Helper class for DAO classes.
JSON formatter wrapper class.
A class to check request restrictions expressed as a JSON object.
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
getByUser(UserIdentity $userIdentity, string $appId, int $flags=self::READ_NORMAL)
Load a BotPassword from the database based on a UserIdentity object.
validateBotPassword(BotPassword $botPassword)
Check if a BotPassword is valid to save in the database (either inserting a new one or updating an ex...
deleteBotPassword(BotPassword $botPassword)
Delete an existing BotPassword in the database.
newUnsavedBotPassword(array $data, int $flags=self::READ_NORMAL)
Create an unsaved BotPassword.
invalidateUserPasswords(string $username)
Invalidate all passwords for a user, by name.
removeUserPasswords(string $username)
Remove all passwords for a user, by name.
getDatabase(int $db)
Get a database connection for the bot passwords database.
__construct(ServiceOptions $options, CentralIdLookup $centralIdLookup, LBFactory $lbFactory)
getByCentralId(int $centralId, string $appId, int $flags=self::READ_NORMAL)
Load a BotPassword from the database.
insertBotPassword(BotPassword $botPassword, Password $password=null)
Save the new BotPassword to the database.
updateBotPassword(BotPassword $botPassword, Password $password=null)
Update an existing BotPassword in the database.
Factory class for creating and checking Password objects.
Represents a password hash for use in authentication.
Definition Password.php:61
Generic operation result class Has warning/error list, boolean status and arbitrary value.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:69
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition User.php:88
An interface for generating database load balancers.
Definition LBFactory.php:42
Interface for database access objects.
Interface for objects representing user identity.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
const DB_PRIMARY
Definition defines.php:27