Make our anti CSRF logic being a form element

refs #5525
This commit is contained in:
Johannes Meyer 2014-08-12 14:43:10 +02:00
parent fe63ce664f
commit e7da9c0a00
2 changed files with 93 additions and 65 deletions

View File

@ -8,10 +8,9 @@ use LogicException;
use Zend_Form;
use Zend_View_Interface;
use Icinga\Application\Icinga;
use Icinga\Web\Session;
use Icinga\Web\Form\Decorator\HelpText;
use Icinga\Web\Form\Decorator\ElementWrapper;
use Icinga\Web\Form\InvalidCSRFTokenException;
use Icinga\Web\Form\Element\CsrfCounterMeasure;
/**
* Base class for forms providing CSRF protection, confirmation logic and auto submission
@ -139,7 +138,7 @@ class Form extends Zend_Form
}
$this->addElements($this->createElements($formData));
$this->addCsrfToken()->addSubmitButton();
$this->addCsrfCounterMeasure()->addSubmitButton();
$this->created = true;
}
@ -209,17 +208,12 @@ class Form extends Zend_Form
*
* @return self
*/
public function addCsrfToken()
public function addCsrfCounterMeasure()
{
if (false === $this->tokenDisabled && $this->getElement($this->tokenElementName) === null) {
$this->addElement(
'hidden',
$this->tokenElementName,
array(
'ignore' => true,
'value' => $this->generateCsrfToken()
)
);
$element = new CsrfCounterMeasure($this->tokenElementName);
$element->setDecorators(array('ViewHelper'));
$this->addElement($element);
}
return $this;
@ -293,7 +287,6 @@ class Form extends Zend_Form
public function isValid($formData)
{
if ($this->isComplete($formData)) {
$this->assertValidCsrfToken($formData);
return parent::isValid($formData);
}
@ -352,56 +345,4 @@ class Form extends Zend_Form
$this->create();
return parent::render($view);
}
/**
* Generate a new (seed, token) pair
*
* @return string
*/
protected function generateCsrfToken()
{
$seed = mt_rand();
$hash = hash('sha256', Session::getSession()->getId() . $seed);
return sprintf('%s|%s', $seed, $hash);
}
/**
* Test the submitted data for a correct CSRF token
*
* @param array $requestData The data sent by the user
*
* @throws InvalidCSRFTokenException When CSRF Validation fails
*/
protected function assertValidCsrfToken(array $requestData)
{
if (false === $this->tokenDisabled) {
if (false === isset($requestData[$this->tokenElementName])
|| false === $this->isValidCsrfToken($requestData[$this->tokenElementName])
) {
throw new InvalidCSRFTokenException();
}
}
}
/**
* Check whether the given value is a valid CSRF token for the current session
*
* @param string $token Value from the CSRF form element
*
* @return bool
*/
protected function isValidCsrfToken($token)
{
if (strpos($token, '|') === false) {
return false;
}
list($seed, $hash) = explode('|', $token);
if (false === is_numeric($seed)) {
return false;
}
return $hash === hash('sha256', Session::getSession()->getId() . $seed);
}
}

View File

@ -0,0 +1,87 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Form\Element;
use Zend_Form_Element_Xhtml;
use Icinga\Web\Session;
use Icinga\Web\Form\InvalidCSRFTokenException;
/**
* CSRF counter measure element
*
* You must not set a value to successfully use this element, just give it a name and you're good to go.
*/
class CsrfCounterMeasure extends Zend_Form_Element_Xhtml
{
/**
* Default form view helper to use for rendering
*
* @var string
*/
public $helper = 'formHidden';
/**
* Initialize this form element
*/
public function init()
{
$this->setRequired(true); // Not requiring this element would not make any sense
$this->setIgnore(true); // We do not want this element's value being retrieved by Form::getValues()
$this->setValue($this->generateCsrfToken());
}
/**
* Check whether $value is a valid CSRF token
*
* @param string $value The value to check
* @param mixed $context Context to use
*
* @return bool True, in case the CSRF token is valid
*
* @throws InvalidCSRFTokenException In case the CSRF token is not valid
*/
public function isValid($value, $context = null)
{
if (parent::isValid($value, $context) && $this->isValidCsrfToken($value)) {
return true;
}
throw new InvalidCSRFTokenException();
}
/**
* Check whether the given value is a valid CSRF token for the current session
*
* @param string $token The CSRF token
*
* @return bool
*/
protected function isValidCsrfToken($token)
{
if (strpos($token, '|') === false) {
return false;
}
list($seed, $hash) = explode('|', $token);
if (false === is_numeric($seed)) {
return false;
}
return $hash === hash('sha256', Session::getSession()->getId() . $seed);
}
/**
* Generate a new (seed, token) pair
*
* @return string
*/
protected function generateCsrfToken()
{
$seed = mt_rand();
$hash = hash('sha256', Session::getSession()->getId() . $seed);
return sprintf('%s|%s', $seed, $hash);
}
}