Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4101552
BotPasswordStore.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
BotPasswordStore.php
View Options
<?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
;
use
MediaWiki\Config\ServiceOptions
;
use
MediaWiki\Json\FormatJson
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\Password\Password
;
use
MediaWiki\Password\PasswordFactory
;
use
MediaWiki\User\CentralId\CentralIdLookup
;
use
MWCryptRand
;
use
MWRestrictions
;
use
StatusValue
;
use
Wikimedia\Rdbms\IConnectionProvider
;
use
Wikimedia\Rdbms\IDatabase
;
use
Wikimedia\Rdbms\IDBAccessObject
;
use
Wikimedia\Rdbms\IReadableDatabase
;
/**
* @author DannyS712
* @since 1.37
*/
class
BotPasswordStore
{
/**
* @internal For use by ServiceWiring
*/
public
const
CONSTRUCTOR_OPTIONS
=
[
MainConfigNames
::
EnableBotPasswords
,
];
private
ServiceOptions
$options
;
private
IConnectionProvider
$dbProvider
;
private
CentralIdLookup
$centralIdLookup
;
/**
* @param ServiceOptions $options
* @param CentralIdLookup $centralIdLookup
* @param IConnectionProvider $dbProvider
*/
public
function
__construct
(
ServiceOptions
$options
,
CentralIdLookup
$centralIdLookup
,
IConnectionProvider
$dbProvider
)
{
$options
->
assertRequiredOptions
(
self
::
CONSTRUCTOR_OPTIONS
);
$this
->
options
=
$options
;
$this
->
centralIdLookup
=
$centralIdLookup
;
$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'
);
}
/**
* Get a database connection for the bot passwords database
* @return IDatabase
* @internal
*/
public
function
getPrimaryDatabase
():
IDatabase
{
return
$this
->
dbProvider
->
getPrimaryDatabase
(
'virtual-botpasswords'
);
}
/**
* Load a BotPassword from the database based on a UserIdentity object
* @param UserIdentity $userIdentity
* @param string $appId
* @param int $flags IDBAccessObject read flags
* @return BotPassword|null
*/
public
function
getByUser
(
UserIdentity
$userIdentity
,
string
$appId
,
int
$flags
=
IDBAccessObject
::
READ_NORMAL
):
?
BotPassword
{
if
(
!
$this
->
options
->
get
(
MainConfigNames
::
EnableBotPasswords
)
)
{
return
null
;
}
$centralId
=
$this
->
centralIdLookup
->
centralIdFromLocalUser
(
$userIdentity
,
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
,
int
$flags
=
IDBAccessObject
::
READ_NORMAL
):
?
BotPassword
{
if
(
!
$this
->
options
->
get
(
MainConfigNames
::
EnableBotPasswords
)
)
{
return
null
;
}
if
(
(
$flags
&
IDBAccessObject
::
READ_LATEST
)
==
IDBAccessObject
::
READ_LATEST
)
{
$db
=
$this
->
dbProvider
->
getPrimaryDatabase
(
'virtual-botpasswords'
);
}
else
{
$db
=
$this
->
dbProvider
->
getReplicaDatabase
(
'virtual-botpasswords'
);
}
$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
]
)
->
recency
(
$flags
)
->
caller
(
__METHOD__
)->
fetchRow
();
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:
* - user: (UserIdentity) UserIdentity to create the password for. Overrides username and centralId.
* - 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
,
int
$flags
=
IDBAccessObject
::
READ_NORMAL
):
?
BotPassword
{
if
(
isset
(
$data
[
'user'
]
)
&&
(
!
$data
[
'user'
]
instanceof
UserIdentity
)
)
{
return
null
;
}
$row
=
(
object
)[
'bp_user'
=>
0
,
'bp_app_id'
=>
trim
(
$data
[
'appId'
]
??
''
),
'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'
]
)
)
{
// Must be a UserIdentity object, already checked above
$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
);
}
/**
* 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
):
StatusValue
{
$res
=
$this
->
validateBotPassword
(
$botPassword
);
if
(
!
$res
->
isGood
()
)
{
return
$res
;
}
$password
??=
PasswordFactory
::
newInvalidPassword
();
$dbw
=
$this
->
getPrimaryDatabase
();
$dbw
->
newInsertQueryBuilder
()
->
insertInto
(
'bot_passwords'
)
->
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
();
$ok
=
(
bool
)
$dbw
->
affectedRows
();
if
(
$ok
)
{
$token
=
$dbw
->
newSelectQueryBuilder
()
->
select
(
'bp_token'
)
->
from
(
'bot_passwords'
)
->
where
(
[
'bp_user'
=>
$botPassword
->
getUserCentralId
(),
'bp_app_id'
=>
$botPassword
->
getAppId
(),
]
)
->
caller
(
__METHOD__
)->
fetchField
();
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
):
StatusValue
{
$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
();
}
$dbw
=
$this
->
getPrimaryDatabase
();
$dbw
->
newUpdateQueryBuilder
()
->
update
(
'bot_passwords'
)
->
set
(
$fields
)
->
where
(
$conds
)
->
caller
(
__METHOD__
)->
execute
();
$ok
=
(
bool
)
$dbw
->
affectedRows
();
if
(
$ok
)
{
$token
=
$dbw
->
newSelectQueryBuilder
()
->
select
(
'bp_token'
)
->
from
(
'bot_passwords'
)
->
where
(
$conds
)
->
caller
(
__METHOD__
)->
fetchField
();
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
*/
private
function
validateBotPassword
(
BotPassword
$botPassword
):
StatusValue
{
$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
*/
public
function
deleteBotPassword
(
BotPassword
$botPassword
):
bool
{
$dbw
=
$this
->
getPrimaryDatabase
();
$dbw
->
newDeleteQueryBuilder
()
->
deleteFrom
(
'bot_passwords'
)
->
where
(
[
'bp_user'
=>
$botPassword
->
getUserCentralId
()
]
)
->
andWhere
(
[
'bp_app_id'
=>
$botPassword
->
getAppId
()
]
)
->
caller
(
__METHOD__
)->
execute
();
return
(
bool
)
$dbw
->
affectedRows
();
}
/**
* Invalidate all passwords for a user, by name
* @param string $username
* @return bool Whether any passwords were invalidated
*/
public
function
invalidateUserPasswords
(
string
$username
):
bool
{
if
(
!
$this
->
options
->
get
(
MainConfigNames
::
EnableBotPasswords
)
)
{
return
false
;
}
$centralId
=
$this
->
centralIdLookup
->
centralIdFromName
(
$username
,
CentralIdLookup
::
AUDIENCE_RAW
,
IDBAccessObject
::
READ_LATEST
);
if
(
!
$centralId
)
{
return
false
;
}
$dbw
=
$this
->
getPrimaryDatabase
();
$dbw
->
newUpdateQueryBuilder
()
->
update
(
'bot_passwords'
)
->
set
(
[
'bp_password'
=>
PasswordFactory
::
newInvalidPassword
()->
toString
()
]
)
->
where
(
[
'bp_user'
=>
$centralId
]
)
->
caller
(
__METHOD__
)->
execute
();
return
(
bool
)
$dbw
->
affectedRows
();
}
/**
* Remove all passwords for a user, by name
* @param string $username
* @return bool Whether any passwords were removed
*/
public
function
removeUserPasswords
(
string
$username
):
bool
{
if
(
!
$this
->
options
->
get
(
MainConfigNames
::
EnableBotPasswords
)
)
{
return
false
;
}
$centralId
=
$this
->
centralIdLookup
->
centralIdFromName
(
$username
,
CentralIdLookup
::
AUDIENCE_RAW
,
IDBAccessObject
::
READ_LATEST
);
if
(
!
$centralId
)
{
return
false
;
}
$dbw
=
$this
->
getPrimaryDatabase
();
$dbw
->
newDeleteQueryBuilder
()
->
deleteFrom
(
'bot_passwords'
)
->
where
(
[
'bp_user'
=>
$centralId
]
)
->
caller
(
__METHOD__
)->
execute
();
return
(
bool
)
$dbw
->
affectedRows
();
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Tue, Aug 18, 17:00 (3 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e3/c9/2ad779d4b538db2cc8006c7cee31
Default Alt Text
BotPasswordStore.php (11 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment