plode( '.', $user_tokens[ $user_id ] ); if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) { // translators: %s is the user ID. return $suppress_errors ? false : new WP_Error( 'token_malformed', sprintf( __( 'Token for user %d is malformed', 'jetpack' ), $user_id ) ); } if ( $user_token_chunks[2] !== (string) $user_id ) { // translators: %1$d is the ID of the requested user. %2$d is the user ID found in the token. return $suppress_errors ? false : new WP_Error( 'user_id_mismatch', sprintf( __( 'Requesting user_id %1$d does not match token user_id %2$d', 'jetpack' ), $user_id, $user_token_chunks[2] ) ); } $possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}"; } else { $stored_blog_token = Jetpack_Options::get_option( 'blog_token' ); if ( $stored_blog_token ) { $possible_normal_tokens[] = $stored_blog_token; } $defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' ); if ( $defined_tokens_string ) { $defined_tokens = explode( ',', $defined_tokens_string ); foreach ( $defined_tokens as $defined_token ) { if ( ';' === $defined_token[0] ) { $possible_special_tokens[] = $defined_token; } else { $possible_normal_tokens[] = $defined_token; } } } } if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) { $possible_tokens = $possible_normal_tokens; } else { $possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens ); } if ( ! $possible_tokens ) { // If no user tokens were found, it would have failed earlier, so this is about blog token. return $suppress_errors ? false : new WP_Error( 'no_possible_tokens', __( 'No blog token found', 'jetpack' ) ); } $valid_token = false; if ( false === $token_key ) { // Use first token. $valid_token = $possible_tokens[0]; } elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) { // Use first normal token. $valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check. } else { // Use the token matching $token_key or false if none. // Ensure we check the full key. $token_check = rtrim( $token_key, '.' ) . '.'; foreach ( $possible_tokens as $possible_token ) { if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) { $valid_token = $possible_token; break; } } } if ( ! $valid_token ) { if ( $user_id ) { // translators: %d is the user ID. return $suppress_errors ? false : new WP_Error( 'no_valid_user_token', sprintf( __( 'Invalid token for user %d', 'jetpack' ), $user_id ) ); } else { return $suppress_errors ? false : new WP_Error( 'no_valid_blog_token', __( 'Invalid blog token', 'jetpack' ) ); } } return (object) array( 'secret' => $valid_token, 'external_user_id' => (int) $user_id, ); } /** * Updates the blog token to a new value. * * @access public * * @param string $token the new blog token value. * @return Boolean Whether updating the blog token was successful. */ public function update_blog_token( $token ) { return Jetpack_Options::update_option( 'blog_token', $token ); } /** * Unlinks the current user from the linked WordPress.com user. * * @access public * @static * * @todo Refactor to properly load the XMLRPC client independently. * * @param Integer $user_id the user identifier. * @param bool $can_overwrite_primary_user Allow for the primary user to be disconnected. * @return Boolean Whether the disconnection of the user was successful. */ public function disconnect_user( $user_id, $can_overwrite_primary_user = false ) { $tokens = Jetpack_Options::get_option( 'user_tokens' ); if ( ! $tokens ) { return false; } if ( Jetpack_Options::get_option( 'master_user' ) === $user_id && ! $can_overwrite_primary_user ) { return false; } if ( ! isset( $tokens[ $user_id ] ) ) { return false; } unset( $tokens[ $user_id ] ); Jetpack_Options::update_option( 'user_tokens', $tokens ); return true; } /** * Returns an array of user_id's that have user tokens for communicating with wpcom. * Able to select by specific capability. * * @param string $capability The capability of the user. * @return array Array of WP_User objects if found. */ public function get_connected_users( $capability = 'any' ) { $connected_users = array(); $user_tokens = Jetpack_Options::get_option( 'user_tokens' ); if ( ! is_array( $user_tokens ) || empty( $user_tokens ) ) { return $connected_users; } $connected_user_ids = array_keys( $user_tokens ); if ( ! empty( $connected_user_ids ) ) { foreach ( $connected_user_ids as $id ) { // Check for capability. if ( 'any' !== $capability && ! user_can( $id, $capability ) ) { continue; } $user_data = get_userdata( $id ); if ( $user_data instanceof \WP_User ) { $connected_users[] = $user_data; } } } return $connected_users; } /** * Fetches a signed token. * * @param object $token the token. * @return WP_Error|string a signed token */ public function get_signed_token( $token ) { if ( ! isset( $token->secret ) || empty( $token->secret ) ) { return new WP_Error( 'invalid_token' ); } list( $token_key, $token_secret ) = explode( '.', $token->secret ); $token_key = sprintf( '%s:%d:%d', $token_key, Constants::get_constant( 'JETPACK__API_VERSION' ), $token->external_user_id ); $timestamp = time(); if ( function_exists( 'wp_generate_password' ) ) { $nonce = wp_generate_password( 10, false ); } else { $nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 ); } $normalized_request_string = join( "\n", array( $token_key, $timestamp, $nonce, ) ) . "\n"; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $signature = base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) ); $auth = array( 'token' => $token_key, 'timestamp' => $timestamp, 'nonce' => $nonce, 'signature' => $signature, ); $header_pieces = array(); foreach ( $auth as $key => $value ) { $header_pieces[] = sprintf( '%s="%s"', $key, $value ); } return join( ' ', $header_pieces ); } }