Come ottenere un nonce unico per ogni richiesta Ajax?

29 gen 2013, 04:59:40
Visualizzazioni: 17.6K
Voti: 12

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.

5
Commenti

Da un'occhiata molto rapida: Stai creando il nonce dopo averlo ricevuto (durante la visualizzazione)? Perché non lo crei durante la chiamata localize?

kaiser kaiser
29 gen 2013 07:36:36

jQuery sta usando il nonce iniziale dall'attributo "data-nonce" nel link a#myelement, e l'idea è che la pagina possa essere elaborata sia via Ajax che autonomamente. Mi sembrava che creare il nonce una sola volta tramite la chiamata localize lo avrebbe escluso dall'elaborazione non-JS, ma potrei sbagliarmi. In ogni caso Wordpress mi restituisce lo stesso nonce...

Tim Tim
29 gen 2013 17:17:11

Inoltre: Mettere il nonce nella chiamata localize non impedirebbe di avere più elementi in una pagina dove ogni elemento potrebbe avere un nonce unico per una richiesta Ajax?

Tim Tim
29 gen 2013 18:11:09

Creare il nonce all'interno della localizzazione lo creerebbe e lo renderebbe disponibile per quello specifico script. Ma potresti anche aggiungere una quantità illimitata di altri valori di localizzazione (con nomi di chiave) con nonce separati.

kaiser kaiser
29 gen 2013 21:32:50

Se hai risolto il problema, ti incoraggiamo a pubblicare la tua risposta e contrassegnarla come "accettata". Aiuterà a mantenere il sito organizzato. Stavo solo sperimentando con il tuo codice e un paio di cose non funzionano per me, quindi raddoppio la richiesta di pubblicare la tua soluzione.

s_ha_dum s_ha_dum
1 feb 2013 17:55:30
Tutte le risposte alla domanda 2
0

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');
1 feb 2013 19:47:34
1

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).

28 nov 2013 06:15:41
Commenti

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!

Solomon Closson Solomon Closson
26 ott 2016 22:22:04