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;
31 use Psr\Log\LoggerInterface;
32 use TitleParser;
33 use Wikimedia\IPUtils;
36 
43 
44  public const CONSTRUCTOR_OPTIONS = [
45  'MaxNameChars',
46  'ReservedUsernames',
47  'InvalidUsernameCharacters'
48  ];
49 
50  public const RIGOR_CREATABLE = 'creatable';
51  public const RIGOR_USABLE = 'usable';
52  public const RIGOR_VALID = 'valid';
53  public const RIGOR_NONE = 'none';
54 
58  private $options;
59 
63  private $contentLang;
64 
68  private $logger;
69 
73  private $titleParser;
74 
78  private $textFormatter;
79 
83  private $reservedUsernames = false;
84 
88  private $hookRunner;
89 
98  public function __construct(
101  LoggerInterface $logger,
104  HookContainer $hookContainer
105  ) {
106  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
107  $this->options = $options;
108  $this->contentLang = $contentLang;
109  $this->logger = $logger;
110  $this->titleParser = $titleParser;
111  $this->textFormatter = $textFormatter;
112  $this->hookRunner = new HookRunner( $hookContainer );
113  }
114 
126  public function isValid( string $name ) : bool {
127  if ( $name === ''
128  || $this->isIP( $name )
129  || strpos( $name, '/' ) !== false
130  || strlen( $name ) > $this->options->get( 'MaxNameChars' )
131  || $name !== $this->contentLang->ucfirst( $name )
132  ) {
133  return false;
134  }
135 
136  // Ensure that the name can't be misresolved as a different title,
137  // such as with extra namespace keys at the start.
138  try {
139  $title = $this->titleParser->parseTitle( $name );
140  } catch ( MalformedTitleException $_ ) {
141  $title = null;
142  }
143 
144  if ( $title === null
145  || $title->getNamespace()
146  || strcmp( $name, $title->getText() )
147  ) {
148  return false;
149  }
150 
151  // Check an additional blacklist of troublemaker characters.
152  // Should these be merged into the title char list?
153  $unicodeBlacklist = '/[' .
154  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
155  '\x{00a0}' . # non-breaking space
156  '\x{2000}-\x{200f}' . # various whitespace
157  '\x{2028}-\x{202f}' . # breaks and control chars
158  '\x{3000}' . # ideographic space
159  '\x{e000}-\x{f8ff}' . # private use
160  ']/u';
161  if ( preg_match( $unicodeBlacklist, $name ) ) {
162  return false;
163  }
164 
165  return true;
166  }
167 
179  public function isUsable( string $name ) : bool {
180  // Must be a valid username, obviously ;)
181  if ( !$this->isValid( $name ) ) {
182  return false;
183  }
184 
185  if ( !$this->reservedUsernames ) {
186  $reservedUsernames = $this->options->get( 'ReservedUsernames' );
187  $this->hookRunner->onUserGetReservedNames( $reservedUsernames );
188  $this->reservedUsernames = $reservedUsernames;
189  }
190 
191  // Certain names may be reserved for batch processes.
192  foreach ( $this->reservedUsernames as $reserved ) {
193  if ( substr( $reserved, 0, 4 ) === 'msg:' ) {
194  $reserved = $this->textFormatter->format(
195  MessageValue::new( substr( $reserved, 4 ) )
196  );
197  }
198  if ( $reserved === $name ) {
199  return false;
200  }
201  }
202  return true;
203  }
204 
217  public function isCreatable( string $name ) : bool {
218  // Ensure that the username isn't longer than 235 bytes, so that
219  // (at least for the builtin skins) user javascript and css files
220  // will work. (T25080)
221  if ( strlen( $name ) > 235 ) {
222  $this->logger->debug(
223  __METHOD__ . ": '$name' uncreatable due to length"
224  );
225  return false;
226  }
227 
228  $invalid = $this->options->get( 'InvalidUsernameCharacters' );
229  // Preg yells if you try to give it an empty string
230  if ( $invalid !== '' &&
231  preg_match( '/[' . preg_quote( $invalid, '/' ) . ']/', $name )
232  ) {
233  $this->logger->debug(
234  __METHOD__ . ": '$name' uncreatable due to wgInvalidUsernameCharacters"
235  );
236  return false;
237  }
238 
239  return $this->isUsable( $name );
240  }
241 
256  public function getCanonical( string $name, string $validate = self::RIGOR_VALID ) {
257  // Force usernames to capital
258  $name = $this->contentLang->ucfirst( $name );
259 
260  // Reject names containing '#'; these will be cleaned up
261  // with title normalisation, but then it's too late to
262  // check elsewhere
263  if ( strpos( $name, '#' ) !== false ) {
264  return false;
265  }
266 
267  // No need to proceed if no validation is requested, just
268  // clean up underscores and return
269  if ( $validate === self::RIGOR_NONE ) {
270  $name = strtr( $name, '_', ' ' );
271  return $name;
272  }
273 
274  // Clean up name according to title rules,
275  // but only when validation is requested (T14654)
276  try {
277  $title = $this->titleParser->parseTitle( $name, NS_USER );
278  } catch ( MalformedTitleException $_ ) {
279  $title = null;
280  }
281 
282  // Check for invalid titles
283  if ( $title === null
284  || $title->getNamespace() !== NS_USER
285  || $title->isExternal()
286  ) {
287  return false;
288  }
289 
290  $name = $title->getText();
291 
292  // RIGOR_NONE handled above
293  switch ( $validate ) {
294  case self::RIGOR_VALID:
295  if ( !$this->isValid( $name ) ) {
296  return false;
297  }
298  return $name;
299  case self::RIGOR_USABLE:
300  if ( !$this->isUsable( $name ) ) {
301  return false;
302  }
303  return $name;
305  if ( !$this->isCreatable( $name ) ) {
306  return false;
307  }
308  return $name;
309  default:
310  throw new InvalidArgumentException(
311  "Invalid parameter value for validation ($validate) in " .
312  __METHOD__
313  );
314  }
315  }
316 
335  public function isIP( string $name ) : bool {
336  $anyIPv4 = '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/';
337  $validIP = IPUtils::isValid( $name );
338  return $validIP || preg_match( $anyIPv4, $name );
339  }
340 
347  public function isValidIPRange( string $range ) : bool {
348  return IPUtils::isValidRange( $range );
349  }
350 
351 }
Wikimedia\Message\ITextFormatter
Definition: ITextFormatter.php:18
MediaWiki\User\UserNameUtils\$textFormatter
ITextFormatter $textFormatter
Definition: UserNameUtils.php:78
MediaWiki\User\UserNameUtils\RIGOR_NONE
const RIGOR_NONE
Definition: UserNameUtils.php:53
MediaWiki\User\UserNameUtils\isIP
isIP(string $name)
Does the string match an anonymous IP address?
Definition: UserNameUtils.php:335
MediaWiki\User\UserNameUtils\RIGOR_VALID
const RIGOR_VALID
Definition: UserNameUtils.php:52
MediaWiki\User\UserNameUtils\isCreatable
isCreatable(string $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: UserNameUtils.php:217
Wikimedia\Message\MessageValue
Value object representing a message for i18n.
Definition: MessageValue.php:16
MediaWiki\User\UserNameUtils\$reservedUsernames
string[] false $reservedUsernames
Cache for isUsable()
Definition: UserNameUtils.php:83
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:25
MediaWiki\User\UserNameUtils\$titleParser
TitleParser $titleParser
Definition: UserNameUtils.php:73
$title
$title
Definition: testCompression.php:38
TitleParser
A title parser service for MediaWiki.
Definition: TitleParser.php:33
MediaWiki\User\UserNameUtils\RIGOR_USABLE
const RIGOR_USABLE
Definition: UserNameUtils.php:51
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:256
MediaWiki\User\UserNameUtils\$options
ServiceOptions $options
Definition: UserNameUtils.php:58
MediaWiki\User\UserNameUtils\$hookRunner
HookRunner $hookRunner
Definition: UserNameUtils.php:88
MediaWiki\User\UserNameUtils\$logger
LoggerInterface $logger
Definition: UserNameUtils.php:68
MediaWiki\User\UserNameUtils\$contentLang
Language $contentLang
Definition: UserNameUtils.php:63
MediaWiki\User
Definition: DefaultOptionsLookup.php:21
MediaWiki\User\UserNameUtils\isValid
isValid(string $name)
Is the input a valid username?
Definition: UserNameUtils.php:126
MediaWiki\User\UserNameUtils\isValidIPRange
isValidIPRange(string $range)
Wrapper for IPUtils::isValidRange.
Definition: UserNameUtils.php:347
MediaWiki\User\UserNameUtils\RIGOR_CREATABLE
const RIGOR_CREATABLE
Definition: UserNameUtils.php:50
MediaWiki\User\UserNameUtils\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: UserNameUtils.php:44
MalformedTitleException
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Definition: MalformedTitleException.php:26
MediaWiki\User\UserNameUtils\__construct
__construct(ServiceOptions $options, Language $contentLang, LoggerInterface $logger, TitleParser $titleParser, ITextFormatter $textFormatter, HookContainer $hookContainer)
Definition: UserNameUtils.php:98
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
Wikimedia\Message\MessageValue\new
static new( $key, $params=[])
Static constructor for easier chaining of ->params() methods.
Definition: MessageValue.php:42
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:569
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:179
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