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 
42 class UserNameUtils implements UserRigorOptions {
43 
47  public const CONSTRUCTOR_OPTIONS = [
48  'MaxNameChars',
49  'ReservedUsernames',
50  'InvalidUsernameCharacters'
51  ];
52 
60  private $options;
61 
65  private $contentLang;
66 
70  private $logger;
71 
75  private $titleParser;
76 
80  private $textFormatter;
81 
85  private $reservedUsernames = false;
86 
90  private $hookRunner;
91 
100  public function __construct(
103  LoggerInterface $logger,
106  HookContainer $hookContainer
107  ) {
108  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
109  $this->options = $options;
110  $this->contentLang = $contentLang;
111  $this->logger = $logger;
112  $this->titleParser = $titleParser;
113  $this->textFormatter = $textFormatter;
114  $this->hookRunner = new HookRunner( $hookContainer );
115  }
116 
128  public function isValid( string $name ): bool {
129  if ( $name === ''
130  || $this->isIP( $name )
131  || strpos( $name, '/' ) !== false
132  || strlen( $name ) > $this->options->get( 'MaxNameChars' )
133  || $name !== $this->contentLang->ucfirst( $name )
134  ) {
135  return false;
136  }
137 
138  // Ensure that the name can't be misresolved as a different title,
139  // such as with extra namespace keys at the start.
140  try {
141  $title = $this->titleParser->parseTitle( $name );
142  } catch ( MalformedTitleException $_ ) {
143  $title = null;
144  }
145 
146  if ( $title === null
147  || $title->getNamespace()
148  || strcmp( $name, $title->getText() )
149  ) {
150  return false;
151  }
152 
153  // Check an additional list of troublemaker characters.
154  // Should these be merged into the title char list?
155  $unicodeList = '/[' .
156  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
157  '\x{00a0}' . # non-breaking space
158  '\x{2000}-\x{200f}' . # various whitespace
159  '\x{2028}-\x{202f}' . # breaks and control chars
160  '\x{3000}' . # ideographic space
161  '\x{e000}-\x{f8ff}' . # private use
162  ']/u';
163  if ( preg_match( $unicodeList, $name ) ) {
164  return false;
165  }
166 
167  return true;
168  }
169 
181  public function isUsable( string $name ): bool {
182  // Must be a valid username, obviously ;)
183  if ( !$this->isValid( $name ) ) {
184  return false;
185  }
186 
187  if ( !$this->reservedUsernames ) {
188  $reservedUsernames = $this->options->get( 'ReservedUsernames' );
189  $this->hookRunner->onUserGetReservedNames( $reservedUsernames );
190  foreach ( $reservedUsernames as &$reserved ) {
191  if ( substr( $reserved, 0, 4 ) === 'msg:' ) {
192  $reserved = $this->textFormatter->format(
193  MessageValue::new( substr( $reserved, 4 ) )
194  );
195  }
196  }
197  $this->reservedUsernames = $reservedUsernames;
198  }
199 
200  // Certain names may be reserved for batch processes.
201  if ( in_array( $name, $this->reservedUsernames, true ) ) {
202  return false;
203  }
204  return true;
205  }
206 
219  public function isCreatable( string $name ): bool {
220  // Ensure that the username isn't longer than 235 bytes, so that
221  // (at least for the builtin skins) user javascript and css files
222  // will work. (T25080)
223  if ( strlen( $name ) > 235 ) {
224  $this->logger->debug(
225  __METHOD__ . ": '$name' uncreatable due to length"
226  );
227  return false;
228  }
229 
230  $invalid = $this->options->get( 'InvalidUsernameCharacters' );
231  // Preg yells if you try to give it an empty string
232  if ( $invalid !== '' &&
233  preg_match( '/[' . preg_quote( $invalid, '/' ) . ']/', $name )
234  ) {
235  $this->logger->debug(
236  __METHOD__ . ": '$name' uncreatable due to wgInvalidUsernameCharacters"
237  );
238  return false;
239  }
240 
241  return $this->isUsable( $name );
242  }
243 
258  public function getCanonical( string $name, string $validate = self::RIGOR_VALID ) {
259  // Force usernames to capital
260  $name = $this->contentLang->ucfirst( $name );
261 
262  // Reject names containing '#'; these will be cleaned up
263  // with title normalisation, but then it's too late to
264  // check elsewhere
265  if ( strpos( $name, '#' ) !== false ) {
266  return false;
267  }
268 
269  // No need to proceed if no validation is requested, just
270  // clean up underscores and return
271  if ( $validate === self::RIGOR_NONE ) {
272  $name = strtr( $name, '_', ' ' );
273  return $name;
274  }
275 
276  // Clean up name according to title rules,
277  // but only when validation is requested (T14654)
278  try {
279  $title = $this->titleParser->parseTitle( $name, NS_USER );
280  } catch ( MalformedTitleException $_ ) {
281  $title = null;
282  }
283 
284  // Check for invalid titles
285  if ( $title === null
286  || $title->getNamespace() !== NS_USER
287  || $title->isExternal()
288  ) {
289  return false;
290  }
291 
292  $name = $title->getText();
293 
294  // RIGOR_NONE handled above
295  switch ( $validate ) {
296  case self::RIGOR_VALID:
297  if ( !$this->isValid( $name ) ) {
298  return false;
299  }
300  return $name;
301  case self::RIGOR_USABLE:
302  if ( !$this->isUsable( $name ) ) {
303  return false;
304  }
305  return $name;
306  case self::RIGOR_CREATABLE:
307  if ( !$this->isCreatable( $name ) ) {
308  return false;
309  }
310  return $name;
311  default:
312  throw new InvalidArgumentException(
313  "Invalid parameter value for validation ($validate) in " .
314  __METHOD__
315  );
316  }
317  }
318 
337  public function isIP( string $name ): bool {
338  $anyIPv4 = '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/';
339  $validIP = IPUtils::isValid( $name );
340  return $validIP || preg_match( $anyIPv4, $name );
341  }
342 
349  public function isValidIPRange( string $range ): bool {
350  return IPUtils::isValidRange( $range );
351  }
352 
353 }
Wikimedia\Message\ITextFormatter
Definition: ITextFormatter.php:18
MediaWiki\User\UserNameUtils\$textFormatter
ITextFormatter $textFormatter
Definition: UserNameUtils.php:80
MediaWiki\User\UserNameUtils\isIP
isIP(string $name)
Does the string match an anonymous IP address?
Definition: UserNameUtils.php:337
MediaWiki\User\UserNameUtils\isCreatable
isCreatable(string $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: UserNameUtils.php:219
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:85
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\User\UserNameUtils\$titleParser
TitleParser $titleParser
Definition: UserNameUtils.php:75
$title
$title
Definition: testCompression.php:38
TitleParser
A title parser service for MediaWiki.
Definition: TitleParser.php:33
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:258
MediaWiki\User\UserNameUtils\$options
ServiceOptions $options
RIGOR_* constants are inherited from UserRigorOptions.
Definition: UserNameUtils.php:60
MediaWiki\User\UserNameUtils\$hookRunner
HookRunner $hookRunner
Definition: UserNameUtils.php:90
MediaWiki\User\UserNameUtils\$logger
LoggerInterface $logger
Definition: UserNameUtils.php:70
MediaWiki\User\UserNameUtils\$contentLang
Language $contentLang
Definition: UserNameUtils.php:65
MediaWiki\User
Definition: ActorCache.php:21
MediaWiki\User\UserNameUtils\isValid
isValid(string $name)
Is the input a valid username?
Definition: UserNameUtils.php:128
MediaWiki\User\UserRigorOptions
Shared interface for rigor levels when dealing with User methods.
Definition: UserRigorOptions.php:31
MediaWiki\User\UserNameUtils\isValidIPRange
isValidIPRange(string $range)
Wrapper for IPUtils::isValidRange.
Definition: UserNameUtils.php:349
NS_USER
const NS_USER
Definition: Defines.php:66
MediaWiki\User\UserNameUtils\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: UserNameUtils.php:47
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:100
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
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:556
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:181
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:42
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:71