CSRF issues when integrating TinyMCE image upload with CakePHP 3.8 - cakephp

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

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
TinyMCE Docs > Introduction & getting started > Uploading images and files
TinyMCE Docs > Configuration reference > Image & file upload options > images_upload_handler
Cookbook > Security > Cross Site Request Forgery (CSRF) Middleware

Related

Laravel Http::fake response with file

I am trying to mock/test an Http request in a feature test to mock the whatsapp APIs, using Http::fake() in the setUp as follow:
Http::fake([
'https://www.test-whatsapp.com/upload/image' => Http::response(UploadedFile::fake()->image('TestWhatsapp.jpg')->get()),
'https://www.test-whatsapp.com/*' => Http::response(),
]);
I'd like to mock that when my app calls https://www.test-whatsapp.com/upload/image i get the file content but it seems that it never get sent. This is my actual test:
public function test_ask_attachment_image()
{
$this->receivedMessage(['type' => 'TEXT', 'text' => 'ask-attachment']);
$this->receivedMessage(['type' => 'IMAGE', 'caption' => 'some text', 'url' => '"https://www.test-whatsapp.com/upload/image']);
Http::assertSent(function (Request $request){
Log::debug($request->url(), [$request->body()]);
return true;
});
}
But the log gives an empty body.
I tried to print_r the whole response but still no sign of the file.
In the end I tried with an existing file using file_get_content but i got the same result.
I have logged the faked file and it get the content right.
How can i fake an endpoint to return me a file content? Thank you for any suggestions!
Edit
After debugging more i found out that the problem is not when i'm trying to create an image but a generic file using the method UploadedFile::fake()->create('TestWhatsapp.mp4', 1000). I get the size and the mimetype right but the file is still empty.

Where can i get the IPhone 11 Skin for CN1 Simulator?

The AppStore now require an IPhone 11 (or high IPhone X) skin for their 6.5inch metadata images. Please can someone point me in the direction to it, for use in my Codenameone simulator.
I am on the latest install (CN1 v6), which has up to IPhoneX.skin in my .codenameone folder, but i would like to future proof as much as i can, so go with the 11. Thanks
We don't have an iPhone 11 skin yet although you can file an RFE on that here. But this doesn't matter for most.
Most people use tools such as these to generate the screenshots in a portable way:
https://www.appstorescreenshot.com/
https://theapplaunchpad.com/
https://screenshot-maker.appinstitute.com/app-screenshot-maker/screenshot?platform=ios
I had the same problem. I use a different approach to get one or more screenshots as required by the stores. In short, I execute the app on "Browser Stack App Live" (that has several real devices, like the required iPhone 11), using a code that programmatically send me one or more screenshots of the app, using a REST request.
Note that on Browser Stack App Live it's not possible to send e-mails, that's why I created my own REST API for screenshot uploads.
It's easy, I tested the following solution before sharing it. On a temporary VPS with Apache + PHP installed, or on your local machine if you have a public IP, create the following upload.php, remembering to update $server_url with your actual url:
<?php
header('Content-Type: application/json; charset=utf-8');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: PUT, GET, POST");
$response = array();
$upload_dir = 'uploads/';
$server_url = 'https://www.example.com/mydir/';
if($_FILES['screenshot'])
{
$screenshot_name = $_FILES["screenshot"]["name"];
$screenshot_tmp_name = $_FILES["screenshot"]["tmp_name"];
$error = $_FILES["screenshot"]["error"];
if($error > 0){
$response = array(
"status" => "error",
"error" => true,
"message" => "Error uploading the file!"
);
}else
{
$random_name = rand(1000,1000000)."-".$screenshot_name;
$upload_name = $upload_dir.strtolower($random_name);
$upload_name = preg_replace('/\s+/', '-', $upload_name);
if(move_uploaded_file($screenshot_tmp_name , $upload_name)) {
$response = array(
"status" => "success",
"error" => false,
"message" => "File uploaded successfully",
"url" => $server_url."/".$upload_name
);
}else
{
$response = array(
"status" => "error",
"error" => true,
"message" => "Error uploading the file!"
);
}
}
}else{
$response = array(
"status" => "error",
"error" => true,
"message" => "No file was sent!"
);
}
echo json_encode($response);
?>
After that, mkdir uploads and chown the permissions of the php file and of the upload dir accordingly.
The server is ready.
On your app, add the following method (remember to change the String url value):
public static void sendMeScreenshot() {
Form form = Display.getInstance().getCurrent();
if (form != null) {
try {
Image screenshot = Image.createImage(form.getWidth(), form.getHeight());
form.paintComponent(screenshot.getGraphics(), true);
String file = FileSystemStorage.getInstance().getAppHomePath() + "/screenshot_" + System.currentTimeMillis() + ".png";
OutputStream output = FileSystemStorage.getInstance().openOutputStream(file);
ImageIO.getImageIO().save(screenshot, output, ImageIO.FORMAT_PNG, 1.0f);
String url = "https://www.example.com/mydir/upload.php";
MultipartRequest request = new MultipartRequest();
request.setUrl(url);
request.addData("screenshot", file, "multipart/form-data");
NetworkManager.getInstance().addToQueue(request);
} catch (IOException ex) {
Log.e(ex);
}
}
}
Finally, use a code like UITimer.timer(5000, false, hi, () -> sendMeScreenshot()); to get a screenshot of the Form you are interested to, after the wanted time.
Test in the Simulator, it should work. Add some logging and check the returned JSON in the Network Monitor. If it's all ok, open your app with Browser Stack App Live, selecting the wanted device (iPhone 11 in this case). You will find the screenshot (or screenshots) on the choosen upload dir of your VPS. You can download them with scp or open directly them in your browser.
This solution is useful if you don't own the required device, but keep in mind to don't keep online your upload.php to don't have security issues.

