Come ottenere un nonce unico per ogni richiesta Ajax?


11

Ho visto un paio di discussioni su come convincere Wordpress a rigenerare un nonce unico per le successive richieste dell'Ajax, ma per la mia vita non riesco davvero a far sì che Wordpress lo faccia-- ogni volta che chiedo quello che penso dovrebbe essere un nuovo nonce, ottengo lo stesso nonce da Wordpress. Capisco il concetto di nonce_life di WP e lo imposto anche su qualcos'altro, ma questo non mi ha aiutato.

Non genera il nonce nell'oggetto JS nell'intestazione tramite localizzazione, lo faccio sulla mia pagina di visualizzazione. Posso ottenere la mia pagina per elaborare la richiesta Ajax, ma quando richiedo un nuovo nonce da WP nel callback, ricevo lo stesso nonce indietro e non so cosa sto facendo di sbagliato ... Alla fine voglio estendere questo in modo che ci possano essere più elementi sulla pagina, ognuno con la possibilità di aggiungere / rimuovere-- quindi ho bisogno di una soluzione che consentirà più richieste Ajax successive da una pagina.

(E dovrei dire che ho inserito tutte queste funzionalità in un plug-in, quindi la "pagina di visualizzazione" del front-end è in realtà una funzione inclusa nel plug-in ...)

funzioni.php: localizza, ma non creo un nonce qui

wp_localize_script('myjs', 'ajaxVars', array('ajaxurl' => 'admin-ajax.php')));

Chiamata a JS:

$("#myelement").click(function(e) {
    e.preventDefault();
    post_id = $(this).data("data-post-id");
    user_id = $(this).data("data-user-id");
    nonce = $(this).data("data-nonce");
    $.ajax({
      type: "POST",
      dataType: "json",
      url: ajaxVars.ajaxurl,
      data: {
         action: "myfaves",
         post_id: post_id,
         user_id: user_id,
         nonce: nonce
      },
      success: function(response) {
         if(response.type == "success") {
            nonce = response.newNonce;
            ... other stuff
         }
      }
  });
});

Ricezione di PHP:

function myFaves() {
   $ajaxNonce = 'myplugin_myaction_nonce_' . $postID;
   if (!wp_verify_nonce($_POST['nonce'], $ajaxNonce))
      exit('Sorry!');

   // Get various POST vars and do some other stuff...

   // Prep JSON response & generate new, unique nonce
   $newNonce = wp_create_nonce('myplugin_myaction_nonce_' . $postID . '_' 
       . str_replace('.', '', gettimeofday(true)));
   $response['newNonce'] = $newNonce;

   // Also let the page process itself if there is no JS/Ajax capability
   } else {
      header("Location: " . $_SERVER["HTTP_REFERER"];
   }
   die();
}

Funzione di visualizzazione PHP frontend, tra cui:

$nonce = wp_create_nonce('myplugin_myaction_nonce_' . $post->ID);
$link = admin_url('admin-ajax.php?action=myfaves&post_id=' . $post->ID
   . '&user_id=' . $user_ID
   . '&nonce=' . $nonce);

echo '<a id="myelement" data-post-id="' . $post->ID
   . '" data-user-id="' . $user_ID
   . '" data-nonce="' . $nonce
   . '" href="' . $link . '">My Link</a>';

A questo punto sarei davvero grato per qualsiasi indizio o suggerimento nel convincere WP a rigenerare un nonce unico per ogni nuova richiesta Ajax ...


AGGIORNAMENTO: ho risolto il mio problema. I frammenti di codice sopra sono validi, tuttavia ho modificato la creazione $ newNonce nel callback di PHP per aggiungere una stringa di microsecondi per assicurarmi che sia unica nelle successive richieste Ajax.


Da uno sguardo molto breve: stai creando il nonce dopo averlo ricevuto (in mostra)? Perché non lo stai creando durante la chiamata localizza?
Kaiser

