Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / password / Password.php
blob18f18dc25b7d7294fcf5cbff1de270d95d823232
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 RuntimeException;
30 /**
31 * Represents a password hash for use in authentication
33 * Note: All password types are transparently prefixed with :<TYPE>:, where <TYPE>
34 * is the registered type of the hash. This prefix is stripped in the constructor
35 * and is added back in the toString() function.
37 * When inheriting this class, there are a couple of expectations
38 * to be fulfilled:
39 * * If Password::toString() is called on an object, and the result is passed back in
40 * to PasswordFactory::newFromCiphertext(), the result will be identical to the original.
41 * With these two points in mind, when creating a new Password sub-class, there are some functions
42 * you have to override (because they are abstract) and others that you may want to override.
44 * The abstract functions that must be overridden are:
45 * * Password::crypt(), which takes a plaintext password and hashes it into a string hash suitable
46 * for being passed to the constructor of that class, and then stores that hash (and whatever
47 * other data) into the internal state of the object.
48 * The functions that can optionally be overridden are:
49 * * Password::parseHash(), which can be useful to override if you need to extract values from or
50 * otherwise parse a password hash when it's passed to the constructor.
51 * * Password::needsUpdate(), which can be useful if a specific password hash has different
52 * logic for when the hash needs to be updated.
53 * * Password::toString(), which can be useful if the hash was changed in the constructor and
54 * needs to be re-assembled before being returned as a string. This function is expected to add
55 * the type back on to the hash, so make sure to do that if you override the function.
56 * * Password::verify() - This function checks if $this->hash was generated with the given
57 * password. The default is to just hash the password and do a timing-safe string comparison with
58 * $this->hash.
60 * After creating a new password hash type, it can be registered using the static
61 * Password::register() method. The default type is set using the Password::setDefaultType() type.
62 * Types must be registered before they can be set as the default.
64 * @since 1.24
66 abstract class Password {
67 /**
68 * @var PasswordFactory Factory that created the object
70 protected $factory;
72 /**
73 * String representation of the hash without the type
74 * @var string|null
76 protected $hash;
78 /**
79 * Array of configuration variables injected from the constructor
80 * @var array
82 protected $config;
84 /**
85 * Hash must fit in user_password, which is a tinyblob
87 private const MAX_HASH_SIZE = 255;
89 /**
90 * Construct the Password object using a string hash
92 * It is strongly recommended not to call this function directly unless you
93 * have a reason to. Use the PasswordFactory class instead.
95 * @param PasswordFactory $factory Factory object that created the password
96 * @param array $config Array of engine configuration options for hashing
97 * @param string|null $hash The raw hash, including the type
99 final public function __construct( PasswordFactory $factory, array $config, ?string $hash = null ) {
100 if ( !$this->isSupported() ) {
101 throw new RuntimeException( 'PHP support not found for ' . get_class( $this ) );
103 if ( !isset( $config['type'] ) ) {
104 throw new InvalidArgumentException( 'Password configuration must contain a type name.' );
106 $this->config = $config;
107 $this->factory = $factory;
109 if ( $hash !== null && strlen( $hash ) >= 3 ) {
110 // Strip the type from the hash for parsing
111 $hash = substr( $hash, strpos( $hash, ':', 1 ) + 1 );
114 $this->hash = $hash;
115 $this->parseHash( $hash );
119 * Get the type name of the password
121 * @return string Password type
123 final public function getType(): string {
124 return $this->config['type'];
128 * Whether current password type is supported on this system.
130 * @return bool
132 protected function isSupported(): bool {
133 return true;
137 * Perform any parsing necessary on the hash to see if the hash is valid
138 * and/or to perform logic for seeing if the hash needs updating.
140 * @param string|null $hash The hash, with the :<TYPE>: prefix stripped
141 * @throws PasswordError If there is an error in parsing the hash
143 protected function parseHash( ?string $hash ): void {
147 * Determine if the hash needs to be updated
149 * @return bool True if needs update, false otherwise
151 abstract public function needsUpdate(): bool;
154 * Checks whether the given password matches the hash stored in this object.
156 * @param string $password Password to check
157 * @return bool
159 public function verify( string $password ): bool {
160 // No need to use the factory because we're definitely making
161 // an object of the same type.
162 $obj = clone $this;
163 $obj->crypt( $password );
165 return hash_equals( $this->toString(), $obj->toString() );
169 * Convert this hash to a string that can be stored in the database
171 * The resulting string should be considered the serialized representation
172 * of this hash, i.e., if the return value were recycled back into
173 * PasswordFactory::newFromCiphertext, the returned object would be equivalent to
174 * this; also, if two objects return the same value from this function, they
175 * are considered equivalent.
177 * @return string
178 * @throws PasswordError if password cannot be serialized to fit a tinyblob.
180 public function toString(): string {
181 $result = ':' . $this->config['type'] . ':' . $this->hash;
182 $this->assertIsSafeSize( $result );
183 return $result;
187 * Assert that hash will fit in a tinyblob field.
189 * This prevents MW from inserting it into the DB
190 * and having MySQL silently truncating it, locking
191 * the user out of their account.
193 * @param string $hash The hash in question.
194 * @throws PasswordError If hash does not fit in DB.
196 final protected function assertIsSafeSize( string $hash ): void {
197 if ( strlen( $hash ) > self::MAX_HASH_SIZE ) {
198 throw new PasswordError( "Password hash is too big" );
203 * Hash a password and store the result in this object
205 * The result of the password hash should be put into the internal
206 * state of the hash object.
208 * @param string $password Password to hash
209 * @throws PasswordError If an internal error occurs in hashing
211 abstract public function crypt( string $password ): void;
214 /** @deprecated since 1.43 use MediaWiki\\Password\\PasswordFactory */
215 class_alias( Password::class, 'Password' );