Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[DI] Validate env vars in config #23888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[DI] Validate env vars in config
  • Loading branch information
ro0NL committed Mar 22, 2018
commit 68ee20eaf187529b029d606a4e5d8673b62df973
8 changes: 8 additions & 0 deletions 8 src/Symfony/Component/Config/Definition/ArrayNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,12 @@ protected function mergeValues($leftSide, $rightSide)

return $leftSide;
}

/**
* {@inheritdoc}
*/
protected function allowPlaceholders(): bool
{
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have you made it false by default for ArrayNode? It seems this will limit how much of a config you can change in other peoples bundles, i.e. you can only put env(json:FOO) if the owner things of you in advance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments on closed PRs are likely to be lost, please consider opening a new issue/PR if you think something is buggy or can be improved. Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case i asked for it on another closed PR :P But yeah.. not sure if there's any real issue now (hence i asked :D)

@mcfedr see symfony/symfony-docs#8382 (comment) , tried to explain the general proces there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, makes sense, so for simple_array nodes it will accept env(json:FOO) but not when it should validate the structure.

}
}
154 changes: 154 additions & 0 deletions 154 src/Symfony/Component/Config/Definition/BaseNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;

/**
* The base node class.
Expand All @@ -25,6 +26,9 @@ abstract class BaseNode implements NodeInterface
{
const DEFAULT_PATH_SEPARATOR = '.';

private static $placeholderUniquePrefix;
private static $placeholders = array();

protected $name;
protected $parent;
protected $normalizationClosures = array();
Expand All @@ -36,6 +40,8 @@ abstract class BaseNode implements NodeInterface
protected $attributes = array();
protected $pathSeparator;

private $handlingPlaceholder = false;

/**
* @throws \InvalidArgumentException if the name contains a period
*/
Expand All @@ -50,6 +56,39 @@ public function __construct(?string $name, NodeInterface $parent = null, string
$this->pathSeparator = $pathSeparator;
}

/**
* Register possible (dummy) values for a dynamic placeholder value. Matching configuration values will be processed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move all sentences but the first one as a phpdoc description.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean split into short & long description right? Did that for now.

* with a provided value, one by one. After a provided value is successfully processed the configuration value is
* returned as is, thus preserving the placeholder.
*/
public static function setPlaceholder(string $placeholder, array $values): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addPlaceholder?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general i prefer a[k] = v to be a setter, and a[] = v to be adder.

We also used set() in di fluent config, same topic :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see also setAttribute here :)

{
if (!$values) {
throw new \InvalidArgumentException('At least one value must be provided.');
}

self::$placeholders[$placeholder] = $values;
}

/**
* Set a common prefix for dynamic placeholder values. Matching configuration values will be skipped from being
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sets + same comment as above about separating the first sentence from the other ones.

* processed and are returned as is, thus preserving the placeholder. An exact match provided by {@see setPlaceholder()}
* might take precedence.
*/
public static function setPlaceholderUniquePrefix(string $prefix): void
{
self::$placeholderUniquePrefix = $prefix;
}

/**
* Reset all current placeholders available.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resets

*/
public static function resetPlaceholders(): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one also needs to be internal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops :) updated.

{
self::$placeholderUniquePrefix = null;
self::$placeholders = array();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having static properties like this is very dangerous as we need to make sure that they are reset properly. I can see the finally calls here and there to reset this, but is there is any other way? Avoiding static properties would be great.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not that dangerous: placeholders are unique strings so there is not really any way to mess up with them, even if someone forgets to reset them.
About removing the static state, that's really hard...
BUT, @ro0NL we should make these methods @internal for sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can settle indeed, i dont see a elegant workaround to avoid static currently, that is without overhauling the config component itself :)

Now marked internal.

Copy link
Contributor Author

@ro0NL ro0NL Mar 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also we deal with it properly where needed; it's not scattered all over the environment.

}

public function setAttribute($key, $value)
{
$this->attributes[$key] = $value;
Expand Down Expand Up @@ -249,6 +288,46 @@ final public function merge($leftSide, $rightSide)
));
}

if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) {
if (!$leftPlaceholders) {
return $rightSide;
}

foreach ($leftPlaceholders as $leftPlaceholder) {
$this->handlingPlaceholder = true;
try {
$this->merge($leftPlaceholder, $rightSide);

return $rightSide;
} catch (InvalidConfigurationException $e) {
} finally {
$this->handlingPlaceholder = false;
}
}

throw $e;
}

if ($rightSide !== $rightPlaceholders = self::resolvePlaceholderValue($rightSide)) {
if (!$rightPlaceholders) {
return $rightSide;
}

foreach ($rightPlaceholders as $rightPlaceholder) {
$this->handlingPlaceholder = true;
try {
$this->merge($leftSide, $rightPlaceholder);

return $rightSide;
} catch (InvalidConfigurationException $e) {
} finally {
$this->handlingPlaceholder = false;
}
}

throw $e;
}

$this->validateType($leftSide);
$this->validateType($rightSide);

Expand All @@ -267,6 +346,27 @@ final public function normalize($value)
$value = $closure($value);
}

// resolve placeholder value
if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
if (!$placeholders) {
return $value;
}