JQuery sta usando il nonce iniziale dall'attributo "data-nonce" nel collegamento a # myelement e l'idea è che la pagina può essere elaborata da Ajax o da sola. Mi è sembrato che la creazione del nonce una volta tramite la chiamata localizzata lo escludesse dall'elaborazione non JS, ma potrei sbagliarmi al riguardo. Ad ogni modo Wordpress mi restituisce lo stesso nonce ...
Tim

Inoltre: mettere il nonce nella chiamata localizzata impedirebbe a uno di avere più elementi su una pagina in cui ogni elemento potrebbe avere un nonce univoco per una richiesta Ajax?
Tim

La creazione del nonce all'interno di localize creerebbe e lo renderebbe disponibile per quello script. Ma potresti anche aggiungere un numero illimitato di altri valori localizzati (con nome chiave) con nonces separati.
Kaiser

Se l'hai risolto, sei invitato a pubblicare la tua risposta e contrassegnarla come "accettata". Aiuterà a mantenere organizzato il sito. Stavo solo scherzando con il tuo codice e un paio di cose non funzionano per me, quindi raddoppia la richiesta di pubblicare la tua soluzione.
s_ha_dum,

Risposte:


6

Ecco una risposta molto lunga alla mia domanda che va oltre la semplice questione della generazione di nonces uniche per le successive richieste Ajax. Questa è una funzione "aggiungi ai preferiti" che è stata resa generica ai fini della risposta (la mia funzione consente agli utenti di aggiungere gli ID post degli allegati fotografici a un elenco di preferiti, ma ciò potrebbe applicarsi a una varietà di altre funzionalità che si basano su Ajax). Ho codificato questo come un plug-in autonomo e mancano alcuni elementi, ma dovrebbero essere sufficienti i dettagli per fornire l'essenza se si desidera replicare la funzionalità. Funzionerà su un singolo post / pagina, ma funzionerà anche in elenchi di post (ad esempio puoi aggiungere / rimuovere elementi ai preferiti in linea tramite Ajax e ogni post avrà il suo nonce unico per ogni richiesta Ajax). Tieni presente che ci '

scripts.php

/**
* Enqueue front-end jQuery
*/
function enqueueFavoritesJS()
{
    // Only show Favorites Ajax JS if user is logged in
    if (is_user_logged_in()) {
        wp_enqueue_script('favorites-js', MYPLUGIN_BASE_URL . 'js/favorites.js', array('jquery'));
        wp_localize_script('favorites-js', 'ajaxVars', array('ajaxurl' => admin_url('admin-ajax.php')));
    }
}
add_action('wp_enqueue_scripts', 'enqueueFavoritesJS');

favourites.js (molte cose di debug che possono essere rimosse)

