2013-07-15 13:58:09 +02:00
|
|
|
<?php
|
|
|
|
// {{{ICINGA_LICENSE_HEADER}}}
|
|
|
|
/**
|
|
|
|
* Icinga 2 Web - Head for multiple monitoring frontends
|
|
|
|
* Copyright (C) 2013 Icinga Development Team
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
|
|
|
* @author Icinga Development Team <info@icinga.org>
|
|
|
|
*/
|
|
|
|
// {{{ICINGA_LICENSE_HEADER}}}
|
|
|
|
|
|
|
|
namespace Icinga\Web;
|
|
|
|
|
|
|
|
use Icinga\Exception\ProgrammingError;
|
2013-07-16 15:39:47 +02:00
|
|
|
use Zend_View_Interface;
|
2013-07-15 13:58:09 +02:00
|
|
|
|
2013-07-15 14:32:18 +02:00
|
|
|
/**
|
|
|
|
* Class Form
|
|
|
|
*
|
|
|
|
* How forms are used in Icinga 2 Web
|
|
|
|
*/
|
2013-07-15 13:58:09 +02:00
|
|
|
abstract class Form extends \Zend_Form
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* The form's request object
|
|
|
|
* @var null
|
|
|
|
*/
|
|
|
|
private $request = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private $tokenDisabled = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Name of the CSRF token element (used to create non-colliding hashes)
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $tokenElementName = 'CSRFToken';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Time to live for the CRSF token
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
private $tokenTimeout = 300;
|
|
|
|
|
2013-07-18 10:32:53 +02:00
|
|
|
/**
|
|
|
|
* Flag to indicate that form is already build
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private $created = false;
|
|
|
|
|
2013-07-15 13:58:09 +02:00
|
|
|
/**
|
|
|
|
* @see Zend_Form::init
|
|
|
|
*/
|
|
|
|
public function init()
|
|
|
|
{
|
|
|
|
if (!$this->tokenDisabled) {
|
|
|
|
$this->initCsrfToken();
|
|
|
|
}
|
2013-07-16 15:39:47 +02:00
|
|
|
}
|
|
|
|
|
2013-07-18 10:32:53 +02:00
|
|
|
/**
|
|
|
|
* Render the form to html
|
|
|
|
* @param Zend_View_Interface $view
|
|
|
|
* @return string
|
|
|
|
*/
|
2013-07-16 15:39:47 +02:00
|
|
|
public function render(Zend_View_Interface $view = null)
|
|
|
|
{
|
2013-07-18 10:32:53 +02:00
|
|
|
// Elements must be there to render the form
|
|
|
|
$this->buildForm();
|
2013-07-16 15:39:47 +02:00
|
|
|
return parent::render($view);
|
2013-07-15 13:58:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add elements to this form (used by extending classes)
|
|
|
|
*/
|
2013-07-15 14:32:18 +02:00
|
|
|
abstract protected function create();
|
2013-07-15 13:58:09 +02:00
|
|
|
|
2013-07-18 13:46:12 +02:00
|
|
|
/**
|
|
|
|
* Method called before validation
|
|
|
|
*/
|
|
|
|
protected function preValid(array $data)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Method called after validation
|
|
|
|
* @param array $data
|
|
|
|
* @param bool &$isValid
|
|
|
|
*/
|
|
|
|
protected function postValid(array $data, &$isValid)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-07-15 13:58:09 +02:00
|
|
|
/**
|
2013-07-15 14:32:18 +02:00
|
|
|
* Setter for request
|
|
|
|
* @param \Zend_Controller_Request_Abstract $request The request object of a session
|
2013-07-15 13:58:09 +02:00
|
|
|
*/
|
2013-07-15 14:32:18 +02:00
|
|
|
public function setRequest(\Zend_Controller_Request_Abstract $request)
|
2013-07-15 13:58:09 +02:00
|
|
|
{
|
|
|
|
$this->request = $request;
|
|
|
|
}
|
|
|
|
|
2013-07-15 14:32:18 +02:00
|
|
|
/**
|
|
|
|
* Getter for request
|
|
|
|
* @return \Zend_Controller_Request_Abstract
|
|
|
|
*/
|
|
|
|
public function getRequest()
|
|
|
|
{
|
|
|
|
return $this->request;
|
|
|
|
}
|
|
|
|
|
2013-07-18 10:32:53 +02:00
|
|
|
/**
|
|
|
|
* Triggers form creation
|
|
|
|
*/
|
|
|
|
public function buildForm()
|
|
|
|
{
|
|
|
|
if ($this->created === false) {
|
|
|
|
$this->create();
|
|
|
|
|
|
|
|
// Empty action if not safe
|
|
|
|
if (!$this->getAction() && $this->getRequest()) {
|
|
|
|
$this->setAction($this->getRequest()->getRequestUri());
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->created = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-15 14:32:18 +02:00
|
|
|
/**
|
|
|
|
* Test if data from array or request is valid
|
|
|
|
*
|
|
|
|
* If $data is null, internal request is selected to test validity
|
|
|
|
*
|
|
|
|
* @param null|\Zend_Controller_Request_Abstract|array $data
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isValid($data)
|
|
|
|
{
|
2013-07-18 13:46:12 +02:00
|
|
|
$checkData = null;
|
2013-07-15 14:32:18 +02:00
|
|
|
|
2013-07-18 10:32:53 +02:00
|
|
|
// Elements must be there to validate
|
|
|
|
$this->buildForm();
|
|
|
|
|
2013-07-15 14:32:18 +02:00
|
|
|
if ($data === null) {
|
2013-07-18 13:46:12 +02:00
|
|
|
$checkData = $this->getRequest()->getParams();
|
2013-07-15 14:32:18 +02:00
|
|
|
} elseif ($data instanceof \Zend_Controller_Request_Abstract) {
|
2013-07-18 13:46:12 +02:00
|
|
|
$checkData = $data->getParams();
|
2013-07-15 14:32:18 +02:00
|
|
|
} else {
|
2013-07-18 13:46:12 +02:00
|
|
|
$checkData = $data;
|
2013-07-15 14:32:18 +02:00
|
|
|
}
|
|
|
|
|
2013-07-18 13:46:12 +02:00
|
|
|
$this->preValid($checkData);
|
|
|
|
$checkValue = parent::isValid($checkData);
|
|
|
|
$this->postValid($checkData, $checkValue);
|
|
|
|
|
|
|
|
return $checkValue;
|
2013-07-15 14:32:18 +02:00
|
|
|
}
|
|
|
|
|
2013-07-15 13:58:09 +02:00
|
|
|
/**
|
|
|
|
* Enable CSRF counter measure
|
|
|
|
*/
|
|
|
|
final public function enableCsrfToken()
|
|
|
|
{
|
|
|
|
$this->tokenDisabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disable CSRF counter measure and remove its field if already added
|
|
|
|
*/
|
|
|
|
final public function disableCsrfToken()
|
|
|
|
{
|
|
|
|
$this->tokenDisabled = true;
|
|
|
|
$this->removeElement($this->tokenElementName);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add CSRF counter measure field to form
|
|
|
|
*/
|
|
|
|
final public function initCsrfToken()
|
|
|
|
{
|
|
|
|
if ($this->tokenDisabled || $this->getElement($this->tokenElementName)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
list($seed, $token) = $this->generateCsrfToken($this->tokenTimeout);
|
|
|
|
|
2013-07-15 14:32:18 +02:00
|
|
|
$this->addElement(
|
|
|
|
'hidden',
|
|
|
|
$this->tokenElementName,
|
|
|
|
array(
|
|
|
|
'value' => sprintf('%s\|/%s', $seed, $token),
|
|
|
|
'decorators' => array('ViewHelper')
|
2013-07-15 13:58:09 +02:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether the form's CSRF token-field has a valid value
|
|
|
|
*
|
|
|
|
* @param int $maxAge Max allowed token age
|
|
|
|
* @param string $sessionId A specific session id
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
final private function hasValidCsrfToken($maxAge, $sessionId = null)
|
|
|
|
{
|
|
|
|
if ($this->tokenDisabled) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->getElement($this->tokenElementName) === null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$elementValue = $this->getElement($this->tokenElementName)->getValue();
|
|
|
|
list($seed, $token) = explode($elementValue, '\|/');
|
|
|
|
|
|
|
|
if (!is_numeric($seed)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$seed -= intval(time() / $maxAge) * $maxAge;
|
|
|
|
$sessionId = $sessionId ? $sessionId : session_id();
|
|
|
|
return $token === hash('sha256', $sessionId . $seed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a new (seed, token) pair
|
|
|
|
*
|
|
|
|
* @param int $maxAge Max allowed token age
|
|
|
|
* @param string $sessionId A specific session id
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
final private function generateCsrfToken($maxAge, $sessionId = null)
|
|
|
|
{
|
|
|
|
$sessionId = $sessionId ? $sessionId : session_id();
|
|
|
|
$seed = mt_rand();
|
|
|
|
$hash = hash('sha256', $sessionId . $seed);
|
|
|
|
$seed += intval(time() / $maxAge) * $maxAge;
|
|
|
|
return array($seed, $hash);
|
|
|
|
}
|
|
|
|
}
|