Plugin Directory

Changeset 3747553


Ignore:
Timestamp:
10/15/2026 03:36:44 AM (3 weeks ago)
Author:
someguy9
Message:

1.6.3

Location:
rightblogger
Files:
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • rightblogger/tags/1.6.3/readme.txt

    r3740227 r3747553  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.6.2
     7Stable tag: 1.6.3
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    114114
    115115== Changelog ==
     116
     117= 1.6.3 =
     118* Fixed: Connecting no longer fails with "Security check failed" and leaves the site unconnected when WordPress asks you to log in again on the way back from RightBlogger.
    116119
    117120= 1.6.2 =
  • rightblogger/tags/1.6.3/rightblogger.php

    r3739703 r3747553  
    44 * Plugin URI: https://rightblogger.com/wordpress
    55 * Description: Connect your WordPress site to RightBlogger to publish AI-generated content directly.
    6  * Version: 1.6.2
     6 * Version: 1.6.3
    77 * Author: RightBlogger
    88 * Author URI: https://rightblogger.com
    … …  
    2020
    2121// Plugin constants
    22 define('RIGHTBLOGGER_VERSION', '1.6.2');
     22define('RIGHTBLOGGER_VERSION', '1.6.3');
    2323define('RIGHTBLOGGER_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2424define('RIGHTBLOGGER_PLUGIN_URL', plugin_dir_url(__FILE__));
    … …  
    6767    const OPTION_BLOCK_ENABLED = 'rightblogger_studio_block_enabled';
    6868
     69    // Per-user (per-site on multisite) token that rides on the connect
     70    // return_url beside the nonce. See handle_callback() for why.
     71    const USER_OPTION_CONNECT_STATE = 'rightblogger_connect_state';
     72
    6973    /**
    7074     * Instance
    7175     */
    7276    private static $instance = null;
    73    
     77
    7478    /**
    7579     * Get instance
    … …  
    323327        // that key unlocks the publish endpoints, which save unfiltered HTML.
    324328        $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
    325         if (!wp_verify_nonce($nonce, 'rightblogger_callback')) {
     329        $state = isset($_GET['rb_state']) ? sanitize_text_field(wp_unslash($_GET['rb_state'])) : '';
     330        // The nonce is bound to the login session that clicked Connect, so a
     331        // site that makes the admin log in again on the way back (cookie
     332        // hardening, an expired session) fails it on a genuine connection and
     333        // RightBlogger is left holding a key the site never saved. The state
     334        // token is bound to the user instead and survives a re-login. Either
     335        // proves this admin started the flow; a forged link carries neither.
     336        if (!wp_verify_nonce($nonce, 'rightblogger_callback') && !$this->connect_state_matches($state)) {
    326337            add_settings_error(
    327338                'rightblogger_messages',
    … …  
    332343            return;
    333344        }
    334        
     345        // One use: a return_url left in history can't connect the site later.
     346        delete_user_option(get_current_user_id(), self::USER_OPTION_CONNECT_STATE);
     347
    335348        $status = sanitize_text_field(wp_unslash($_GET['status']));
    336349       
    … …  
    565578        // Format: rbwp_64hexchars
    566579        return (bool) preg_match('/^rbwp_[a-f0-9]{64}$/', $key);
     580    }
     581
     582    /**
     583     * The current user's connect state token, minted on demand. Reused while it
     584     * has an hour left, so two open settings tabs agree and a click is never
     585     * minutes from expiry, then rotated. handle_callback() deletes it on use.
     586     */
     587    private function connect_state_token() {
     588        $user_id = get_current_user_id();
     589        $state = get_user_option(self::USER_OPTION_CONNECT_STATE, $user_id);
     590        if (!is_array($state) || empty($state['token']) || empty($state['expires'])
     591            || (int) $state['expires'] - time() < HOUR_IN_SECONDS) {
     592            $state = array(
     593                'token'   => wp_generate_password(32, false),
     594                'expires' => time() + DAY_IN_SECONDS,
     595            );
     596            update_user_option($user_id, self::USER_OPTION_CONNECT_STATE, $state);
     597        }
     598        return $state['token'];
     599    }
     600
     601    /**
     602     * Whether $token is the current user's unexpired connect state token.
     603     */
     604    private function connect_state_matches($token) {
     605        if (!is_string($token) || $token === '') {
     606            return false;
     607        }
     608        $state = get_user_option(self::USER_OPTION_CONNECT_STATE, get_current_user_id());
     609        return is_array($state) && !empty($state['token']) && !empty($state['expires'])
     610            && (int) $state['expires'] > time()
     611            && hash_equals((string) $state['token'], $token);
    567612    }
    568613   
    … …  
    11401185
    11411186        // Build connect URL. RightBlogger appends the connection params to
    1142         // return_url and keeps its existing query, so the nonce comes back with
    1143         // them and handle_callback() can tell this admin started the flow.
     1187        // return_url and keeps its existing query, so the nonce and state token
     1188        // come back with them and handle_callback() can tell this admin started
     1189        // the flow.
    11441190        $return_url = add_query_arg(
    1145             '_wpnonce',
    1146             wp_create_nonce('rightblogger_callback'),
     1191            array(
     1192                '_wpnonce' => wp_create_nonce('rightblogger_callback'),
     1193                'rb_state' => $this->connect_state_token(),
     1194            ),
    11471195            admin_url('options-general.php?page=rightblogger')
    11481196        );
  • rightblogger/tags/1.6.3/uninstall.php

    r3739703 r3747553  
    2929delete_transient('rightblogger_conn_status');
    3030
     31// Connect state tokens are user options, stored per site under the blog prefix.
     32global $wpdb;
     33delete_metadata('user', 0, $wpdb->get_blog_prefix() . 'rightblogger_connect_state', '', true);
     34
  • rightblogger/trunk/readme.txt

    r3740227 r3747553  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.6.2
     7Stable tag: 1.6.3
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    114114
    115115== Changelog ==
     116
     117= 1.6.3 =
     118* Fixed: Connecting no longer fails with "Security check failed" and leaves the site unconnected when WordPress asks you to log in again on the way back from RightBlogger.
    116119
    117120= 1.6.2 =
  • rightblogger/trunk/rightblogger.php

    r3739703 r3747553  
    44 * Plugin URI: https://rightblogger.com/wordpress
    55 * Description: Connect your WordPress site to RightBlogger to publish AI-generated content directly.
    6  * Version: 1.6.2
     6 * Version: 1.6.3
    77 * Author: RightBlogger
    88 * Author URI: https://rightblogger.com
    … …  
    2020
    2121// Plugin constants
    22 define('RIGHTBLOGGER_VERSION', '1.6.2');
     22define('RIGHTBLOGGER_VERSION', '1.6.3');
    2323define('RIGHTBLOGGER_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2424define('RIGHTBLOGGER_PLUGIN_URL', plugin_dir_url(__FILE__));
    … …  
    6767    const OPTION_BLOCK_ENABLED = 'rightblogger_studio_block_enabled';
    6868
     69    // Per-user (per-site on multisite) token that rides on the connect
     70    // return_url beside the nonce. See handle_callback() for why.
     71    const USER_OPTION_CONNECT_STATE = 'rightblogger_connect_state';
     72
    6973    /**
    7074     * Instance
    7175     */
    7276    private static $instance = null;
    73    
     77
    7478    /**
    7579     * Get instance
    … …  
    323327        // that key unlocks the publish endpoints, which save unfiltered HTML.
    324328        $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
    325         if (!wp_verify_nonce($nonce, 'rightblogger_callback')) {
     329        $state = isset($_GET['rb_state']) ? sanitize_text_field(wp_unslash($_GET['rb_state'])) : '';
     330        // The nonce is bound to the login session that clicked Connect, so a
     331        // site that makes the admin log in again on the way back (cookie
     332        // hardening, an expired session) fails it on a genuine connection and
     333        // RightBlogger is left holding a key the site never saved. The state
     334        // token is bound to the user instead and survives a re-login. Either
     335        // proves this admin started the flow; a forged link carries neither.
     336        if (!wp_verify_nonce($nonce, 'rightblogger_callback') && !$this->connect_state_matches($state)) {
    326337            add_settings_error(
    327338                'rightblogger_messages',
    … …  
    332343            return;
    333344        }
    334        
     345        // One use: a return_url left in history can't connect the site later.
     346        delete_user_option(get_current_user_id(), self::USER_OPTION_CONNECT_STATE);
     347
    335348        $status = sanitize_text_field(wp_unslash($_GET['status']));
    336349       
    … …  
    565578        // Format: rbwp_64hexchars
    566579        return (bool) preg_match('/^rbwp_[a-f0-9]{64}$/', $key);
     580    }
     581
     582    /**
     583     * The current user's connect state token, minted on demand. Reused while it
     584     * has an hour left, so two open settings tabs agree and a click is never
     585     * minutes from expiry, then rotated. handle_callback() deletes it on use.
     586     */
     587    private function connect_state_token() {
     588        $user_id = get_current_user_id();
     589        $state = get_user_option(self::USER_OPTION_CONNECT_STATE, $user_id);
     590        if (!is_array($state) || empty($state['token']) || empty($state['expires'])
     591            || (int) $state['expires'] - time() < HOUR_IN_SECONDS) {
     592            $state = array(
     593                'token'   => wp_generate_password(32, false),
     594                'expires' => time() + DAY_IN_SECONDS,
     595            );
     596            update_user_option($user_id, self::USER_OPTION_CONNECT_STATE, $state);
     597        }
     598        return $state['token'];
     599    }
     600
     601    /**
     602     * Whether $token is the current user's unexpired connect state token.
     603     */
     604    private function connect_state_matches($token) {
     605        if (!is_string($token) || $token === '') {
     606            return false;
     607        }
     608        $state = get_user_option(self::USER_OPTION_CONNECT_STATE, get_current_user_id());
     609        return is_array($state) && !empty($state['token']) && !empty($state['expires'])
     610            && (int) $state['expires'] > time()
     611            && hash_equals((string) $state['token'], $token);
    567612    }
    568613   
    … …  
    11401185
    11411186        // Build connect URL. RightBlogger appends the connection params to
    1142         // return_url and keeps its existing query, so the nonce comes back with
    1143         // them and handle_callback() can tell this admin started the flow.
     1187        // return_url and keeps its existing query, so the nonce and state token
     1188        // come back with them and handle_callback() can tell this admin started
     1189        // the flow.
    11441190        $return_url = add_query_arg(
    1145             '_wpnonce',
    1146             wp_create_nonce('rightblogger_callback'),
     1191            array(
     1192                '_wpnonce' => wp_create_nonce('rightblogger_callback'),
     1193                'rb_state' => $this->connect_state_token(),
     1194            ),
    11471195            admin_url('options-general.php?page=rightblogger')
    11481196        );
  • rightblogger/trunk/uninstall.php

    r3739703 r3747553  
    2929delete_transient('rightblogger_conn_status');
    3030
     31// Connect state tokens are user options, stored per site under the blog prefix.
     32global $wpdb;
     33delete_metadata('user', 0, $wpdb->get_blog_prefix() . 'rightblogger_connect_state', '', true);
     34
Note: See TracChangeset for help on using the changeset viewer.