MediaWiki  master
UserNameUtils.php
Go to the documentation of this file.
1 <?php
2 
23 namespace MediaWiki\User;
24 
25 use InvalidArgumentException;
26 use Language;
30 use Psr\Log\LoggerInterface;
31 use TitleFactory;
32 use Wikimedia\IPUtils;
35 
42 
43  public const CONSTRUCTOR_OPTIONS = [
44  'MaxNameChars',
45  'ReservedUsernames',
46  'InvalidUsernameCharacters'
47  ];
48 
49  public const RIGOR_CREATABLE = 'creatable';
50  public const RIGOR_USABLE = 'usable';
51  public const RIGOR_VALID = 'valid';
52  public const RIGOR_NONE = 'none';
53 
57  private $options;
58 
62  private $contentLang;
63 
67  private $logger;
68 
72  private $titleFactory;
73 
77  private $textFormatter;
78 
82  private $reservedUsernames = false;
83 
87  private $hookRunner;
88 
97  public function __construct(
100  LoggerInterface $logger,
103  HookContainer $hookContainer
104  ) {
105  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
106  $this->options = $options;
107  $this->contentLang = $contentLang;
108  $this->logger = $logger;
109  $this->titleFactory = $titleFactory;
110  $this->textFormatter = $textFormatter;
111  $this->hookRunner = new HookRunner( $hookContainer );
112  }
113 
125  public function isValid( string $name ) : bool {
126  if ( $name === ''
127  || $this->isIP( $name )
128  || strpos( $name, '/' ) !== false
129  || strlen( $name ) > $this->options->get( 'MaxNameChars' )
130  || $name !== $this->contentLang->ucfirst( $name )
131  ) {
132  return false;
133  }
134 
135  // Ensure that the name can't be misresolved as a different title,
136  // such as with extra namespace keys at the start.
137  $title = $this->titleFactory->newFromText( $name );
138  if ( $title === null
139  || $title->getNamespace()
140  || strcmp( $name, $title->getPrefixedText() )
141  ) {
142  return false;
143  }
144 
145  // Check an additional blacklist of troublemaker characters.
146  // Should these be merged into the title char list?
147  $unicodeBlacklist = '/[' .
148  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
149  '\x{00a0}' . # non-breaking space
150  '\x{2000}-\x{200f}' . # various whitespace
151  '\x{2028}-\x{202f}' . # breaks and control chars
152  '\x{3000}' . # ideographic space
153  '\x{e000}-\x{f8ff}' . # private use
154  ']/u';
155  if ( preg_match( $unicodeBlacklist, $name ) ) {
156  return false;
157  }
158 
159  return true;
160  }
161 
173  public function isUsable( string $name ) : bool {
174  // Must be a valid username, obviously ;)
175  if ( !$this->isValid( $name ) ) {
176  return false;
177  }
178 
179  if ( !$this->reservedUsernames ) {
180  $reservedUsernames = $this->options->get( 'ReservedUsernames' );
181  $this->hookRunner->onUserGetReservedNames( $reservedUsernames );
182  $this->reservedUsernames = $reservedUsernames;
183  }
184 
185  // Certain names may be reserved for batch processes.
186  foreach ( $this->reservedUsernames as $reserved ) {
187  if ( substr( $reserved, 0, 4 ) === 'msg:' ) {
188  $reserved = $this->textFormatter->format(
189  MessageValue::new( substr( $reserved, 4 ) )
190  );
191  }
192  if ( $reserved === $name ) {
193  return false;
194  }
195  }
196  return true;
197  }
198 
211  public function isCreatable( string $name ) : bool {
212  // Ensure that the username isn't longer than 235 bytes, so that
213  // (at least for the builtin skins) user javascript and css files
214  // will work. (T25080)
215  if ( strlen( $name ) > 235 ) {
216  $this->logger->debug(
217  __METHOD__ . ": '$name' uncreatable due to length"
218  );
219  return false;
220  }
221 
222  $invalid = $this->options->get( 'InvalidUsernameCharacters' );
223  // Preg yells if you try to give it an empty string
224  if ( $invalid !== '' &&
225  preg_match( '/[' . preg_quote( $invalid, '/' ) . ']/', $name )
226  ) {
227  $this->logger->debug(
228  __METHOD__ . ": '$name' uncreatable due to wgInvalidUsernameCharacters"
229  );
230  return false;
231  }
232 
233  return $this->isUsable( $name );
234  }
235 
250  public function getCanonical( string $name, string $validate = self::RIGOR_VALID ) {
251  // Force usernames to capital
252  $name = $this->contentLang->ucfirst( $name );
253 
254  // Reject names containing '#'; these will be cleaned up
255  // with title normalisation, but then it's too late to
256  // check elsewhere
257  if ( strpos( $name, '#' ) !== false ) {
258  return false;
259  }
260 
261  // No need to proceed if no validation is requested, just
262  // clean up underscores and return
263  if ( $validate === self::RIGOR_NONE ) {
264  $name = strtr( $name, '_', ' ' );
265  return $name;
266  }
267 
268  // Clean up name according to title rules,
269  // but only when validation is requested (T14654)
270  $title = $this->titleFactory->newFromText( $name, NS_USER );
271 
272  // Check for invalid titles
273  if ( $title === null
274  || $title->getNamespace() !== NS_USER
275  || $title->isExternal()
276  ) {
277  return false;
278  }
279 
280  $name = $title->getText();
281 
282  // RIGOR_NONE handled above
283  switch ( $validate ) {
284  case self::RIGOR_VALID:
285  if ( !$this->isValid( $name ) ) {
286  return false;
287  }
288  return $name;
289  case self::RIGOR_USABLE:
290  if ( !$this->isUsable( $name ) ) {
291  return false;
292  }
293  return $name;
295  if ( !$this->isCreatable( $name ) ) {
296  return false;
297  }
298  return $name;
299  default:
300  throw new InvalidArgumentException(
301  "Invalid parameter value for validation ($validate) in " .
302  __METHOD__
303  );
304  }
305  }
306 
325  public function isIP( string $name ) : bool {
326  $anyIPv4 = '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/';
327  $validIP = IPUtils::isValid( $name );
328  return $validIP || preg_match( $anyIPv4, $name );
329  }
330 
337  public function isValidIPRange( string $range ) : bool {
338  return IPUtils::isValidRange( $range );
339  }
340 
341 }
MediaWiki\User\UserNameUtils\__construct
__construct(ServiceOptions $options, Language $contentLang, LoggerInterface $logger, TitleFactory $titleFactory, ITextFormatter $textFormatter, HookContainer $hookContainer)
Definition: UserNameUtils.php:97
Wikimedia\Message\ITextFormatter
Definition: ITextFormatter.php:18
MediaWiki\User\UserNameUtils\$textFormatter
ITextFormatter $textFormatter
Definition: UserNameUtils.php:77
MediaWiki\User\UserNameUtils\RIGOR_NONE
const RIGOR_NONE
Definition: UserNameUtils.php:52
MediaWiki\User\UserNameUtils\isIP
isIP(string $name)
Does the string match an anonymous IP address?
Definition: UserNameUtils.php:325
MediaWiki\User\UserNameUtils\RIGOR_VALID
const RIGOR_VALID
Definition: UserNameUtils.php:51
MediaWiki\User\UserNameUtils\isCreatable
isCreatable(string $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: UserNameUtils.php:211
Wikimedia\Message\MessageValue
Value object representing a message for i18n.
Definition: MessageValue.php:14
MediaWiki\User\UserNameUtils\$reservedUsernames
string[] false $reservedUsernames
Cache for isUsable()
Definition: UserNameUtils.php:82
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:25
$title
$title
Definition: testCompression.php:38
MediaWiki\User\UserNameUtils\RIGOR_USABLE
const RIGOR_USABLE
Definition: UserNameUtils.php:50
MediaWiki\User\UserNameUtils\getCanonical
getCanonical(string $name, string $validate=self::RIGOR_VALID)
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: UserNameUtils.php:250
MediaWiki\User\UserNameUtils\$options
ServiceOptions $options
Definition: UserNameUtils.php:57
MediaWiki\User\UserNameUtils\$hookRunner
HookRunner $hookRunner
Definition: UserNameUtils.php:87
MediaWiki\User\UserNameUtils\$titleFactory
TitleFactory $titleFactory
Definition: UserNameUtils.php:72
MediaWiki\User\UserNameUtils\$logger
LoggerInterface $logger
Definition: UserNameUtils.php:67
MediaWiki\User\UserNameUtils\$contentLang
Language $contentLang
Definition: UserNameUtils.php:62
MediaWiki\User
Definition: DefaultOptionsLookup.php:21
MediaWiki\User\UserNameUtils\isValid
isValid(string $name)
Is the input a valid username?
Definition: UserNameUtils.php:125
MediaWiki\User\UserNameUtils\isValidIPRange
isValidIPRange(string $range)
Wrapper for IPUtils::isValidRange.
Definition: UserNameUtils.php:337
MediaWiki\User\UserNameUtils\RIGOR_CREATABLE
const RIGOR_CREATABLE
Definition: UserNameUtils.php:49
MediaWiki\User\UserNameUtils\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: UserNameUtils.php:43
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:41
TitleFactory
Creates Title objects.
Definition: TitleFactory.php:33
Wikimedia\Message\MessageValue\new
static new( $key, $params=[])
Static constructor for easier chaining of ->params() methods.
Definition: MessageValue.php:38
NS_USER
const NS_USER
Definition: Defines.php:71
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:44
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:563
MediaWiki\User\UserNameUtils\isUsable
isUsable(string $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: UserNameUtils.php:173
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:41
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:62