NelmioCorsBundle icon indicating copy to clipboard operation
NelmioCorsBundle copied to clipboard

Cannot use Symfony arrays in config

Open atay opened this issue 5 years ago • 14 comments

In the latest version I've been trying to use:

nelmio_cors:
    paths:
        '^/':
            allow_origin: '%env(csv:CORS_ALLOW_ORIGIN_CSV)%'

where CORS_ALLOW_ORIGIN_CSV is a CSV value, but NelmioCorsBundle recognizes it as a string value instead of array and returns an error Fatal error: Uncaught ErrorException: Warning: in_array() expects parameter 2 to be array, string given in /var/www/pimcore/vendor/nelmio/cors-bundle/DependencyInjection/NelmioCorsExtension.php:60

atay avatar Feb 20 '20 06:02 atay

Did you try allow_origin: ['%env(csv:CORS_ALLOW_ORIGIN_CSV)%'] ?

Juarrow avatar Mar 12 '20 23:03 Juarrow

@Incubbus yes, but this variable is still a string, it's converted to array later, so it does not throw Exception as above, but it just doesn't work

atay avatar Mar 13 '20 06:03 atay

I have the same error here.

pounard avatar Mar 17 '21 09:03 pounard

In my case, I have a fixed number of allowed origins, so I was able to workaround this limitation using this procedure:

First in config/services.yaml:

parameters:
    computed_absolute_url: "%router.request_context.scheme%://%router.request_context.host%%env(ABSOLUTE_URL_PORT)%%router.request_context.base_url%"

    # Allowed CORS origins.
    env(NELMIO_CORS_ORIGIN_FRONTEND): "http://localhost:8080/"
    env(NELMIO_CORS_ORIGIN_BACKEND): "%computed_absolute_url%"
    nelmio_cors_origin_frontend: "%env(resolve:NELMIO_CORS_ORIGIN_FRONTEND)%"
    nelmio_cors_origin_backend: "%env(resolve:NELMIO_CORS_ORIGIN_BACKEND)%"

Please note that there are a few custom env vars hanging in computed_absolute_url but you get the point.

Then in config/packages/nelmio_cors.yaml:

nelmio_cors:
    defaults:
        allow_origin: ["%nelmio_cors_origin_frontend%", "%nelmio_cors_origin_backend%"]
        # ...
    paths:
        # ...

pounard avatar Mar 17 '21 10:03 pounard

The error we get is mostly due to the fact that the extension does some work around the defaults, but when reaching the extension being called, env variables are not resolved. There might be a few options to make it work: either resolve those at runtime (ie. upon service initialisation during request) or may be trying in a compiler pass ? But that second option probably wouldn't work, althought it might worth it to at least try.

pounard avatar Mar 17 '21 10:03 pounard

Any updates on fixing this issue? I would like to have only one configuration that reads the list of allowed origins from an environment variable instead of having multiple hard-coded configurations and bandaids. This is definitely a bundle configuration issue.

speller avatar Mar 14 '22 12:03 speller

If someone can send a PR for this it'd be welcome. I'm not super familiar with env var params. I guess this might need to allow strings in the Configuration class check at runtime once env vars are resolved that we have an array?

Seldaek avatar Feb 15 '23 13:02 Seldaek

Is there a decent workaround that is possible?

MetalArend avatar Apr 05 '23 09:04 MetalArend

same issue here..

I've encountered this issue and started debugging because I didn't understand what was going wrong and I still don't.

Symfony version: 5.4.16 nelmio/cors-bundle: 2.2.0

Steps to reproduce

In my parameters.yaml I've got the following content:

env(TEST2): '["http://test1.localhost/","http://test2.localhost/"]'
test: ["http://test1.localhost/","http://test2.localhost/"]
test2: '%env(json:TEST2)%'

When I dump how env(TEST2) is processed, I get the following result:

wietse@201b65690067:/var/vhost$ console debug:container --env-var=TEST2     

Symfony Container Environment Variables
=======================================

 // Displaying detailed environment variable usage matching TEST2                                                       

%env(json:TEST2)%
-----------------

 ----------------- --------------------------------------------------------- 
  Default value     "["http://test1.localhost/","http://test2.localhost/"]"  
  Real value        n/a                                                      
  Processed value   [                                                        
                      "http://test1.localhost/",                             
                      "http://test2.localhost/"                              
                    ]                                                        
 ----------------- --------------------------------------------------------- 

I bind the two params I've created to some params I can inject in a controller in services.yaml

services:
    _defaults:
        autowire: true
        autoconfigure: true
        bind:
            $test: '%test%'
            $test2: '%test2%'

