I'm trying to incorporate PagSeguro (a payment gateway - Brazil's version of PayPal) into my site. After the customer finishes with PagSeguro, they send data (via POST) to a function which I specify. However, I'm not receiving the POST. After doing all the troubleshooting I could think of, I contacted PagSeguro. They said that their log indicates that the POST is being sent as normal but they are receiving an HTTP 302 response.
In order to figure out why this is happening, I created a form with hidden values to simulate sending a POST to my function. I put this form under a different domain just in case it had something to do with that. Every time I send the POST from my simulation form, I receive an HTTP 200 response, and my log indicates that the POST was received.
How is it possible that PagSeguro is receiving a different response than my simulation? Could it have something to do with the server or is it something to do with my script?
Here is the function (using CodeIgniter) that should be receiving the POST:
function pagseguro_retorno(){
if (count($_POST) == 0) {
return FALSE;
}
$msg = 'POST RECEIVED';
$simulate = $this->input->post('Simulate');
if ( ! empty($simulate)){
$result = 'VERIFICADO';
$msg .= ' FROM SIMULATOR';
} else {
$this->load->library(PagSeguroNpi);
$result = $this->PagSeguroNpi->notificationPost();
}
$this->log($msg);
if ($result == "VERIFICADO") {
$id = $this->input->post('Referencia');//cart id
$this->load->model('transacao_model');
$trans_row = $this->transacao_model->get_transaction($id);
if ( ! is_object($trans_row)){
//LOAD NEW TRANSACTION
if ( ! $this->new_transaction($id)){
$notice = "Unable to load new transaction</p><p>";
$this->log($notice);
$notice .= '<pre>'.print_r($_POST, TRUE).'</pre>';
$this->email_notice($notice);
}
}
$this->load->model('carrinho_model');
if($_POST['StatusTransacao'] == 'Aprovado'){
$status = 'apr';
}elseif($_POST['StatusTransacao'] == 'Em AnĂ¡lise'){
$status = 'anl';
}elseif($_POST['StatusTransacao'] == 'Aguardando Pagto'){
$status = 'wtg';
}elseif($_POST['StatusTransacao'] == 'Completo'){
$status = 'cmp';
//nothing more happens here - client must click on 'mark as shipped' before cart is removed and history data is loaded
}elseif($_POST['StatusTransacao'] == 'Cancelado'){
//reshelf - don't set $status, because the cart's about to be destroyed
$this->carrinho_model->reshelf(array($id));
}
if (isset($status)){
$this->carrinho_model->update_carrinho($id, array('status' => $status));
}
} else if ($result == "FALSO") {
$notice = "PagSeguro return was invalid.";
$this->log($notice);
$notice .= '<pre>'.print_r($_POST, TRUE).'</pre>';
$this->email_notice($notice);
} else {
$notice = "Error in PagSeguro request";
$this->log($notice);
$notice .= '<pre>'.print_r($_POST, TRUE).'</pre>';
$this->email_notice($notice);
}
}
SECURITY UPDATE:
After posting, I soon realized that I was opening myself up to hack attempts. The function necessarily has to be public, so anyone who knows the name of the function could access it and post 'simulate' to get immediate verification. Then they could pass whatever data they wanted.
I changed the name of the function to something that would be impossible to guess, and when not in production mode, I've disabled the simulate option.
The problem was that my CSRF protection causes a redirect when the proper CSRF code isn't sent with the POST. My simulator was working, because I copied the HTML generated by a dynamic simulator that I made on the site in question. I then pasted the HTML to create a static simulator and put it under a different URL. Along with the HTML, I inadvertently pasted the CSRF code as well. The static simulator worked fine until the code expired. I quit using it after a couple times, so I didn't discover that it was no longer working until today.
I know that this exact scenario probably won't happen again in the next 500 years, but things like CSRF security can cause problems with things like payment gateways if not disabled for the function receiving the POST.
Related
On Concrete5-8.1.0 I have created a custom block with Ajax functionality based largely on the concrete5 docs - Implementing Ajax in Block View Templates. However, unlike the example I do not want to reload the block view, I want to pass specific messages based on the input. I tried a simple echo '{"msg":"ok"}'; and return '{"msg":"ok"}); as a test, but requests to the function yielded an empty response.
I found How To Send JSON Responses with Concrete5.7 and used Option 2 (for greater control of error codes) resulting in the following test code:
public function action_submit($token = false, $bID = false) {
if ($this->bID != $bID) {
return false;
}
if (Core::make('token')->validate('get_paper', $token)) {
//save to database
//removed for brevity
//send email
if ($this->emailto != '') {
//removed for brevity
}
if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
return new Response(
json_encode(array('msg' => 'ok')),
200,
['Content-Type' => 'application/json']
);
} else {
Redirect::page($page)->send();
}
}
else {
return false;
}
exit;
}
The database save and email function as expected, but the response is still empty. In Chrome Dev Tools, I see the correct Content-Type (as a test, I tried text/html and saw that change in dev tools), but no content. Interestingly, if I change the status from 200 to 500 I not only see the status change reflected in dev tools, I also see the {"msg":"ok"} content that I'm expecting, but changing the status back to 200 and the content is again empty.
It seems I'm missing something simple... I have verified that all caching is turned off within C5 (site is still in development), I have also verified the jQuery request includes cache:false, but the solution escapes me.
I have a form and I don't do client-side validation other than required attribute. I'm doing the validation on the server side.
lyrics/add.php
<form action="../scripts/lyrics/submit_lyrics.php" id="lyricsForm" method="post" autocomplete="off" enctype="multipart/form-data">
Form data is processed in a seperate php file. Retrieving and validating the form data in the following way:
scripts/lyrics/submit_lyrics.php
$form_data = new FormData(["artist", "album", "song", "year", "track_no", "lyrics"], "sssiis");
$form_data->validate();
What the validate method does is
public function validate() {
$valid = $this->check_form_data($fields);
$fields = implode(",", $fields);
if (!$valid) {
header("location: ".BASE."lyrics/add?status=error&problem=input&specifics=$fields");
exit;
}
}
check the form data and redirect the page (scripts/lyrics/submit_lyrics.php) to the form page (lyrics/add.php) with the information on validation (if it failed). Then I output an error message indicating that there's something wrong with the input using GET method.
I'm curious if I can do this using POST. I would need to modify this line
header("location: ".BASE."lyrics/add?status=error&problem=input&specifics=$fields");
make it redirect the page to BASE."lyrics/add" and also send the validation information using POST to that page. So I'd still be able output the validation error, but using POST instead of GET.
Is this possible?
No, this is not possible because you are rewriting the request. The browser will always send a "GET" request in response to a redirect request, unless it receives a 307 request in which it will repeat the request using the same method and parameters. See this related question on Programmers.
The workaround is to redirect to a new page with an embedded form that you generate, and have javascript POST that form for the user. It's an extra request but it's the only way to have the client make a second POST.
Using u_mulder's suggesion (storing values in a session), I've solved the problem in the following way:
scripts/lyrics/submit_lyrics.php
public function validate() {
$valid = $this->check_form_data($fields); // check if input fields are valid
$fields = implode(";", $fields); // invalid input fields
if (!$valid) { // not all fields are valid, so we return
session_start(); // start session
if (!isset($_SESSION["problem"])) { // making sure session variable doesn't exist
$_SESSION["problem"] = "input"; // error type: input, file_upload, db_insert, etc.
$_SESSION["specifics"] = $fields; // artist, album, etc. (for input)
}
header("location: ".BASE."lyrics/add?status=error"); // redirect to form page
exit;
}
}
lyrics/add.php
if (isset($_GET["status"])) {
$status = $_GET["status"];
switch ($status) {
case "error":
session_start();
if (isset($_SESSION["problem"])) {
$problem = $_SESSION["problem"];
$specifics = explode(";", $_SESSION["specifics"]);
$error_message = "There was an error.";
switch ($problem) {
case "input":
$error_message = "Invalid input on the following fields:";
break;
// other cases ...
}
session_destroy();
output("Error!", $error_message, $specifics);
} else {
// if the user reloads the page, session data is lost
// but we're still in the error page "lyrics/add?status=error" and have no error to show
// we either show an appropriate message or redirect to "lyrics/add"
session_destroy();
// output("Error!", "You're in the form validation error page, but there aren't any errors. Did you reload the page?");
// header("location: ".BASE."lyrics/add");
// exit;
}
break;
case "success":
// form submitted successfully
break;
}
} else {
// show form
}
In my contact.php page, I am sending emails to admin and user after passing the validation. The code looks something below:
// If any validation error occurs
if(count($errors) > 0)
{
// show error messages
}
else
{
// sending emails to admin and user
set_flash_msg('msg', 'Thank you for contacting us.', 'success');
redirect_to('contact');
}
But the code in else part related flash message doesn't work. If I put this code in if part then it works without any issue. The function set_flash_msg() is responsible to create session based flash message. Its code is below:
// Set flash message
function set_flash_msg($name, $msg, $class)
{
global $name;
if(empty($_SESSION[$name]) && empty($_SESSION[$name . '_class']))
{
$_SESSION[$name] = $msg;
$_SESSION[$name . '_class'] = $class;
}
}
To display this message, another function is called below the above code as:
// Show flash message
get_flash_msg();
There is no issue regarding session setting because in if part, it works fine. The same logic and coding flow is used in my backend too where I set flash message when a new page is added and show the flash message on page listing page.php. If it works at there then why it doesn't work on my front-end contact.php page? Any idea?
In my contact.php page, I am grabbing the form values particularly "name" as:
// Preparing form variables
$name = isset($_POST['txtname']) ? $db_obj->sanitize_input($_POST['txtname']) : '';
The local variable $name conflicts with global variable $name in set_flash_msg() function. Changing $name to $uname in above code solved the problem.
I'm working on custom module and in my IndexController.php I'd written this function to add user to database
public function addAction() {
if($this->getRequest()->getParam('name', '') == ''){
$this->_redirect('etech/user');
//die; or exit;
}
$form = $this->getRequest()->getParams();
$user = Mage::getModel('test/test');
foreach ($form as $key => $val){
$user->setData($key, $val);
}
try{
$user->save();
}catch(Exception $e){
print_r($e);
}
$this->_redirect('etech/user', array('msg'=>'success'));
}
I want to prevent users from accessing this url directly as www.example.com/index.php/etech/user/add/. For this I'd made a check if($this->getRequest()->getParam('name', '') == ''){}. The redirect is working well except the code in there keeps executing and user sees a success message which should not be seen. For this, I'd used old fashioned exit or die to stop executing the code then it doesn't even redirect.
What is the magento way to handle it? Also, as I'm using getRequest()->getParams(), it return both parameters either in get or post. Isn't any way out to get only post parametrs?
It is correct to use $this->_redirect(), but you must follow it up with a return, ideally return $this;. You could also use exit or die, as you have been doing, but as I'm sure you know it would be better to let Magento do whatever it wants to do before redirecting you.
As long as you return immediately after $this->_redirect(), you won't have any issues.
Edit: And as for the request params question, I think you can call something like $this->getRequest()->getPostData() (that was false). The general convention is to use getParams() regardless of whether the data was sent via GET or POST, because technically your code shouldn't be concerned about that.
Edit #2:
If the general convention doesn't apply and you desperately need to restrict access to your page based on POST vs. GET, here's a handy snippet from Mohammad:
public function addAction()
{
if ($this->getRequest()->isPost()) {
// echo 'post'; do your stuff
} else {
// echo 'get'; redirect
}
}
I have a form on a php page that is submitted to the same page.
I noticed that if I reload/refresh the page the form gets re-submitted.
How do I code to avoid this in the most easy way?
One possibility is, to implement the post-redirect-get approach.
Simply said, a POST request will be never delivered to the browser. Instead you execute all necessary actions and store the information you need in the session, and then you make a redirect with code 303.
$page = 'show_result.php';
header('Location: '.$page, true, 303);
exit;
Doing it this way, the browser will show the "show_result.php" page (a GET request) instead of the page requested with POST. This is also the page that is added to the history, so refreshing and using the back button will never do another POST request. As a nice side effect you get rid of browser warnings about resending data, normally the user cannot decide what to do then anyway.
I think the biggest problem with this approach is, that you need a session to store error messages, that means you have to rely on cookies. If you do no redirect to display errors, the browser will show the warning about resending data.
This assume a lot of things, but maybe is what you are looking for:
if ($_POST)
{
$success = false;
/*
* if all goes OK managing POST data make $success = true;
*
*/
if ($success)
{
// this will redirects to your original
// form's page but using GET method
// so re-submitting will be no possible
header("location: {$_SERVER['PHP_SELF']}");
exit;
}
}
According to HTTP standard, you ought to make browser to do a GET request after sending POST one.
Here is a sketch example to do the form handling:
<?
if ($_SERVER['REQUEST_METHOD']=='POST') {
$err = array();
//performing all validations and raising corresponding errors
if (empty($_POST['name']) $err[] = "Username field is required";
if (empty($_POST['text']) $err[] = "Comments field is required";
if (!$err) {
//if no errors - saving data and redirect
header("Location: ".$_SERVER['PHP_SELF']);
exit;
} else {
// all field values should be escaped according to HTML standard
foreach ($_POST as $key => $val) {
$form[$key] = htmlspecialchars($val);
}
} else {
$form['name'] = $form['comments'] = '';
}
include 'form.tpl.php';
?>