Drupal 8 - sending files to browser via route module

I have a module, and inside this module, there should be a new route in the .routing.yml with:
path: '/file_exporter/{filename}'
defaults:
_controller: '\Drupal\file_exporter\Controller\ExportController::file_export'
Inside the ExportController, there is happening a bit magic, where a file is created depending on the user and other circumstances, and this works fine, and i have this file in a temp folder inside the module.
But how could i send it to the browser with drupal?
Target ist, that i have a link on another site to /fileexporter/file_123.xyz and a click on this link lets the browser directly download the new generated file_123.xyz
Is there a class which i could extend, or a function that i could use in Drupal 8 to send files direct to browsers via a Route and a Controller?
The trick is in using BinaryFileResponse.
Here's an example function that handles setting up the HTTP headers and content type and returns a BinaryFileResponse:
<?php
// $uri: the file you want to send, as a URI (e.g. private://somefile.txt)
// $ofilename: the output filename, this will be displayed in the browser.
// $contenttype: the mime content type for the browser
// $delete_after_send: delete the file once it's been sent to the browser
function mymodule_transfer_file($uri, $ofilename, $contenttype = NULL, $delete_after_send = FALSE) {
$mime = \Drupal::service('file.mime_type.guesser')->guess($uri);
$headers = array(
'Content-Type' => $mime . '; name="' . Unicode::mimeHeaderEncode(basename($uri)) . '"',
'Content-Length' => filesize($uri),
'Content-Disposition' => 'attachment; filename="' . Unicode::mimeHeaderEncode($ofilename) . '"',
'Cache-Control' => 'private',
);
if (isset($contenttype)) {
$headers['Content-Type'] = $contenttype;
}
if ($delete_after_send) {
// Delete after end of script.
drupal_register_shutdown_function('file_unmanaged_delete', $uri);
}
$uri_obj = new Uri($uri);
return new BinaryFileResponse($uri, 200, $headers, $uri_obj->getScheme() !== 'private');
}

Angular : show image from REST Service

