web-dev-qa-db-fra.com

API nonce dans les paramètres avec navigation par onglets

Je reformule ceci parce que, comme cela a été correctement souligné, le message d'origine était "terriblement inadéquat". J'ai fait un plugin qui a des vues à onglets pour les différents paramètres. Il est basé sur cela Wordpress Plugin Template . Le modèle utilise l'API de paramètres WP pour construire la page des paramètres et afficher les onglets. Le formulaire utilise la valeur par défaut _wpnonce pour le bouton soumettre/enregistrer les paramètres.

Les onglets sont des éléments de lien qui modifient la chaîne de requête de l'URL de la page, par exemple, à partir de /wp-admin/options-general.php?page=my_plugin_settings&tab=tab_1 à /wp-admin/options-general.php?page=my_plugin_settings&tab=tab_2.

La question est maintenant: comment créer un nonce pour les onglets et le vérifier lorsque la page se charge ou que l'utilisateur sélectionne l'un des onglets.

class-my-plugin.php . L'exemple de code est simplifié pour plus de clarté.

class My_Plugin {

    private static $_instance = null;

    public $admin = null;

    public $settings = null;

    public $_token;

    public function __construct( $file = '', $version = '1.0.0' ) {
        $this->_version = $version;
        $this->_token   = 'my_plugin';
    }
}

class-my-plugin-settings.php . L'exemple de code est simplifié pour plus de clarté.

class My_Plugin_Settings {

    private static $_instance = null;

    public $parent = null;

    public $base = '';

    public $settings = array();

    public function __construct( $parent ) {
        $this->parent = $parent;

        $this->base = 'wpt_';

        add_action( 'init', array( $this, 'init_settings' ), 11 );

        add_action( 'admin_init', array( $this, 'register_settings' ) );

        add_action( 'admin_menu', array( $this, 'add_menu_item' ) );

        add_filter( $this->base . 'menu_settings', array( $this, 'configure_settings' ) );
    }

    /**
     * Initialise settings
     */
    public function init_settings() {
        $this->settings = $this->settings_fields();
    }

    /**
     * Add settings page to admin menu
     */
    public function add_menu_item() {
        // Code omitted for brevity.
    }

    /**
     * Prepare default settings page arguments
     */
    private function menu_settings() {
        // Code omitted for brevity.
    }

    /**
     * Container for settings page arguments
     */
    public function configure_settings( $settings = array() ) {
        return $settings;
    }

    /**
     * Build settings fields
     */
    private function settings_fields() {

        $settings['tab_1'] = array(
            'title'       => __( 'Tab 1', 'my_plugin' ),
            'description' => __( 'The first settings screen.', 'my_plugin' ),
            'fields'      => array(
                // Form fields etc. here
            ),
        );

        $settings['tab_2'] = array(
            'title'       => __( 'Tab 2', 'my_plugin' ),
            'description' => __( 'The second settings screen.', 'my_plugin' ),
            'fields'      => array(
                // Form fields etc. here
            ),
        );

        $settings = apply_filters( $this->parent->_token . '_settings_fields', $settings );

        return $settings;
    }

    /**
     * Register plugin settings
     */
    public function register_settings() {
        if ( is_array( $this->settings ) ) {

            // Check posted/selected tab.
            $current_section = '';
            if ( isset( $_POST['tab'] ) && $_POST['tab'] ) { // NONCE warning
                $current_section = $_POST['tab']; // NONCE warning
            } else {
                if ( isset( $_GET['tab'] ) && $_GET['tab'] ) { // NONCE warning
                    $current_section = $_GET['tab']; // Nonce warning
                }
            }

            foreach ( $this->settings as $section => $data ) {

                if ( $current_section && $current_section !== $section ) {
                    continue;
                }

                // Add section to page.
                add_settings_section( $section, $data['title'], array( $this, 'settings_section' ), $this->parent->_token . '_settings' );

                foreach ( $data['fields'] as $field ) {

                    // Validation callback for field.
                    $validation = '';
                    if ( isset( $field['callback'] ) ) {
                        $validation = $field['callback'];
                    }

                    // Register field.
                    $option_name = $this->base . $field['id'];
                    register_setting( $this->parent->_token . '_settings', $option_name, $validation );

                    // Add field to page.
                    add_settings_field(
                        $field['id'],
                        $field['label'],
                        array( $this->parent->admin, 'display_field' ),
                        $this->parent->_token . '_settings',
                        $section,
                        array(
                            'field'  => $field,
                            'prefix' => $this->base,
                        )
                    );
                }

                if ( ! $current_section ) {
                    break;
                }
            }
        }
    }

    /**
     * Settings section.
     *
     * @param array $section Array of section ids.
     * @return void
     */
    public function settings_section( $section ) {
        $html = '<p> ' . $this->settings[ $section['id'] ]['description'] . '</p>' . "\n";
        echo $html;
    }

