I am using codeigniter on the backend .
Recently i heard about the term "CSRF" and decided to protect the requests.
Almost all actions on my site is through Ajax & some times i am creating/appending page content using DOM manipulation [here the doubt, how can i inject the CSRF token to the view files ?]
Okkey ,after that how can i validate that ?
Assume that i added these values as token and passed to server ,then can i use constructors to check & validate this ?
Ex :
Class Cl_Controller extends Ci_controller
{
function __construct()
{
//loading libraries,models,helpers etc...
if (isset($this->input->get_post("CSRF_TOKEN")) || _another_condition_)
{
// The CSRF TOKEN is invalid or null ,the action cannot be done...
}
}
function register()
{
//some codes...
}
function delete_user()
{
//some codes
}
}
Is it possible to do some thing link this ?
Please suggest me some good ideas & usual practices.
Thank you.
For my ajax calls I usually perform two checks;
Make sure it is an ajax request, using a small helper file.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('ajax_check')) {
/**
* Check AJAX
*
* Checks to see if you (or the royal I) are dealing with an AJAX call.
*
* #return boolean
*/
function ajax_check() {
if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
return TRUE;
} else {
show_404();
return FALSE;
}
}
}
if ( ! function_exists('ajax_response')) {
/**
* JSON Response Wrapper
*
* Wraps up any data nicely for sending back to an ajax call
*
* #return string
*/
function ajax_response($status, $data) {
if (!is_array($data)) {
$data = array();
}
// Set the JSON header appropriately
header('Content-Type: application/json');
// Echo out the array into json
echo json_encode(array_merge(array('status' => $status), $data));
exit;
}
}
if ( ! function_exists('ajax_force_fail')) {
/**
* Force AJAX Failure
*
* If you ever need to, force an AJAX to fail
*/
function ajax_force_fail() {
$_ci =& get_instance();
$_ci->output->set_status_header(500);
}
}
Usage like;
public function some_function() {
$this->load->helper('ajax');
ajax_check();
try {
// do something
ajax_response('success', array('data' => $some_var));
} catch (Exception $e) {
ajax_response('failure', array('data' => $e->getMessage()));
}
}
And a similar approach to xsrf.
File:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('xsrf_get_token')) {
/**
* Get XSRF Token
*
* Returns a token that exists for one request that verifies that
* the action was executed by the person that requested it
*
* #return string
*/
function xsrf_get_token() {
$ci =& get_instance();
if ($ci->session->userdata('xsrf_hash')) {
$token = $ci->session->userdata('xsrf_hash');
} else {
// Generate the token
$token = sha1(microtime().$ci->uri->uri_string());
// Set it in the session
$ci->session->set_userdata('xsrf_hash', $token);
}
//Return it
return $token;
}
}
if ( ! function_exists('xsrf_get_token_field')) {
/**
* Get XSRF Token Field
*
* Returns an xhtml form element to include xsrf token.
* You can specify the id/name attribute of the input.
* Has a dependancy to get_xsrf_token().
*
* #param string The id/name to be used
* #return string
*/
function xsrf_get_token_field($name='auth_token') {
return '<input type="hidden" id="'.$name.'" name="'.$name.'" value="' .xsrf_get_token(). '" />';
}
}
if ( ! function_exists('xsrf_delete_token')) {
/**
* Delete XSRF Token
*
* Deletes the xsrf token
*
* #return boolean
*/
function xsrf_delete_token() {
$ci =& get_instance();
if ($ci->session->userdata('xsrf_hash')) {
$ci->session->unset_userdata('xsrf_hash');
return TRUE;
} else {
return FALSE;
}
}
}
if ( ! function_exists('xsrf_check_token')) {
/**
* Get XSRF Token Field
*
* Checks that the token is still valid, returns true if so.
* Deletes old token after valid or fail.
* Has a dependacy to xsrf_delete_token()
*
* #param string The challenge token
* #return boolean
*/
function xsrf_check_token($challenge_token) {
// CI
$ci =& get_instance();
// Get the stored token
$token = $ci->session->userdata('xsrf_hash');
// Delete the old token
xsrf_delete_token();
// Returns if the token is the right token
return ($token == $challenge_token);
}
}
Usage (controller);
public function some_other_function() {
$this->form_validation->set_rules('username', 'Username', 'required|callback_check_token');
if($this->form_validation->run() == TRUE ) {
// do something
} else {
// something else
}
}
// callback function
public function check_token($val) {
if (xsrf_check_token($val) == TRUE) {
return TRUE;
} else {
$this->form_validation->set_message('check_token', 'Oops');
return FALSE;
}
}
In view;
<form action="" method="post">
<?php echo xsrf_get_token_field(); ?>
...
</form>
Related
I am working on an application that I need to post a secret variable. I wrote this code.
<form target="_blank" action="/validate/lista.php" method="POST">
<input type="hidden" name="evento" value="<?php echo $pname ?>" />
<button class="btn btn-block btn-md btn-outline-success">Lista</button>
</form>
My problem is that if the user inspect the element with chrome or whatever, he can see the value and change it before POST.
I could use SESSION but every user has a different session ID and this way I would need to POST the session ID (because they are separete applications), which I think is not secure. Or is it ok?
How can I prevent this? I am new to programming...
Thank you
Maintain HTML Form State safely ('Conversation' Tracking)
Keep track of the 'state' of an HTML Form as it is processed by the client and the server.
The typical 'conversation' is:
Send a new form to the client, often for a specific user who has to login.
The client enters data and returns it.
It is validated and may be sent out again.
The data changes are applied.
the client is informed of the result.
It sounds simple. Alas, we need to keep track of the 'state' of the form during the 'conversation'.
We need to record the state in a hidden field. This can open us up to various 'failure modes'.
This answer is one method of reliably keeping track of the 'conversations'.
Including people being 'malicious'. It happens. ;-/
This is a data change form so we don't want it applied to the wrong person.
There are various requirements:
Sensible ones:
prevent a form being processed twice
Ask a user to confirm the data if the form is too old
Malicious ones:
Changing the form to appear to be from a different user
Using an old copy of the form
Changing other hidden data to corrupt the user data
Now, we cannot prevent the client changing the hidden data, or storing it to replay later. etc.
What to do?
We need to ensure that if it is changed then we can detect that it is tampered with and tell the user about it. We do nothing.
If they send us an old stored valid copy then we can detect that as well.
Is there a simple way of doing this? Oh yes! :)
Answers:
Give each form a unique id: makes it easy to determine if we have already seen it.
Give each form a timestamp of when it was first created.
we can then decide the max age we allow to use it.
If it is too old then we just copy the entered data to a new form and ask the user to confirm it. see Captcha :)
When we process the form we store the form id.
The first check before processing a form is to see if we have already processed it
Identifying 'tampering'?
We encrypt it with AES! :) Only the server needs to know the password so there are no client issues.
If it is changed then the decrypt will fail and we just issue a new form to the user with the data input on it. :)
Is it a lot of code? Not really. And it makes forms processing safe.
One advantage is that has the protection for the CSRF attack built in so no separate code needed.
Program Code (FormState Class)
<?php
/**
* every 'data edit' form has one of these - without exeception.
*
* This ensures that the form I sent out came from me.
*
* It has:
* 1) A unique #id
* 2) A date time stamp and a lifetime
*
* Can be automatically generated and checked.
*/
class FormState {
const MAX_FORM_AGE = 600; // seconds
const ENC_PASSWORD = '327136823981d9e57652bba2acfdb1f2';
const ENC_IV = 'f9928260b550dbb2eecb6e10fcf630ba';
protected $state = array();
public function __construct($prevState = '')
{
if (!empty($prevState)) {
$this->reloadState($prevState); // will not be valid if fails
return;
}
$this->setNewForm();
}
/**
* Generate a new unique id and timestanp
*
* #param $name - optional name for the form
*/
public function setNewForm($name = '')
{
$this->state = array();
$this->state['formid'] = sha1(uniqid(true)); // each form has a unique id
$this->state['when'] = time();
if (!empty($name)) {
$this->setAttribute('name', $name);
}
}
/**
* retrieve attribute value
*
* #param $name attribute name to use
* #param $default value to return if attribute does not exist
*
* #return string / number
*/
public function getAttribute($name, $default = null)
{
if (isset($this->state[$name])) {
return $this->state[$name];
} else {
return $default;
}
}
/**
* store attribute value
*
* #param $name attribute name to use
* #param $value value to save
*/
public function setAttribute($name, $value)
{
$this->state[$name] = $value;
}
/**
* get the array
*/
public function getAllAttributes()
{
return $this->state;
}
/**
* the unique form id
*
* #return hex string
*/
public function getFormId()
{
return $this->getAttribute('formid');
}
/**
* Age of the form in seconds
* #return int seconds
*/
public function getAge()
{
if ($this->isValid()) {
return time() - $this->state['when'];
}
return 0;
}
/**
* check the age of the form
*
*#param $ageSeconds is age older than the supplied age
*/
public function isOutOfDate($ageSeconds = self::MAX_FORM_AGE)
{
return $this->getAge() >= $ageSeconds;
}
/**
* was a valid string passed when restoring it
* #return boolean
*/
public function isValid()
{
return is_array($this->state) && !empty($this->state);
}
/** -----------------------------------------------------------------------
* Encode as string - these are encrypted to ensure they are not tampered with
*/
public function asString()
{
$serialized = serialize($this->state);
$encrypted = $this->encrypt_decrypt('encrypt', $serialized);
$result = base64_encode($encrypted);
return $result;
}
/**
* Restore the saved attributes - it must be a valid string
*
* #Param $prevState
* #return array Attributes
*/
public function fromString($prevState)
{
$encrypted = #base64_decode($prevState);
if ($encrypted === false) {
return false;
}
$serialized = $this->encrypt_decrypt('decrypt', $encrypted);
if ($serialized === false) {
return false;
}
$object = #unserialize($serialized);
if ($object === false) {
return false;
}
if (!is_array($object)) {
throw new \Exception(__METHOD__ .' failed to return object: '. $object, 500);
}
return $object;
}
public function __toString()
{
return $this->asString();
}
/**
* Restore the previous state of the form
* will not be valid if not a valid string
*
* #param $prevState an encoded serialized array
* #return bool isValid or not
*/
public function reloadState($prevState)
{
$this->state = array();
$state = $this->fromString($prevState);
if ($state !== false) {
$this->state = $state;
}
return $this->isValid();
}
/**
* simple method to encrypt or decrypt a plain text string
* initialization vector(IV) has to be the same when encrypting and decrypting
*
* #param string $action: can be 'encrypt' or 'decrypt'
* #param string $string: string to encrypt or decrypt
*
* #return string
*/
public function encrypt_decrypt($action, $string)
{
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = self::ENC_PASSWORD;
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$secret_iv_len = openssl_cipher_iv_length($encrypt_method);
$secret_iv = substr(self::ENC_IV, 0, $secret_iv_len);
if ( $action == 'encrypt' ) {
$output = openssl_encrypt($string, $encrypt_method, $secret_key, OPENSSL_RAW_DATA, $secret_iv);
} else if( $action == 'decrypt' ) {
$output = openssl_decrypt($string, $encrypt_method, $secret_key, OPENSSL_RAW_DATA, $secret_iv);
}
if ($output === false) {
// throw new \Exception($action .' failed: '. $string, 500);
}
return $output;
}
}
Example Code
Full Example Application Source Code (Q49924789)
Website Using the supplied Source Code
FormState source code
Do we have an existing form?
$isExistingForm = !empty($_POST['formState']);
$selectedAction = 'start-NewForm'; // default action
if ($isExistingForm) { // restore state
$selectedAction = $_POST['selectedAction'];
$formState = new \FormState($_POST['formState']); // it may be invalid
if (!$formState->isValid() && $selectedAction !== 'start-NewForm') {
$selectedAction = "formState-isWrong"; // force user to start a new form
}
} else {
$_POST = array(); // yes, $_POST is just another PHP array
$formState = new \FormState();
}
Start New Form
$formState = new \FormState();
$_POST = array();
$displayMsg = "New formstate created. FormId: ". $formState->getFormId();
Store UserId (Database Id) in the FormState
$formState->setAttribute('userId' $userId);
Check a form being to old?
$secsToBeOutOfDate = 3;
if ($formState->isOutOfDate($secsToBeOutOfDate)) {
$errorMsg = 'Out Of Date Age: '. $secsToBeOutOfDate .'secs'
.', ActualAge: '. $formState->getAge();
}
Reload State from the form hidden field.
$formState = new \FormState('this is rubbish!!');
$errorMsg = "formState: isValid(): ". ($formState->isValid() ? 'True' : 'False');
Check if a form has already been processed.
if (isset($_SESSION['processedForms'][$formState->getFormId()])) {
$errorMsg = 'This form has already been processed. (' . $formState->getFormId() .')';
break;
}
$_SESSION['processedForms'][$formState->getFormId()] = true;
$displayMsg = "Form processed and added to list.";
I'm trying to achieve the following scenario:
1. user display the page addBook.php
2. user starts filling the form
3. but when he wants to select the book Author from the Author combo box, the Author is not yet created in the database so the user clicks a link to add a new Author
5. user is redirected to addAuthor.php
6. the user fill the form and when he submits it, he goes back to addBook.php with all the previous data already present and the new Author selected.
The things is: I have scenarios where there is more than one level of recursion. (Example: Add Book => Add Author => Add Country)
How can I do that?
At step #3, the link submit the form so that I can save it in session.
To handle recursion, I can use a Stack and push the current from on the Stack each time I click a link. And pop the last form of the Stack when the user completes the action correctly or click a cancel button.
My problem is:
How can I handle the back button of the browser?
If instead of clicking the "cancel" button, the user click on the back button, how could I kown that I need to pop the last element?
Do you known some common pattern to achieve that?
You must use javascript on the client and hook into the window unload event, serialize the form and send the answer to the server, which saves it in the session.
$(window).unload(function() {
$.ajax({
url : 'autosave.php',
data : $('#my_form').serialize()
});
});
on server
// autosave.php
$_SESSION['autosave_data'] = $_POST['autosave_data'];
// addbook.php
if (isset($_SESSION['autosave_data'])) {
// populate the fields
}
This is the solution I developed to answer my problem.
As the problem was not a client side problem but truly a server side one. Following the php classes I used in my project:
First the main class of the stack functionality. The inclusion need to be done before the session_start as the object will be stored in the session
class Stack {
private $stack;
private $currentPosition;
private $comeFromCancelledAction = false;
public function __construct() {
$this->clear();
}
/* ----------------------------------------------------- */
/* PUBLICS METHODS */
/* ----------------------------------------------------- */
/**
* Clear the stack history
*/
public function clear() {
$this->stack = array();
$this->currentPosition = -1;
}
/**
* get the current position of the stack
*/
public function getCurrentPosition() {
return $this->currentPosition;
}
/**
* Add a new element on the stack
* Increment the current position
*
* #param $url the url to add on the stack
* #param $data optionnal, the data that could be stored with this $url
*/
public function add($url, &$data = array()) {
if (count($this->stack) != $this->currentPosition) {
// the currentPosition is not the top of the stack
// need to slice the array to discard dirty urls
$this->stack = array_slice($this->stack, 0, $this->currentPosition+1);
}
$this->currentPosition++;
$this->stack[] = array('url' => $url, 'data' => $data, 'previousData' => null, 'linked_data' => null);
}
/**
* Add the stack position parameter in the URL and do a redirect
* Exit the current script.
*/
public function redirect() {
header('location:'.$this->addStackParam($this->getUrl($this->currentPosition)), 301);
exit;
}
/**
* get the URL of a given position
* return null if the position is not valid
*/
public function getUrl($position) {
if (isset($this->stack[$position])) {
return $this->stack[$position]['url'];
} else {
return null;
}
}
/**
* get the Data of a given position
* return a reference of the data
*/
public function &getData($position) {
if (isset($this->stack[$position])) {
return $this->stack[$position]['data'];
} else {
return null;
}
}
/**
* Update the context of the current position
*/
public function storeCurrentData(&$data) {
$this->stack[$this->currentPosition]['data'] = $data;
}
/**
* store some data that need to be fixed in sub flow
* (for example the id of the parent object)
*/
public function storeLinkedData($data) {
$this->stack[$this->currentPosition]['linked_data'] = $data;
}
/**
* Update the context of the current position
*/
public function storePreviousData(&$data) {
$this->stack[$this->currentPosition]['previousData'] = $data;
}
/**
* Compute all linked data for every positions before the current one and return an array
* containing all keys / values
* Should be called in sub flow to fixed some data.
*
* Example: if you have tree pages: dad.php, mum.php and child.php
* when creating a "child" object from a "dad", the dad_id should be fixed
* but when creating a "child" object from a "mum", the mum_id should be fixed and a combo for choosing a dad should be displayed
*/
public function getLinkedData() {
$totalLinkedData = array();
for($i = 0; $i < $this->currentPosition; $i++) {
$linkedData = $this->stack[$i]['linked_data'];
if ($linkedData != null && count($linkedData) > 0) {
foreach($linkedData as $key => $value) {
$totalLinkedData[$key] = $value;
}
}
}
return $totalLinkedData;
}
/**
* Main method of the Stack class.
* Should be called on each page before any output as this method should do redirects.
*
* #param $handler StackHandler object that will be called at each step of the stack process
* Let the caller to be notified when something appens.
* #return the data
*/
public function initialise(StackHandler $handler) {
if (!isset($_GET['stack']) || !ctype_digit($_GET['stack'])) {
// no stack info, acces the page directly
$this->clear();
$this->add($this->getCurrentUrl()); //add the ?stack=<position number>
$this->storeLinkedData($handler->getLinkedData());
$this->redirect(); //do a redirect to the same page
} else {
// $_GET['stack'] is set and is a number
$position = $_GET['stack'];
if ($this->currentPosition == $position) {
// ok the user stay on the same page
// or just comme from the redirection
if (!empty($_POST['action'])) {
// user submit a form and need to do an action
if ($_POST['action'] == 'cancel') {
$currentData = array_pop($this->stack);
$this->currentPosition--;
$handler->onCancel($currentData);
// redirect to the next page with ?stack=<current position + 1>
$this->redirect();
} else {
// store the action for future use
$this->stack[$this->currentPosition]['action'] = $_POST['action'];
$currentData = $this->getData($this->currentPosition);
list($currentData, $nextUrl) = $handler->onAction($currentData, $_POST['action']);
// store current form for future use
$this->storeCurrentData($currentData);
// add the new page on the stack
$this->add($nextUrl);
// redirect to the next page with ?stack=<current position + 1>
$this->redirect();
}
} else if (isset($this->stack[$this->currentPosition]['action'])) {
// no action, and an action exists for this position
$currentData = $this->getData($this->currentPosition);
$action = $this->stack[$this->currentPosition]['action'];
if ($this->comeFromCancelledAction) {
//we return from a cancelled action
$currentData = $handler->onReturningFromCancelledAction($action, $currentData);
$this->comeFromCancelledAction = false;
} else {
$previousData = $this->getPreviousData();
if ($previousData != null) {
//we return from a sucessful action
$currentData = $handler->onReturningFromSuccesAction($action, $currentData, $previousData);
$this->resetPreviousData();
}
}
$this->storeCurrentData( $currentData );
}
$currentData = $this->getData($this->currentPosition);
if ($currentData == null) {
$currentData = $handler->getInitialData();
$this->storeCurrentData( $currentData );
}
return $currentData;
} else if ($this->getUrl($position) == $this->getCurrentUrl()) {
// seems that the user pressed the back or next button of the browser
// set the current position
$this->currentPosition = $position;
return $this->getData($position);
} else {
// the position does not exist or the url is incorrect
// redirect to the last known position
$this->redirect();
}
}
}
/**
* call this method after completing an action and need to redirect to the previous page.
* If you need to give some data to the previous action, use $dataForPreviousAction
*/
public function finishAction($dataForPreviousAction = null) {
$pop = array_pop($this->stack);
$this->currentPosition--;
$this->storePreviousData($dataForPreviousAction);
$this->redirect();
}
/* ----------------------------------------------------- */
/* PRIVATE METHODS */
/* ----------------------------------------------------- */
/**
* get the previous data for the current position
* used when a sub flow finish an action to give some data to the parent flow
*/
private function &getPreviousData() {
if (isset($this->stack[$this->currentPosition])) {
return $this->stack[$this->currentPosition]['previousData'];
} else {
return null;
}
}
/**
* get the current url without the stack parameter
*
* Attention: this method calls "basename" on PHP_SELF do strip the folder structure
* and assume that every pages are in the same directory.
*
* The "stack" parameter is removed from the query string
*
* Example: for the page "http://myserver.com/path/to/a.php?id=1&stack=2"
* PHP_SELF will be: /path/to/a.php
* QUERY_STRING wille be: id=1&stack=2
* This method will return: "a.php?id=1"
*/
private function getCurrentUrl() {
$basename = basename($_SERVER['PHP_SELF']);
if ($_SERVER['QUERY_STRING'] != '') {
return $basename.$this->removeQueryStringKey('?'.$_SERVER['QUERY_STRING'], 'stack');
} else {
return $basename;
}
}
/**
* add the "stack" parameter in an url
*/
private function addStackParam($url) {
return $url . (strpos($url, '?') === false ? '?' : '&') . 'stack=' . $this->currentPosition;
}
/**
* Usefull private method to remove a key=value from a query string.
*/
private function removeQueryStringKey($url, $key) {
$url = preg_replace('/(?:&|(\?))'.$key.'=[^&]*(?(1)&|)?/i', "$1", $url);
return $url != '?' ? $url : '';
}
/**
* reset the previous data so that the data are not used twice
*/
private function resetPreviousData() {
$this->stack[$this->currentPosition]['previousData'] = null;
}
}
Then define the abstract StackHandler class
abstract class StackHandler {
/**
* return the initial data to store for this current page
*/
public function &getInitialData() {
return null;
}
/**
* return an array containing the key/values that need to be fixed in sub flows
*/
public function getLinkedData() {
return null;
}
/**
* user ask to go to a sub page
*/
public function onAction(&$currentData, $action) {
$currentData = $_POST;
$nextUrl = $_POST['action'];
return array($currentData, $nextUrl);
}
public function onCancel(&$currentData) {
}
public function onReturningFromCancelledAction($action, &$currentData) {
}
public function onReturningFromSuccesAction($action, &$currentData, $previousData) {
}
}
Then add the following lines at the top of your pages. Adapt the handler it to fit your needs.
// be sure that a stack object exist in the session
if (!isset($_SESSION['stack'])) {
$_SESSION['stack'] = new Stack();
}
$myDad = $_SESSION['stack']->initialise(new DadStackHandler());
class DadStackHandler extends StackHandler {
/**
* return the initial data to store for this current page
*/
public function &getInitialData() {
if(! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])){
// update
$myDad = new Dad($_GET['id_dad']);
} else {
// creation
$myDad = new Dad();
}
return $myDad;
}
/**
* return an array containing the key/values that need to be fixed in sub flows
*/
public function getLinkedData() {
$linkedData = array();
if (! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])) {
$linkedData['id_dad'] = $_GET['id_dad'];
}
return $linkedData;
}
/**
* user ask to go to a sub page
*/
public function onAction(&$myDad, $action) {
//in order not to loose user inputs, save them in the current data
$myDad->name = $_POST['name'];
$nextUrl = null;
// find the next url based on the action name
if ($action == 'child') {
$nextUrl = 'child.php';
}
return array($myDad, $nextUrl);
}
public function onCancel(&$myDad) {
// probably nothing to do, leave the current data untouched
// or update current data
return $myDad;
}
public function onReturningFromCancelledAction($action, &$myDad) {
// probably nothing to do, leave the current data untouched
// called when returning from child.php
return $myDad;
}
public function onReturningFromSuccesAction($action, &$myDad, $newId) {
// update the id of the foreign field if needed
// or update the current data
// not a good example as in real life child should be a list and not a foreign key
// $myDad->childId = $newId;
$myDad->numberOfChildren++;
return $myDad;
}
}
...
if (user submit form and all input are correct) {
if ($myDad->save()) {
// the user finish an action, so we should redirect him to the previous one
if ($_SESSION['stack']->getCurrentPosition() > 0) {
$_SESSION['stack']->finishAction($myDad->idDad);
} else {
// default redirect, redirect to the same page in view more or redirect to a list page
}
}
}
I hope this could help others.
Google has been able to crawl to my website, but it is not able to index anything.
When I fetch as Google to my website, I get an error message:
Fatal Error: Call to a member function getcookieshouldbereceived() on a non-object in /var/www/magento/app/code/core/mage/core/controller/varien/action.php on line 497
I then opened action.php file to see what's wrong. In the file action.php line 497 is this :
if ($session->getCookieShouldBeReceived()) {
The paragraph containing line 497 is:
/**
* Retrieve action method name
*
* #param string $action
* #return string
*/
public function getActionMethodName($action)
{
return $action . 'Action';
}
/**
* Dispatch event before action
*
* #return void
*/
public function preDispatch()
{
if (!$this->getFlag('', self::FLAG_NO_CHECK_INSTALLATION)) {
if (!Mage::isInstalled()) {
$this->setFlag('', self::FLAG_NO_DISPATCH, true);
$this->_redirect('install');
return;
}
}
// Prohibit disabled store actions
if (Mage::isInstalled() && !Mage::app()->getStore()->getIsActive()) {
Mage::app()->throwStoreException();
}
if ($this->_rewrite()) {
return;
}
if (!$this->getFlag('', self::FLAG_NO_START_SESSION)) {
$checkCookie = in_array($this->getRequest()->getActionName(), $this->_cookieCheckActions)
&& !$this->getRequest()->getParam('nocookie', false);
$cookies = Mage::getSingleton('core/cookie')->get();
/** #var $session Mage_Core_Model_Session */
$session = Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace))->start();
if (empty($cookies)) {
if ($session->getCookieShouldBeReceived()) {
$this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true);
$session->unsCookieShouldBeReceived();
$session->setSkipSessionIdFlag(true);
} elseif ($checkCookie) {
if (isset($_GET[$session->getSessionIdQueryParam()]) && Mage::app()->getUseSessionInUrl()
&& $this->_sessionNamespace != Mage_Adminhtml_Controller_Action::SESSION_NAMESPACE
) {
$session->setCookieShouldBeReceived(true);
} else {
$this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true);
}
}
}
Could you help me figure out what is wrong ?
Thanks !!
i had same problem. I think when a bot visit the site, $session object is NULL, not react like a normal browser. I modified;
if ($session->getCookieShouldBeReceived()) {
to
if (is_object($session) && $session->getCookieShouldBeReceived()) {
and my problem is solved. Google indexed my site normally.I know, its not a good thing to modifying core code but i'm new about magento. Is someone know how to make this change whithout change core code?
How to Logout action performed in my website who are login via facebook
my controller:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Main extends CI_Controller {
public function Main(){
parent::__construct();
parse_str( $_SERVER['QUERY_STRING'], $_REQUEST );
$CI = & get_instance();
$CI->config->load("facebook",TRUE);
$config = $CI->config->item('facebook');
$this->load->library('Facebook', $config);
}
function index(){
// Try to get the user's id on Facebook
$userId = $this->facebook->getUser();
// If user is not yet authenticated, the id will be zero
if($userId == 0){
// Generate a login url
$data['url'] = $this->facebook->getLoginUrl(array('scope'=>'email'));
$this->load->view('main_index', $data);
} else {
// Get user's data and print it
$user = $this->facebook->api('/me');
print_r($user);
}
}
}
?>
my view
Click here to login
config/facebook.php
<?php
$config['appId'] = 'xxxxxxxxxxxxxx';//i have my id like 1411574xxxxxxxxxxxxxx
$config['secret'] = 'xxxxxxxxxx';// i have my id like 2f3917995d2024xxxxxxxxxxxxxx
my library :Facebook.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
require_once "base_facebook.php";
/**
* Extends the BaseFacebook class with the intent of using
* PHP sessions to store user ids and access tokens.
*/
class Facebook extends BaseFacebook
{
const FBSS_COOKIE_NAME = 'fbss';
// We can set this to a high number because the main session
// expiration will trump this.
const FBSS_COOKIE_EXPIRE = 31556926; // 1 year
// Stores the shared session ID if one is set.
protected $sharedSessionID;
/**
* Identical to the parent constructor, except that
* we start a PHP session to store the user ID and
* access token if during the course of execution
* we discover them.
*
* #param Array $config the application configuration. Additionally
* accepts "sharedSession" as a boolean to turn on a secondary
* cookie for environments with a shared session (that is, your app
* shares the domain with other apps).
* #see BaseFacebook::__construct in facebook.php
*/
public function __construct($config) {
if (!session_id()) {
session_start();
}
parent::__construct($config);
if (!empty($config['sharedSession'])) {
$this->initSharedSession();
}
}
protected static $kSupportedKeys =
array('state', 'code', 'access_token', 'user_id');
protected function initSharedSession() {
$cookie_name = $this->getSharedSessionCookieName();
if (isset($_COOKIE[$cookie_name])) {
$data = $this->parseSignedRequest($_COOKIE[$cookie_name]);
if ($data && !empty($data['domain']) &&
self::isAllowedDomain($this->getHttpHost(), $data['domain'])) {
// good case
$this->sharedSessionID = $data['id'];
return;
}
// ignoring potentially unreachable data
}
// evil/corrupt/missing case
$base_domain = $this->getBaseDomain();
$this->sharedSessionID = md5(uniqid(mt_rand(), true));
$cookie_value = $this->makeSignedRequest(
array(
'domain' => $base_domain,
'id' => $this->sharedSessionID,
)
);
$_COOKIE[$cookie_name] = $cookie_value;
if (!headers_sent()) {
$expire = time() + self::FBSS_COOKIE_EXPIRE;
setcookie($cookie_name, $cookie_value, $expire, '/', '.'.$base_domain);
} else {
// #codeCoverageIgnoreStart
self::errorLog(
'Shared session ID cookie could not be set! You must ensure you '.
'create the Facebook instance before headers have been sent. This '.
'will cause authentication issues after the first request.'
);
// #codeCoverageIgnoreEnd
}
}
/**
* Provides the implementations of the inherited abstract
* methods. The implementation uses PHP sessions to maintain
* a store for authorization codes, user ids, CSRF states, and
* access tokens.
*/
protected function setPersistentData($key, $value) {
if (!in_array($key, self::$kSupportedKeys)) {
self::errorLog('Unsupported key passed to setPersistentData.');
return;
}
$session_var_name = $this->constructSessionVariableName($key);
$_SESSION[$session_var_name] = $value;
}
protected function getPersistentData($key, $default = false) {
if (!in_array($key, self::$kSupportedKeys)) {
self::errorLog('Unsupported key passed to getPersistentData.');
return $default;
}
$session_var_name = $this->constructSessionVariableName($key);
return isset($_SESSION[$session_var_name]) ?
$_SESSION[$session_var_name] : $default;
}
protected function clearPersistentData($key) {
if (!in_array($key, self::$kSupportedKeys)) {
self::errorLog('Unsupported key passed to clearPersistentData.');
return;
}
$session_var_name = $this->constructSessionVariableName($key);
unset($_SESSION[$session_var_name]);
}
protected function clearAllPersistentData() {
foreach (self::$kSupportedKeys as $key) {
$this->clearPersistentData($key);
}
if ($this->sharedSessionID) {
$this->deleteSharedSessionCookie();
}
}
protected function deleteSharedSessionCookie() {
$cookie_name = $this->getSharedSessionCookieName();
unset($_COOKIE[$cookie_name]);
$base_domain = $this->getBaseDomain();
setcookie($cookie_name, '', 1, '/', '.'.$base_domain);
}
protected function getSharedSessionCookieName() {
return self::FBSS_COOKIE_NAME . '_' . $this->getAppId();
}
protected function constructSessionVariableName($key) {
$parts = array('fb', $this->getAppId(), $key);
if ($this->sharedSessionID) {
array_unshift($parts, $this->sharedSessionID);
}
return implode('_', $parts);
}
}
my library base_facebook.php
my library fb_ca_chain_bundle.crt
now i am log in successfully but unable to logout how to log out implement in this please
Try this link
$params = array( 'next' => 'https://www.myapp.com/after_logout' );
$facebook->getLogoutUrl($params); // $params is optional.
use this in view and access the facebook
I am using SimpleBrowser that is a part of SimpleTest PHP framework.
The idea is to imitate user interactions with the website and record returned HTML code into a file for further comparison. But something goes wrong here as empty HTML is sometimes returned.
getTransportError() returns Nothing fetched
It happens in completely random places and I can't use back() function because most pages are submitted forms.
require_once('simpletest/browser.php');
class TesterBrowser extends SimpleBrowser
{
/**
* Test the page against the reference. If reference is missing, is it created
* Uses md5 checksum to check if files are identical
*
* #param string $forcename Optional. Substitude autogenerated filename.
* #param boolean $forceRef Optional. Force file to be saved as the reference
*
* #access public
*
* #return void
*/
public function testPage($forcename = "")
{
//who called me?
//$callers=debug_backtrace();
//$whocalledme = $callers[1]['function'];
//get the current source
$html = $this->getContent();
//generate filename
$filename = empty($forcename) ? preg_replace('/[^\w\-'. ''. ']+/u', '-', $this->getUrl()) : $forcename;
$filename .= ".html";
//is there a gauge?
if(file_exists("ref/".$filename) && filesize(dirname(__FILE__)."/ref/".$filename) > 0)
{
//is there a difference
file_put_contents(dirname(__FILE__)."/actual/".$filename, $html);
if(filesize(dirname(__FILE__)."/actual/".$filename) == 0)
{
return false;
}
if(md5_file(dirname(__FILE__)."/actual/".$filename) != md5_file(dirname(__FILE__)."/ref/".$filename))
{
echo $this->getUrl() . " (" . $filename . ") has changed \r\n";
}
}
else
{
file_put_contents(dirname(__FILE__)."/ref/".$filename, $html);
if(filesize(dirname(__FILE__)."/ref/".$filename) == 0)
{
return false;
}
}
return true;
}
/**
* Output the string to the terminal
*
* #param mixed $string String to output
*
* #access public
*
* #return void
*/
public function output($string)
{
echo date("d-m-Y H:i:s") . " - $string... \r\n";
//update date so that it will be the same on every page
exec('date -s "24 JUN 2013 10:00:00"');
}
/**
* Restore the server date using external NTP server
*
* #access public
*
* #return void
*/
public function restoreDate(){
$this->output("Restoring the date&time from NTP server");
exec("ntpdate 0.uk.pool.ntp.org");
exec("hwclock -systohc");
}
}
And the way tests are performed:
class Tester
{
public $browser = null;
const BASEURL = "http://ticketing/";
function __construct(){
$this->browser = new TesterBrowser();
$this->browser->setConnectionTimeout(180);
//get the list of class method to be run
$methods = array();
foreach(get_class_methods($this) as $var)
{
if(0 === strpos($var, 'test')) //they all start with test
{
$methods[] = $var;
}
}
$methods[] = "cleanUp";
//now we need to run these methods
foreach($methods as $m){
while($this->$m() == false){
$this->browser->output("Empty page, trying again");
sleep(5);
}
}
}
//index page
function testGetIndexPage()
{
$this->browser->output("Getting index page");
$this->browser->get(self::BASEURL);
return $this->browser->testPage();
}
//try to enter wrong password
function testWrongPassword()
{
$this->browser->output("Entering wrong credentials");
$this->browser->setField("username", "wrong");
$this->browser->setField("password", "wrong");
$this->browser->clickSubmitByName("submit");
return $this->browser->testPage("wrong-credentials");
}
//Delete ticket though admin
function testDeleteTicketThroughAdmin()
{
$this->browser->output("Deleting the ticket through admin page");
$this->browser->setField("bulk[]", "375341");
$this->browser->setField("bulkaction", "delete");
$this->browser->clickSubmit("Do Action");
return $this->browser->testPage("deleted-ticket-admin");
}
//Restore the date
function cleanUp()
{
$this->browser->restoreDate();
return true;
}
}
$tester = new Tester();
There are of course much more test performed and this is a stripped version.
I have googled a lot about this problem, there seems to be no adequate documentation whatsoever.
Solved. It was a timeout issue although for some reason no adequate error message is implemented.
$this->browser->setConnectionTimeout(180);