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