zendframework / zend-validator

Validator component from Zend Framework
BSD 3-Clause "New" or "Revised" License
181 stars 136 forks source link

Overwriting CSRF Validator does not work anymore #208

Closed koseduhemak closed 6 years ago

koseduhemak commented 6 years ago

I try to migrate from ZF2 to ZF3 but many viewHelpers and validators do not work. But only the ones who overwrite ZendFrameworks viewhelpers / validators do not work...

I want f.e. to overwrite the CSRF validator to allow a higher timeout by default.

I have the following module configuration:

'modules' => array(
    ...
    'Zend\Validator',
    ...
    'Base',
    ...
)

Module config of Base module:

namespace Base;
...
return [
    ...
    'validators' => array(
        'factories' => [
            \Zend\Validator\Csrf::class => Factory\Validator\CsrfFactory::class
        ]
    )
    ...
]

CsrfFactory:

<?php

namespace Base\Factory\Validator;

use Base\Validator\Csrf;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;

class CsrfFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        return new Csrf();
    }

}

Base\Validator\Csrf:

<?php

namespace Base\Validator;

class Csrf extends \Zend\Validator\Csrf
{
    protected $timeout = 1; // for testing => every form should display error message after submit after one second
}

I also debugged with xdebug and set a break point in the CsrfFactory (return statement) to see, if it is used (but it isn't). I thought I can overwrite services / validators etc. easily in ZF3... Did I miss something? Did the config keys change?

Also on stackoverflow: https://stackoverflow.com/questions/47590816/zendframework-3-overwrite-csrf-validator

koseduhemak commented 6 years ago

Debugging \Zend\Form\Element\Csrf class, line 75:

/**
 * Get CSRF validator
 *
 * @return CsrfValidator
 */
public function getCsrfValidator()
{
    if (null === $this->csrfValidator) {
        $csrfOptions = $this->getCsrfValidatorOptions();
        $csrfOptions = array_merge($csrfOptions, ['name' => $this->getName()]);
        $this->setCsrfValidator(new CsrfValidator($csrfOptions));
    }
    return $this->csrfValidator;
} 

Why does CsrfValidator gets directly instantiated instead of retrieved via service_manager? This does not allow extension of CsrfValidator class. Or does the CsrfValidator gets injected previously in the process?

froschdesign commented 6 years ago

@koseduhemak This belongs to zend-validator:

Your first code works if you use the ValidatorManager:

$validator = $container->get(\Zend\Validator\ValidatorPluginManager::class)->get(
    \Zend\Validator\Csrf::class
);
var_dump($validator); // Base\Validator\Csrf

This belongs to zend-form:

If you want to set the timeout options for the form element, then you can use the csrf_options:

$element = new Zend\Form\Element\Csrf('foo');
$element->setOptions(
    [
        'csrf_options' => [
            'timeout' => 777,
            'salt'    => 'MySalt',
        ],
    ]
);

Documentation: https://docs.zendframework.com/zend-form/element/csrf/#basic-usage

You can also set your own Csrf-validator (must extends Zend\Validator\Csrf):

$element = new Zend\Form\Element\Csrf('foo');
$element->setCsrfValidator($validator);

Documentation: https://docs.zendframework.com/zend-form/element/csrf/#public-methods

Is your problem zend-validator or zend-form?

koseduhemak commented 6 years ago

Thank you @froschdesign for your answer.

Problem is, that I want to overwrite every existing instance of \Zend\Validator\Csrf in my forms. My custom Csrf validator extends zend's Csrf validator class. I thought I could overwrite it simply by using config array key validators and by specifying a factory there using \Zend\Validator\Csrf::class as key and Factory\Validator\CsrfFactory::class as value.

Otherwise I have to go through every class and replace the \Zend\Validator\Csrf with my custom \Base\Validator\Csrf. I am just surprised that we can not use the config key 'validators' in module.config.php to overwrite an existing validator as we can do with "normal" services by service_manager config key. As the first part of your answer states, the custom Csrf class is properly set in ValidatorManager so I would expect that it is also attached to every \Zend\Validator\Csrf instance, referenced from a form/fieldset class.

I think I will open an issue about this weird behavior in zend-form repo, unless you think it's related to zend-validator, because the validators instantiation seems to be the problem here (instantiation is not done by ValidatorPluginManager in \Zend\Form\Elements\Csrf instead instantiating the validator is done directly in a non-extensible way).

froschdesign commented 6 years ago

@koseduhemak

Problem is, that I want to overwrite every existing instance of \Zend\Validator\Csrf in my forms.

This is very important: "in my forms" !

You can overwrite the factory for the Csrf form element and add your Csrf validator from the Validator manager:

'form_elements' => [
    'factories' => [
        Zend\Form\Elements\Csrf::class => My\Form\Element\CsrfFactory::class,
    ],
],

I am just surprised that we can not use the config key 'validators' in module.config.php to overwrite an existing validator as we can do with "normal" services by service_manager config key.

It works like that, see your own code above.

I would expect that it is also attached to every \Zend\Validator\Csrf instance, referenced from a form/fieldset class.

No, because the Csrf form element does not use the validator manager at this point. It create a new instance of the Zend\Validator\Csrf class.

https://github.com/zendframework/zend-form/blob/1e34504d29f36f870e72a4a5de0b6e5c85db310f/src/Element/Csrf.php#L82-L86

I think I will open an issue about this weird behavior…

No weird behavior here, it works like described. The element has a default Csrf validator, which can be configured by options and you can overwrite this default validator. If you want overwrite this default validator in all your forms, then replace the element or his factory.

https://docs.zendframework.com/zend-form/advanced/#creating-custom-elements