Merge "maintenance: Script to rename titles for Unicode uppercasing changes"
[lhc/web/wiklou.git] / includes / password / Password.php
1 <?php
2 /**
3 * Implements the Password class for the MediaWiki software.
4 *
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.
9 *
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.
14 *
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
19 *
20 * @file
21 */
22
23 use Wikimedia\Assert\Assert;
24
25 /**
26 * Represents a password hash for use in authentication
27 *
28 * Note: All password types are transparently prefixed with :<TYPE>:, where <TYPE>
29 * is the registered type of the hash. This prefix is stripped in the constructor
30 * and is added back in the toString() function.
31 *
32 * When inheriting this class, there are a couple of expectations
33 * to be fulfilled:
34 * * If Password::toString() is called on an object, and the result is passed back in
35 * to PasswordFactory::newFromCiphertext(), the result will be identical to the original.
36 * With these two points in mind, when creating a new Password sub-class, there are some functions
37 * you have to override (because they are abstract) and others that you may want to override.
38 *
39 * The abstract functions that must be overridden are:
40 * * Password::crypt(), which takes a plaintext password and hashes it into a string hash suitable
41 * for being passed to the constructor of that class, and then stores that hash (and whatever
42 * other data) into the internal state of the object.
43 * The functions that can optionally be overridden are:
44 * * Password::parseHash(), which can be useful to override if you need to extract values from or
45 * otherwise parse a password hash when it's passed to the constructor.
46 * * Password::needsUpdate(), which can be useful if a specific password hash has different
47 * logic for when the hash needs to be updated.
48 * * Password::toString(), which can be useful if the hash was changed in the constructor and
49 * needs to be re-assembled before being returned as a string. This function is expected to add
50 * the type back on to the hash, so make sure to do that if you override the function.
51 * * Password::verify() - This function checks if $this->hash was generated with the given
52 * password. The default is to just hash the password and do a timing-safe string comparison with
53 * $this->hash.
54 *
55 * After creating a new password hash type, it can be registered using the static
56 * Password::register() method. The default type is set using the Password::setDefaultType() type.
57 * Types must be registered before they can be set as the default.
58 *
59 * @since 1.24
60 */
61 abstract class Password {
62 /**
63 * @var PasswordFactory Factory that created the object
64 */
65 protected $factory;
66
67 /**
68 * String representation of the hash without the type
69 * @var string
70 */
71 protected $hash;
72
73 /**
74 * Array of configuration variables injected from the constructor
75 * @var array
76 */
77 protected $config;
78
79 /**
80 * Hash must fit in user_password, which is a tinyblob
81 */
82 const MAX_HASH_SIZE = 255;
83
84 /**
85 * Construct the Password object using a string hash
86 *
87 * It is strongly recommended not to call this function directly unless you
88 * have a reason to. Use the PasswordFactory class instead.
89 *
90 * @throws MWException If $config does not contain required parameters
91 *
92 * @param PasswordFactory $factory Factory object that created the password
93 * @param array $config Array of engine configuration options for hashing
94 * @param string|null $hash The raw hash, including the type
95 */
96 final public function __construct( PasswordFactory $factory, array $config, $hash = null ) {
97 if ( !$this->isSupported() ) {
98 throw new Exception( 'PHP support not found for ' . get_class( $this ) );
99 }
100 if ( !isset( $config['type'] ) ) {
101 throw new Exception( 'Password configuration must contain a type name.' );
102 }
103 $this->config = $config;
104 $this->factory = $factory;
105
106 if ( $hash !== null && strlen( $hash ) >= 3 ) {
107 // Strip the type from the hash for parsing
108 $hash = substr( $hash, strpos( $hash, ':', 1 ) + 1 );
109 }
110
111 $this->hash = $hash;
112 $this->parseHash( $hash );
113 }
114
115 /**
116 * Get the type name of the password
117 *
118 * @return string Password type
119 */
120 final public function getType() {
121 return $this->config['type'];
122 }
123
124 /**
125 * Whether current password type is supported on this system.
126 *
127 * @return bool
128 */
129 protected function isSupported() {
130 return true;
131 }
132
133 /**
134 * Perform any parsing necessary on the hash to see if the hash is valid
135 * and/or to perform logic for seeing if the hash needs updating.
136 *
137 * @param string $hash The hash, with the :<TYPE>: prefix stripped
138 * @throws PasswordError If there is an error in parsing the hash
139 */
140 protected function parseHash( $hash ) {
141 }
142
143 /**
144 * Determine if the hash needs to be updated
145 *
146 * @return bool True if needs update, false otherwise
147 */
148 abstract public function needsUpdate();
149
150 /**
151 * Compare one Password object to this object
152 *
153 * By default, do a timing-safe string comparison on the result of
154 * Password::toString() for each object. This can be overridden to do
155 * custom comparison, but it is not recommended unless necessary.
156 *
157 * @deprecated since 1.33, use verify()
158 * @codeCoverageIgnore
159 *
160 * @param Password|string $other The other password
161 * @return bool True if equal, false otherwise
162 */
163 public function equals( $other ) {
164 wfDeprecated( __METHOD__, '1.33' );
165
166 if ( is_string( $other ) ) {
167 return $this->verify( $other );
168 }
169
170 return hash_equals( $this->toString(), $other->toString() );
171 }
172
173 /**
174 * Checks whether the given password matches the hash stored in this object.
175 *
176 * @param string $password Password to check
177 * @return bool
178 */
179 public function verify( $password ) {
180 Assert::parameterType( 'string', $password, '$password' );
181
182 // No need to use the factory because we're definitely making
183 // an object of the same type.
184 $obj = clone $this;
185 $obj->crypt( $password );
186
187 return hash_equals( $this->toString(), $obj->toString() );
188 }
189
190 /**
191 * Convert this hash to a string that can be stored in the database
192 *
193 * The resulting string should be considered the seralized representation
194 * of this hash, i.e., if the return value were recycled back into
195 * PasswordFactory::newFromCiphertext, the returned object would be equivalent to
196 * this; also, if two objects return the same value from this function, they
197 * are considered equivalent.
198 *
199 * @return string
200 * @throws PasswordError if password cannot be serialized to fit a tinyblob.
201 */
202 public function toString() {
203 $result = ':' . $this->config['type'] . ':' . $this->hash;
204 $this->assertIsSafeSize( $result );
205 return $result;
206 }
207
208 /**
209 * Assert that hash will fit in a tinyblob field.
210 *
211 * This prevents MW from inserting it into the DB
212 * and having MySQL silently truncating it, locking
213 * the user out of their account.
214 *
215 * @param string $hash The hash in question.
216 * @throws PasswordError If hash does not fit in DB.
217 */
218 final protected function assertIsSafeSize( $hash ) {
219 if ( strlen( $hash ) > self::MAX_HASH_SIZE ) {
220 throw new PasswordError( "Password hash is too big" );
221 }
222 }
223
224 /**
225 * Hash a password and store the result in this object
226 *
227 * The result of the password hash should be put into the internal
228 * state of the hash object.
229 *
230 * @param string $password Password to hash
231 * @throws PasswordError If an internal error occurs in hashing
232 */
233 abstract public function crypt( $password );
234 }