wiki.techinc.nl/includes/password/PasswordFactory.php
Umherirrender e662614f95 Use explicit nullable type on parameter arguments
Implicitly marking parameter $... as nullable is deprecated in php8.4,
the explicit nullable type must be used instead

Created with autofix from Ide15839e98a6229c22584d1c1c88c690982e1d7a

Break one long line in SpecialPage.php

Bug: T376276
Change-Id: I807257b2ba1ab2744ab74d9572c9c3d3ac2a968e
2024-10-16 20:58:33 +02:00

275 lines
8.1 KiB
PHP

<?php
/**
* Implements the Password class for the MediaWiki software.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
declare( strict_types = 1 );
namespace MediaWiki\Password;
use InvalidArgumentException;
use MediaWiki\Config\Config;
use MediaWiki\MainConfigNames;
use MWCryptRand;
use Wikimedia\ObjectFactory\ObjectFactory;
/**
* Factory class for creating and checking Password objects
*
* @since 1.24
*/
final class PasswordFactory {
/**
* The default PasswordHash type
*
* @var string
* @see PasswordFactory::setDefaultType
*/
private $default = '';
/**
* Mapping of password types to classes
*
* @var array[]
* @see PasswordFactory::register
* @see Setup.php
*/
private $types = [
'' => [ 'type' => '', 'class' => InvalidPassword::class ],
];
private const MIN_RANDOM_PASSWORD_LENGTH = 10;
/**
* Most of the time you'll want to use MediaWikiServices::getInstance()->getPasswordFactory
* instead.
* @param array $config Mapping of password type => config
* @param string $default Default password type
* @see PasswordFactory::register
* @see PasswordFactory::setDefaultType
*/
public function __construct( array $config = [], string $default = '' ) {
foreach ( $config as $type => $options ) {
$this->register( $type, $options );
}
if ( $default !== '' ) {
$this->setDefaultType( $default );
}
}
/**
* Register a new type of password hash
*
* @param string $type Unique type name for the hash. Will be prefixed to the password hashes
* to identify what hashing method was used.
* @param array $config Array of configuration options. 'class' is required (the Password
* subclass name), everything else is passed to the constructor of that class.
*/
public function register( string $type, array $config ): void {
$config['type'] = $type;
$this->types[$type] = $config;
}
/**
* Set the default password type
*
* This type will be used for creating new passwords when the type is not specified.
* Passwords of a different type will be considered outdated and in need of update.
*
* @param string $type Password hash type
* @throws InvalidArgumentException If the type is not registered
*/
public function setDefaultType( string $type ): void {
if ( !isset( $this->types[$type] ) ) {
throw new InvalidArgumentException( "Invalid password type $type." );
}
$this->default = $type;
}
/**
* Get the default password type
*
* @return string
*/
public function getDefaultType(): string {
return $this->default;
}
/**
* @deprecated since 1.32 Initialize settings using the constructor
* Emitting deprecation warnings since 1.41.
*
* Initialize the internal static variables using the global variables
*
* @param Config $config Configuration object to load data from
*/
public function init( Config $config ): void {
wfDeprecated( __METHOD__, '1.32' );
foreach ( $config->get( MainConfigNames::PasswordConfig ) as $type => $options ) {
$this->register( $type, $options );
}
$this->setDefaultType( $config->get( MainConfigNames::PasswordDefault ) );
}
/**
* Get the list of types of passwords
*
* @return array[]
*/
public function getTypes(): array {
return $this->types;
}
/**
* Create a new Password object from an existing string hash
*
* Parse the type of a hash and create a new hash object based on the parsed type.
* Pass the raw hash to the constructor of the new object. Use InvalidPassword type
* if a null hash is given.
*
* @param string|null $hash Existing hash or null for an invalid password
* @return Password
* @throws PasswordError If hash is invalid or type is not recognized
*/
public function newFromCiphertext( ?string $hash ): Password {
if ( $hash === null || $hash === '' ) {
return new InvalidPassword( $this, [ 'type' => '' ], null );
} elseif ( $hash[0] !== ':' ) {
throw new PasswordError( 'Invalid hash given' );
}
$type = substr( $hash, 1, strpos( $hash, ':', 1 ) - 1 );
return $this->newFromTypeAndHash( $type, $hash );
}
/**
* Create a new Password object of the given type.
*
* @param string $type Existing type
* @return Password
* @throws PasswordError If type is not recognized
*/
public function newFromType( string $type ): Password {
return $this->newFromTypeAndHash( $type, null );
}
/**
* Create a new Password object of the given type, optionally with an existing string hash.
*
* @param string $type Existing type
* @param string|null $hash Existing hash
* @return Password
* @throws PasswordError If hash is invalid or type is not recognized
*/
private function newFromTypeAndHash( string $type, ?string $hash ): Password {
if ( !isset( $this->types[$type] ) ) {
throw new PasswordError( "Unrecognized password hash type $type." );
}
$config = $this->types[$type];
// @phan-suppress-next-line PhanTypeInvalidCallableArrayKey
return ObjectFactory::getObjectFromSpec( $config, [
'extraArgs' => [ $this, $config, $hash ],
'assertClass' => Password::class,
] );
}
/**
* Create a new Password object from a plaintext password
*
* If no existing object is given, make a new default object. If one is given, clone that
* object. Then pass the plaintext to Password::crypt().
*
* @param string|null $password Plaintext password, or null for an invalid password
* @param Password|null $existing Optional existing hash to get options from
* @return Password
*/
public function newFromPlaintext( ?string $password, ?Password $existing = null ): Password {
if ( $password === null ) {
return new InvalidPassword( $this, [ 'type' => '' ], null );
}
if ( $existing === null ) {
$obj = $this->newFromType( $this->default );
} else {
$obj = clone $existing;
}
$obj->crypt( $password );
return $obj;
}
/**
* Determine whether a password object needs updating
*
* Check whether the given password is of the default type. If it is,
* pass off further needsUpdate checks to Password::needsUpdate.
*
* @param Password $password
*
* @return bool True if needs update, false otherwise
*/
public function needsUpdate( Password $password ): bool {
if ( $password->getType() !== $this->default ) {
return true;
} else {
return $password->needsUpdate();
}
}
/**
* Generate a random string suitable for a password
*
* @param int $minLength Minimum length of password to generate
* @return string
*/
public static function generateRandomPasswordString( int $minLength = 10 ): string {
// Decide the final password length based on our min password length,
// requiring at least a minimum of self::MIN_RANDOM_PASSWORD_LENGTH chars.
$length = max( self::MIN_RANDOM_PASSWORD_LENGTH, $minLength );
// Multiply by 1.25 to get the number of hex characters we need
$hex = MWCryptRand::generateHex( ceil( $length * 1.25 ) );
// Convert from base 16 to base 32 to get a proper password like string
return substr( \Wikimedia\base_convert( $hex, 16, 32, $length ), -$length );
}
/**
* Create an InvalidPassword
*
* @return InvalidPassword
*/
public static function newInvalidPassword(): InvalidPassword {
static $password = null;
if ( $password === null ) {
$factory = new self();
$password = new InvalidPassword( $factory, [ 'type' => '' ], null );
}
return $password;
}
}
/** @deprecated since 1.43 use MediaWiki\\Password\\PasswordFactory */
class_alias( PasswordFactory::class, 'PasswordFactory' );