foreach ($placeholders as $placeholder) {
$this->handlingPlaceholder = true;
try {
$this->normalize($placeholder);

return $value;
} catch (InvalidConfigurationException $e) {
} finally {
$this->handlingPlaceholder = false;
}
}

throw $e;
}

// replace value with their equivalent
foreach ($this->equivalentValues as $data) {
if ($data[0] === $value) {
Expand Down Expand Up @@ -308,8 +408,35 @@ public function getParent()
*/
final public function finalize($value)
{
if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
if (!$placeholders) {
return $value;
}

foreach ($placeholders as $placeholder) {
$this->handlingPlaceholder = true;
try {
$this->finalize($placeholder);

return $value;
} catch (InvalidConfigurationException $e) {
} finally {
$this->handlingPlaceholder = false;
}
}

throw $e;
}

$this->validateType($value);

if ($this->handlingPlaceholder && !$this->allowPlaceholders()) {
$e = new InvalidConfigurationException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', get_class($this), $this->getPath()));
$e->setPath($this->getPath());

throw $e;
}

$value = $this->finalizeValue($value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case the value is a placeholder, I think we should preserve the value before the finalization (which will be the placeholder) and return this one instead. Otherwise, transformations done on the placeholder would break it. It should still be called though.

this advice should be challenged though, as it may depend on the usage of the finalization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's exactly what we do no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// Perform validation on the final value if a closure has been set.
Expand All @@ -318,6 +445,10 @@ final public function finalize($value)
try {
$value = $closure($value);
} catch (Exception $e) {
if ($e instanceof UnsetKeyException && $this->handlingPlaceholder) {
continue;
}

throw $e;
} catch (\Exception $e) {
throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s', $this->getPath(), $e->getMessage()), $e->getCode(), $e);
Expand Down Expand Up @@ -363,4 +494,27 @@ abstract protected function mergeValues($leftSide, $rightSide);
* @return mixed The finalized value
*/
abstract protected function finalizeValue($value);

/**
* Test if placeholder values are allowed for this node.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests

*/
protected function allowPlaceholders(): bool
{
return true;
}

private static function resolvePlaceholderValue($value)
{
if (\is_string($value)) {
if (isset(self::$placeholders[$value])) {
return self::$placeholders[$value];
}

if (0 === strpos($value, self::$placeholderUniquePrefix, 0)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3rd arg not needed

return array();
}
}

return $value;
}
}
8 changes: 8 additions & 0 deletions 8 src/Symfony/Component/Config/Definition/EnumNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,12 @@ protected function finalizeValue($value)

return $value;
}

/**
* {@inheritdoc}
*/
protected function allowPlaceholders(): bool
{
return false;
}
}
2 changes: 2 additions & 0 deletions 2 src/Symfony/Component/Config/Definition/Processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* This class is the entry point for config normalization/merging/finalization.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @final since version 4.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this PR? If yes, should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then: missing changelog/upgrade entries

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changelog entry added. Suggestion for an upgrade note?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!
in *4.1: The ... class has been made final
in *5.0: The ... class has been made final

:)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done :)

*/
class Processor
{
Expand Down
1 change: 1 addition & 0 deletions 1 src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* added PSR-11 `ContainerBagInterface` and its `ContainerBag` implementation to access parameters as-a-service
* added support for service's decorators autowiring
* deprecated the `TypedReference::canBeAutoregistered()` and `TypedReference::getRequiringClass()` methods
* environment variables are validated when used in extension configuration

4.0.0
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Component\DependencyInjection\Compiler;

use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
Expand All @@ -37,6 +38,7 @@ public function process(ContainerBuilder $container)
$definitions = $container->getDefinitions();
$aliases = $container->getAliases();
$exprLangProviders = $container->getExpressionLanguageProviders();
$configAvailable = class_exists(BaseNode::class);

foreach ($container->getExtensions() as $extension) {
if ($extension instanceof PrependExtensionInterface) {
Expand All @@ -53,6 +55,9 @@ public function process(ContainerBuilder $container)
if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof Extension) {
// create a dedicated bag so that we can track env vars per-extension
$resolvingBag = new MergeExtensionConfigurationParameterBag($resolvingBag);
if ($configAvailable) {
BaseNode::setPlaceholderUniquePrefix($resolvingBag->getEnvPlaceholderUniquePrefix());
}
}
$config = $resolvingBag->resolveValue($config);

Expand All @@ -75,6 +80,10 @@ public function process(ContainerBuilder $container)
}

throw $e;
} finally {
if ($configAvailable) {
BaseNode::resetPlaceholders();
}
}

if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
Expand Down Expand Up @@ -132,6 +141,11 @@ public function getEnvPlaceholders()
{
return null !== $this->processedEnvPlaceholders ? $this->processedEnvPlaceholders : parent::getEnvPlaceholders();
}

public function getUnusedEnvPlaceholders(): array
{
return null === $this->processedEnvPlaceholders ? array() : array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function __construct()
);

$this->optimizationPasses = array(array(
new ValidateEnvPlaceholdersPass(),
new ResolveChildDefinitionsPass(),
new ServiceLocatorTagPass(),
new DecoratorServicePass(),
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.