$(document).ready(function()
{
    // Toggle item in Favorites
    $(".faves-link").click(function(e) {
        // Prevent self eval of requests and use Ajax instead
        e.preventDefault();
        var $this = $(this);
        console.log("Starting click event...");

        // Fetch initial variables from the page
        post_id = $this.attr("data-post-id");
        user_id = $this.attr("data-user-id");
        the_toggle = $this.attr("data-toggle");
        ajax_nonce = $this.attr("data-nonce");

        console.log("data-post-id: " + post_id);
        console.log("data-user-id: " + user_id);
        console.log("data-toggle: " + the_toggle);
        console.log("data-nonce: " + ajax_nonce);
        console.log("Starting Ajax...");

        $.ajax({
            type: "POST",
            dataType: "json",
            url: ajaxVars.ajaxurl,
            data: {
                // Send JSON back to PHP for eval
                action : "myFavorites",
                post_id: post_id,
                user_id: user_id,
                _ajax_nonce: ajax_nonce,
                the_toggle: the_toggle
            },
            beforeSend: function() {
                if (the_toggle == "y") {
                    $this.text("Removing from Favorites...");
                    console.log("Removing...");
                } else {
                    $this.text("Adding to Favorites...");
                    console.log("Adding...");
                }
            },
            success: function(response) {
                // Process JSON sent from PHP
                if(response.type == "success") {
                    console.log("Success!");
                    console.log("New nonce: " + response.newNonce);
                    console.log("New toggle: " + response.theToggle);
                    console.log("Message from PHP: " + response.message);
                    $this.text(response.message);
                    $this.attr("data-toggle", response.theToggle);
                    // Set new nonce
                    _ajax_nonce = response.newNonce;
                    console.log("_ajax_nonce is now: " + _ajax_nonce);
                } else {
                    console.log("Failed!");
                    console.log("New nonce: " + response.newNonce);
                    console.log("Message from PHP: " + response.message);
                    $this.parent().html("<p>" + response.message + "</p>");
                    _ajax_nonce = response.newNonce;
                    console.log("_ajax_nonce is now: " + _ajax_nonce);
                }
            },
            error: function(e, x, settings, exception) {
                // Generic debugging
                var errorMessage;
                var statusErrorMap = {
                    '400' : "Server understood request but request content was invalid.",
                    '401' : "Unauthorized access.",
                    '403' : "Forbidden resource can't be accessed.",
                    '500' : "Internal Server Error",
                    '503' : "Service Unavailable"
                };
                if (x.status) {
                    errorMessage = statusErrorMap[x.status];
                    if (!errorMessage) {
                        errorMessage = "Unknown Error.";
                    } else if (exception == 'parsererror') {
                        errorMessage = "Error. Parsing JSON request failed.";
                    } else if (exception == 'timeout') {
                        errorMessage = "Request timed out.";
                    } else if (exception == 'abort') {
                        errorMessage = "Request was aborted by server.";
                    } else {
                        errorMessage = "Unknown Error.";
                    }
                    $this.parent().html(errorMessage);
                    console.log("Error message is: " + errorMessage);
                } else {
                    console.log("ERROR!!");
                    console.log(e);
                }
            }
        }); // Close $.ajax
    }); // End click event
});

Funzioni (display front-end e azione Ajax)

Per visualizzare il link Aggiungi / Rimuovi Preferiti, chiamalo semplicemente sulla tua pagina / post tramite:

