mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-04-08 17:15:08 +02:00
Refactor Form-builder [WIP]
Refactor Form-builder so that it is an abstract extension class of Zend_Form. refs #4355
This commit is contained in:
parent
b3c1a7a1ed
commit
b351cf61a7
@ -29,7 +29,39 @@
|
|||||||
use Icinga\Web\ActionController;
|
use Icinga\Web\ActionController;
|
||||||
use Icinga\Authentication\Credentials as Credentials;
|
use Icinga\Authentication\Credentials as Credentials;
|
||||||
use Icinga\Authentication\Manager as AuthManager;
|
use Icinga\Authentication\Manager as AuthManager;
|
||||||
use Icinga\Form\Builder as FormBuilder;
|
use Icinga\Form\Form;
|
||||||
|
|
||||||
|
|
||||||
|
// @TODO: I (jom) suppose this is not the best place, but
|
||||||
|
// finding a "bedder" one is your part mr. hein :)
|
||||||
|
class Auth_Form extends Form
|
||||||
|
{
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$this->addElement('text', 'username', array(
|
||||||
|
'label' => t('Username'),
|
||||||
|
'required' => true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$this->addElement('password', 'password', array(
|
||||||
|
'label' => t('Password'),
|
||||||
|
'required' => true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$this->addElement('submit', 'submit', array(
|
||||||
|
'label' => t('Login'),
|
||||||
|
'class' => 'pull-right'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$this->disableCsrfToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSubmitted()
|
||||||
|
{
|
||||||
|
return parent::isSubmitted('submit');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AuthenticationController
|
* Class AuthenticationController
|
||||||
@ -47,33 +79,6 @@ class AuthenticationController extends ActionController
|
|||||||
*/
|
*/
|
||||||
protected $modifiesSession = true;
|
protected $modifiesSession = true;
|
||||||
|
|
||||||
private function getAuthForm()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'username' => array(
|
|
||||||
'text',
|
|
||||||
array(
|
|
||||||
'label' => t('Username'),
|
|
||||||
'required' => true,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'password' => array(
|
|
||||||
'password',
|
|
||||||
array(
|
|
||||||
'label' => t('Password'),
|
|
||||||
'required' => true
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'submit' => array(
|
|
||||||
'submit',
|
|
||||||
array(
|
|
||||||
'label' => t('Login'),
|
|
||||||
'class' => 'pull-right'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -81,13 +86,9 @@ class AuthenticationController extends ActionController
|
|||||||
{
|
{
|
||||||
$this->replaceLayout = true;
|
$this->replaceLayout = true;
|
||||||
$credentials = new Credentials();
|
$credentials = new Credentials();
|
||||||
$this->view->form = FormBuilder::fromArray(
|
$this->view->form = new Auth_Form();
|
||||||
$this->getAuthForm(),
|
$this->view->form->setRequest($this->_request);
|
||||||
array(
|
$this->view->form->bindToModel($credentials);
|
||||||
"CSRFProtection" => false, // makes no sense here
|
|
||||||
"model" => &$credentials
|
|
||||||
)
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
$auth = AuthManager::getInstance(null, array(
|
$auth = AuthManager::getInstance(null, array(
|
||||||
"writeSession" => true
|
"writeSession" => true
|
||||||
@ -97,7 +98,8 @@ class AuthenticationController extends ActionController
|
|||||||
}
|
}
|
||||||
if ($this->getRequest()->isPost() && $this->view->form->isSubmitted()) {
|
if ($this->getRequest()->isPost() && $this->view->form->isSubmitted()) {
|
||||||
$this->view->form->repopulate();
|
$this->view->form->repopulate();
|
||||||
if ($this->view->form->isValid()) {
|
// @TODO: Re-enable this once the CSRF validation works
|
||||||
|
if (true) { //($this->view->form->isValid($this->getRequest())) {
|
||||||
if (!$auth->authenticate($credentials)) {
|
if (!$auth->authenticate($credentials)) {
|
||||||
$this->view->form->getElement('password')->addError(t('Please provide a valid username and password'));
|
$this->view->form->getElement('password')->addError(t('Please provide a valid username and password'));
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,407 +0,0 @@
|
|||||||
<?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\Form;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class that helps building and validating forms and offers a rudimentary
|
|
||||||
* data-binding mechanismn.
|
|
||||||
*
|
|
||||||
* The underlying form can be accessed by expicitly calling $builder->getForm() or
|
|
||||||
* by directly calling the forms method (which is, in case of populate() the preferred way)
|
|
||||||
* like: $builder->getElements()
|
|
||||||
*
|
|
||||||
* @method \Zend_Form_Element getElement(string $name)
|
|
||||||
* @method \Zend_Form addElement(\string $element, string $name = null, array $options = null)
|
|
||||||
* @method \Zend_Form setView(\Zend_View $view)
|
|
||||||
* @package Icinga\Form
|
|
||||||
*/
|
|
||||||
class Builder
|
|
||||||
{
|
|
||||||
const CSRF_ID = "icinga_csrf_id";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Zend_Form
|
|
||||||
*/
|
|
||||||
private $form;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var null
|
|
||||||
*/
|
|
||||||
private $boundModel = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $disableCSRF = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructrs a new Formbuilder, containing an empty form if no
|
|
||||||
* $form parameter is given or the Zend form from the $form parameter.
|
|
||||||
*
|
|
||||||
* @param \Zend_Form $form The form to use with this Builder
|
|
||||||
* @param Array $options an optional array of Options:
|
|
||||||
* - CSRFProtection true to add a crsf token to the
|
|
||||||
* form (default), false to remove it
|
|
||||||
* - model An referenced array or object to use
|
|
||||||
* for value binding
|
|
||||||
**/
|
|
||||||
public function __construct(\Zend_Form $form = null, array $options = array())
|
|
||||||
{
|
|
||||||
if ($form === null) {
|
|
||||||
$myModel = array(
|
|
||||||
"username" => "",
|
|
||||||
"password" => ""
|
|
||||||
);
|
|
||||||
|
|
||||||
$form = new \Zend_Form();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setZendForm($form);
|
|
||||||
|
|
||||||
if (isset($options["CSRFProtection"])) {
|
|
||||||
$this->disableCSRF = !$options["CSRFProtection"];
|
|
||||||
}
|
|
||||||
if (isset($options["model"])) {
|
|
||||||
$this->bindToModel($options["model"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setter for Zend_Form
|
|
||||||
* @param \Zend_Form $form
|
|
||||||
*/
|
|
||||||
public function setZendForm(\Zend_Form $form)
|
|
||||||
{
|
|
||||||
$this->form = $form;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for Zent_Form
|
|
||||||
* @return \Zend_Form
|
|
||||||
*/
|
|
||||||
public function getForm()
|
|
||||||
{
|
|
||||||
if (!$this->disableCSRF) {
|
|
||||||
$this->addCSRFFieldToForm();
|
|
||||||
}
|
|
||||||
if (!$this->form) {
|
|
||||||
return new \Zend_Form();
|
|
||||||
}
|
|
||||||
return $this->form;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add elements to form
|
|
||||||
* @param array $elements
|
|
||||||
*/
|
|
||||||
public function addElementsFromConfig(array $elements)
|
|
||||||
{
|
|
||||||
foreach ($elements as $key => $values) {
|
|
||||||
$this->addElement($values[0], $key, $values[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quick add elements to a new builder instance
|
|
||||||
* @param array $elements
|
|
||||||
* @param array $options
|
|
||||||
* @return Builder
|
|
||||||
*/
|
|
||||||
public static function fromArray(array $elements, $options = array())
|
|
||||||
{
|
|
||||||
$builder = new Builder(null, $options);
|
|
||||||
$builder->addElementsFromConfig($elements);
|
|
||||||
return $builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that the form is valid
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isValid(array $data = null)
|
|
||||||
{
|
|
||||||
if ($data === null) {
|
|
||||||
$data = $_POST;
|
|
||||||
}
|
|
||||||
return $this->hasValidToken() && $this->form->isValid($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if the form was submitted
|
|
||||||
* @param string $btnName
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isSubmitted($btnName = 'submit')
|
|
||||||
{
|
|
||||||
$btn = $this->getElement($btnName);
|
|
||||||
if (!$btn || !isset($_POST[$btnName])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return $_POST[$btnName] === $btn->getLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the form
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return $this->getForm()->render();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return $this->getForm()->__toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable CSRF token field
|
|
||||||
*/
|
|
||||||
public function enableCSRF()
|
|
||||||
{
|
|
||||||
$this->disableCSRF = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable CSRF token field
|
|
||||||
*/
|
|
||||||
public function disableCSRF()
|
|
||||||
{
|
|
||||||
$this->disableCSRF = true;
|
|
||||||
$this->form->removeElement(self::CSRF_ID);
|
|
||||||
$this->form->removeElement(self::CSRF_ID."_seed");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add CSRF field to form
|
|
||||||
*/
|
|
||||||
private function addCSRFFieldToForm()
|
|
||||||
{
|
|
||||||
if (!$this->form || $this->disableCSRF || $this->form->getElement(self::CSRF_ID)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
list($seed, $token) = $this->getSeedTokenPair();
|
|
||||||
|
|
||||||
$this->form->addElement("hidden", self::CSRF_ID);
|
|
||||||
$this->form->getElement(self::CSRF_ID)
|
|
||||||
->setValue($token)
|
|
||||||
->setDecorators(array('ViewHelper'));
|
|
||||||
|
|
||||||
$this->form->addElement("hidden", self::CSRF_ID."_seed");
|
|
||||||
$this->form->getElement(self::CSRF_ID."_seed")
|
|
||||||
->setValue($seed)
|
|
||||||
->setDecorators(array('ViewHelper'));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind model to a form
|
|
||||||
* @param $model
|
|
||||||
*/
|
|
||||||
public function bindToModel(&$model)
|
|
||||||
{
|
|
||||||
$this->boundModel = &$model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Repopulate
|
|
||||||
*/
|
|
||||||
public function repopulate()
|
|
||||||
{
|
|
||||||
if (!empty($_POST)) {
|
|
||||||
$this->populate($_POST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate form
|
|
||||||
* @param $data
|
|
||||||
* @param bool $ignoreModel
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function populate($data, $ignoreModel = false)
|
|
||||||
{
|
|
||||||
if (is_array($data)) {
|
|
||||||
$this->form->populate($data);
|
|
||||||
} elseif (is_object($data)) {
|
|
||||||
$this->populateFromObject($data);
|
|
||||||
} else {
|
|
||||||
throw new \InvalidArgumentException("Builder::populate() expects and object or array, $data given");
|
|
||||||
}
|
|
||||||
if ($this->boundModel === null || $ignoreModel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->updateModel();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate form object
|
|
||||||
* @param $data
|
|
||||||
*/
|
|
||||||
private function populateFromObject($data)
|
|
||||||
{
|
|
||||||
/** @var \Zend_Form_Element $element */
|
|
||||||
|
|
||||||
foreach ($this->form->getElements() as $name => $element) {
|
|
||||||
if (isset($data->$name)) {
|
|
||||||
$element->setValue($data->$name);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$getter = "get".ucfirst($name);
|
|
||||||
if (method_exists($data, $getter)) {
|
|
||||||
$element->setValue($data->$getter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update model instance
|
|
||||||
*/
|
|
||||||
public function updateModel()
|
|
||||||
{
|
|
||||||
if (is_array($this->boundModel)) {
|
|
||||||
$this->updateArrayModel();
|
|
||||||
} elseif (is_object($this->boundModel)) {
|
|
||||||
$this->updateObjectModel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updater for objects
|
|
||||||
*/
|
|
||||||
private function updateObjectModel()
|
|
||||||
{
|
|
||||||
/** @var \Zend_Form_Element $element */
|
|
||||||
|
|
||||||
foreach ($this->form->getElements() as $name => $element) {
|
|
||||||
if (isset($this->boundModel->$name)) {
|
|
||||||
$this->boundModel->$name = $element->getValue();
|
|
||||||
} else {
|
|
||||||
$setter = "set".ucfirst($name);
|
|
||||||
if (method_exists($this->boundModel, $setter)) {
|
|
||||||
$this->boundModel->$setter($element->getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updater for arrays
|
|
||||||
*/
|
|
||||||
private function updateArrayModel()
|
|
||||||
{
|
|
||||||
/** @var \Zend_Form_Element $element */
|
|
||||||
|
|
||||||
foreach ($this->form->getElements() as $name => $element) {
|
|
||||||
if (isset($this->boundModel[$name])) {
|
|
||||||
$this->boundModel[$name] = $element->getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronize model with form
|
|
||||||
*/
|
|
||||||
public function syncWithModel()
|
|
||||||
{
|
|
||||||
$this->populate($this->boundModel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Magic caller, pass through method calls to form
|
|
||||||
* @param $fn
|
|
||||||
* @param array $args
|
|
||||||
* @return mixed
|
|
||||||
* @throws \BadMethodCallException
|
|
||||||
*/
|
|
||||||
public function __call($fn, array $args)
|
|
||||||
{
|
|
||||||
if (method_exists($this->form, $fn)) {
|
|
||||||
return call_user_func_array(array($this->form, $fn), $args);
|
|
||||||
} else {
|
|
||||||
throw new \BadMethodCallException(
|
|
||||||
"Method $fn does not exist either ".
|
|
||||||
"in \Icinga\Form\Builder nor in Zend_Form"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the token parameter is valid
|
|
||||||
*
|
|
||||||
* @param int $maxAge Max allowed token age
|
|
||||||
* @param string $sessionId A specific session id (useful for tests?)
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function hasValidToken($maxAge = 600, $sessionId = null)
|
|
||||||
{
|
|
||||||
if ($this->disableCSRF) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->getForm()->getElement(self::CSRF_ID) == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sessionId = $sessionId ? $sessionId : session_id();
|
|
||||||
$seed = $this->getForm()->getElement(self::CSRF_ID.'_seed')->getValue();
|
|
||||||
|
|
||||||
if (! is_numeric($seed)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove quantitized timestamp portion so maxAge applies
|
|
||||||
$seed -= (intval(time() / $maxAge) * $maxAge);
|
|
||||||
$token = $this->getElement(self::CSRF_ID)->getValue();
|
|
||||||
return $token === hash('sha256', $sessionId . $seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a new seed/token pair
|
|
||||||
*
|
|
||||||
* @param int $maxAge Max allowed token age
|
|
||||||
* @param string $sessionId A specific session id (useful for tests?)
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getSeedTokenPair($maxAge = 600, $sessionId = null)
|
|
||||||
{
|
|
||||||
$sessionId = $sessionId ? $sessionId : session_id();
|
|
||||||
$seed = mt_rand();
|
|
||||||
$hash = hash('sha256', $sessionId . $seed);
|
|
||||||
|
|
||||||
// Add quantitized timestamp portion to apply maxAge
|
|
||||||
$seed += (intval(time() / $maxAge) * $maxAge);
|
|
||||||
return array($seed, $hash);
|
|
||||||
}
|
|
||||||
}
|
|
345
library/Icinga/Form/Form.php
Normal file
345
library/Icinga/Form/Form.php
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
<?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\Form;
|
||||||
|
|
||||||
|
use Icinga\Exception\ProgrammingError;
|
||||||
|
|
||||||
|
abstract class Form extends \Zend_Form
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The form's request object
|
||||||
|
* @var null
|
||||||
|
*/
|
||||||
|
private $request = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying model for this form
|
||||||
|
* @var null
|
||||||
|
*/
|
||||||
|
private $boundModel = 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Zend_Form::init
|
||||||
|
*/
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
if (!$this->tokenDisabled) {
|
||||||
|
$this->initCsrfToken();
|
||||||
|
}
|
||||||
|
$this->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add elements to this form (used by extending classes)
|
||||||
|
*/
|
||||||
|
abstract public function create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a request object wherewith the form can work
|
||||||
|
*
|
||||||
|
* @param $request The request object of a session
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setRequest($request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the form's values are valid
|
||||||
|
*
|
||||||
|
* If $data is not given, $_POST is used.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
/* @TODO: isValid cannot be overwritten this way. The CSRF functionality needs to
|
||||||
|
* be implemented as separate Zend_Form_Element with its own validator.
|
||||||
|
*
|
||||||
|
public function isValid(array $data = null)
|
||||||
|
{
|
||||||
|
if ($data === null) {
|
||||||
|
$data = $_POST;
|
||||||
|
}
|
||||||
|
return $this->hasValidCsrfToken($this->tokenTimeout) && parent::isValid($data);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the form was submitted
|
||||||
|
*
|
||||||
|
* @param string $elementName
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSubmitted($elementName)
|
||||||
|
{
|
||||||
|
$element = $this->getElement($elementName);
|
||||||
|
if ($element) {
|
||||||
|
if ($this->request->isGet()) {
|
||||||
|
return $this->request->getQuery($elementName) === $element->getLabel();
|
||||||
|
} elseif ($this->request->isPost()) {
|
||||||
|
return $this->request->getPost($elementName) === $element->getLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate form with the given information
|
||||||
|
*
|
||||||
|
* $model might be an array or object where each key/property represents the name
|
||||||
|
* of an element. In case its a object it might also have getter for each element.
|
||||||
|
* (Naming: getExample) If $updateModel is false the underlying model, if any,
|
||||||
|
* will not be updated.
|
||||||
|
*
|
||||||
|
* @param array|object $model
|
||||||
|
* @param bool $updateModel
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function populate($model, $updateModel = true)
|
||||||
|
{
|
||||||
|
if (is_array($model)) {
|
||||||
|
parent::populate($model);
|
||||||
|
} elseif (is_object($model)) {
|
||||||
|
$this->populateFromObject($model);
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException("Expected array or object. $model given");
|
||||||
|
}
|
||||||
|
if ($updateModel && $this->boundModel !== null) {
|
||||||
|
$this->updateModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate form with the given object
|
||||||
|
*
|
||||||
|
* @param object $model
|
||||||
|
*/
|
||||||
|
private function populateFromObject($model)
|
||||||
|
{
|
||||||
|
foreach ($this->getElements() as $name => $element) {
|
||||||
|
if (isset($model->$name)) {
|
||||||
|
$element->setValue($model->$name);
|
||||||
|
} else {
|
||||||
|
$getter = 'get' . ucfirst($name);
|
||||||
|
if (method_exists($model, $getter)) {
|
||||||
|
$element->setValue($model->$getter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repopulate form with the current request
|
||||||
|
*/
|
||||||
|
public function repopulate()
|
||||||
|
{
|
||||||
|
if ($this->request->isPost()) {
|
||||||
|
$this->populate($this->request->getPost());
|
||||||
|
} elseif ($this->request->isGet()) {
|
||||||
|
$this->populate($this->request->getQuery());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind a model to this form
|
||||||
|
*
|
||||||
|
* $model might be an array or object where each key/property represents
|
||||||
|
* the name of an element. In case its a object it might also have getter
|
||||||
|
* and setter for each element. (Naming: getExample, setExample)
|
||||||
|
*
|
||||||
|
* @param array|object $model
|
||||||
|
*/
|
||||||
|
public function bindToModel(&$model)
|
||||||
|
{
|
||||||
|
$this->boundModel = &$model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize form with model
|
||||||
|
*
|
||||||
|
* Feed the form's elements with default values by using the current model.
|
||||||
|
*
|
||||||
|
* @throws ProgrammingError
|
||||||
|
*/
|
||||||
|
public function syncWithModel()
|
||||||
|
{
|
||||||
|
if ($this->boundModel === null) {
|
||||||
|
throw new ProgrammingError('You need to bind a model to this form first');
|
||||||
|
}
|
||||||
|
$this->populate($this->boundModel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update model with the form's values
|
||||||
|
*
|
||||||
|
* This is the inverse of syncWithModel(). Feed the
|
||||||
|
* model with values from the form's elements.
|
||||||
|
*
|
||||||
|
* @throws ProgrammingError
|
||||||
|
*/
|
||||||
|
public function updateModel()
|
||||||
|
{
|
||||||
|
if (is_array($this->boundModel)) {
|
||||||
|
$this->updateArrayModel();
|
||||||
|
} elseif (is_object($this->boundModel)) {
|
||||||
|
$this->updateObjectModel();
|
||||||
|
} else {
|
||||||
|
throw new ProgrammingError('You need to bind a model to this form first');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update model of type object
|
||||||
|
*/
|
||||||
|
private function updateObjectModel()
|
||||||
|
{
|
||||||
|
foreach ($this->getElements() as $name => $element) {
|
||||||
|
if (isset($this->boundModel->$name)) {
|
||||||
|
$this->boundModel->$name = $element->getValue();
|
||||||
|
} else {
|
||||||
|
$setter = 'set' . ucfirst($name);
|
||||||
|
if (method_exists($this->boundModel, $setter)) {
|
||||||
|
$this->boundModel->$setter($element->getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update model of type array
|
||||||
|
*/
|
||||||
|
private function updateArrayModel()
|
||||||
|
{
|
||||||
|
foreach ($this->getElements() as $name => $element) {
|
||||||
|
if (isset($this->boundModel[$name])) {
|
||||||
|
$this->boundModel[$name] = $element->getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
$this->addElement('hidden', $this->tokenElementName, array(
|
||||||
|
'value' => sprintf('%s\|/%s', $seed, $token),
|
||||||
|
'decorators' => array('ViewHelper')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user