In my application, I needed to disable CSRF token validation for certain actions. This is a hack I wrote to accomplish said task.
As far as I can tell, CHttpRequest "happens" before CUrlManager, so route information is not available in a proper fashion. Because this application uses certain CUrlManager settings that make URLs look like "controller/action", this works. There probably are nicer ways of doing this, but this might be useful to someone.
One such way would be instantiating a “temporary” CUrlManager for finding out the proper route, but that’d be hack-ish as well.
The component:
<?php
class HttpRequest extends CHttpRequest
{
public $noCsrfValidationRoutes = array();
protected function normalizeRequest()
{
parent::normalizeRequest();
$route = implode('/', array_slice(explode('/', Yii::app()->getUrlManager()->parseUrl($this)), 0, 2));
if($this->enableCsrfValidation && array_search($route, $this->noCsrfValidationRoutes) !== false)
Yii::app()->detachEventHandler('onbeginRequest',array($this,'validateCsrfToken'));
}
}
?>
The config snippet
...
'request'=>array(
'class'=>'HttpRequest',
'enableCsrfValidation'=>true,
'noCsrfValidationRoutes'=>array('some/route', 'some/otherroute'),
'enableCookieValidation'=>true,
),
...
It’s better to do check in validateCsrfToken function only on POST requests, not at all requests. Like this:
public function validateCsrfToken($event)
{
// only validate POST requests
if ($this->getIsPostRequest())
{
$route = Yii::app()->urlManager->parseUrl(Yii::app()->getRequest());
if(in_array($route, $this->dontCheckCsrf)) return true;
...
I looked through the code above and modified/optimised it a bit, so you can try it out:
<?php
class HttpRequest extends CHttpRequest {
public $noCsrfValidationRoutes = array();
/**
* Normalizes the request data.
* This method strips off slashes in request data if get_magic_quotes_gpc() returns true.
* It also performs CSRF validation if {@link enableCsrfValidation} is true.
*/
protected function normalizeRequest()
{
parent::normalizeRequest();
if ($this->getIsPostRequest() && $this->enableCsrfValidation && !$this->checkCurrentRoute())
Yii::app()->detachEventHandler('onbeginRequest', array($this, 'validateCsrfToken'));
}
/**
* Checks if current route should be validated by validateCsrfToken()
* @return boolean true if current route should be validated
*/
private function checkCurrentRoute() {
foreach ($this->noCsrfValidationRoutes as $checkPath)
{
if (($pos = strpos($checkPath, "*")) !== false)
{
$checkPath = substr($checkPath, 0, $pos - 1);
if (strpos($this->pathInfo, $checkPath) == 0)
return false;
} elseif ($this->pathInfo === $checkPath)
return false;
}
return true;
}
}
Also, as an option, you may overwrite validateCsrfToken($event) instead of normalizeRequest() and put $this->checkCurrentRoute() to the first check, e.g.
public function validateCsrfToken($event)
{
if($this->getIsPostRequest() && $this->checkCurrentRoute())
{
parental code...
}
}
Not sure at the moment what would be faster and if there’s any significant difference at all.
If I disable CSRF token validation application-wise, POST is ok. I think that the credt card payment gateway that should send me a POST request does someting strange I cannot understand.
The noCsrfValidationRoutes configuration array should contain the URL in final form and not in the form of route/action form, so if you have URL Manager components configured to translate request you have to put in the HttpRequest component configuration array in the final form.
In my case I needed simply to change these lines of code: