Come ottenere un nonce unico per ogni richiesta Ajax?
Ho visto diverse discussioni su come far rigenerare a WordPress un nonce unico per le richieste Ajax successive, ma non riesco a farlo funzionare - ogni volta che richiedo quello che dovrebbe essere un nuovo nonce, WordPress mi restituisce lo stesso nonce. Capisco il concetto di nonce_life in WP e anche come modificarlo, ma questo non mi ha aiutato.
Non genero il nonce nell'oggetto JS nell'header tramite localizzazione - lo faccio nella mia pagina di visualizzazione. Riesco a far elaborare la richiesta Ajax alla mia pagina, ma quando richiedo un nuovo nonce a WP nel callback, ottengo lo stesso nonce, e non capisco cosa sto sbagliando... Alla fine voglio estendere questo sistema per avere più elementi nella pagina, ognuno con la capacità di aggiungere/rimuovere - quindi ho bisogno di una soluzione che permetta multiple richieste Ajax successive da una singola pagina.
(E dovrei dire che ho inserito tutta questa funzionalità in un plugin, quindi la "pagina di visualizzazione" front-end è in realtà una funzione inclusa nel plugin...)
functions.php: localizzo, ma non creo un nonce qui
wp_localize_script('myjs', 'ajaxVars', array('ajaxurl' => 'admin-ajax.php')));
JavaScript chiamante:
$("#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;
... altre operazioni
}
}
});
});
PHP ricevente:
function myFaves() {
$ajaxNonce = 'myplugin_myaction_nonce_' . $postID;
if (!wp_verify_nonce($_POST['nonce'], $ajaxNonce))
exit('Scusa!');
// Ottieni varie variabili POST e fai altre operazioni...
// Prepara la risposta JSON e genera un nuovo nonce unico
$newNonce = wp_create_nonce('myplugin_myaction_nonce_' . $postID . '_'
. str_replace('.', '', gettimeofday(true)));
$response['newNonce'] = $newNonce;
// Permetti anche alla pagina di elaborarsi se non c'è capacità JS/Ajax
} else {
header("Location: " . $_SERVER["HTTP_REFERER"];
}
die();
}
Funzione PHP di visualizzazione 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 . '">Il mio Link</a>';
A questo punto sarei davvero grato per qualsiasi indizio o suggerimento per far rigenerare a WP 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 di $newNonce nel callback PHP per aggiungere una stringa di microsecondi e assicurarmi che sia univoca per le richieste Ajax successive.
Ecco una risposta molto dettagliata alla mia domanda che va oltre la semplice generazione di nonce unici per richieste Ajax successive. Si tratta di una funzionalità "aggiungi ai preferiti" realizzata in modo generico per scopi dimostrativi (nel mio caso permette agli utenti di aggiungere gli ID dei post degli allegati fotografici a una lista di preferiti, ma può essere adattata a varie altre funzionalità basate su Ajax). Ho sviluppato questo codice come plugin autonomo, con alcuni elementi mancanti - ma dovrebbe essere sufficientemente dettagliato per cogliere l'essenza se desiderate replicare la funzionalità. Funziona sia su singoli post/pagine che su liste di post (ad esempio, potete aggiungere/rimuovere elementi dai preferiti in linea via Ajax e ogni post avrà il proprio nonce unico per ogni richiesta Ajax). Tenete presente che probabilmente esiste un modo più efficiente e/o elegante per implementarlo, e attualmente funziona solo per Ajax - non mi sono ancora preoccupato di elaborare i dati $_POST non-Ajax.
scripts.php
/**
* Carica jQuery per il front-end
*/
function enqueueFavoritesJS()
{
// Mostra JS Ajax per Preferiti solo se l'utente è loggato
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');
favorites.js (Molti elementi di debug rimovibili)
$(document).ready(function()
{
// Aggiungi/Rimuovi elemento dai Preferiti
$(".faves-link").click(function(e) {
// Previeni l'auto-valutazione delle richieste e usa Ajax
e.preventDefault();
var $this = $(this);
console.log("Avvio evento click...");
// Recupera variabili iniziali dalla pagina
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("Avvio Ajax...");
$.ajax({
type: "POST",
dataType: "json",
url: ajaxVars.ajaxurl,
data: {
// Invia JSON a PHP per la valutazione
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("Rimozione dai Preferiti...");
console.log("Rimozione...");
} else {
$this.text("Aggiunta ai Preferiti...");
console.log("Aggiunta...");
}
},
success: function(response) {
// Elabora JSON inviato da PHP
if(response.type == "success") {
console.log("Successo!");
console.log("Nuovo nonce: " + response.newNonce);
console.log("Nuovo toggle: " + response.theToggle);
console.log("Messaggio da PHP: " + response.message);
$this.text(response.message);
$this.attr("data-toggle", response.theToggle);
// Imposta nuovo nonce
_ajax_nonce = response.newNonce;
console.log("_ajax_nonce è ora: " + _ajax_nonce);
} else {
console.log("Fallito!");
console.log("Nuovo nonce: " + response.newNonce);
console.log("Messaggio da PHP: " + response.message);
$this.parent().html("<p>" + response.message + "</p>");
_ajax_nonce = response.newNonce;
console.log("_ajax_nonce è ora: " + _ajax_nonce);
}
},
error: function(e, x, settings, exception) {
// Debug generico
var errorMessage;
var statusErrorMap = {
'400' : "Il server ha compreso la richiesta ma il contenuto era invalido.",
'401' : "Accesso non autorizzato.",
'403' : "Impossibile accedere alla risorsa vietata.",
'500' : "Errore interno del server",
'503' : "Servizio non disponibile"
};
if (x.status) {
errorMessage = statusErrorMap[x.status];
if (!errorMessage) {
errorMessage = "Errore sconosciuto.";
} else if (exception == 'parsererror') {
errorMessage = "Errore. Analisi della richiesta JSON fallita.";
} else if (exception == 'timeout') {
errorMessage = "Timeout della richiesta.";
} else if (exception == 'abort') {
errorMessage = "Richiesta interrotta dal server.";
} else {
errorMessage = "Errore sconosciuto.";
}
$this.parent().html(errorMessage);
console.log("Messaggio di errore: " + errorMessage);
} else {
console.log("ERRORE!!");
console.log(e);
}
}
}); // Chiusura $.ajax
}); // Fine evento click
});
Funzioni (visualizzazione front-end & azione Ajax)
Per mostrare il link Aggiungi/Rimuovi dai Preferiti, chiamatelo semplicemente nella vostra pagina/post con:
if (function_exists('myFavoritesLink') {
myFavoritesLink($user_ID, $post->ID);
}
Funzione per la visualizzazione front-end:
function myFavoritesLink($user_ID, $postID)
{
global $user_ID;
if (is_user_logged_in()) {
// Imposta valore iniziale del toggle e testo del link - aggiornato dal callback
$myUserMeta = get_user_meta($user_ID, 'myMetadata', true);
if (is_array($myUserMeta['metadata']) && in_array($postID, $myUserMeta['metadata'])) {
$toggle = "y";
$linkText = "Rimuovi dai Preferiti";
} else {
$toggle = "n";
$linkText = "Aggiungi ai Preferiti";
}
// Crea nonce solo per Ajax per la richiesta iniziale
// Nuovo nonce restituito nel 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 {
// Utente non loggato
echo '<p>Accedi per utilizzare la funzionalità Preferiti.</p>' . "\n";
}
}
Funzione per l'azione Ajax:
/**
* Attiva/disattiva aggiunta/rimozione per Preferiti
*/
function toggleFavorites()
{
if (is_user_logged_in()) {
// Verifica nonce
$ajaxNonce = 'myplugin_myaction' . $_POST['post_id'];
if (! wp_verify_nonce($_POST['_ajax_nonce'], $ajaxNonce)) {
exit('Spiacente!');
}
// Elabora variabili POST
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);
// Inizializza array myUserMeta se non esiste
if ($myUserMeta['myMetadata'] === '' || ! is_array($myUserMeta['myMetadata'])) {
$myUserMeta['myMetadata'] = array();
}
// Attiva/disattiva l'elemento nella lista Preferiti
if ($toggle === "y" && in_array($postID, $myUserMeta['myMetadata'])) {
// Rimuovi elemento dalla lista Preferiti
$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 = "Aggiungi ai Preferiti";
} else {
// Aggiungi elemento alla lista Preferiti
$myUserMeta['myMetadata'][] = $postID;
$newToggle = "y";
$message = "Rimuovi dai Preferiti";
}
// Prepara la risposta
// Nonce per la prossima richiesta - unico con stringa microtime aggiunta
$newNonce = wp_create_nonce('myplugin_myaction_' . $postID . '_'
. str_replace('.', '', gettimeofday(true)));
$updateUserMeta = update_user_meta($userID, 'myMetadata', $myUserMeta);
// Risposta a jQuery
if($updateUserMeta === false) {
$response['type'] = "error";
$response['theToggle'] = $toggle;
$response['message'] = "Impossibile aggiornare i tuoi Preferiti.";
$response['newNonce'] = $newNonce;
} else {
$response['type'] = "success";
$response['theToggle'] = $newToggle;
$response['message'] = $message;
$response['newNonce'] = $newNonce;
}
// Elabora con Ajax, altrimenti elabora autonomamente
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();
} // Fine is_user_logged_in()
}
add_action('wp_ajax_myFavorites', 'toggleFavorites');

Devo davvero mettere in dubbio il ragionamento alla base dell'ottenimento di un nuovo nonce per ogni richiesta ajax. Il nonce originale scadrà, ma può essere utilizzato più volte fino a quel momento. Far sì che javascript lo riceva tramite ajax vanifica lo scopo, specialmente fornendolo in caso di errore. (Lo scopo dei nonce è fornire un po' di sicurezza per associare un'azione a un utente entro un determinato lasso di tempo.)
Non dovrei menzionare altre risposte, ma sono nuovo e non posso commentare sopra, quindi riguardo alla "soluzione" postata, stai ottenendo un nuovo nonce ogni volta ma non lo stai utilizzando nella richiesta. Sarebbe certamente complicato ottenere gli stessi microsecondi ogni volta per far corrispondere ogni nuovo nonce creato in quel modo. Il codice PHP sta verificando rispetto al nonce originale, e javascript sta fornendo il nonce originale...quindi funziona (perché non è ancora scaduto).

Il problema è che il nonce scade dopo essere stato utilizzato e restituirà -1 nella funzione ajax ogni volta. Questo è un problema se stai validando parti di un form in PHP e restituisci errori da stampare. Il nonce del form è stato utilizzato, ma in realtà si è verificato un errore nella validazione PHP dei campi, e quando il form viene inviato di nuovo, questa volta non è possibile verificarlo e check_ajax_referer
restituisce -1, che non è ciò che vogliamo!
