I just managed to skip all the rather horrible and complex looking Magento form config by getting Zend_Form working in the controllers instead. (I reserve the right to retract what I said just there about Magento forms, seeing as I haven’t actually tried it that way yet!)

Anyway, as you know, CSRF (cross site request forgery) tokens are pretty important to secure up your forms, and Zend_Form_Element_Hash does a fine job of taking care of this, however the problem I got was the old “session already started” message. What I did was create my own Form_Element_Hash. It’s a copy paste job of the original Zend class, only it uses the Magento session. Here it is:

<?php /**  * Created by PhpStorm.  * User: delboy1978uk  * Date: 03/06/15  * Time: 15:30  */ class Del_Form_Element_Hash extends Zend_Form_Element_Xhtml {     /**      * Use formHidden view helper by default      * @var string      */     public $helper = 'formHidden';     /**      * Actual hash used.      *      * @var mixed      */     protected $_hash;     /**      * Salt for CSRF token      * @var string      */     protected $_salt = '54lt4ndP3pp3r';     /**      * @var Zend_Session_Namespace      */     protected $_session;     /**      * TTL for CSRF token      * @var int      */     protected $_timeout = 300;     /**      * Constructor      *      * Creates session namespace for CSRF token, and adds validator for CSRF      * token.      *      * @param array|string|Zend_Config $spec      * @param null $options      * @throws Zend_Form_Exception      */     public function __construct($spec, $options = null)     {         parent::__construct($spec, $options);         $this->setAllowEmpty(false)
            ->setRequired(true)
            ->initCsrfValidator();
    }

    /**
     * Set session object
     *
     * @param  Mage_Core_Model_Session $session
     * @return Del_Form_Element_Hash
     */
    public function setSession($session)
    {
        $this->_session = $session;
        return $this;
    }

    /**
     * Get session object
     *
     * Instantiate session object if none currently exists
     *
     * @return Mage_Core_Model_Session
     */
    public function getSession()
    {
        if (null === $this->_session)
        {
            $this->_session = Mage::getSingleton('core/session');
        }
        return $this->_session;
    }

    /**
     * Initialize CSRF validator
     *
     * Creates Session namespace, and initializes CSRF token in session.
     * Additionally, adds validator for validating CSRF token.
     *
     * @return Del_Form_Element_Hash
     */
    public function initCsrfValidator()
    {
        $session = $this->getSession();
        $key = $session->getData('csrf');
        if (isset($key))
        {
            $rightHash = $session->getData('csrf');
        }
        else
        {
            $rightHash = null;
        }

        $this->addValidator('Identical', true, array($rightHash));
        return $this;
    }

    /**
     * Salt for CSRF token
     *
     * @param  string $salt
     * @return Del_Form_Element_Hash
     */
    public function setSalt($salt)
    {
        $this->_salt = (string) $salt;
        return $this;
    }

    /**
     * Retrieve salt for CSRF token
     *
     * @return string
     */
    public function getSalt()
    {
        return $this->_salt;
    }

    /**
     * Retrieve CSRF token
     *
     * If no CSRF token currently exists, generates one.
     *
     * @return string
     */
    public function getHash()
    {
        if (null === $this->_hash) {
            $this->_generateHash();
        }
        return $this->_hash;
    }

    /**
     * Get session namespace for CSRF token
     *
     * Generates a session namespace based on salt, element name, and class.
     *
     * @return string
     */
    public function getSessionName()
    {
        return __CLASS__ . '_' . $this->getSalt() . '_' . $this->getName();
    }

    /**
     * Set timeout for CSRF session token
     *
     * @param  int $ttl
     * @return Del_Form_Element_Hash
     */
    public function setTimeout($ttl)
    {
        $this->_timeout = (int) $ttl;
        return $this;
    }

    /**
     * Get CSRF session token timeout
     *
     * @return int
     */
    public function getTimeout()
    {
        return $this->_timeout;
    }

    /**
     * Override getLabel() to always be empty
     *
     * @return null
     */
    public function getLabel()
    {
        return null;
    }

    /**
     * Initialize CSRF token in session
     *
     * @return void
     */
    public function initCsrfToken()
    {
        $session = $this->getSession();
        $session->setData('csrf',$this->getHash());
    }

    /**
     * Render CSRF token in form
     *
     * @param  Zend_View_Interface $view
     * @return string
     */
    public function render(Zend_View_Interface $view = null)
    {
        $this->initCsrfToken();
        return parent::render($view);
    }

    /**
     * Generate CSRF token
     *
     * Generates CSRF token and stores both in {@link $_hash} and element
     * value.
     *
     * @return void
     */
    protected function _generateHash()
    {
        $this->_hash = md5(
            mt_rand(1,1000000)
            .  $this->getSalt()
            .  $this->getName()
            .  mt_rand(1,1000000)
        );
        $this->setValue($this->_hash);
    }

}
Advertisements