Diff: STRATO-apps/wordpress_03/app/wp-includes/class-wp-application-passwords.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * WP_Application_Passwords class
4 + *
5 + * @package WordPress
6 + * @since 5.6.0
7 + */
8 +
9 + /**
10 + * Class for displaying, modifying, and sanitizing application passwords.
11 + *
12 + * @package WordPress
13 + */
14 + #[AllowDynamicProperties]
15 + class WP_Application_Passwords {
16 +
17 + /**
18 + * The application passwords user meta key.
19 + *
20 + * @since 5.6.0
21 + *
22 + * @var string
23 + */
24 + const USERMETA_KEY_APPLICATION_PASSWORDS = '_application_passwords';
25 +
26 + /**
27 + * The option name used to store whether application passwords are in use.
28 + *
29 + * @since 5.6.0
30 + *
31 + * @var string
32 + */
33 + const OPTION_KEY_IN_USE = 'using_application_passwords';
34 +
35 + /**
36 + * The generated application password length.
37 + *
38 + * @since 5.6.0
39 + *
40 + * @var int
41 + */
42 + const PW_LENGTH = 24;
43 +
44 + /**
45 + * Checks if application passwords are being used by the site.
46 + *
47 + * This returns true if at least one application password has ever been created.
48 + *
49 + * @since 5.6.0
50 + *
51 + * @return bool
52 + */
53 + public static function is_in_use() {
54 + $network_id = get_main_network_id();
55 + return (bool) get_network_option( $network_id, self::OPTION_KEY_IN_USE );
56 + }
57 +
58 + /**
59 + * Creates a new application password.
60 + *
61 + * @since 5.6.0
62 + * @since 5.7.0 Returns WP_Error if application name already exists.
63 + * @since 6.8.0 The hashed password value now uses wp_fast_hash() instead of phpass.
64 + *
65 + * @param int $user_id User ID.
66 + * @param array $args {
67 + * Arguments used to create the application password.
68 + *
69 + * @type string $name The name of the application password.
70 + * @type string $app_id A UUID provided by the application to uniquely identify it.
71 + * }
72 + * @return array|WP_Error {
73 + * Application password details, or a WP_Error instance if an error occurs.
74 + *
75 + * @type string $0 The generated application password in plain text.
76 + * @type array $1 {
77 + * The details about the created password.
78 + *
79 + * @type string $uuid The unique identifier for the application password.
80 + * @type string $app_id A UUID provided by the application to uniquely identify it.
81 + * @type string $name The name of the application password.
82 + * @type string $password A one-way hash of the password.
83 + * @type int $created Unix timestamp of when the password was created.
84 + * @type null $last_used Null.
85 + * @type null $last_ip Null.
86 + * }
87 + * }
88 + */
89 + public static function create_new_application_password( $user_id, $args = array() ) {
90 + if ( ! empty( $args['name'] ) ) {
91 + $args['name'] = sanitize_text_field( $args['name'] );
92 + }
93 +
94 + if ( empty( $args['name'] ) ) {
95 + return new WP_Error( 'application_password_empty_name', __( 'An application name is required to create an application password.' ), array( 'status' => 400 ) );
96 + }
97 +
98 + $new_password = wp_generate_password( static::PW_LENGTH, false );
99 + $hashed_password = self::hash_password( $new_password );
100 +
101 + $new_item = array(
102 + 'uuid' => wp_generate_uuid4(),
103 + 'app_id' => empty( $args['app_id'] ) ? '' : $args['app_id'],
104 + 'name' => $args['name'],
105 + 'password' => $hashed_password,
106 + 'created' => time(),
107 + 'last_used' => null,
108 + 'last_ip' => null,
109 + );
110 +
111 + $passwords = static::get_user_application_passwords( $user_id );
112 + $passwords[] = $new_item;
113 + $saved = static::set_user_application_passwords( $user_id, $passwords );
114 +
115 + if ( ! $saved ) {
116 + return new WP_Error( 'db_error', __( 'Could not save application password.' ) );
117 + }
118 +
119 + $network_id = get_main_network_id();
120 + if ( ! get_network_option( $network_id, self::OPTION_KEY_IN_USE ) ) {
121 + update_network_option( $network_id, self::OPTION_KEY_IN_USE, true );
122 + }
123 +
124 + /**
125 + * Fires when an application password is created.
126 + *
127 + * @since 5.6.0
128 + * @since 6.8.0 The hashed password value now uses wp_fast_hash() instead of phpass.
129 + *
130 + * @param int $user_id The user ID.
131 + * @param array $new_item {
132 + * The details about the created password.
133 + *
134 + * @type string $uuid The unique identifier for the application password.
135 + * @type string $app_id A UUID provided by the application to uniquely identify it.
136 + * @type string $name The name of the application password.
137 + * @type string $password A one-way hash of the password.
138 + * @type int $created Unix timestamp of when the password was created.
139 + * @type null $last_used Null.
140 + * @type null $last_ip Null.
141 + * }
142 + * @param string $new_password The generated application password in plain text.
143 + * @param array $args {
144 + * Arguments used to create the application password.
145 + *
146 + * @type string $name The name of the application password.
147 + * @type string $app_id A UUID provided by the application to uniquely identify it.
148 + * }
149 + */
150 + do_action( 'wp_create_application_password', $user_id, $new_item, $new_password, $args );
151 +
152 + return array( $new_password, $new_item );
153 + }
154 +
155 + /**
156 + * Gets a user's application passwords.
157 + *
158 + * @since 5.6.0
159 + *
160 + * @param int $user_id User ID.
161 + * @return array {
162 + * The list of application passwords.
163 + *
164 + * @type array ...$0 {
165 + * @type string $uuid The unique identifier for the application password.
166 + * @type string $app_id A UUID provided by the application to uniquely identify it.
167 + * @type string $name The name of the application password.
168 + * @type string $password A one-way hash of the password.
169 + * @type int $created Unix timestamp of when the password was created.
170 + * @type int|null $last_used The Unix timestamp of the GMT date the application password was last used.
171 + * @type string|null $last_ip The IP address the application password was last used by.
172 + * }
173 + * }
174 + */
175 + public static function get_user_application_passwords( $user_id ) {
176 + $passwords = get_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, true );
177 +
178 + if ( ! is_array( $passwords ) ) {
179 + return array();
180 + }
181 +
182 + $save = false;
183 +
184 + foreach ( $passwords as $i => $password ) {
185 + if ( ! isset( $password['uuid'] ) ) {
186 + $passwords[ $i ]['uuid'] = wp_generate_uuid4();
187 + $save = true;
188 + }
189 + }
190 +
191 + if ( $save ) {
192 + static::set_user_application_passwords( $user_id, $passwords );
193 + }
194 +
195 + return $passwords;
196 + }
197 +
198 + /**
199 + * Gets a user's application password with the given UUID.
200 + *
201 + * @since 5.6.0
202 + *
203 + * @param int $user_id User ID.
204 + * @param string $uuid The password's UUID.
205 + * @return array|null {
206 + * The application password if found, null otherwise.
207 + *
208 + * @type string $uuid The unique identifier for the application password.
209 + * @type string $app_id A UUID provided by the application to uniquely identify it.
210 + * @type string $name The name of the application password.
211 + * @type string $password A one-way hash of the password.
212 + * @type int $created Unix timestamp of when the password was created.
213 + * @type int|null $last_used The Unix timestamp of the GMT date the application password was last used.
214 + * @type string|null $last_ip The IP address the application password was last used by.
215 + * }
216 + */
217 + public static function get_user_application_password( $user_id, $uuid ) {
218 + $passwords = static::get_user_application_passwords( $user_id );
219 +
220 + foreach ( $passwords as $password ) {
221 + if ( $password['uuid'] === $uuid ) {
222 + return $password;
223 + }
224 + }
225 +
226 + return null;
227 + }
228 +
229 + /**
230 + * Checks if an application password with the given name exists for this user.
231 + *
232 + * @since 5.7.0
233 + *
234 + * @param int $user_id User ID.
235 + * @param string $name Application name.
236 + * @return bool Whether the provided application name exists.
237 + */
238 + public static function application_name_exists_for_user( $user_id, $name ) {
239 + $passwords = static::get_user_application_passwords( $user_id );
240 +
241 + foreach ( $passwords as $password ) {
242 + if ( strtolower( $password['name'] ) === strtolower( $name ) ) {
243 + return true;
244 + }
245 + }
246 +
247 + return false;
248 + }
249 +
250 + /**
251 + * Updates an application password.
252 + *
253 + * @since 5.6.0
254 + * @since 6.8.0 The actual password should now be hashed using wp_fast_hash().
255 + *
256 + * @param int $user_id User ID.
257 + * @param string $uuid The password's UUID.
258 + * @param array $update {
259 + * Information about the application password to update.
260 + *
261 + * @type string $uuid The unique identifier for the application password.
262 + * @type string $app_id A UUID provided by the application to uniquely identify it.
263 + * @type string $name The name of the application password.
264 + * @type string $password A one-way hash of the password.
265 + * @type int $created Unix timestamp of when the password was created.
266 + * @type int|null $last_used The Unix timestamp of the GMT date the application password was last used.
267 + * @type string|null $last_ip The IP address the application password was last used by.
268 + * }
269 + * @return true|WP_Error True if successful, otherwise a WP_Error instance is returned on error.
270 + */
271 + public static function update_application_password( $user_id, $uuid, $update = array() ) {
272 + $passwords = static::get_user_application_passwords( $user_id );
273 +
274 + foreach ( $passwords as &$item ) {
275 + if ( $item['uuid'] !== $uuid ) {
276 + continue;
277 + }
278 +
279 + if ( ! empty( $update['name'] ) ) {
280 + $update['name'] = sanitize_text_field( $update['name'] );
281 + }
282 +
283 + $save = false;
284 +
285 + if ( ! empty( $update['name'] ) && $item['name'] !== $update['name'] ) {
286 + $item['name'] = $update['name'];
287 + $save = true;
288 + }
289 +
290 + if ( $save ) {
291 + $saved = static::set_user_application_passwords( $user_id, $passwords );
292 +
293 + if ( ! $saved ) {
294 + return new WP_Error( 'db_error', __( 'Could not save application password.' ) );
295 + }
296 + }
297 +
298 + /**
299 + * Fires when an application password is updated.
300 + *
301 + * @since 5.6.0
302 + * @since 6.8.0 The password is now hashed using wp_fast_hash() instead of phpass.
303 + * Existing passwords may still be hashed using phpass.
304 + *
305 + * @param int $user_id The user ID.
306 + * @param array $item {
307 + * The updated application password details.
308 + *
309 + * @type string $uuid The unique identifier for the application password.
310 + * @type string $app_id A UUID provided by the application to uniquely identify it.
311 + * @type string $name The name of the application password.
312 + * @type string $password A one-way hash of the password.
313 + * @type int $created Unix timestamp of when the password was created.
314 + * @type int|null $last_used The Unix timestamp of the GMT date the application password was last used.
315 + * @type string|null $last_ip The IP address the application password was last used by.
316 + * }
317 + * @param array $update The information to update.
318 + */
319 + do_action( 'wp_update_application_password', $user_id, $item, $update );
320 +
321 + return true;
322 + }
323 +
324 + return new WP_Error( 'application_password_not_found', __( 'Could not find an application password with that id.' ) );
325 + }
326 +
327 + /**
328 + * Records that an application password has been used.
329 + *
330 + * @since 5.6.0
331 + *
332 + * @param int $user_id User ID.
333 + * @param string $uuid The password's UUID.
334 + * @return true|WP_Error True if the usage was recorded, a WP_Error if an error occurs.
335 + */
336 + public static function record_application_password_usage( $user_id, $uuid ) {
337 + $passwords = static::get_user_application_passwords( $user_id );
338 +
339 + foreach ( $passwords as &$password ) {
340 + if ( $password['uuid'] !== $uuid ) {
341 + continue;
342 + }
343 +
344 + // Only record activity once a day.
345 + if ( $password['last_used'] + DAY_IN_SECONDS > time() ) {
346 + return true;
347 + }
348 +
349 + $password['last_used'] = time();
350 + $password['last_ip'] = $_SERVER['REMOTE_ADDR'];
351 +
352 + $saved = static::set_user_application_passwords( $user_id, $passwords );
353 +
354 + if ( ! $saved ) {
355 + return new WP_Error( 'db_error', __( 'Could not save application password.' ) );
356 + }
357 +
358 + return true;
359 + }
360 +
361 + // Specified application password not found!
362 + return new WP_Error( 'application_password_not_found', __( 'Could not find an application password with that id.' ) );
363 + }
364 +
365 + /**
366 + * Deletes an application password.
367 + *
368 + * @since 5.6.0
369 + *
370 + * @param int $user_id User ID.
371 + * @param string $uuid The password's UUID.
372 + * @return true|WP_Error Whether the password was successfully found and deleted, a WP_Error otherwise.
373 + */
374 + public static function delete_application_password( $user_id, $uuid ) {
375 + $passwords = static::get_user_application_passwords( $user_id );
376 +
377 + foreach ( $passwords as $key => $item ) {
378 + if ( $item['uuid'] === $uuid ) {
379 + unset( $passwords[ $key ] );
380 + $saved = static::set_user_application_passwords( $user_id, $passwords );
381 +
382 + if ( ! $saved ) {
383 + return new WP_Error( 'db_error', __( 'Could not delete application password.' ) );
384 + }
385 +
386 + /**
387 + * Fires when an application password is deleted.
388 + *
389 + * @since 5.6.0
390 + *
391 + * @param int $user_id The user ID.
392 + * @param array $item The data about the application password.
393 + */
394 + do_action( 'wp_delete_application_password', $user_id, $item );
395 +
396 + return true;
397 + }
398 + }
399 +
400 + return new WP_Error( 'application_password_not_found', __( 'Could not find an application password with that id.' ) );
401 + }
402 +
403 + /**
404 + * Deletes all application passwords for the given user.
405 + *
406 + * @since 5.6.0
407 + *
408 + * @param int $user_id User ID.
409 + * @return int|WP_Error The number of passwords that were deleted or a WP_Error on failure.
410 + */
411 + public static function delete_all_application_passwords( $user_id ) {
412 + $passwords = static::get_user_application_passwords( $user_id );
413 +
414 + if ( $passwords ) {
415 + $saved = static::set_user_application_passwords( $user_id, array() );
416 +
417 + if ( ! $saved ) {
418 + return new WP_Error( 'db_error', __( 'Could not delete application passwords.' ) );
419 + }
420 +
421 + foreach ( $passwords as $item ) {
422 + /** This action is documented in wp-includes/class-wp-application-passwords.php */
423 + do_action( 'wp_delete_application_password', $user_id, $item );
424 + }
425 +
426 + return count( $passwords );
427 + }
428 +
429 + return 0;
430 + }
431 +
432 + /**
433 + * Sets a user's application passwords.
434 + *
435 + * @since 5.6.0
436 + *
437 + * @param int $user_id User ID.
438 + * @param array $passwords {
439 + * The list of application passwords.
440 + *
441 + * @type array ...$0 {
442 + * @type string $uuid The unique identifier for the application password.
443 + * @type string $app_id A UUID provided by the application to uniquely identify it.
444 + * @type string $name The name of the application password.
445 + * @type string $password A one-way hash of the password.
446 + * @type int $created Unix timestamp of when the password was created.
447 + * @type int|null $last_used The Unix timestamp of the GMT date the application password was last used.
448 + * @type string|null $last_ip The IP address the application password was last used by.
449 + * }
450 + * }
451 + * @return int|bool User meta ID if the key didn't exist (ie. this is the first time that an application password
452 + * has been saved for the user), true on successful update, false on failure or if the value passed
453 + * is the same as the one that is already in the database.
454 + */
455 + protected static function set_user_application_passwords( $user_id, $passwords ) {
456 + return update_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, $passwords );
457 + }
458 +
459 + /**
460 + * Sanitizes and then splits a password into smaller chunks.
461 + *
462 + * @since 5.6.0
463 + *
464 + * @param string $raw_password The raw application password.
465 + * @return string The chunked password.
466 + */
467 + public static function chunk_password(
468 + #[\SensitiveParameter]
469 + $raw_password
470 + ) {
471 + $raw_password = preg_replace( '/[^a-z\d]/i', '', $raw_password );
472 +
473 + return trim( chunk_split( $raw_password, 4, ' ' ) );
474 + }
475 +
476 + /**
477 + * Hashes a plaintext application password.
478 + *
479 + * @since 6.8.0
480 + *
481 + * @param string $password Plaintext password.
482 + * @return string Hashed password.
483 + */
484 + public static function hash_password(
485 + #[\SensitiveParameter]
486 + string $password
487 + ): string {
488 + return wp_fast_hash( $password );
489 + }
490 +
491 + /**
492 + * Checks a plaintext application password against a hashed password.
493 + *
494 + * @since 6.8.0
495 + *
496 + * @param string $password Plaintext password.
497 + * @param string $hash Hash of the password to check against.
498 + * @return bool Whether the password matches the hashed password.
499 + */
500 + public static function check_password(
501 + #[\SensitiveParameter]
502 + string $password,
503 + string $hash
504 + ): bool {
505 + if ( ! str_starts_with( $hash, '$generic$' ) ) {
506 + /*
507 + * If the hash doesn't start with `$generic$`, it is a hash created with `wp_hash_password()`.
508 + * This is the case for application passwords created before 6.8.0.
509 + */
510 + return wp_check_password( $password, $hash );
511 + }
512 +
513 + return wp_verify_fast_hash( $password, $hash );
514 + }
515 + }
516 +