2013-07-15 13:58:09 +02:00
|
|
|
<?php
|
|
|
|
// {{{ICINGA_LICENSE_HEADER}}}
|
|
|
|
// {{{ICINGA_LICENSE_HEADER}}}
|
|
|
|
|
|
|
|
namespace Icinga\Web;
|
|
|
|
|
2014-04-24 10:13:47 +02:00
|
|
|
use Zend_Form;
|
2014-07-16 09:54:58 +02:00
|
|
|
use Icinga\Web\Session;
|
2014-04-24 10:13:47 +02:00
|
|
|
use Icinga\Web\Form\Decorator\HelpText;
|
2014-07-10 11:13:45 +02:00
|
|
|
use Icinga\Web\Form\Decorator\ElementWrapper;
|
2014-04-24 10:13:47 +02:00
|
|
|
use Icinga\Web\Form\InvalidCSRFTokenException;
|
2013-07-15 13:58:09 +02:00
|
|
|
|
2013-07-15 14:32:18 +02:00
|
|
|
/**
|
2013-08-12 11:23:01 +02:00
|
|
|
* Base class for forms providing CSRF protection, confirmation logic and auto submission
|
2013-07-15 14:32:18 +02:00
|
|
|
*/
|
2013-08-26 16:56:23 +02:00
|
|
|
class Form extends Zend_Form
|
2013-07-15 13:58:09 +02:00
|
|
|
{
|
2013-07-24 10:56:41 +02:00
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* The view script to use when rendering this form
|
2013-08-21 11:02:53 +02:00
|
|
|
*
|
2013-08-12 11:23:01 +02:00
|
|
|
* @var string
|
2013-07-24 10:56:41 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
protected $viewScript;
|
2013-07-24 10:56:41 +02:00
|
|
|
|
2013-08-26 15:06:07 +02:00
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Whether this form should NOT add random generated "challenge" tokens that are associated with the user's current
|
|
|
|
* session in order to prevent Cross-Site Request Forgery (CSRF). It is the form's responsibility to verify the
|
|
|
|
* existence and correctness of this token
|
2013-08-26 15:06:07 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @var bool
|
2013-08-26 15:06:07 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
protected $tokenDisabled = false;
|
2013-09-11 17:19:18 +02:00
|
|
|
|
2013-07-24 10:56:41 +02:00
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Name of the CSRF token element
|
2013-08-12 11:23:01 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @var string
|
2013-07-24 10:56:41 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
protected $tokenElementName = 'CSRFToken';
|
2013-09-11 17:19:18 +02:00
|
|
|
|
2013-07-18 10:32:53 +02:00
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Set the view script to use when rendering this form
|
2013-08-12 11:23:01 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @param string $viewScript The view script to use
|
2013-08-21 11:02:53 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @return self
|
2013-07-18 10:32:53 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function setViewScript($viewScript)
|
2013-07-16 15:39:47 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
$this->viewScript = $viewScript;
|
|
|
|
return $this;
|
2013-07-15 13:58:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Return the view script being used when rendering this form
|
|
|
|
*
|
|
|
|
* @return string
|
2013-07-15 13:58:09 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function getViewScript()
|
2013-08-27 14:37:22 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
return $this->viewScript;
|
2013-08-27 14:37:22 +02:00
|
|
|
}
|
2013-07-15 13:58:09 +02:00
|
|
|
|
2013-08-27 14:27:31 +02:00
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Disable CSRF counter measure and remove its field if already added
|
2013-08-27 14:27:31 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @param bool $disabled Set true in order to disable CSRF protection for this form, otherwise false
|
2013-08-27 14:27:31 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @return self
|
2013-08-27 14:27:31 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function setTokenDisabled($disabled = true)
|
2013-08-27 14:27:31 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
$this->tokenDisabled = (bool) $disabled;
|
|
|
|
|
|
|
|
if ($disabled && $this->getElement($this->tokenElementName) !== null) {
|
|
|
|
$this->removeElement($this->tokenElementName);
|
2013-08-27 14:27:31 +02:00
|
|
|
}
|
2014-04-24 10:13:47 +02:00
|
|
|
|
2014-07-10 11:13:45 +02:00
|
|
|
return $this;
|
2013-08-27 14:27:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Return whether CSRF counter measures are disabled for this form
|
2013-08-27 14:27:31 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @return bool
|
2013-08-27 14:27:31 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function getTokenDisabled()
|
2013-08-27 14:27:31 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
return $this->tokenDisabled;
|
2013-08-27 14:27:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Set the name to use for the CSRF element
|
|
|
|
*
|
|
|
|
* @param string $name The name to set
|
2013-08-27 14:27:31 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @return self
|
2013-08-27 14:27:31 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function setTokenElementName($name)
|
2013-08-27 14:27:31 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
$this->tokenElementName = $name;
|
|
|
|
return $this;
|
2013-08-27 14:27:31 +02:00
|
|
|
}
|
|
|
|
|
2013-07-18 10:32:53 +02:00
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Return the name of the CSRF element
|
2013-08-12 11:23:01 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @return string
|
2013-07-18 10:32:53 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function getTokenElementName()
|
2013-07-18 10:32:53 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
return $this->tokenElementName;
|
2013-07-18 10:32:53 +02:00
|
|
|
}
|
|
|
|
|
2013-08-05 11:05:11 +02:00
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Create and return the elements to add to this form
|
|
|
|
*
|
|
|
|
* Intended to be implemented by concrete form classes.
|
|
|
|
*
|
|
|
|
* @return array
|
2013-08-05 11:05:11 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function createElements()
|
2013-08-05 11:05:11 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
return array();
|
2013-08-05 11:05:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Add a new element
|
|
|
|
*
|
|
|
|
* Additionally, all structural form element decorators by Zend are replaced with our own ones.
|
|
|
|
*
|
|
|
|
* @param string|Zend_Form_Element $element String element type, or an object of type Zend_Form_Element
|
|
|
|
* @param string $name The name of the element to add if $element is a string
|
|
|
|
* @param array $options The options for the element if $element is a string
|
|
|
|
*
|
|
|
|
* @return self
|
2013-08-12 11:23:01 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @see Zend_Form::addElement()
|
2013-08-05 11:05:11 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function addElement($element, $name = null, $options = null)
|
2013-08-05 11:05:11 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
parent::addElement($element, $name, $options);
|
|
|
|
$el = $name !== null ? $this->getElement($name) : $element;
|
2013-08-05 11:05:11 +02:00
|
|
|
|
2014-07-10 11:13:45 +02:00
|
|
|
if ($el) {
|
|
|
|
if (strpos(strtolower(get_class($el)), 'hidden') !== false) {
|
|
|
|
$el->setDecorators(array('ViewHelper'));
|
|
|
|
} else {
|
|
|
|
$el->removeDecorator('HtmlTag');
|
|
|
|
$el->removeDecorator('Label');
|
|
|
|
$el->removeDecorator('DtDdWrapper');
|
|
|
|
$el->addDecorator(new ElementWrapper());
|
|
|
|
$el->addDecorator(new HelpText());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
2013-08-05 11:05:11 +02:00
|
|
|
}
|
|
|
|
|
2014-07-18 09:43:03 +02:00
|
|
|
/**
|
|
|
|
* Add CSRF counter measure field to this form
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
*/
|
|
|
|
public function addCsrfToken()
|
|
|
|
{
|
|
|
|
if (false === $this->tokenDisabled && $this->getElement($this->tokenElementName) === null) {
|
|
|
|
$this->addElement(
|
|
|
|
'hidden',
|
|
|
|
$this->tokenElementName,
|
|
|
|
array(
|
|
|
|
'value' => $this->generateCsrfToken()
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-08-26 15:06:07 +02:00
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Load the default decorators
|
|
|
|
*
|
|
|
|
* Overwrites Zend_Form::loadDefaultDecorators to avoid having the HtmlTag-Decorator added
|
2013-08-26 15:06:07 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @return self
|
2013-08-26 15:06:07 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
public function loadDefaultDecorators()
|
2013-08-26 15:06:07 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
if ($this->loadDefaultDecoratorsIsDisabled()) {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
$decorators = $this->getDecorators();
|
|
|
|
if (empty($decorators)) {
|
|
|
|
if ($this->viewScript) {
|
|
|
|
$this->addDecorator('ViewScript', array('viewScript' => $this->viewScript));
|
|
|
|
} else {
|
|
|
|
$this->addDecorator('FormElements')
|
|
|
|
//->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form'))
|
|
|
|
->addDecorator('Form');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
2013-08-26 15:06:07 +02:00
|
|
|
}
|
|
|
|
|
2013-07-15 13:58:09 +02:00
|
|
|
/**
|
|
|
|
* Generate a new (seed, token) pair
|
2013-08-12 11:23:01 +02:00
|
|
|
*
|
2014-04-24 10:13:47 +02:00
|
|
|
* @return string
|
2013-07-24 10:56:41 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
protected function generateCsrfToken()
|
2013-07-24 10:56:41 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
$seed = mt_rand();
|
2014-07-17 13:15:42 +02:00
|
|
|
$hash = hash('sha256', Session::getSession()->getId() . $seed);
|
2014-07-10 11:13:45 +02:00
|
|
|
return sprintf('%s|%s', $seed, $hash);
|
2013-07-24 10:56:41 +02:00
|
|
|
}
|
2013-09-03 18:43:17 +02:00
|
|
|
|
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Test the submitted data for a correct CSRF token
|
2013-09-03 18:43:17 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @param array $requestData The POST data sent by the user
|
2013-09-03 18:43:17 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @throws InvalidCSRFTokenException When CSRF Validation fails
|
2013-09-03 18:43:17 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
protected function assertValidCsrfToken(array $requestData)
|
2013-09-03 18:43:17 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
if (false === $this->tokenDisabled) {
|
|
|
|
if (false === isset($requestData[$this->tokenElementName])
|
|
|
|
|| false === $this->isValidCsrfToken($requestData[$this->tokenElementName])
|
|
|
|
) {
|
|
|
|
throw new InvalidCSRFTokenException();
|
2014-04-24 10:13:47 +02:00
|
|
|
}
|
2013-09-03 18:43:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-10 11:13:45 +02:00
|
|
|
* Check whether the given value is a valid CSRF token for the current session
|
2013-09-03 18:43:17 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @param string $token Value from the CSRF form element
|
2014-04-24 10:13:47 +02:00
|
|
|
*
|
2014-07-10 11:13:45 +02:00
|
|
|
* @return bool
|
2013-09-03 18:43:17 +02:00
|
|
|
*/
|
2014-07-10 11:13:45 +02:00
|
|
|
protected function isValidCsrfToken($token)
|
2013-09-03 18:43:17 +02:00
|
|
|
{
|
2014-07-10 11:13:45 +02:00
|
|
|
if (strpos($token, '|') === false) {
|
|
|
|
return false;
|
2013-09-03 18:43:17 +02:00
|
|
|
}
|
|
|
|
|
2014-07-10 11:13:45 +02:00
|
|
|
list($seed, $hash) = explode('|', $token);
|
|
|
|
|
|
|
|
if (false === is_numeric($seed)) {
|
|
|
|
return false;
|
2013-09-03 18:43:17 +02:00
|
|
|
}
|
2014-04-24 10:13:47 +02:00
|
|
|
|
2014-07-17 13:15:42 +02:00
|
|
|
return $hash === hash('sha256', Session::getSession()->getId() . $seed);
|
2013-09-03 18:43:17 +02:00
|
|
|
}
|
2013-07-15 13:58:09 +02:00
|
|
|
}
|