1<?php 2 3namespace MediaWiki\ParamValidator\TypeDef; 4 5use User; 6use Wikimedia\Message\DataMessageValue; 7use Wikimedia\ParamValidator\ParamValidator; 8use Wikimedia\ParamValidator\TypeDef\TypeDefTestCase; 9use Wikimedia\ParamValidator\ValidationException; 10 11/** 12 * @covers MediaWiki\ParamValidator\TypeDef\UserDef 13 */ 14class UserDefTest extends TypeDefTestCase { 15 16 protected static $testClass = UserDef::class; 17 18 private $wgHooks = null; 19 20 protected function setUp(): void { 21 global $wgHooks; 22 23 parent::setUp(); 24 25 // We don't have MediaWikiIntegrationTestCase's methods available, so we have to do it ourself. 26 $this->wgHooks = $wgHooks; 27 $wgHooks['InterwikiLoadPrefix'][] = function ( $prefix, &$iwdata ) { 28 if ( $prefix === 'interwiki' ) { 29 $iwdata = [ 30 'iw_url' => 'http://example.com/', 31 'iw_local' => 0, 32 'iw_trans' => 0, 33 ]; 34 return false; 35 } 36 }; 37 } 38 39 protected function tearDown(): void { 40 global $wgHooks; 41 42 $wgHooks = $this->wgHooks; 43 44 parent::tearDown(); 45 } 46 47 public function provideValidate() { 48 // General tests of string inputs 49 $data = [ 50 'Basic' => [ 'name', 'Some user', 'Some user' ], 51 'Normalized' => [ 'name', 'some_user', 'Some user' ], 52 'External' => [ 'interwiki', 'm>some_user', 'm>some_user' ], 53 'IPv4' => [ 'ip', '192.168.0.1', '192.168.0.1' ], 54 'IPv4, normalized' => [ 'ip', '192.168.000.001', '192.168.0.1' ], 55 'IPv6' => [ 'ip', '2001:DB8:0:0:0:0:0:0', '2001:DB8:0:0:0:0:0:0' ], 56 'IPv6, normalized' => [ 'ip', '2001:0db8::', '2001:DB8:0:0:0:0:0:0' ], 57 'IPv6, with leading ::' => [ 'ip', '::1', '0:0:0:0:0:0:0:1' ], 58 'IPv4 range' => [ 'cidr', '192.168.000.000/16', '192.168.0.0/16' ], 59 'IPv6 range' => [ 'cidr', '2001:0DB8::/64', '2001:DB8:0:0:0:0:0:0/64' ], 60 'Usemod IP' => [ 'ip', '192.168.0.xxx', '192.168.0.xxx' ], 61 'Bogus IP' => [ '', '192.168.0.256', null ], 62 'Bogus Usemod IP' => [ '', '192.268.0.xxx', null ], 63 'Usemod IP as range' => [ '', '192.168.0.xxx/16', null ], 64 'Bad username' => [ '', '[[Foo]]', null ], 65 'No namespaces' => [ '', 'Talk:Foo', null ], 66 'No namespaces (2)' => [ '', 'Help:Foo', null ], 67 'No namespaces (except User is ok)' => [ 'name', 'User:some_user', 'Some user' ], 68 'No namespaces (except User is ok) (IPv6)' => [ 'ip', 'User:::1', '0:0:0:0:0:0:0:1' ], 69 'No interwiki prefixes' => [ '', 'interwiki:Foo', null ], 70 'No fragment in IP' => [ '', '192.168.0.256#', null ], 71 ]; 72 foreach ( $data as $key => [ $type, $input, $expect ] ) { 73 $ex = new ValidationException( 74 DataMessageValue::new( 'paramvalidator-baduser', [], 'baduser' ), 75 'test', $input, [] 76 ); 77 if ( $type === '' ) { 78 yield $key => [ $input, $ex ]; 79 continue; 80 } 81 82 yield $key => [ $input, $expect ]; 83 84 yield "$key, only '$type' allowed" => [ 85 $input, 86 $expect, 87 [ UserDef::PARAM_ALLOWED_USER_TYPES => [ $type ] ], 88 ]; 89 90 $types = array_diff( [ 'name', 'ip', 'cidr', 'interwiki' ], [ $type ] ); 91 yield "$key, without '$type' allowed" => [ 92 $input, 93 $ex, 94 [ UserDef::PARAM_ALLOWED_USER_TYPES => $types ], 95 ]; 96 97 $obj = $type === 'name' ? User::newFromName( $expect ) : User::newFromAnyId( 0, $expect, null ); 98 yield "$key, returning object" => [ $input, $obj, [ UserDef::PARAM_RETURN_OBJECT => true ] ]; 99 } 100 101 // Test input by user ID 102 // We can't test not returning object here, because we don't have a test 103 // database and there's no "UserFactory" (yet) to inject a mock of. 104 $input = '#1234'; 105 $ex = new ValidationException( 106 DataMessageValue::new( 'paramvalidator-baduser', [], 'baduser' ), 107 'test', $input, [] 108 ); 109 yield 'User ID' => [ $input, $ex, [ UserDef::PARAM_RETURN_OBJECT => true ] ]; 110 yield 'User ID, with \'id\' allowed, returning object' => [ 111 $input, 112 User::newFromId( 1234 ), 113 [ UserDef::PARAM_ALLOWED_USER_TYPES => [ 'id' ], UserDef::PARAM_RETURN_OBJECT => true ], 114 ]; 115 116 // Tests for T232672 (consistent treatment of whitespace and BIDI characters) 117 $data = [ 118 'name' => [ 'Foo', [ 1 ], 'Foo' ], 119 'interwiki' => [ 'm>some_user', [ 1, 2, 6 ], null ], 120 'ip (v4)' => [ '192.168.0.1', [ 1, 3, 4 ], '192.168.0.1' ], 121 'ip (v6)' => [ '2001:DB8:0:0:0:0:0:0', [ 2, 5, 6 ], '2001:DB8:0:0:0:0:0:0' ], 122 'ip (v6, colons)' => [ '::1', [ 1, 2 ], '0:0:0:0:0:0:0:1' ], 123 'cidr (v4)' => [ '192.168.0.0/16', [ 1, 3, 4, 11, 12, 13 ], '192.168.0.0/16' ], 124 'cidr (v6)' => [ '2001:db8::/64', [ 2, 5, 6, 20, 21, 22 ], '2001:DB8:0:0:0:0:0:0/64' ], 125 ]; 126 foreach ( $data as $key => [ $name, $positions, $expect ] ) { 127 $input = " $name "; 128 yield "T232672: leading/trailing whitespace for $key" => [ $input, $expect ?? $input ]; 129 130 $input = "_{$name}_"; 131 yield "T232672: leading/trailing underscores for $key" => [ $input, $expect ?? $input ]; 132 133 $positions = array_merge( [ 0, strlen( $name ) ], $positions ); 134 foreach ( $positions as $i ) { 135 $input = substr_replace( $name, "\u{200E}", $i, 0 ); 136 yield "T232672: U+200E at position $i for $key" => [ $input, $expect ?? $input ]; 137 } 138 } 139 } 140 141 public function provideNormalizeSettings() { 142 return [ 143 'Basic test' => [ 144 [ 'param-foo' => 'bar' ], 145 [ 146 'param-foo' => 'bar', 147 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'cidr', 'interwiki' ], 148 ], 149 ], 150 'Types not overridden' => [ 151 [ 152 'param-foo' => 'bar', 153 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id' ], 154 ], 155 [ 156 'param-foo' => 'bar', 157 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id' ], 158 ], 159 ], 160 ]; 161 } 162 163 public function provideCheckSettings() { 164 $keys = [ 'Y', UserDef::PARAM_ALLOWED_USER_TYPES, UserDef::PARAM_RETURN_OBJECT ]; 165 $ismultiIssue = 'Multi-valued user-type parameters with PARAM_RETURN_OBJECT or allowing IDs ' 166 . 'should set low values (<= 10) for PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2.' 167 . ' (Note that "<= 10" is arbitrary. If something hits this, we can investigate a real limit ' 168 . 'once we have a real use case to look at.)'; 169 170 return [ 171 'Basic test' => [ 172 [], 173 self::STDRET, 174 [ 175 'issues' => [ 'X' ], 176 'allowedKeys' => $keys, 177 'messages' => [], 178 ], 179 ], 180 'Test with everything' => [ 181 [ 182 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name' ], 183 UserDef::PARAM_RETURN_OBJECT => true, 184 ], 185 self::STDRET, 186 [ 187 'issues' => [ 'X' ], 188 'allowedKeys' => $keys, 189 'messages' => [], 190 ], 191 ], 192 'Bad types' => [ 193 [ 194 UserDef::PARAM_ALLOWED_USER_TYPES => 'name', 195 UserDef::PARAM_RETURN_OBJECT => 1, 196 ], 197 self::STDRET, 198 [ 199 'issues' => [ 200 'X', 201 UserDef::PARAM_RETURN_OBJECT => 'PARAM_RETURN_OBJECT must be boolean, got integer', 202 UserDef::PARAM_ALLOWED_USER_TYPES => 'PARAM_ALLOWED_USER_TYPES must be an array, got string', 203 ], 204 'allowedKeys' => $keys, 205 'messages' => [], 206 ], 207 ], 208 'PARAM_ALLOWED_USER_TYPES cannot be empty' => [ 209 [ 210 UserDef::PARAM_ALLOWED_USER_TYPES => [], 211 ], 212 self::STDRET, 213 [ 214 'issues' => [ 215 'X', 216 UserDef::PARAM_ALLOWED_USER_TYPES => 'PARAM_ALLOWED_USER_TYPES cannot be empty', 217 ], 218 'allowedKeys' => $keys, 219 'messages' => [], 220 ], 221 ], 222 'PARAM_ALLOWED_USER_TYPES invalid values' => [ 223 [ 224 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id', 'ssn', 'Q-number' ], 225 ], 226 self::STDRET, 227 [ 228 'issues' => [ 229 'X', 230 UserDef::PARAM_ALLOWED_USER_TYPES 231 => 'PARAM_ALLOWED_USER_TYPES contains invalid values: ssn, Q-number', 232 ], 233 'allowedKeys' => $keys, 234 'messages' => [], 235 ], 236 ], 237 'ISMULTI generally ok' => [ 238 [ 239 ParamValidator::PARAM_ISMULTI => true, 240 ], 241 self::STDRET, 242 [ 243 'issues' => [ 'X' ], 244 'allowedKeys' => $keys, 245 'messages' => [], 246 ], 247 ], 248 'ISMULTI with ID not ok (1)' => [ 249 [ 250 ParamValidator::PARAM_ISMULTI => true, 251 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'id' ], 252 ], 253 self::STDRET, 254 [ 255 'issues' => [ 'X', $ismultiIssue ], 256 'allowedKeys' => $keys, 257 'messages' => [], 258 ], 259 ], 260 'ISMULTI with ID not ok (2)' => [ 261 [ 262 ParamValidator::PARAM_ISMULTI => true, 263 ParamValidator::PARAM_ISMULTI_LIMIT1 => 10, 264 ParamValidator::PARAM_ISMULTI_LIMIT2 => 11, 265 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'id' ], 266 ], 267 self::STDRET, 268 [ 269 'issues' => [ 'X', $ismultiIssue ], 270 'allowedKeys' => $keys, 271 'messages' => [], 272 ], 273 ], 274 'ISMULTI with ID ok with low limits' => [ 275 [ 276 ParamValidator::PARAM_ISMULTI => true, 277 ParamValidator::PARAM_ISMULTI_LIMIT1 => 10, 278 ParamValidator::PARAM_ISMULTI_LIMIT2 => 10, 279 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'id' ], 280 ], 281 self::STDRET, 282 [ 283 'issues' => [ 'X' ], 284 'allowedKeys' => $keys, 285 'messages' => [], 286 ], 287 ], 288 'ISMULTI with RETURN_OBJECT also not ok' => [ 289 [ 290 ParamValidator::PARAM_ISMULTI => true, 291 UserDef::PARAM_RETURN_OBJECT => true, 292 ], 293 self::STDRET, 294 [ 295 'issues' => [ 'X', $ismultiIssue ], 296 'allowedKeys' => $keys, 297 'messages' => [], 298 ], 299 ], 300 'ISMULTI with RETURN_OBJECT also ok with low limits' => [ 301 [ 302 ParamValidator::PARAM_ISMULTI => true, 303 ParamValidator::PARAM_ISMULTI_LIMIT1 => 10, 304 ParamValidator::PARAM_ISMULTI_LIMIT2 => 10, 305 UserDef::PARAM_RETURN_OBJECT => true, 306 ], 307 self::STDRET, 308 [ 309 'issues' => [ 'X' ], 310 'allowedKeys' => $keys, 311 'messages' => [], 312 ], 313 ], 314 ]; 315 } 316 317 public function provideGetInfo() { 318 return [ 319 'Basic test' => [ 320 [], 321 [ 322 'subtypes' => [ 'name', 'ip', 'cidr', 'interwiki' ], 323 ], 324 [ 325 // phpcs:ignore Generic.Files.LineLength.TooLong 326 ParamValidator::PARAM_TYPE => '<message key="paramvalidator-help-type-user"><text>1</text><list listType="text"><text><message key="paramvalidator-help-type-user-subtype-name"></message></text><text><message key="paramvalidator-help-type-user-subtype-ip"></message></text><text><message key="paramvalidator-help-type-user-subtype-cidr"></message></text><text><message key="paramvalidator-help-type-user-subtype-interwiki"></message></text></list><num>4</num></message>', 327 ], 328 ], 329 'Specific types' => [ 330 [ 331 ParamValidator::PARAM_ISMULTI => true, 332 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id' ], 333 UserDef::PARAM_RETURN_OBJECT => true, 334 ], 335 [ 336 'subtypes' => [ 'name', 'id' ], 337 ], 338 [ 339 // phpcs:ignore Generic.Files.LineLength.TooLong 340 ParamValidator::PARAM_TYPE => '<message key="paramvalidator-help-type-user"><text>2</text><list listType="text"><text><message key="paramvalidator-help-type-user-subtype-name"></message></text><text><message key="paramvalidator-help-type-user-subtype-id"></message></text></list><num>2</num></message>', 341 ], 342 ], 343 ]; 344 } 345 346} 347