0
votes

I'm using CakePHP 3.8 to create a CMS for a website. I need a simple WYSIWYG editor with image upload. I'd previously used CKEditor, but was having problems getting the image upload working, so thought I'd try TinyMCE instead.

So, I downloaded TinyMCE 5 (with all standard plugins), linked it in in the head section of my page, and created a form with a TinyMCE textarea like this:

<fieldset>
    <legend>New Page</legend>
    <?php
        echo $this->Flash->render();
        echo $this->Form->create($newpage);
        echo $this->Form->control('title');
        echo $this->Form->control('content', 
                      array('label' => 'Page Content',
                            'type' => 'textarea', 
                            'id' => 'editor_area'));
        echo $this->Form->button('Save');
        echo $this->Form->end();
    ?>
</fieldset>

<script>
    tinymce.init({
        selector:'#editor_area',
        height: 500,
        menubar: false,
        images_upload_url: '<?php echo IMG_UPLOAD_URL ?>',
        toolbar: [
            'undo redo | cut copy paste | styleselect | bold italic underline removeformat | alignleft aligncenter alignright | charmap | bullist numlist | link image'
        ],
        plugins: ['advlist lists link autolink image charmap imagetools code']
    });
</script>

This works fine, text area appears with the editor etc. The upload url in images_upload_url points to the following UploadsController.php (I've left out the details for brevity; can add them in if needed):

<?php
namespace App\Controller\Admin;

use App\Controller\AppController;

class UploadsController extends AppController
{

    public function uploadImage() {

        $result = array();
        $result['success'] = 'success';

        // Process file upload

        return $this->response->withType('application/json')
                    ->withStringBody(json_encode($result));

    }

}

When I upload an image, I get the following error in the console:

Failed to load resource: the server responded with a status of 403 (Forbidden) 

The output from CakePHP shows the error:

Error: CSRF token mismatch.

The debugger shows that the POST includes the following:

Cookie: CAKEPHP=dvsktjv7vp8la5nv7dv19634d1; csrfToken=53e5718e13a1e963d51f9c93c48471a478b35c02b565d6f0699cd2a335775c2b17986cfc2cc587ff7343a6573e3eb2e498a9cb962397599c023417d1dfa9506c; ckCsrfToken=7l2PEC0g06819qQcLwdX5ul7E7jNRa3r61jENt2x

I'm not sure where to go from here.

(Or if there's a more straightforward way to include a free/inexpensive WYSIWYG editor with a decent image/file uploader, I'm open to suggestions! It's a website for a school, so budget is very small and can't be a monthly cost.)

1

1 Answers

3
votes

The cookie data is only one part of the CSRF protection mechanism, the client needs to send the CSRF token in either the request data or the X-CSRF-Token header too.

I'm not overly familiar with TinyMCE image uploads, but looking at the docs, you'll probably need a custom upload handler, where you can add additional data, the CSRF token that is.

Taking the example from the TinyMCE docs, the handler could look something like this, where the CSRF token is appended to the form data:

images_upload_handler: function (blobInfo, success, failure) {
    var xhr, formData;

    xhr = new XMLHttpRequest();
    xhr.withCredentials = false;
    xhr.open('POST', <?= json_encode(IMG_UPLOAD_URL) ?>);

    xhr.onload = function() {
        var json;

        if (xhr.status != 200) {
            failure('HTTP Error: ' + xhr.status);
            return;
        }

        json = JSON.parse(xhr.responseText);

        if (!json || typeof json.location != 'string') {
            failure('Invalid JSON: ' + xhr.responseText);
            return;
        }

        success(json.location);
    };

    formData = new FormData();
    formData.append('file', blobInfo.blob(), blobInfo.filename());
    // append CSRF token in the form data
    formData.append('_csrfToken', <?= json_encode($this->request->getParam('_csrfToken')) ?>);

    xhr.send(formData);
}

Also according to the docs the response JSON must contain a property named location that contains the web path to the uploaded file, that might be in the code that you've left out, mentioning it just in case.

See also