Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / password / PasswordFactory.php
blob1de40fe546ead4a9b16462a7eaf2c625623ef5f0
1 <?php
2 /**
3 * Implements the Password class for the MediaWiki software.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
23 declare( strict_types = 1 );
25 namespace MediaWiki\Password;
27 use InvalidArgumentException;
28 use MediaWiki\Config\Config;
29 use MediaWiki\MainConfigNames;
30 use MWCryptRand;
31 use Wikimedia\ObjectFactory\ObjectFactory;
33 /**
34 * Factory class for creating and checking Password objects
36 * @since 1.24
38 final class PasswordFactory {
39 /**
40 * The default PasswordHash type
42 * @var string
43 * @see PasswordFactory::setDefaultType
45 private $default = '';
47 /**
48 * Mapping of password types to classes
50 * @var array[]
51 * @see PasswordFactory::register
52 * @see Setup.php
54 private $types = [
55 '' => [ 'type' => '', 'class' => InvalidPassword::class ],
58 private const MIN_RANDOM_PASSWORD_LENGTH = 10;
60 /**
61 * Most of the time you'll want to use MediaWikiServices::getInstance()->getPasswordFactory
62 * instead.
63 * @param array $config Mapping of password type => config
64 * @param string $default Default password type
65 * @see PasswordFactory::register
66 * @see PasswordFactory::setDefaultType
68 public function __construct( array $config = [], string $default = '' ) {
69 foreach ( $config as $type => $options ) {
70 $this->register( $type, $options );
73 if ( $default !== '' ) {
74 $this->setDefaultType( $default );
78 /**
79 * Register a new type of password hash
81 * @param string $type Unique type name for the hash. Will be prefixed to the password hashes
82 * to identify what hashing method was used.
83 * @param array $config Array of configuration options. 'class' is required (the Password
84 * subclass name), everything else is passed to the constructor of that class.
86 public function register( string $type, array $config ): void {
87 $config['type'] = $type;
88 $this->types[$type] = $config;
91 /**
92 * Set the default password type
94 * This type will be used for creating new passwords when the type is not specified.
95 * Passwords of a different type will be considered outdated and in need of update.
97 * @param string $type Password hash type
98 * @throws InvalidArgumentException If the type is not registered
100 public function setDefaultType( string $type ): void {
101 if ( !isset( $this->types[$type] ) ) {
102 throw new InvalidArgumentException( "Invalid password type $type." );
104 $this->default = $type;
108 * Get the default password type
110 * @return string
112 public function getDefaultType(): string {
113 return $this->default;
117 * @deprecated since 1.32 Initialize settings using the constructor
118 * Emitting deprecation warnings since 1.41.
120 * Initialize the internal static variables using the global variables
122 * @param Config $config Configuration object to load data from
124 public function init( Config $config ): void {
125 wfDeprecated( __METHOD__, '1.32' );
126 foreach ( $config->get( MainConfigNames::PasswordConfig ) as $type => $options ) {
127 $this->register( $type, $options );
130 $this->setDefaultType( $config->get( MainConfigNames::PasswordDefault ) );
134 * Get the list of types of passwords
136 * @return array[]
138 public function getTypes(): array {
139 return $this->types;
143 * Create a new Password object from an existing string hash
145 * Parse the type of a hash and create a new hash object based on the parsed type.
146 * Pass the raw hash to the constructor of the new object. Use InvalidPassword type
147 * if a null hash is given.
149 * @param string|null $hash Existing hash or null for an invalid password
150 * @return Password
151 * @throws PasswordError If hash is invalid or type is not recognized
153 public function newFromCiphertext( ?string $hash ): Password {
154 if ( $hash === null || $hash === '' ) {
155 return new InvalidPassword( $this, [ 'type' => '' ], null );
156 } elseif ( $hash[0] !== ':' ) {
157 throw new PasswordError( 'Invalid hash given' );
160 $type = substr( $hash, 1, strpos( $hash, ':', 1 ) - 1 );
161 return $this->newFromTypeAndHash( $type, $hash );
165 * Create a new Password object of the given type.
167 * @param string $type Existing type
168 * @return Password
169 * @throws PasswordError If type is not recognized
171 public function newFromType( string $type ): Password {
172 return $this->newFromTypeAndHash( $type, null );
176 * Create a new Password object of the given type, optionally with an existing string hash.
178 * @param string $type Existing type
179 * @param string|null $hash Existing hash
180 * @return Password
181 * @throws PasswordError If hash is invalid or type is not recognized
183 private function newFromTypeAndHash( string $type, ?string $hash ): Password {
184 if ( !isset( $this->types[$type] ) ) {
185 throw new PasswordError( "Unrecognized password hash type $type." );
188 $config = $this->types[$type];
190 // @phan-suppress-next-line PhanTypeInvalidCallableArrayKey
191 return ObjectFactory::getObjectFromSpec( $config, [
192 'extraArgs' => [ $this, $config, $hash ],
193 'assertClass' => Password::class,
194 ] );
198 * Create a new Password object from a plaintext password
200 * If no existing object is given, make a new default object. If one is given, clone that
201 * object. Then pass the plaintext to Password::crypt().
203 * @param string|null $password Plaintext password, or null for an invalid password
204 * @param Password|null $existing Optional existing hash to get options from
205 * @return Password
207 public function newFromPlaintext( ?string $password, ?Password $existing = null ): Password {
208 if ( $password === null ) {
209 return new InvalidPassword( $this, [ 'type' => '' ], null );
212 if ( $existing === null ) {
213 $obj = $this->newFromType( $this->default );
214 } else {
215 $obj = clone $existing;
218 $obj->crypt( $password );
220 return $obj;
224 * Determine whether a password object needs updating
226 * Check whether the given password is of the default type. If it is,
227 * pass off further needsUpdate checks to Password::needsUpdate.
229 * @param Password $password
231 * @return bool True if needs update, false otherwise
233 public function needsUpdate( Password $password ): bool {
234 if ( $password->getType() !== $this->default ) {
235 return true;
236 } else {
237 return $password->needsUpdate();
242 * Generate a random string suitable for a password
244 * @param int $minLength Minimum length of password to generate
245 * @return string
247 public static function generateRandomPasswordString( int $minLength = 10 ): string {
248 // Decide the final password length based on our min password length,
249 // requiring at least a minimum of self::MIN_RANDOM_PASSWORD_LENGTH chars.
250 $length = max( self::MIN_RANDOM_PASSWORD_LENGTH, $minLength );
251 // Multiply by 1.25 to get the number of hex characters we need
252 $hex = MWCryptRand::generateHex( ceil( $length * 1.25 ) );
253 // Convert from base 16 to base 32 to get a proper password like string
254 return substr( \Wikimedia\base_convert( $hex, 16, 32, $length ), -$length );
258 * Create an InvalidPassword
260 * @return InvalidPassword
262 public static function newInvalidPassword(): InvalidPassword {
263 static $password = null;
265 if ( $password === null ) {
266 $factory = new self();
267 $password = new InvalidPassword( $factory, [ 'type' => '' ], null );
270 return $password;
274 /** @deprecated since 1.43 use MediaWiki\\Password\\PasswordFactory */
275 class_alias( PasswordFactory::class, 'PasswordFactory' );