nelmio / NelmioSecurityBundle

Adds extra security-related features in your Symfony application
https://symfony.com/bundles/NelmioSecurityBundle/
MIT License
651 stars 83 forks source link

Service dependency injection for controller action: Getting CSP nonce in controller #345

Open Adambean opened 3 months ago

Adambean commented 3 months ago

Hello,

Does this bundle expose any services for use with a controller action via dependency injection? debug:autowiring didn't appear to reveal any.

The purpose for this is I need to provide a ReCAPTCHA bundle form instance with a nonce from this CSP provider.

martijnc commented 3 months ago

You can use ContentSecurityPolicyListener::getNonce('script') to get a script nonce that will be included in the generated CSP header. You can find the listener in the container as nelmio_security.csp_listener.

Adambean commented 3 months ago

Thanks for your reply @martijnc.

Is the "nelmio_security.csp_listener" service available to dependency injection? I don't see anything from "nelmio_security" available when searching for it with debug:autowiring --all, and it can't be invoked from the container via get(). (ContentSecurityPolicyListener::getNonce() can't be called statically.)

martijnc commented 3 months ago

The nelmio_security.csp_listener service isn't marked public so get()-ing it from the container may not work because it's likely being inlined. But you should be able to inject it into your service with DI.

Can you find it with debug:container nelmio_security.csp_listener?

Adambean commented 3 months ago

I see 26 services containing "nelmio_security" via debug:container, including "nelmio_security.csp_listener" as "Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener". Unfortunately none of these are available for auto-wiring (not present in debug:autowiring), therefore they can't be used for DI.

I did attempt to declare "Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener" in my services configuration to be available for auto-wiring however the service doesn't appear to be compatible with that:

Cannot autowire service "Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener": argument "$report" of method "__construct()" references class "Nelmio\SecurityBundle\ContentSecurityPolicy\DirectiveSet" but no such service exists.

I think perhaps I've missed an approach to DI that predates auto-wiring? I went from the Symfony 2/3 days where using $this->get() or $this->container->get() up to Symfony 6/7 whereby auto-wiring was the thing to do. I'll read the service container documentation to see what I've missed.

martijnc commented 3 months ago

Because autowiring for the service does not work, you need to wire it manually via the service configuration. See "Explicitly Configuring Services and Arguments "on the documentation page you linked. You don't need to redefine the ContentSecurityPolicyListener service, you need to reference the existing one by its ID.

If your service is App\Security\ServiceThatNeedsTheNonce with the listener as the first constructor argument, the configuration for it would look something like this:

services:
    App\Security\ServiceThatNeedsTheNonce:
        arguments:
            - '@nelmio_security.csp_listener'
Adambean commented 3 months ago

That seemed to work. I had to also include the other arguments that I had previously given with auto-wiring, but the CSP listener now goes into my class as expected. Thanks for the workaround.

Is there a particular reason why the CSP listener class would not support auto-wiring, or is it a pending improvement?

Seldaek commented 2 months ago

It's just old code predating autowiring.. These days I guess there would not be service names at all and rather just use classes with autowiring, but tbh I am not 100% sure about the latest bundle best practices.

Anyway could be fixed for sure, but it's work :)