MediaWiki REL1_40
BotPasswordStore.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\User;
24
25use BotPassword;
28use FormatJson;
32use MWCryptRand;
34use Password;
36use StatusValue;
37use User;
40
46
50 public const CONSTRUCTOR_OPTIONS = [
54 ];
55
57 private $options;
58
60 private $lbFactory;
61
63 private $centralIdLookup;
64
70 public function __construct(
71 ServiceOptions $options,
72 CentralIdLookup $centralIdLookup,
73 LBFactory $lbFactory
74 ) {
75 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
76 $this->options = $options;
77 $this->centralIdLookup = $centralIdLookup;
78 $this->lbFactory = $lbFactory;
79 }
80
87 public function getDatabase( int $db ): IDatabase {
88 if ( $this->options->get( MainConfigNames::BotPasswordsCluster ) ) {
89 $loadBalancer = $this->lbFactory->getExternalLB(
90 $this->options->get( MainConfigNames::BotPasswordsCluster )
91 );
92 } else {
93 $loadBalancer = $this->lbFactory->getMainLB(
94 $this->options->get( MainConfigNames::BotPasswordsDatabase )
95 );
96 }
97 return $loadBalancer->getConnectionRef(
98 $db,
99 [],
100 $this->options->get( MainConfigNames::BotPasswordsDatabase )
101 );
102 }
103
111 public function getByUser(
112 UserIdentity $userIdentity,
113 string $appId,
114 int $flags = self::READ_NORMAL
115 ): ?BotPassword {
116 if ( !$this->options->get( MainConfigNames::EnableBotPasswords ) ) {
117 return null;
118 }
119
120 $centralId = $this->centralIdLookup->centralIdFromLocalUser(
121 $userIdentity,
123 $flags
124 );
125 return $centralId ? $this->getByCentralId( $centralId, $appId, $flags ) : null;
126 }
127
135 public function getByCentralId(
136 int $centralId,
137 string $appId,
138 int $flags = self::READ_NORMAL
139 ): ?BotPassword {
140 if ( !$this->options->get( MainConfigNames::EnableBotPasswords ) ) {
141 return null;
142 }
143
144 [ $index, $options ] = DBAccessObjectUtils::getDBOptions( $flags );
145 $db = $this->getDatabase( $index );
146 $row = $db->selectRow(
147 'bot_passwords',
148 [ 'bp_user', 'bp_app_id', 'bp_token', 'bp_restrictions', 'bp_grants' ],
149 [ 'bp_user' => $centralId, 'bp_app_id' => $appId ],
150 __METHOD__,
151 $options
152 );
153 return $row ? new BotPassword( $row, true, $flags ) : null;
154 }
155
168 public function newUnsavedBotPassword(
169 array $data,
170 int $flags = self::READ_NORMAL
171 ): ?BotPassword {
172 if ( isset( $data['user'] ) && ( !$data['user'] instanceof UserIdentity ) ) {
173 return null;
174 }
175
176 $row = (object)[
177 'bp_user' => 0,
178 'bp_app_id' => trim( $data['appId'] ?? '' ),
179 'bp_token' => '**unsaved**',
180 'bp_restrictions' => $data['restrictions'] ?? MWRestrictions::newDefault(),
181 'bp_grants' => $data['grants'] ?? [],
182 ];
183
184 if (
185 $row->bp_app_id === '' ||
186 strlen( $row->bp_app_id ) > BotPassword::APPID_MAXLENGTH ||
187 !$row->bp_restrictions instanceof MWRestrictions ||
188 !is_array( $row->bp_grants )
189 ) {
190 return null;
191 }
192
193 $row->bp_restrictions = $row->bp_restrictions->toJson();
194 $row->bp_grants = FormatJson::encode( $row->bp_grants );
195
196 if ( isset( $data['user'] ) ) {
197 // Must be a UserIdentity object, already checked above
198 $row->bp_user = $this->centralIdLookup->centralIdFromLocalUser(
199 $data['user'],
200 CentralIdLookup::AUDIENCE_RAW,
201 $flags
202 );
203 } elseif ( isset( $data['username'] ) ) {
204 $row->bp_user = $this->centralIdLookup->centralIdFromName(
205 $data['username'],
206 CentralIdLookup::AUDIENCE_RAW,
207 $flags
208 );
209 } elseif ( isset( $data['centralId'] ) ) {
210 $row->bp_user = $data['centralId'];
211 }
212 if ( !$row->bp_user ) {
213 return null;
214 }
215
216 return new BotPassword( $row, false, $flags );
217 }
218
228 public function insertBotPassword(
229 BotPassword $botPassword,
230 Password $password = null
231 ): StatusValue {
232 $res = $this->validateBotPassword( $botPassword );
233 if ( !$res->isGood() ) {
234 return $res;
235 }
236
237 if ( $password === null ) {
238 $password = PasswordFactory::newInvalidPassword();
239 }
240 $fields = [
241 'bp_user' => $botPassword->getUserCentralId(),
242 'bp_app_id' => $botPassword->getAppId(),
243 'bp_token' => MWCryptRand::generateHex( User::TOKEN_LENGTH ),
244 'bp_restrictions' => $botPassword->getRestrictions()->toJson(),
245 'bp_grants' => FormatJson::encode( $botPassword->getGrants() ),
246 'bp_password' => $password->toString(),
247 ];
248
249 $dbw = $this->getDatabase( DB_PRIMARY );
250 $dbw->insert(
251 'bot_passwords',
252 $fields,
253 __METHOD__,
254 [ 'IGNORE' ]
255 );
256
257 $ok = (bool)$dbw->affectedRows();
258 if ( $ok ) {
259 $token = $dbw->selectField(
260 'bot_passwords',
261 'bp_token',
262 [
263 'bp_user' => $botPassword->getUserCentralId(),
264 'bp_app_id' => $botPassword->getAppId(),
265 ],
266 __METHOD__
267 );
268 return StatusValue::newGood( $token );
269 }
270 return StatusValue::newFatal( 'botpasswords-insert-failed', $botPassword->getAppId() );
271 }
272
282 public function updateBotPassword(
283 BotPassword $botPassword,
284 Password $password = null
285 ): StatusValue {
286 $res = $this->validateBotPassword( $botPassword );
287 if ( !$res->isGood() ) {
288 return $res;
289 }
290
291 $conds = [
292 'bp_user' => $botPassword->getUserCentralId(),
293 'bp_app_id' => $botPassword->getAppId(),
294 ];
295 $fields = [
296 'bp_token' => MWCryptRand::generateHex( User::TOKEN_LENGTH ),
297 'bp_restrictions' => $botPassword->getRestrictions()->toJson(),
298 'bp_grants' => FormatJson::encode( $botPassword->getGrants() ),
299 ];
300 if ( $password !== null ) {
301 $fields['bp_password'] = $password->toString();
302 }
303
304 $dbw = $this->getDatabase( DB_PRIMARY );
305 $dbw->update(
306 'bot_passwords',
307 $fields,
308 $conds,
309 __METHOD__
310 );
311
312 $ok = (bool)$dbw->affectedRows();
313 if ( $ok ) {
314 $token = $dbw->selectField(
315 'bot_passwords',
316 'bp_token',
317 $conds,
318 __METHOD__
319 );
320 return StatusValue::newGood( $token );
321 }
322 return StatusValue::newFatal( 'botpasswords-update-failed', $botPassword->getAppId() );
323 }
324
332 private function validateBotPassword( BotPassword $botPassword ): StatusValue {
333 $res = StatusValue::newGood();
334
335 $restrictions = $botPassword->getRestrictions()->toJson();
336 if ( strlen( $restrictions ) > BotPassword::RESTRICTIONS_MAXLENGTH ) {
337 $res->fatal( 'botpasswords-toolong-restrictions' );
338 }
339
340 $grants = FormatJson::encode( $botPassword->getGrants() );
341 if ( strlen( $grants ) > BotPassword::GRANTS_MAXLENGTH ) {
342 $res->fatal( 'botpasswords-toolong-grants' );
343 }
344
345 return $res;
346 }
347
354 public function deleteBotPassword( BotPassword $botPassword ): bool {
355 $dbw = $this->getDatabase( DB_PRIMARY );
356 $dbw->delete(
357 'bot_passwords',
358 [
359 'bp_user' => $botPassword->getUserCentralId(),
360 'bp_app_id' => $botPassword->getAppId(),
361 ],
362 __METHOD__
363 );
364
365 return (bool)$dbw->affectedRows();
366 }
367
373 public function invalidateUserPasswords( string $username ): bool {
374 if ( !$this->options->get( MainConfigNames::EnableBotPasswords ) ) {
375 return false;
376 }
377
378 $centralId = $this->centralIdLookup->centralIdFromName(
379 $username,
380 CentralIdLookup::AUDIENCE_RAW,
381 CentralIdLookup::READ_LATEST
382 );
383 if ( !$centralId ) {
384 return false;
385 }
386
387 $dbw = $this->getDatabase( DB_PRIMARY );
388 $dbw->update(
389 'bot_passwords',
390 [ 'bp_password' => PasswordFactory::newInvalidPassword()->toString() ],
391 [ 'bp_user' => $centralId ],
392 __METHOD__
393 );
394 return (bool)$dbw->affectedRows();
395 }
396
402 public function removeUserPasswords( string $username ): bool {
403 if ( !$this->options->get( MainConfigNames::EnableBotPasswords ) ) {
404 return false;
405 }
406
407 $centralId = $this->centralIdLookup->centralIdFromName(
408 $username,
409 CentralIdLookup::AUDIENCE_RAW,
410 CentralIdLookup::READ_LATEST
411 );
412 if ( !$centralId ) {
413 return false;
414 }
415
416 $dbw = $this->getDatabase( DB_PRIMARY );
417 $dbw->delete(
418 'bot_passwords',
419 [ 'bp_user' => $centralId ],
420 __METHOD__
421 );
422 return (bool)$dbw->affectedRows();
423 }
424
425}
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition WebStart.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.
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
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,...
A class containing constants representing the names of configuration variables.
const BotPasswordsDatabase
Name constant for the BotPasswordsDatabase setting, for use with Config::get()
const EnableBotPasswords
Name constant for the EnableBotPasswords setting, for use with Config::get()
const BotPasswordsCluster
Name constant for the BotPasswordsCluster setting, for use with Config::get()
getByUser(UserIdentity $userIdentity, string $appId, int $flags=self::READ_NORMAL)
Load a BotPassword from the database based on a UserIdentity object.
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.
internal since 1.36
Definition User.php:71
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition User.php:90
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:36
const DB_PRIMARY
Definition defines.php:28