Securing an AJAX file-writing

In my earlier post I mentioned the FileWriter class: my actual implementation, the one that comes with the Adapter Classes plugin, I use the adclasses_Functions and adclasses_Globals adapter classes but the bulk and logic of it remains the same: it uses WordPress Filesystem API to write a file to disk.
As I’ve said in that earlier post the FileWriter class comes with no security at all baked in it. The choice was made to be able to call it in any WordPress situation.

Where it’s called

Push the button to add drama
Push the button to add drama

The screen above shows one use I make of the FileWriter class: clicking the button labeled Save functions to file triggers an AJAX action that dumps the current list of user defined functions to file. The file will be used to build the façade for the Functions adapter class in tests.

Watch the button

On the admin page dedicated to the plugin settings I enqueue the following jQuery code, please note that the ajaxurl variable is conveniently already localized by WordPress

/**
 * Handles the click of the 'save functions' button in the Adapter Classes settings page.
 * @return {none} Appends content to the #saving_results label.
 */
jQuery( document ).ready(function() {
    jQuery('#save_functions').click(function() {
        // get the nonce value from the nonce field on the page
        nonce = jQuery('input#_wpnonce').val();
        jQuery.ajax({
            type:'POST',
            url:ajaxurl,
            data:{
                action: 'adclasses_save_functions',
                _ajax_nonce: nonce,
            },
            success: function (data, textStatus, XMLHttpRequest){
                jQuery('#saving_results').html('');
                jQuery('#saving_results').append(data);
            },
            error: function(data, textStatus, errorThrown){
                alert(errorThrown);
            }
        });
    });
}); 

which is pretty basic and will call a fucntion that will:

* get a list of the currently defined functions
* try to write the said functions to file via the FileWriter

all this passing along, to the function, the nonce value generated using wp_nonce_field PHP instruction.

Laying out the security

Since the damage that can be made allowing a file writing to be made via a JavaScript method is huge I have to take some precautions and prevent such cases using the WordPress nonce (number used once) mechanism.
In my code I use the function wp_nonce_field while generating the output

<?php wp_nonce_field('adclasses_plugin_options'); ?>

and it will actually generate two hidden input fields like

<input type="hidden" id="_wpnonce" name="_wpnonce" value="a7bdce7682">
<input type="hidden" name="_wp_http_referer" value="/wp-admin/options-general.php?page=adclasses">

You know who sent me over to do you know what

If you imagine the AJAX request generated by the button click as a guy skulking in dark WordPress alleys and then knocking on WordPress AJAX handling system then the two generated inputs will be the you know who (_wp_http_referer) and the password (_wpnonce) catch phrases and the metaphore works well in keeping in mind that:

* <code>_wp_http_referer</code> will not change over time, no matter how many times you call the function the *you know who* will always be the same
* <code>_wpnonce</code> will change on each page refresh and this is good because it's the secret password

Just pass the door

On the other side of the imaginary door sits the WordPress AJAX system that will answer the AJAX request knocking and wil ask whom the caller is searching for; in coding terms this means that the function that’s hooked to receive AJAX requests (the save_functions function pasted below) will get to the door and ask from whom the calls comes from and which is the password. If the caller answers the security question right then the function acts and, finally and eventually, writes the file.

/**
 * Saves a list of the defined user functions to file
 * @return none
 */
public function save_functions()
{
    // the url to get back to
    $url = 'options-general.php?page=adclasses';

    // the nonce to check
    $nonce_action = 'adclasses_plugin_options';
    // verify the nonce
    if ( ! check_ajax_referer('adclasses_plugin_options', '_ajax_nonce', false)){
        die ('There was an error verifying the call source.');
    }

    // prepare the file contents
    $defined_functions = get_defined_functions();
    $user_functions = $defined_functions['user'];
    $contents = implode(',', $user_functions);
    // the file to save the functions list to
    $filename = ADCLASSES_ROOT . 'assets/functions.txt';

    // the actual writing attempts
    try {
        $result = $this->file_writer->put_contents($url, $contents, $filename);
        if (!$result) {
            die(esc_html__('Permission to write is requested to save the functions list.', 'adclasses'));
        }
        // prepare the success message
        $filename_markup = '<code>' . $filename . '</code>';
        $message = sprintf(esc_html__('Functions saved to file in %1$s', 'adclasses') , $filename_markup);
        die($message);
    }
    catch(Excpetions $e) {
        if ($e instanceof RuntimeException) {
            die(esc_html__('The functions could not be saved to file.', 'adclasses'));
        }
        else {
            die(esc_html__('There was an error writing the functions to file.', 'adclasses'));
        }
    }
}

I appreciate your input