From cb3686e9615cd3f5bf9287d504f9ed5fd6833bb7 Mon Sep 17 00:00:00 2001 From: Ben Younes <2910651+ousamabenyounes@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:25:03 +0000 Subject: [PATCH 1/2] fix: resolve enable_introspection env variable at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The security.enable_introspection option was evaluated at container-compile time. When it was backed by an environment variable, the value was still an unresolved placeholder string (always truthy), so introspection stayed enabled regardless of the variable. Pass the parameter reference to a new Executor::setIntrospectionQueryEnabled() method call so the value — including an env variable — is resolved when the service is instantiated. The existing enable/disableIntrospectionQuery() methods are kept and now delegate to it. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../OverblogGraphQLExtension.php | 12 ++- src/Request/Executor.php | 14 ++- .../enableIntrospectionEnvVar/config.yml | 17 ++++ .../EnableIntrospectionEnvVarTest.php | 94 +++++++++++++++++++ 4 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 tests/Functional/App/config/enableIntrospectionEnvVar/config.yml create mode 100644 tests/Functional/Security/EnableIntrospectionEnvVarTest.php diff --git a/src/DependencyInjection/OverblogGraphQLExtension.php b/src/DependencyInjection/OverblogGraphQLExtension.php index 7191c3466..448d84ce6 100644 --- a/src/DependencyInjection/OverblogGraphQLExtension.php +++ b/src/DependencyInjection/OverblogGraphQLExtension.php @@ -182,15 +182,17 @@ private function setConfigBuilders(array $config, ContainerBuilder $container): private function setSecurity(array $config, ContainerBuilder $container): void { $executorDefinition = $container->getDefinition(Executor::class); - if ($config['security']['enable_introspection']) { - $executorDefinition->addMethodCall('enableIntrospectionQuery'); - } else { - $executorDefinition->addMethodCall('disableIntrospectionQuery'); - } foreach ($config['security'] as $key => $value) { $container->setParameter(sprintf('%s.%s', $this->getAlias(), $key), $value); } + + // Pass the parameter reference (not the compile-time value) so that an + // "enable_introspection" backed by an env variable is resolved at + // runtime instead of always evaluating truthy as a placeholder string. + $executorDefinition->addMethodCall('setIntrospectionQueryEnabled', [ + sprintf('%%%s.enable_introspection%%', $this->getAlias()), + ]); } private function setErrorHandler(array $config, ContainerBuilder $container): void diff --git a/src/Request/Executor.php b/src/Request/Executor.php index b4464a5e6..5d6936b44 100644 --- a/src/Request/Executor.php +++ b/src/Request/Executor.php @@ -117,12 +117,22 @@ public function setMaxQueryComplexity(int $maxQueryComplexity): void public function enableIntrospectionQuery(): void { - DocumentValidator::addRule(new DisableIntrospection(DisableIntrospection::DISABLED)); + $this->setIntrospectionQueryEnabled(true); } public function disableIntrospectionQuery(): void { - DocumentValidator::addRule(new DisableIntrospection(DisableIntrospection::ENABLED)); + $this->setIntrospectionQueryEnabled(false); + } + + /** + * Toggles the introspection query at runtime so the value can be provided by + * an environment variable (resolved when the service is instantiated) rather + * than being evaluated at container-compile time. + */ + public function setIntrospectionQueryEnabled(bool $enabled): void + { + DocumentValidator::addRule(new DisableIntrospection($enabled ? DisableIntrospection::DISABLED : DisableIntrospection::ENABLED)); } /** diff --git a/tests/Functional/App/config/enableIntrospectionEnvVar/config.yml b/tests/Functional/App/config/enableIntrospectionEnvVar/config.yml new file mode 100644 index 000000000..d05fb1425 --- /dev/null +++ b/tests/Functional/App/config/enableIntrospectionEnvVar/config.yml @@ -0,0 +1,17 @@ +imports: + - { resource: ../config.yml } + - { resource: ../connection/services.yml } + +overblog_graphql: + security: + enable_introspection: '%env(bool:GRAPHQL_ENABLE_INTROSPECTION)%' + definitions: + class_namespace: "Overblog\\GraphQLBundle\\IntrospectionEnvVar\\__DEFINITIONS__" + schema: + query: Query + mutation: ~ + mappings: + types: + - + type: yaml + dir: "%kernel.project_dir%/config/queryComplexity/mapping" diff --git a/tests/Functional/Security/EnableIntrospectionEnvVarTest.php b/tests/Functional/Security/EnableIntrospectionEnvVarTest.php new file mode 100644 index 000000000..1bb5f012c --- /dev/null +++ b/tests/Functional/Security/EnableIntrospectionEnvVarTest.php @@ -0,0 +1,94 @@ + [ + [ + 'message' => 'GraphQL introspection is not allowed, but the query contained __schema or __type', + 'locations' => [ + [ + 'line' => 2, + 'column' => 3, + ], + ], + ], + ], + ]; + + $this->assertResponse($this->introspectionQuery, $expected, self::ANONYMOUS_USER, 'enableIntrospectionEnvVar'); + } finally { + $this->restoreEnv($previous); + } + } + + public function testIntrospectionEnabledViaEnvVar(): void + { + $previous = getenv(self::ENV_VAR); + putenv(self::ENV_VAR.'=true'); + $_ENV[self::ENV_VAR] = 'true'; + $_SERVER[self::ENV_VAR] = 'true'; + + try { + $client = self::createClientAuthenticated(self::ANONYMOUS_USER, 'enableIntrospectionEnvVar'); + $result = self::sendRequest($client, $this->introspectionQuery, true); + + static::assertArrayHasKey('data', $result); + static::assertArrayNotHasKey('errors', $result); + } finally { + $this->restoreEnv($previous); + } + } + + /** + * @param string|false $previous the value returned by getenv() before the test + */ + private function restoreEnv($previous): void + { + if (false === $previous) { + putenv(self::ENV_VAR); + unset($_ENV[self::ENV_VAR], $_SERVER[self::ENV_VAR]); + } else { + putenv(self::ENV_VAR.'='.$previous); + $_ENV[self::ENV_VAR] = $previous; + $_SERVER[self::ENV_VAR] = $previous; + } + } +} From 3728c1f75dfb7f1459db2bd7dacfdd33c6299fcb Mon Sep 17 00:00:00 2001 From: Ben Younes <2910651+ousamabenyounes@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:54:44 +0000 Subject: [PATCH 2/2] test: cover runtime introspection toggles --- .../EnableIntrospectionEnvVarTest.php | 26 +++++++++------ tests/Request/ExecutorTest.php | 32 +++++++++++++++++++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/tests/Functional/Security/EnableIntrospectionEnvVarTest.php b/tests/Functional/Security/EnableIntrospectionEnvVarTest.php index 1bb5f012c..0138d9d35 100644 --- a/tests/Functional/Security/EnableIntrospectionEnvVarTest.php +++ b/tests/Functional/Security/EnableIntrospectionEnvVarTest.php @@ -18,6 +18,9 @@ */ final class EnableIntrospectionEnvVarTest extends TestCase { + private const CONFIG_NAME = 'enableIntrospectionEnvVar'; + private const ENV_DISABLED = 'false'; + private const ENV_ENABLED = 'true'; private const ENV_VAR = 'GRAPHQL_ENABLE_INTROSPECTION'; private string $introspectionQuery = <<<'EOF' @@ -34,9 +37,7 @@ final class EnableIntrospectionEnvVarTest extends TestCase public function testIntrospectionDisabledViaEnvVar(): void { $previous = getenv(self::ENV_VAR); - putenv(self::ENV_VAR.'=false'); - $_ENV[self::ENV_VAR] = 'false'; - $_SERVER[self::ENV_VAR] = 'false'; + $this->setEnv(self::ENV_DISABLED); try { $expected = [ @@ -53,7 +54,7 @@ public function testIntrospectionDisabledViaEnvVar(): void ], ]; - $this->assertResponse($this->introspectionQuery, $expected, self::ANONYMOUS_USER, 'enableIntrospectionEnvVar'); + $this->assertResponse($this->introspectionQuery, $expected, self::ANONYMOUS_USER, self::CONFIG_NAME); } finally { $this->restoreEnv($previous); } @@ -62,12 +63,10 @@ public function testIntrospectionDisabledViaEnvVar(): void public function testIntrospectionEnabledViaEnvVar(): void { $previous = getenv(self::ENV_VAR); - putenv(self::ENV_VAR.'=true'); - $_ENV[self::ENV_VAR] = 'true'; - $_SERVER[self::ENV_VAR] = 'true'; + $this->setEnv(self::ENV_ENABLED); try { - $client = self::createClientAuthenticated(self::ANONYMOUS_USER, 'enableIntrospectionEnvVar'); + $client = self::createClientAuthenticated(self::ANONYMOUS_USER, self::CONFIG_NAME); $result = self::sendRequest($client, $this->introspectionQuery, true); static::assertArrayHasKey('data', $result); @@ -83,12 +82,19 @@ public function testIntrospectionEnabledViaEnvVar(): void private function restoreEnv($previous): void { if (false === $previous) { - putenv(self::ENV_VAR); + static::assertTrue(putenv(self::ENV_VAR)); unset($_ENV[self::ENV_VAR], $_SERVER[self::ENV_VAR]); } else { - putenv(self::ENV_VAR.'='.$previous); + static::assertTrue(putenv(self::ENV_VAR.'='.$previous)); $_ENV[self::ENV_VAR] = $previous; $_SERVER[self::ENV_VAR] = $previous; } } + + private function setEnv(string $value): void + { + static::assertTrue(putenv(self::ENV_VAR.'='.$value)); + $_ENV[self::ENV_VAR] = $value; + $_SERVER[self::ENV_VAR] = $value; + } } diff --git a/tests/Request/ExecutorTest.php b/tests/Request/ExecutorTest.php index 802616607..f1008f62a 100644 --- a/tests/Request/ExecutorTest.php +++ b/tests/Request/ExecutorTest.php @@ -5,7 +5,11 @@ namespace Overblog\GraphQLBundle\Tests\Request; use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter; +use GraphQL\Language\Parser; +use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Schema; +use GraphQL\Validator\DocumentValidator; +use GraphQL\Validator\Rules\DisableIntrospection; use Overblog\GraphQLBundle\Executor\Executor; use Overblog\GraphQLBundle\Request\Executor as RequestExecutor; use PHPUnit\Framework\MockObject\MockObject; @@ -15,6 +19,8 @@ final class ExecutorTest extends TestCase { + private const INTROSPECTION_QUERY = '{ __schema { queryType { name } } }'; + protected function getMockedExecutor(): RequestExecutor { /** @var EventDispatcher&MockObject $dispatcher */ @@ -40,4 +46,30 @@ public function testGetSchemasName(): void $this->assertSame($executor->getSchemasNames(), ['schema1', 'schema2']); } + + public function testIntrospectionCompatibilityMethodsToggleValidation(): void + { + $executor = $this->getMockedExecutor(); + $schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'value' => ['type' => 'String'], + ], + ]), + ]); + $document = Parser::parse(self::INTROSPECTION_QUERY); + + try { + $executor->disableIntrospectionQuery(); + $errors = DocumentValidator::validate($schema, $document); + self::assertCount(1, $errors); + self::assertSame(DisableIntrospection::introspectionDisabledMessage(), $errors[0]->getMessage()); + + $executor->enableIntrospectionQuery(); + self::assertSame([], DocumentValidator::validate($schema, $document)); + } finally { + $executor->enableIntrospectionQuery(); + } + } }