[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-application-passwords.php (source)

   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  }


Generated : Tue Nov 10 08:20:01 2026 Cross-referenced by PHPXref