if (function_exists('myFavoritesLink') {
    myFavoritesLink($user_ID, $post->ID);
}

Funzione display frontale:

function myFavoritesLink($user_ID, $postID)
{
    global $user_ID;
    if (is_user_logged_in()) {
        // Set initial element toggle value & link text - udpated by callback
        $myUserMeta = get_user_meta($user_ID, 'myMetadata', true);
        if (is_array($myUserMeta['metadata']) && in_array($postID, $myUserMeta['metadata'])) {
            $toggle = "y";
            $linkText = "Remove from Favorites";
        } else {
            $toggle = "n";
            $linkText = "Add to Favorites";
        }

        // Create Ajax-only nonce for initial request only
        // New nonce returned in callback
        $ajaxNonce = wp_create_nonce('myplugin_myaction_' . $postID);
        echo '<p class="faves-action"><a class="faves-link"' 
            . ' data-post-id="' . $postID 
            . '" data-user-id="' . $user_ID  
            . '" data-toggle="' . $toggle 
            . '" data-nonce="' . $ajaxNonce 
            . '" href="#">' . $linkText . '</a></p>' . "\n";

    } else {
        // User not logged in
        echo '<p>Sign in to use the Favorites feature.</p>' . "\n";
    }

}

Funzione di azione Ajax:

/**
* Toggle add/remove for Favorites
*/
function toggleFavorites()
{
    if (is_user_logged_in()) {
        // Verify nonce
        $ajaxNonce = 'myplugin_myaction' . $_POST['post_id'];
        if (! wp_verify_nonce($_POST['_ajax_nonce'], $ajaxNonce)) {
            exit('Sorry!');
        }
        // Process POST vars
        if (isset($_POST['post_id']) && is_numeric($_POST['post_id'])) {
            $postID = $_POST['post_id'];
        } else {
            return;
        }
        if (isset($_POST['user_id']) && is_numeric($_POST['user_id'])) {
            $userID = $_POST['user_id'];
        } else {
            return;
        }
        if (isset($_POST['the_toggle']) && ($_POST['the_toggle'] === "y" || $_POST['the_toggle'] === "n")) {
            $toggle = $_POST['the_toggle'];
        } else {
            return;
        }

        $myUserMeta = get_user_meta($userID, 'myMetadata', true);

        // Init myUserMeta array if it doesn't exist
        if ($myUserMeta['myMetadata'] === '' || ! is_array($myUserMeta['myMetadata'])) {
            $myUserMeta['myMetadata'] = array();
        }

        // Toggle the item in the Favorites list
        if ($toggle === "y" && in_array($postID, $myUserMeta['myMetadata'])) {
            // Remove item from Favorites list
            $myUserMeta['myMetadata'] = array_flip($myUserMeta['myMetadata']);
            unset($myUserMeta['myMetadata'][$postID]);
            $myUserMeta['myMetadata'] = array_flip($myUserMeta['myMetadata']);
            $myUserMeta['myMetadata'] = array_values($myUserMeta['myMetadata']);
            $newToggle = "n";
            $message = "Add to Favorites";
        } else {
            // Add item to Favorites list
            $myUserMeta['myMetadata'][] = $postID;
            $newToggle = "y";
            $message = "Remove from Favorites";
        }

        // Prep for the response
        // Nonce for next request - unique with microtime string appended
        $newNonce = wp_create_nonce('myplugin_myaction_' . $postID . '_' 
            . str_replace('.', '', gettimeofday(true)));
        $updateUserMeta = update_user_meta($userID, 'myMetadata', $myUserMeta);

        // Response to jQuery
        if($updateUserMeta === false) {
            $response['type'] = "error";
            $response['theToggle'] = $toggle;
            $response['message'] = "Your Favorites could not be updated.";
            $response['newNonce'] = $newNonce;
        } else {
            $response['type'] = "success";
            $response['theToggle'] = $newToggle;
            $response['message'] = $message;
            $response['newNonce'] = $newNonce;
        }

        // Process with Ajax, otherwise process with self
        if (! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && 
            strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
                $response = json_encode($response);
                echo $response;
        } else {
            header("Location: " . $_SERVER["HTTP_REFERER"]);
        }
        exit();
    } // End is_user_logged_in()
}
add_action('wp_ajax_myFavorites', 'toggleFavorites');

3

Devo davvero mettere in discussione il ragionamento alla base di ottenere un nuovo nonce per ogni richiesta Ajax. Il nonce originale scadrà, ma può essere utilizzato più di una volta fino a quando non lo fa. Fare in modo che il javascript lo riceva tramite Ajax sconfigge lo scopo, soprattutto fornendo un caso di errore. (Lo scopo dei nonci è un po 'di sicurezza per associare un'azione a un utente entro un periodo di tempo.)

Non dovrei menzionare altre risposte, ma sono nuovo e non posso commentare sopra, quindi per quanto riguarda la "soluzione" pubblicata, ricevi sempre un nuovo nonce ma non lo usi nella richiesta. Sarebbe sicuramente complicato ottenere i microsecondi ogni volta per abbinare ogni nuovo nonce creato in quel modo. Il codice PHP sta verificando rispetto al nonce originale e javascript fornisce il nonce originale ... quindi funziona (perché non è ancora scaduto).


1
Il problema è che nonce scade dopo che viene utilizzato e restituisce -1 in funzione ajax dopo ogni volta. Questo è un problema se si stanno convalidando parti di un modulo in PHP e si restituiscono errori da stampare. È stato utilizzato il modulo nonce, ma in realtà si è verificato un errore nella convalida php dei campi e quando il modulo viene inviato di nuovo, questa volta, non è possibile verificarlo e check_ajax_refererrestituisce -1, che non è quello che vogliamo!
Solomon Closson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.