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