I create some controller method:

/** @Route(path="/test", methods={"GET"}) */
public function test(): Response
{
    return new JsonResponse(['test' => $this->test, 'test2' => $this->test2]);
}

and the response I get is when calling the endpoint via an HTTP request is:

{
    "test": [
        "http://test1.localhost/",
        "http://test2.localhost/"
    ],
    "test2": [
        "http://test1.localhost/",
        "http://test2.localhost/"
    ]
}

When I use '%test% as param in nelmio.defaults.allow_origin everything works as expected

nelmio_cors:
    defaults:
        allow_credentials: true
        origin_regex: true
        allow_origin: '%test%'
        allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
        allow_headers: ['Content-Type', 'Authorization']
        expose_headers: ['Link', 'Content-Disposition']
        max_age: 3600
    paths:
        '^/': null

The value which comes in as allow_origin NelmioCorsExtension is the following:

array(2) {
  [0]=>
  string(23) "http://test1.localhost/"
  [1]=>
  string(23) "http://test2.localhost/"
}

So far so good..

When I now use '%test2% as param the following happens:

nelmio_cors:
    defaults:
        allow_credentials: true
        origin_regex: true
        allow_origin: '%test2%'
        allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
        allow_headers: ['Content-Type', 'Authorization']
        expose_headers: ['Link', 'Content-Disposition']
        max_age: 3600
    paths:
        '^/': null

The value which comes in as allow_origin in NelmioCorsExtionsion is now:

env_75acc66cb0a67e10_json_TEST2_1e2da8c52400b233885b68ca5818285b

and this will throw the error:

in_array(): Argument #2 ($haystack) must be of type array, string given

when trying to do

in_array('*', $defaults['allow_origin'])

in NelmioCorsExtension::L49

When I now do the following in nelmio_cors_.yaml:

nelmio_cors:
    defaults:
        allow_credentials: true
        origin_regex: true
        allow_origin: [ '%test2%' ] 
        allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
        allow_headers: ['Content-Type', 'Authorization']
        expose_headers: ['Link', 'Content-Disposition']
        max_age: 3600
    paths:
        '^/': null

I expect it passes NelmioCorsExtension because the value is now an array:

array(1) {
  [0]=>
  string(64) "env_75acc66cb0a67e10_json_TEST2_1e2da8c52400b233885b68ca5818285b"
}

But it now fails with the message:

 Invalid type for path "nelmio_cors.defaults.allow_origin.0". Expected one of "bool", "int", "float", "string", but got "array"

in BaseNode.php::L573:

exception trace:

Exception trace:
  at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:573
 Symfony\Component\Config\Definition\BaseNode->doValidateType() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:407
 Symfony\Component\Config\Definition\BaseNode->normalize() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:390
 Symfony\Component\Config\Definition\BaseNode->normalize() at /var/vhost/vendor/symfony/config/Definition/PrototypedArrayNode.php:255
 Symfony\Component\Config\Definition\PrototypedArrayNode->normalizeValue() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:410
 Symfony\Component\Config\Definition\BaseNode->normalize() at /var/vhost/vendor/symfony/config/Definition/ArrayNode.php:287
 Symfony\Component\Config\Definition\ArrayNode->normalizeValue() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:410
 Symfony\Component\Config\Definition\BaseNode->normalize() at /var/vhost/vendor/symfony/config/Definition/ArrayNode.php:287
 Symfony\Component\Config\Definition\ArrayNode->normalizeValue() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:410
 Symfony\Component\Config\Definition\BaseNode->normalize() at /var/vhost/vendor/symfony/config/Definition/Processor.php:32
 Symfony\Component\Config\Definition\Processor->process() at /var/vhost/vendor/symfony/config/Definition/Processor.php:46
 Symfony\Component\Config\Definition\Processor->processConfiguration() at /var/vhost/vendor/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php:86
 Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass->process() at /var/vhost/vendor/symfony/dependency-injection/Compiler/Compiler.php:82
 Symfony\Component\DependencyInjection\Compiler\Compiler->compile() at /var/vhost/vendor/symfony/dependency-injection/ContainerBuilder.php:757
 Symfony\Component\DependencyInjection\ContainerBuilder->compile() at /var/vhost/vendor/symfony/http-kernel/Kernel.php:546
 Symfony\Component\HttpKernel\Kernel->initializeContainer() at /var/vhost/vendor/symfony/http-kernel/Kernel.php:787
 Symfony\Component\HttpKernel\Kernel->preBoot() at /var/vhost/vendor/symfony/http-kernel/Kernel.php:128
 Symfony\Component\HttpKernel\Kernel->boot() at /var/vhost/vendor/symfony/framework-bundle/Console/Application.php:168
 Symfony\Bundle\FrameworkBundle\Console\Application->registerCommands() at /var/vhost/vendor/symfony/framework-bundle/Console/Application.php:74
 Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /var/vhost/vendor/symfony/console/Application.php:171
 Symfony\Component\Console\Application->run() at /var/vhost/bin/console:43

