1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
<?php
namespace MediaWiki\ParamValidator\TypeDef;
use ExternalUserNames;
// phpcs:ignore MediaWiki.Classes.UnusedUseStatement.UnusedUse
use MediaWiki\User\UserIdentity;
use Title;
use User;
use Wikimedia\IPUtils;
use Wikimedia\Message\MessageValue;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef;
/**
* Type definition for user types
*
* Failure codes:
* - 'baduser': The value was not a valid MediaWiki user. No data.
*
* @since 1.35
*/
class UserDef extends TypeDef {
/**
* (string[]) Allowed types of user.
*
* One or more of the following values:
* - 'name': User names are allowed.
* - 'ip': IP ("anon") usernames are allowed.
* - 'cidr': IP ranges are allowed.
* - 'interwiki': Interwiki usernames are allowed.
* - 'id': Allow specifying user IDs, formatted like "#123".
*
* Default is `[ 'name', 'ip', 'cidr', 'interwiki' ]`.
*
* Avoid combining 'id' with PARAM_ISMULTI, as it may result in excessive
* DB lookups. If you do combine them, consider setting low values for
* PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2 to mitigate it.
*/
public const PARAM_ALLOWED_USER_TYPES = 'param-allowed-user-types';
/**
* (bool) Whether to return a UserIdentity object.
*
* If false, the validated user name is returned as a string. Default is false.
*
* Avoid setting true with PARAM_ISMULTI, as it may result in excessive DB
* lookups. If you do combine them, consider setting low values for
* PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2 to mitigate it.
*/
public const PARAM_RETURN_OBJECT = 'param-return-object';
public function validate( $name, $value, array $settings, array $options ) {
list( $type, $user ) = $this->processUser( $value );
if ( !$user || !in_array( $type, $settings[self::PARAM_ALLOWED_USER_TYPES], true ) ) {
$this->failure( 'baduser', $name, $value, $settings, $options );
}
return empty( $settings[self::PARAM_RETURN_OBJECT] ) ? $user->getName() : $user;
}
public function normalizeSettings( array $settings ) {
if ( isset( $settings[self::PARAM_ALLOWED_USER_TYPES] ) ) {
$settings[self::PARAM_ALLOWED_USER_TYPES] = array_values( array_intersect(
[ 'name', 'ip', 'cidr', 'interwiki', 'id' ],
$settings[self::PARAM_ALLOWED_USER_TYPES]
) );
}
if ( empty( $settings[self::PARAM_ALLOWED_USER_TYPES] ) ) {
$settings[self::PARAM_ALLOWED_USER_TYPES] = [ 'name', 'ip', 'cidr', 'interwiki' ];
}
return parent::normalizeSettings( $settings );
}
public function checkSettings( string $name, $settings, array $options, array $ret ) : array {
$ret = parent::checkSettings( $name, $settings, $options, $ret );
$ret['allowedKeys'] = array_merge( $ret['allowedKeys'], [
self::PARAM_ALLOWED_USER_TYPES, self::PARAM_RETURN_OBJECT,
] );
if ( !is_bool( $settings[self::PARAM_RETURN_OBJECT] ?? false ) ) {
$ret['issues'][self::PARAM_RETURN_OBJECT] = 'PARAM_RETURN_OBJECT must be boolean, got '
. gettype( $settings[self::PARAM_RETURN_OBJECT] );
}
$hasId = false;
if ( isset( $settings[self::PARAM_ALLOWED_USER_TYPES] ) ) {
if ( !is_array( $settings[self::PARAM_ALLOWED_USER_TYPES] ) ) {
$ret['issues'][self::PARAM_ALLOWED_USER_TYPES] = 'PARAM_ALLOWED_USER_TYPES must be an array, '
. 'got ' . gettype( $settings[self::PARAM_ALLOWED_USER_TYPES] );
} elseif ( $settings[self::PARAM_ALLOWED_USER_TYPES] === [] ) {
$ret['issues'][self::PARAM_ALLOWED_USER_TYPES] = 'PARAM_ALLOWED_USER_TYPES cannot be empty';
} else {
$bad = array_diff(
$settings[self::PARAM_ALLOWED_USER_TYPES],
[ 'name', 'ip', 'cidr', 'interwiki', 'id' ]
);
if ( $bad ) {
$ret['issues'][self::PARAM_ALLOWED_USER_TYPES] =
'PARAM_ALLOWED_USER_TYPES contains invalid values: ' . implode( ', ', $bad );
}
$hasId = in_array( 'id', $settings[self::PARAM_ALLOWED_USER_TYPES], true );
}
}
if ( !empty( $settings[ParamValidator::PARAM_ISMULTI] ) &&
( $hasId || !empty( $settings[self::PARAM_RETURN_OBJECT] ) ) &&
(
( $settings[ParamValidator::PARAM_ISMULTI_LIMIT1] ?? 100 ) > 10 ||
( $settings[ParamValidator::PARAM_ISMULTI_LIMIT2] ?? 100 ) > 10
)
) {
$ret['issues'][] = 'Multi-valued user-type parameters with PARAM_RETURN_OBJECT or allowing IDs '
. 'should set low values (<= 10) for PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2.'
. ' (Note that "<= 10" is arbitrary. If something hits this, we can investigate a real limit '
. 'once we have a real use case to look at.)';
}
return $ret;
}
/**
* Process $value to a UserIdentity, if possible
* @param string $value
* @return array [ string $type, UserIdentity|null $user ]
* @phan-return array{0:string,1:UserIdentity|null}
*/
private function processUser( string $value ) : array {
// A user ID?
if ( preg_match( '/^#(\d+)$/D', $value, $m ) ) {
return [ 'id', User::newFromId( $m[1] ) ];
}
// An interwiki username?
if ( ExternalUserNames::isExternal( $value ) ) {
$name = User::getCanonicalName( $value, false );
return [
'interwiki',
is_string( $name ) ? User::newFromAnyId( 0, $value, null ) : null
];
}
// A valid user name?
$user = User::newFromName( $value, 'valid' );
if ( $user ) {
return [ 'name', $user ];
}
// (T232672) Reproduce the normalization applied in User::getCanonicalName() when
// performing the checks below.
if ( strpos( $value, '#' ) !== false ) {
return [ '', null ];
}
$t = Title::newFromText( $value ); // In case of explicit "User:" prefix, sigh.
if ( !$t || $t->getNamespace() !== NS_USER || $t->isExternal() ) { // likely
$t = Title::newFromText( "User:$value" );
}
if ( !$t || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
// If it wasn't a valid User-namespace title, fail.
return [ '', null ];
}
$value = $t->getText();
// An IP?
$b = IPUtils::RE_IP_BYTE;
if ( IPUtils::isValid( $value ) ||
// See comment for User::isIP. We don't just call that function
// here because it also returns true for things like
// 300.300.300.300 that are neither valid usernames nor valid IP
// addresses.
preg_match( "/^$b\.$b\.$b\.xxx$/D", $value )
) {
return [ 'ip', User::newFromAnyId( 0, IPUtils::sanitizeIP( $value ), null ) ];
}
// A range?
if ( IPUtils::isValidRange( $value ) ) {
return [ 'cidr', User::newFromAnyId( 0, IPUtils::sanitizeIP( $value ), null ) ];
}
// Fail.
return [ '', null ];
}
public function getParamInfo( $name, array $settings, array $options ) {
$info = parent::getParamInfo( $name, $settings, $options );
$info['subtypes'] = $settings[self::PARAM_ALLOWED_USER_TYPES];
return $info;
}
public function getHelpInfo( $name, array $settings, array $options ) {
$info = parent::getParamInfo( $name, $settings, $options );
$isMulti = !empty( $settings[ParamValidator::PARAM_ISMULTI] );
$subtypes = [];
foreach ( $settings[self::PARAM_ALLOWED_USER_TYPES] as $st ) {
// Messages: paramvalidator-help-type-user-subtype-name,
// paramvalidator-help-type-user-subtype-ip, paramvalidator-help-type-user-subtype-cidr,
// paramvalidator-help-type-user-subtype-interwiki, paramvalidator-help-type-user-subtype-id
$subtypes[] = MessageValue::new( "paramvalidator-help-type-user-subtype-$st" );
}
$info[ParamValidator::PARAM_TYPE] = MessageValue::new( 'paramvalidator-help-type-user' )
->params( $isMulti ? 2 : 1 )
->textListParams( $subtypes )
->numParams( count( $subtypes ) );
return $info;
}
}
|