After reseaches and tests, I still can't show an image form ReST API on my Angular App. I have images available on my ReST web service, why do I use a ReST service? Because in order to access you need to be authenticated (I use oAuth 2 protocol). When I use POSTMan (ReST client very usefull) everything works great, the image is displayed without doing nothing. But when I try to display it with Angular after a $http it doesn't work.
Here are the headers received form the service :
Content-Length → 51756
Content-Type → image/jpeg; charset=binary
Server → Apache/2.4.9 (Win64) PHP/5.5.12
X-Powered-By → PHP/5.5.12
Here is my Angular code :
var data64 = $base64.encode(unescape(encodeURIComponent(data)));
scope.src = 'data:image/jpeg;charset=binary;base64,' + data64;
and my HTML :
<img ng-src="{{src}}" border="0" />
For information I use angular-base64 (https://github.com/ninjatronic/angular-base64) for the encodage. Without "unescape" and "encodeURIComponent" I have an error, I've tried to remove white spaces but it still doesn't work.
Thank you :)
Seems that this will not work since you tell the browser that the image data is base64 encoded, but you also transformed it with unescape and encodeURIComponent.
Why don't you fetch your image data into a binary data structure (requires a modern browser), instead of into a string:
$http.get(req, {responseType: "arraybuffer"}).
success(function(data) {
$scope.src = 'data:image/jpeg;base64,' + _arrayBufferToBase64(data);
});
_arrayBufferToBase64 is defined here.
A different approach would be to install a request interceptor, recognize the image url and add the oauth headers for this case.
I tryed this way in angular 8+ and works fine:
imageToShow: any;
createImageFromBlob(image: Blob) {
let reader = new FileReader();
reader.addEventListener("load", () => {
this.imageToShow = reader.result;
}, false);
if (image) {
reader.readAsDataURL(image);
}
}
and also call it like this:
getImageFromService() {
this.api.getImage(key).subscribe(data => {
this.createImageFromBlob(data);
}, error => {
console.log(error);
});
}

blueimp jquery upload SyntaxError: Unexpected token <

I'm using blueimp to upload images in cakephp 2.x. Images are upload but issue is after uploading images.I got error "SyntaxError: Unexpected token <" and json data in html format. I tried to figured it out myself and find a solution given on SO. I tried to resolve my issue by following answer given in solution but could not. (sorry for bad English)
my controller code
App::import('Vendor', 'UploadHandler', array('file' => 'file.upload/UploadHandler.php'));
$this->layout = 'upload';
$options = array(
// 'upload_dir' => WWW_ROOT . DS . 'img',
'accept_file_types' => '/\.(gif|jpe?g|png)$/i',
);
$upload_handler = new UploadHandler($options);
}
I changed url In main.js(blueimp js file)
// Initialize the jQuery File Upload widget:
$('#fileupload').fileupload({
// Uncomment the following to send cross-domain cookies:
//xhrFields: {withCredentials: true},
url: 'index'
});
json data displayed on index page.
{"files":[{"name":"1186225_383357368459160_1371777554_n.jpg","size":58938,"url":"http:\/\/localhost\/ec\/cakephp\/app\/webroot\/files\/1186225_383357368459160_1371777554_n.jpg","thumbnailUrl":"http:\/\/localhost\/ec\/cakephp\/app\/webroot\/files\/thumbnail\/1186225_383357368459160_1371777554_n.jpg","deleteUrl":"http:\/\/localhost\/ec\/cakephp\/app\/webroot\/?file=1186225_383357368459160_1371777554_n.jpg","deleteType":"DELETE"},{"name":"e0775308650b201469fc68765dc4ff7a.jpg","size":150919,"url":"http:\/\/localhost\/ec\/cakephp\/app\/webroot\/files\/e0775308650b201469fc68765dc4ff7a.jpg","thumbnailUrl":"http:\/\/localhost\/ec\/cakephp\/app\/webroot\/files\/thumbnail\/e0775308650b201469fc68765dc4ff7a.jpg","deleteUrl":"http:\/\/localhost\/ec\/cakephp\/app\/webroot\/?file=e0775308650b201469fc68765dc4ff7a.jpg","deleteType":"DELETE"}]}
i had the same problem.
it was the validate variable
wrong
public $validate;
correct
public $validate = array();

Resources