    /**
     * Load settings page content.
     *
     * @return void
     */
    public function settings_page() {

        // Build page HTML.
        $html      = '<div class="wrap" id="' . $this->parent->_token . '_settings">' . "\n";
            $html .= '<h2>' . __( 'Plugin Settings', 'my_plugin' ) . '</h2>' . "\n";

            $tab = '';
        //phpcs:disable
        if ( isset( $_GET['tab'] ) && $_GET['tab'] ) {
            $tab .= $_GET['tab'];
        }
        //phpcs:enable

        // Show page tabs.
        if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {

            $html .= '<h2 class="nav-tab-wrapper">' . "\n";

            $c = 0;
            foreach ( $this->settings as $section => $data ) {

                // Set tab class.
                $class = 'nav-tab';
                if ( ! isset( $_GET['tab'] ) ) { // NONCE warning
                    if ( 0 === $c ) {
                        $class .= ' nav-tab-active'; 
                    }
                } else {
                    if ( isset( $_GET['tab'] ) && $section == $_GET['tab'] ) { // Nonce warning
                        $class .= ' nav-tab-active';
                    }
                }

                // Set tab link.
                $tab_link = add_query_arg( array( 'tab' => $section ) );
                if ( isset( $_GET['settings-updated'] ) ) { // NONCE warning
                    $tab_link = remove_query_arg( 'settings-updated', $tab_link );
                }

                // Output tab.
                $html .= '<a href="' . $tab_link . '" class="' . esc_attr( $class ) . '">' . esc_html( $data['title'] ) . '</a>' . "\n";

                ++$c;
            }

            $html .= '</h2>' . "\n";
        }

            $html .= '<form method="post" action="options.php" enctype="multipart/form-data">' . "\n";

                // Get settings fields.
                ob_start();
                settings_fields( $this->parent->_token . '_settings' );
                do_settings_sections( $this->parent->_token . '_settings' );
                $html .= ob_get_clean();

                $html .= '<p class="submit">' . "\n";
                $html .= '<input type="hidden" name="tab" value="' . esc_attr( $tab ) . '" />' . "\n";
                $html .= '<input name="Submit" type="submit" class="button-primary" value="' . esc_attr( __( 'Save Settings', 'my_plugin' ) ) . '" />' . "\n";
                $html .= '</p>' . "\n";
                $html .= '</form>' . "\n";
                $html .= '</div>' . "\n";

        echo $html;
    }

    /**
     * Main My_Plugin_Settings Instance
     *
     * Ensures only one instance of My_Plugin_Settings is loaded or can be loaded.
     *
     * @since 1.0.0
     * @static
     * @see My_Plugin()
     * @param object $parent Object instance.
     * @return object My_Plugin_Settings instance
     */
    public static function instance( $parent ) {
        if ( is_null( self::$_instance ) ) {
            self::$_instance = new self( $parent );
        }
        return self::$_instance;
    } // End instance()

}

Sortie du formulaire . Modifie en fonction de l'onglet sélectionné.

<div class="wrap">
    <h2>Heading</h2>
    <p>Plugin description.</p>
    <h2 class="nav-tab-wrapper">
        <a href="/wp-admin/options-general.php?page=my_plugin_settings&amp;tab=tab_1" class="nav-tab nav-tab-active">Tab 1</a>
        <a href="/wp-admin/options-general.php?page=my_plugin_settings&amp;tab=tab_2" class="nav-tab">Tab 2</a>
    </h2>
    <form method="post" action="options.php" enctype="multipart/form-data">
        <input type="hidden" name="option_page" value="my_plugin_settings"><input type="hidden" name="action" value="update"><input type="hidden" id="_wpnonce" name="_wpnonce" value="$integer"><input type="hidden" name="_wp_http_referer" value="/wp-admin/options-general.php?page=my_plugin_settings&amp;tab=tab_1">
        <h2>Tab 1</h2>
        <p>
            Description for Tab 1 screen.</p>
        <table class="form-table">
            <!-- Form table contents -->
        </table>
        <p class="submit">
            <input type="hidden" name="tab" value="upload">
            <input name="Submit" type="submit" class="button-primary" value="Save Settings">
        </p>
    </form>
</div>

POST ORIGINAL: J'utilise PHPCS avec Wordpress-Supplémentaire normes de codage pour vérifier le code de mon plugin. Je reçois cet avertissement:

AVERTISSEMENT | Traitement des données du formulaire sans vérification de nonce.

Le code en question affiche une navigation par onglets dans la page des paramètres:

