I have a project in Cakephp 3.6 in which 3 actions in MessageController are called by Ajax. I have a problem, however, when I send a request to one of the action, XHR returns to me this:
{
"message": "CSRF token mismatch.",
"url": "\/messages\/changepriority\/8",
"code": 403,
"file": "D:\\xampp\\htdocs\\myapp\\vendor\\cakephp\\cakephp\\src\\Http\\Middleware\\CsrfProtectionMiddleware.php",
"line": 195
}
This is one of the action what I try to call from Ajax:
public function changepriority($id=null)
{
$this->autoRender = false;
$message = $this->Messages->get($id);
$message->priority = ($message->priority === false) ? true : false;
if ($this->Messages->save($message)) {
echo json_encode($message);
}
}
And this is my ajax:
$(".email-star").click(function(){
var idmessage = this.id;
$.ajax({
headers : {
'X-CSRF-Token': $('[name="_csrfToken"]').val()
},
dataType: "json",
type: "POST",
evalScripts: true,
async:true,
url: '<?php echo Router::url(array('controller'=>'Messages','action'=>'changepriority'));?>' +'/'+idmessage,
success: function(data){
if(data['priority'] === false) {
$("#imp_" + idmessage).removeClass("fas").removeClass('full-star').addClass( "far" );
}
else {
$("#imp_" + idmessage).removeClass("far").addClass( "fas" ).addClass("full-star");
}
}
});
});
I have read the documentation about Cross Site Request Forgery, and I tried to turn off the Csrf for these action first with:
public function beforeFilter(Event $event)
{
$this->getEventManager()->off($this->Csrf);
}
and then with:
public function beforeFilter(Event $event)
{
$this->Security->setConfig('unlockedActions', ['index', 'changepriority']);
}
But nothing. The Xhr return always the CSRF token mismatch.
What can I do ?
Edit:
I change the action in this way:
public function changepriority($id=null)
{
$this->autoRender = false;
$message = $this->Messages->get($id);
$message->priority = ($message->priority === false) ? true : false;
if ($this->Messages->save($message)) {
$content = json_encode($message);
$this->response->getBody()->write($content);
$this->response = $this->response->withType('json');
return $this->response;
}
}
In that way the action works. Can it be like that?
First check your $('[name="_csrfToken"]').val() output.
If you didn't get any output, need to check csrfToken hidden field is exist or not. Just right click in your page and click View Page Source
If not exist, you don't follow proper way when you create Form. Basically, when forms are created with the Cake\View\Helper\FormHelper, a hidden field is added containing the CSRF token.
If everything is correct, add the following line inside your ajax call after header
beforeSend: function (xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('[name="_csrfToken"]').val());
},
Ps. Disabling the CSRF is not recommended by cakePHP and most of the developer aware of this. Hope this help.
beforeSend: function (xhr) {
xhr.setRequestHeader('X-CSRF-Token', <?= json_encode($this->request->getAttribute('csrfToken')) ?>);
},
Related
I tried creating a universal delete function in 3 ways.
function DeleteByID($table, $id){
1. DB::table("$table")->delete("$id");
2. DB::table("$table")->find("$id")->delete();
3. DB::table("$table")->where('id', '=', "$id")->delete();
}
I'm using ajax to send the request to an Ajax Controller class, which sends it to the according controller class of the specific subject. Everything goes fine with the ajax request, it does what it should do. But deleting something from the table doesn't work.
And yes, I am putting the right table names into the $table parameter when I'm calling the DeleteByID($table, $id) function.
Update 1
removed every double "" from the ajax request to the call of the delete function.
DB::table($table)->where('id', '=', $value)->delete();
Is what it is now. Still doesn't work.
Update 2
This triggers the DeleteRole function. This will open a modal, asking if you are sure you want to delete the record. there will be another button with onclick="DeleteRole(this.id, true)" with ofcourse the id send with it.
<a id="{{$role->id}}" onclick="DeleteRole(this.id, false)">
<button class="btn btn-neutral btn-icon btn-round" data-toggle="modal"
id="{{$role->id}}" data-target="#rolesModalDelete">
<i class="material-icons" style="color:rgba(185,14,22,0.81)">clear</i>
</button>
</a>
AJAX Request:
function DeleteRole(id, bool){
let contentModal = $('#DeleteRoleContent');
if(bool === false){
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$.ajax({
url: '/DeleteRole',
type: 'POST',
dataType: "json",
beforeSend: function (xhr) {
const token = jQuery('meta[name="csrf_token"]').attr('content');
if (token) {
return xhr.setRequestHeader('X-CSRF-TOKEN', token);
}
},
data: {
roleID: id,
popup: bool,
},
success: function (data) {
contentModal.empty();
contentModal.append(data);
}
});
}else if (bool === true){
let row = $('#' + id);
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$.ajax({
url: '/DeleteRole',
type: 'POST',
dataType: "json",
beforeSend: function (xhr) {
const token = jQuery('meta[name="csrf_token"]').attr('content');
if (token) {
return xhr.setRequestHeader('X-CSRF-TOKEN', token);
}
},
data: {
roleID: id,
popup: bool,
},
success: function (data) {
row.empty();
}
});
}
}
Update 3
The if statement where it checks if the $_POST['popup'] is true or not, was ignored. Changed it, so the deletion works now. But now the succes handler won't be called. the ajaxrequest returns 200 OK.
public function RoleDelete()
{
if($_POST['roleID']){
if($_POST['popup'] == 0){
$htmlObject = $this->roleController->GetData($_POST['roleID']);
echo json_encode($htmlObject);
} else {
if($_POST['roleID'] !== null) {
$this->db->DeleteByID('rollen', $_POST['roleID']);
} else {
echo json_encode('ID is null');
}
}
} else {
echo json_encode('Geen gegevens gevonden, is het record al verwijderd? AC 256;');
}
}
You should try this
Do not use "" for variable
1. DB::table($table)->delete($id);
2. DB::table($table)->find($id)->delete();
3. DB::table($table)->where('id', '=', $id)->delete();
I apologize if this comes off as a "make sure it's plugged in" kind of answer, but sometimes the simple stuff is the easiest to overlook, especially when you're tunnel-visioned on the code. That said, are you sure your DB credentials actually have delete-permissions?
The if statement where it checks if the $_POST['popup'] is true or not, was ignored. Changed it, so the deletion works now. But now the succes handler won't be called. the ajaxrequest returns 200 OK.
public function RoleDelete(){
if($_POST['roleID']){
if($_POST['popup'] == 0){
$htmlObject = $this->roleController->GetData($_POST['roleID']);
echo json_encode($htmlObject);
} else {
if($_POST['roleID'] !== null) {
$this->db->DeleteByID('rollen', $_POST['roleID']);
} else {
echo json_encode('ID is null');
}
}
} else {
echo json_encode('Geen gegevens gevonden, is het record al verwijderd? AC 256;');
}
}
I have been trying to send some data to a controller via AJAX but for the life of me I can`t seem to make it work; everytime I make the request, a 403 forbidden error is thrown.
this is the ajax request:
$.ajax({
type: 'post',
url:"<?php echo Router::url(array('controller'=>'Atls','action'=>'saveTime', '_ext' => 'json'));?>",
dataType: 'json',
data: {atl_id: idTimerPaused, time: actual_time},
beforeSend: function(xhr){
},
success: function (response) {
console.log('Nailed It');
},
error: function(jqXHR, exception){
console.log(jqXHR);
}
});
return false;
the controller action:
public function saveTime()
{
if ($this->request->is('post') && $this->request->is('ajax')) {
$content = $this->request->getData();
$query = $this->Atls->query();
$result = $query
->update()
->set(
$query->newExpr('actual_time = '. $content['time'])
)
->where([
'id' => $content['atl_id']
])
->execute();
$this->set(compact('content'));
$this->set('_serialize', ['content']);
$this->render('ajax_response', 'ajax');
}
}
I have loaded the extensions on the routes.php file (Router::extensions('json', 'xml');)
The request handler is also loaded and the function is allowed:
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
$this->Auth->allow('saveTime');
//Change layout for Ajax requests
$this->viewBuilder()->layout('appTemplate');
if ($this->request->is('ajax')) {
$this->viewBuilder()->layout('ajax');
}
}
that "ajax_response" view has also been added.
I can't see where the problem could be. So any help I can get to work this out would be much appreciated.
Did you use the 'Csrf'-Component? In my case this was the problem.
https://book.cakephp.org/3.0/en/controllers/components/csrf.html#csrf-protection-and-ajax-requests
When you got an 403 forbidden error in most cases the session is expired and the user has to login again.
I am using a MVC and I have a button that is using an AJAX call to remove an uploaded image on the site.
This is my Model:
public function remove_document($documentID, $documentName)
{
$objData = $this->objDB
-> setStoredProc('attritionRemoveDocument')
-> setParam('documentID', $documentID)
-> setParam('documentName', $documentName)
-> execStoredProc()
-> parseXML();
return $objData->data->response;
}
The response back from this is either true or false.
Here is my controller:
public function deleteFile()
{
// Get the documentID we are removing
$documentID = $this->input->post('documentID');
$documentName = $this->input->post('documentName');
// Check if the file is even there
if (file_exists('./uploads/'.$documentName)){
// Remove file
unlink('./uploads/'.$documentName);
$removeFile = $this->submit_model->remove_document($documentID, $documentName);
return $removeFile;
}
}
And finally, my AJAX Call:
$('[name=deleteDocument]').click(function() {
var documentID = $(this).attr('documentID'),
documentName = $(this).attr('documentName');
//Delete the image
$.ajax({
type: 'POST',
url: '../deleteFile',
dataType: 'xml',
data: {
'documentID': documentID,
'documentName': documentName
},
success: function(msg) {
// On Success, remove the current file section
console.log(msg);
}
});
});
When i echo the $removeFile value in the controller, I see the true/false value however it never makes it to the success function of the AJAX call.
Any ideas?
in Controller
if the result is true
echo "OK"
if the result is false
echo "NO"
in your View and in success part
success: function(msg) {
if(msg=="OK"){
alert("DELETED");
}else{
alert("NOT deleted");
}
Your controller should not return BOOL if you are using it with ajax , Your AJAX call , should control the result by success method .
I am using codeigniter, i have written a function to check if a user password exists which it does. This is my model
The model: user
public function get_some_password($username,$password) {
$this->db->where('user_password', $password);
$this->db->where('user_username',$username);
$query=$this->db->get('some_users_table');
if($query->num_rows()==1){
return true;
}else{
return false;
}
the controller
public function check_password() {
$username=$this->uri->segment(3);
$temp_pass= $this->input->post('current_password');
$password=md5($temp_pass);
$this->user->get_some_password($username,$password);
}
The ajax on the view
//done on page load
var success1 = $(".success"); //a div on the view that appears if success
var error1 = $(".error"); //a div on the view that appears if error
success1.hide();
error1.hide();
$('#change_password').click(function() {
var username = $('#username').val();
dataString2 = $('#changpassword').serialize();
$.ajax({
type: "POST",
url: '<?php echo base_url(); ?>controller_name/check_password/' + username,
data: dataString2,
success: function() {
$('.success').html('password successfully updated!'),
success1.slideDown('slow');
},
error: function() {
$('.error').html('Wrong current password!'),
error1.slideDown('slow');
}
});
The problem: Ajax loads the success div even when the username or password returned is false, where am i missing something
This is a correct behavior as jquery error is executed when response code is not 200:
1) You can parse returned value in success method.
e.g.
success: function(data) {
if (data == 'true') {
// Success
} else {
// Error
}
}
2) You can return error code from server 404, 500, 503 ... To trigger execution of error function.
e.g.
header("Status: 404 Not Found");
note: Header should executed before any output is done.
Try in your controller:
public function check_password() {
$username=$this->uri->segment(3);
$temp_pass= $this->input->post('current_password');
$password=md5($temp_pass);
if(!$this->user->get_some_password($username,$password)) {
$this->output->set_status_header('500');
return;
}
...
}
no title that fits this bug but this how it goes, i have form with a submit button when pressed jquery ajax calls the controller and the form validation is done if it fails the form is redrawn if it passes the page is redirected to the home page with flash message successes and thats where the bug happens it redraws the whole page in the content(header header footer footer). i hope it makes sense seeing is believing so here is the code
side notes: "autform" is a lib for creating forms "rest" is a lib for templates.
the jquery code:
$("form.user_form").live("submit",function() {
$("#loader").removeClass('hidden');
$.ajax({
async :false,
type: $(this).attr('method'),
url: $(this).attr('action'),
cache: false,
data: $(this).serialize(),
success: function(data) {
$("#center").html(data);
$('div#notification').hide().slideDown('slow').delay(20000).slideUp('slow');
}
})
return false;
});
the controller
function forgot_password()
{
$this->form_validation->set_rules('login',lang('email_or_login'), 'trim|required|xss_clean');
$this->autoform->add(array('name'=>'login', 'type'=>'text', 'label'=> lang('email_or_login')));
$data['errors'] = array();
if ($this->form_validation->run($this)) { // validation ok
if (!is_null($data = $this->auth->forgot_password(
$this->form_validation->set_value('login')))) {
$this-> _show_message(lang('auth_message_new_password_sent'));
} else {
$data['message']=$this-> _message(lang('error_found'), false); // fail
$errors = $this->auth->get_error_message();
foreach ($errors as $k => $v){
$this->autoform->set_error($k, lang($v));
}
}
}
$this->autoform->add(array('name'=>'forgot_button', 'type'=>'submit','value' =>lang('new_password')));
$data['form']= $this->autoform->generate('','class="user_form"');
$this->set_page('forms/default', $data);
if ( !$this->input->is_ajax_request()) { $this->rest->setPage(''); }
else { echo $this->rest->setPage_ajax('content'); }
}
}
function _show_message($message, $state = true)
{
if($state)
{
$data = '<div id="notification" class="success"><strong>'.$message.'</strong></div>';
}else{
$data = '<div id="notification" class="bug"><strong>'.$message.'</strong></div>';
}
$this->session->set_flashdata('note', $data);
redirect(base_url(),'refresh');
}
i think it as if the redirect call is caught by ajax and instead of sending me the home page it loads the home page in the place of the form.
thanks for any help
regards
OK found the problem and solution, it seemed you cant call a redirect in the middle of an Ajax call that is trying to return a chunk of HTML to a div, the result will be placing the redirected HTML in the div.
The solution as suggested by PhilTem at http://codeigniter.com/forums/viewthread/210403/
is when you want to redirect and the call is made by Ajax then return a value with the redirect URI back to Ajax and let it redirect instead.
For anyone interested in the code:
The Jquery Ajax code:
$("form.user_form").live("submit", function(event) {
event.preventDefault();
$("#loader").removeClass('hidden');
$.ajax({
type: $(this).attr('method'),
url: $(this).attr('action'),
cache: false,
dataType:"html",
data: $(this).serialize(),
success: function(data) {
var res = $(data).filter('span.redirect');
if ($(res).html() != null) {
[removed].href=$(res).html();
return false;
}
$("#center").html(data);
},
error: function() {
}
})
return false;
});
The PHP Controller
function _show_message($message, $state = true, $redirect = '')
{
if ($state)
{
$data = '<div id="notification" class="success"><strong>'.$message.'</strong></div>';
} else {
$data = '<div id="notification" class="bug"><strong>'.$message.'</strong></div>';
}
$this->session->set_flashdata('note', $data);
if ( !$this->input->is_ajax_request())
{
redirect(base_url() . $redirect, 'location', 302);
}
else
{
echo '<span class="redirect">'.base_url().$redirect.'</span>';
}
}
just use individual errors
json_encode(array(
'fieldname' => form_error('fieldname')
));
AJAX
success: function(cb)
{
if(fieldname)
{
{fieldname}.after(cb.fieldname)
}
}
see this