My guess is that the env variables are only processed after the bundle extension is loaded because env variables are only loaded at runtime.

Also interesting:

When I remove all checks in NelmioCorsExtension::load() which already expects an array. The configuration processor which is default is still failing because an allow_origin is defined as an ArrayNode. In other words, I expects this is a bigger issue in Symfony itself. Because it impossible to use %env(json:VARIABLE)% or %env(csv:VARIABLE)% as Symfony describes without failing on the Configuration validation

In ArrayNode.php line 260:
                                                                                                 
  [Symfony\Component\Config\Definition\Exception\InvalidTypeException]                           
  Invalid type for path "nelmio_cors.defaults.allow_origin". Expected "array", but got "string"  
                                                                                                 

Exception trace:
  at /var/vhost/vendor/symfony/config/Definition/ArrayNode.php:260
 Symfony\Component\Config\Definition\ArrayNode->validateType() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:564
 Symfony\Component\Config\Definition\BaseNode->doValidateType() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:407
 Symfony\Component\Config\Definition\BaseNode->normalize() at /var/vhost/vendor/symfony/config/Definition/ArrayNode.php:287
 Symfony\Component\Config\Definition\ArrayNode->normalizeValue() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:410
 Symfony\Component\Config\Definition\BaseNode->normalize() at /var/vhost/vendor/symfony/config/Definition/ArrayNode.php:287
 Symfony\Component\Config\Definition\ArrayNode->normalizeValue() at /var/vhost/vendor/symfony/config/Definition/BaseNode.php:410
 Symfony\Component\Config\Definition\BaseNode->normalize() at /var/vhost/vendor/symfony/config/Definition/Processor.php:32
 Symfony\Component\Config\Definition\Processor->process() at /var/vhost/vendor/symfony/config/Definition/Processor.php:46
 Symfony\Component\Config\Definition\Processor->processConfiguration() at /var/vhost/vendor/symfony/dependency-injection/Extension/Extension.php:113
 Symfony\Component\DependencyInjection\Extension\Extension->processConfiguration() at /var/vhost/vendor/nelmio/cors-bundle/DependencyInjection/NelmioCorsExtension.php:30
 Nelmio\CorsBundle\DependencyInjection\NelmioCorsExtension->load() at /var/vhost/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php:79
 Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass->process() at /var/vhost/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php:42
 Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass->process() at /var/vhost/vendor/symfony/dependency-injection/Compiler/Compiler.php:82
 Symfony\Component\DependencyInjection\Compiler\Compiler->compile() at /var/vhost/vendor/symfony/dependency-injection/ContainerBuilder.php:759
 Symfony\Component\DependencyInjection\ContainerBuilder->compile() at /var/vhost/vendor/symfony/http-kernel/Kernel.php:546
 Symfony\Component\HttpKernel\Kernel->initializeContainer() at /var/vhost/vendor/symfony/http-kernel/Kernel.php:787
 Symfony\Component\HttpKernel\Kernel->preBoot() at /var/vhost/vendor/symfony/http-kernel/Kernel.php:128
 Symfony\Component\HttpKernel\Kernel->boot() at /var/vhost/vendor/symfony/framework-bundle/Console/Application.php:168
 Symfony\Bundle\FrameworkBundle\Console\Application->registerCommands() at /var/vhost/vendor/symfony/framework-bundle/Console/Application.php:74
 Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /var/vhost/vendor/symfony/console/Application.php:171
 Symfony\Component\Console\Application->run() at /var/vhost/bin/console:43

WietseGielen avatar Apr 05 '23 10:04 WietseGielen

Yes, env params are only available at runtime

Seldaek avatar Apr 12 '23 11:04 Seldaek

But they are indeed available at runtime, to set up your application. So is there maybe a way to tell the bundle to avoid the check on compile time, and do a check on runtime?

MetalArend avatar Apr 17 '23 16:04 MetalArend

We probably will have to wait for https://github.com/symfony/symfony/issues/40906? :thinking:

MetalArend avatar Apr 17 '23 16:04 MetalArend

Hey! The same problem, does anybody find workaround?

YuriyChmil avatar Feb 07 '24 16:02 YuriyChmil

We write our own solution. 🤣

alexndlm avatar Feb 07 '24 16:02 alexndlm