Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.74% |
45 / 47 |
|
92.31% |
12 / 13 |
CRAP | |
0.00% |
0 / 1 |
PasswordFactory | |
95.74% |
45 / 47 |
|
92.31% |
12 / 13 |
25 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
register | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setDefaultType | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getDefaultType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
init | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getTypes | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newFromCiphertext | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
newFromType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newFromTypeAndHash | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
newFromPlaintext | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
needsUpdate | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
generateRandomPasswordString | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
newInvalidPassword | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
2.26 |
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 | declare( strict_types = 1 ); |
24 | |
25 | use MediaWiki\Config\Config; |
26 | use MediaWiki\MainConfigNames; |
27 | use Wikimedia\ObjectFactory\ObjectFactory; |
28 | |
29 | /** |
30 | * Factory class for creating and checking Password objects |
31 | * |
32 | * @since 1.24 |
33 | */ |
34 | final class PasswordFactory { |
35 | /** |
36 | * The default PasswordHash type |
37 | * |
38 | * @var string |
39 | * @see PasswordFactory::setDefaultType |
40 | */ |
41 | private $default = ''; |
42 | |
43 | /** |
44 | * Mapping of password types to classes |
45 | * |
46 | * @var array[] |
47 | * @see PasswordFactory::register |
48 | * @see Setup.php |
49 | */ |
50 | private $types = [ |
51 | '' => [ 'type' => '', 'class' => InvalidPassword::class ], |
52 | ]; |
53 | |
54 | /** |
55 | * Most of the time you'll want to use MediaWikiServices::getInstance()->getPasswordFactory |
56 | * instead. |
57 | * @param array $config Mapping of password type => config |
58 | * @param string $default Default password type |
59 | * @see PasswordFactory::register |
60 | * @see PasswordFactory::setDefaultType |
61 | */ |
62 | public function __construct( array $config = [], string $default = '' ) { |
63 | foreach ( $config as $type => $options ) { |
64 | $this->register( $type, $options ); |
65 | } |
66 | |
67 | if ( $default !== '' ) { |
68 | $this->setDefaultType( $default ); |
69 | } |
70 | } |
71 | |
72 | /** |
73 | * Register a new type of password hash |
74 | * |
75 | * @param string $type Unique type name for the hash. Will be prefixed to the password hashes |
76 | * to identify what hashing method was used. |
77 | * @param array $config Array of configuration options. 'class' is required (the Password |
78 | * subclass name), everything else is passed to the constructor of that class. |
79 | */ |
80 | public function register( string $type, array $config ): void { |
81 | $config['type'] = $type; |
82 | $this->types[$type] = $config; |
83 | } |
84 | |
85 | /** |
86 | * Set the default password type |
87 | * |
88 | * This type will be used for creating new passwords when the type is not specified. |
89 | * Passwords of a different type will be considered outdated and in need of update. |
90 | * |
91 | * @param string $type Password hash type |
92 | * @throws InvalidArgumentException If the type is not registered |
93 | */ |
94 | public function setDefaultType( string $type ): void { |
95 | if ( !isset( $this->types[$type] ) ) { |
96 | throw new InvalidArgumentException( "Invalid password type $type." ); |
97 | } |
98 | $this->default = $type; |
99 | } |
100 | |
101 | /** |
102 | * Get the default password type |
103 | * |
104 | * @return string |
105 | */ |
106 | public function getDefaultType(): string { |
107 | return $this->default; |
108 | } |
109 | |
110 | /** |
111 | * @deprecated since 1.32 Initialize settings using the constructor |
112 | * Emitting deprecation warnings since 1.41. |
113 | * |
114 | * Initialize the internal static variables using the global variables |
115 | * |
116 | * @param Config $config Configuration object to load data from |
117 | */ |
118 | public function init( Config $config ): void { |
119 | wfDeprecated( __METHOD__, '1.32' ); |
120 | foreach ( $config->get( MainConfigNames::PasswordConfig ) as $type => $options ) { |
121 | $this->register( $type, $options ); |
122 | } |
123 | |
124 | $this->setDefaultType( $config->get( MainConfigNames::PasswordDefault ) ); |
125 | } |
126 | |
127 | /** |
128 | * Get the list of types of passwords |
129 | * |
130 | * @return array[] |
131 | */ |
132 | public function getTypes(): array { |
133 | return $this->types; |
134 | } |
135 | |
136 | /** |
137 | * Create a new Password object from an existing string hash |
138 | * |
139 | * Parse the type of a hash and create a new hash object based on the parsed type. |
140 | * Pass the raw hash to the constructor of the new object. Use InvalidPassword type |
141 | * if a null hash is given. |
142 | * |
143 | * @param string|null $hash Existing hash or null for an invalid password |
144 | * @return Password |
145 | * @throws PasswordError If hash is invalid or type is not recognized |
146 | */ |
147 | public function newFromCiphertext( ?string $hash ): Password { |
148 | if ( $hash === null || $hash === '' ) { |
149 | return new InvalidPassword( $this, [ 'type' => '' ], null ); |
150 | } elseif ( $hash[0] !== ':' ) { |
151 | throw new PasswordError( 'Invalid hash given' ); |
152 | } |
153 | |
154 | $type = substr( $hash, 1, strpos( $hash, ':', 1 ) - 1 ); |
155 | return $this->newFromTypeAndHash( $type, $hash ); |
156 | } |
157 | |
158 | /** |
159 | * Create a new Password object of the given type. |
160 | * |
161 | * @param string $type Existing type |
162 | * @return Password |
163 | * @throws PasswordError If type is not recognized |
164 | */ |
165 | public function newFromType( string $type ): Password { |
166 | return $this->newFromTypeAndHash( $type, null ); |
167 | } |
168 | |
169 | /** |
170 | * Create a new Password object of the given type, optionally with an existing string hash. |
171 | * |
172 | * @param string $type Existing type |
173 | * @param string|null $hash Existing hash |
174 | * @return Password |
175 | * @throws PasswordError If hash is invalid or type is not recognized |
176 | */ |
177 | private function newFromTypeAndHash( string $type, ?string $hash ): Password { |
178 | if ( !isset( $this->types[$type] ) ) { |
179 | throw new PasswordError( "Unrecognized password hash type $type." ); |
180 | } |
181 | |
182 | $config = $this->types[$type]; |
183 | |
184 | // @phan-suppress-next-line PhanTypeInvalidCallableArrayKey |
185 | return ObjectFactory::getObjectFromSpec( $config, [ |
186 | 'extraArgs' => [ $this, $config, $hash ], |
187 | 'assertClass' => Password::class, |
188 | ] ); |
189 | } |
190 | |
191 | /** |
192 | * Create a new Password object from a plaintext password |
193 | * |
194 | * If no existing object is given, make a new default object. If one is given, clone that |
195 | * object. Then pass the plaintext to Password::crypt(). |
196 | * |
197 | * @param string|null $password Plaintext password, or null for an invalid password |
198 | * @param Password|null $existing Optional existing hash to get options from |
199 | * @return Password |
200 | */ |
201 | public function newFromPlaintext( ?string $password, Password $existing = null ): Password { |
202 | if ( $password === null ) { |
203 | return new InvalidPassword( $this, [ 'type' => '' ], null ); |
204 | } |
205 | |
206 | if ( $existing === null ) { |
207 | $obj = $this->newFromType( $this->default ); |
208 | } else { |
209 | $obj = clone $existing; |
210 | } |
211 | |
212 | $obj->crypt( $password ); |
213 | |
214 | return $obj; |
215 | } |
216 | |
217 | /** |
218 | * Determine whether a password object needs updating |
219 | * |
220 | * Check whether the given password is of the default type. If it is, |
221 | * pass off further needsUpdate checks to Password::needsUpdate. |
222 | * |
223 | * @param Password $password |
224 | * |
225 | * @return bool True if needs update, false otherwise |
226 | */ |
227 | public function needsUpdate( Password $password ): bool { |
228 | if ( $password->getType() !== $this->default ) { |
229 | return true; |
230 | } else { |
231 | return $password->needsUpdate(); |
232 | } |
233 | } |
234 | |
235 | /** |
236 | * Generate a random string suitable for a password |
237 | * |
238 | * @param int $minLength Minimum length of password to generate |
239 | * @return string |
240 | */ |
241 | public static function generateRandomPasswordString( int $minLength = 10 ): string { |
242 | // Decide the final password length based on our min password length, |
243 | // stopping at a minimum of 10 chars. |
244 | $length = max( 10, $minLength ); |
245 | // Multiply by 1.25 to get the number of hex characters we need |
246 | // Generate random hex chars |
247 | $hex = MWCryptRand::generateHex( ceil( $length * 1.25 ) ); |
248 | // Convert from base 16 to base 32 to get a proper password like string |
249 | return substr( Wikimedia\base_convert( $hex, 16, 32, $length ), -$length ); |
250 | } |
251 | |
252 | /** |
253 | * Create an InvalidPassword |
254 | * |
255 | * @return InvalidPassword |
256 | */ |
257 | public static function newInvalidPassword(): InvalidPassword { |
258 | static $password = null; |
259 | |
260 | if ( $password === null ) { |
261 | $factory = new self(); |
262 | $password = new InvalidPassword( $factory, [ 'type' => '' ], null ); |
263 | } |
264 | |
265 | return $password; |
266 | } |
267 | } |