class Example_Class {
    public function settings_page() {
        $tab = '';

        if ( isset( $_GET['tab'] ) && $_GET['tab'] ) { // WARNING
            $tab .= $_GET['tab']; // WARNING
        }

        if ( isset( $_GET['tab'] ) && $_GET['tab'] ) { // WARNING
            $tab .= $_GET['tab']; // WARNING
        }

        // Show page tabs
        if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {

            $html .= '<h2 class="nav-tab-wrapper">' . chr( 0x0D ) . chr( 0x0A );

            $c = 0;
            foreach ( $this->settings as $section => $data ) {

                // Set tab class
                $class = 'nav-tab';
                if ( ! isset( $_GET['tab'] ) ) { // WARNING
                    if ( 0 === $c ) {
                        $class .= ' nav-tab-active';
                    }
                } else {
                    if ( isset( $_GET['tab'] ) && $section === $_GET['tab'] ) { // WARNING
                        $class .= ' nav-tab-active';
                    }
                }

                // Set tab link
                $tab_link = add_query_arg( array( 'tab' => $section ) );
                if ( isset( $_GET['settings-updated'] ) ) { // WARNING
                    $tab_link = remove_query_arg( 'settings-updated', $tab_link );
                }

                // Output tab
                $html .= '<a href="' . $tab_link . '" class="' . esc_attr( $class ) . '">' . esc_html( $data['title'] ) . '</a>' . chr( 0x0D ) . chr( 0x0A );

                ++$c;
            }

            $html .= '</h2>' . chr( 0x0D ) . chr( 0x0A );
        }
    }

    public function register_settings() {
        if ( is_array( $this->settings ) ) {

            // Check posted/selected tab
            $current_section = '';
            if ( isset( $_POST['tab'] ) && $_POST['tab'] ) { // WARNING
                $current_section = $_POST['tab'];
            } else {
                if ( isset( $_GET['tab'] ) && $_GET['tab'] ) { // WARNING
                    $current_section = $_GET['tab']; // WARNING
                }
            }

            // Unrelated code omitted
        }
    }
}

Je pensais que l'API gérait automatiquement les nonces? Dois-je m'inquiéter? Ou le code est-il OK tel quel? Sinon, comment dois-je résoudre ce problème?

EDIT: à la lumière des réponses, l'API fournit un nonce par défaut <input type="hidden" id="_wpnonce" name="_wpnonce" value="$int"> & le champ caché associé <input type="hidden" name="_wp_http_referer" value="/wp-admin/options-general.php?page=_settings">. Comment vérifier? J'ai essayé, sans succès:

if ( isset( $_POST['tab'] ) && $_POST['tab'] ) {
    if ( ! wp_verify_nonce( '_wpnonce' ) ) {
        wp_die( 'Go away!' );
    } else {
        $current_section = sanitize_text_field( wp_unslash( $_POST['tab'] ) );
    }
} else {
    if ( isset( $_GET['tab'] ) && $_GET['tab'] ) {
        if ( ! wp_verify_nonce( '_wpnonce' ) ) {
            wp_die( 'Go away!' );
        } else {
            $current_section = sanitize_text_field( wp_unslash( $_GET['tab'] ) );
        }
    }
}
3
Chris J. Zähller

L'API gère le nonce pour la partie formulaire car vous utilisez le settings_fields appel, qui génère le nonce * -options, et vous passez les données au options.php fichier à enregistrer, qui vérifie ce nonce pour vous avant d'enregistrer les paramètres. Cette partie de l'API Paramètres fait en effet pour vous.

Cependant, votre code d'onglet n'est pas sous cette forme. Ce ne sont que des liens. Les liens n'ont pas de nonces sur eux, et le code que vous devez utiliser les données des parties GET ne fait aucune vérification de nonce.

Maintenant, techniquement, c'est correct tant que vous n'enregistrez pas de données ici. Le but d'un nonce est de vérifier l'intention lors de la soumission de données, et si vous ne soumettez pas de données enregistrées ou utilisées de manière réelle, vous n'avez pas besoin de vérifier cette intention. La seule chose que votre sélection d'onglets fait ici est de changer les champs affichés sur la page.

Vous pouvez envisager d'éliminer complètement les onglets et d'afficher l'intégralité du formulaire, avec tous les paramètres, sur la même page. Si vous voulez une organisation sous forme d'onglets, vous feriez mieux d'utiliser javascript ou CSS pour décorer la page. Tenez également compte de l'accessibilité, dans la mesure où le formulaire dans son ensemble peut être préférable pour les utilisateurs qui souhaitent effectuer toutes les modifications en même temps plutôt que de devoir configurer des éléments sur plusieurs pages ou y accéder avec des liens en haut de la page.

1
Otto

C'est un bon avertissement. Aucune API ne s'occupe du nonce.

Vous devez utiliser verify_nonce () ou check_admin_referer () avant de lire depuis $ _GET ou $ _POST.

Et il est préférable d'utiliser l'ensemble complet de normes de codage, nommé simplement WordPress, qui comprend Core, Docs et Extra.

1
KAGG Design