From 8306530e6078132a661498e8ccf80813f782d857 Mon Sep 17 00:00:00 2001 From: Martynas Narbutas Date: Sat, 3 Dec 2016 11:01:12 +0100 Subject: [PATCH 1/4] [Security] AbstractVoter method supportsAttribute gives false positive if attribute is zero (0) --- .../Authorization/Voter/AbstractVoter.php | 2 +- .../Authorization/Voter/AbstractVoterTest.php | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php index efa156228e..3d8bbc2d3f 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php @@ -26,7 +26,7 @@ abstract class AbstractVoter implements VoterInterface */ public function supportsAttribute($attribute) { - return in_array($attribute, $this->getSupportedAttributes()); + return in_array($attribute, $this->getSupportedAttributes(), true); } /** diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php index 2ab943bd8c..c122587158 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php @@ -16,6 +16,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; class AbstractVoterTest extends \PHPUnit_Framework_TestCase { + /** + * @var TokenInterface + */ protected $token; protected function setUp() @@ -23,6 +26,9 @@ class AbstractVoterTest extends \PHPUnit_Framework_TestCase $this->token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); } + /** + * @return array + */ public function getTests() { return array( @@ -53,6 +59,71 @@ class AbstractVoterTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); } + + /** + * @return array + */ + public function getSupportsAttributeData() + { + return array( + 'positive_string_edit' => array( + 'expected' => true, + 'attribute' => 'EDIT', + 'message' => 'expected TRUE given as attribute EDIT is supported', + ), + 'positive_string_create' => array( + 'expected' => true, + 'attribute' => 'CREATE', + 'message' => 'expected TRUE as given attribute CREATE is supported', + ), + + 'negative_string_read' => array( + 'expected' => false, + 'attribute' => 'READ', + 'message' => 'expected FALSE as given attribute READ is not supported', + ), + 'negative_string_random' => array( + 'expected' => false, + 'attribute' => 'random', + 'message' => 'expected FALSE as given attribute "random" is not supported', + ), + 'negative_string_0' => array( + 'expected' => false, + 'attribute' => '0', + 'message' => 'expected FALSE as given attribute "0" is not supported', + ), + // this set of data gives false positive if in_array is not used with strict flag set to 'true' + 'negative_int_0' => array( + 'expected' => false, + 'attribute' => 0, + 'message' => 'expected FALSE as given attribute 0 is not string', + ), + 'negative_int_1' => array( + 'expected' => false, + 'attribute' => 1, + 'message' => 'expected FALSE as given attribute 1 is not string', + ), + 'negative_int_7' => array( + 'expected' => false, + 'attribute' => 7, + 'message' => 'expected FALSE as attribute 7 is not string', + ), + ); + } + + /** + * @dataProvider getSupportsAttributeData + * + * @param bool $expected + * @param string $attribute + * @param string $message + */ + public function testSupportsAttribute($expected, $attribute, $message) + { + $voter = new AbstractVoterTest_Voter(); + + $this->assertEquals($expected, $voter->supportsAttribute($attribute), $message); + } } class AbstractVoterTest_Voter extends AbstractVoter From b587a7294f7049ed0d77875c46ba87ece0368a06 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 27 Mar 2015 23:19:17 +0800 Subject: [PATCH 2/4] [config] Fix issue when key removed and left value only When a key attribute is mapped and the key is removed from the value array, if only 'value' element is left in the array, it should replace its wrapper array. Assume the original value array is as follows (key attribute is 'id'). ```php array( 'things' => array( array('id' => 'option1', 'value' => 'value1'), array('id' => 'option2', 'value' => 'value2') ) ) ``` After normalized, the above shall be converted to the following array. ```php array( 'things' => array( 'option1' => 'value1', 'option2' => 'value2' ) ) ``` It's also possible to mix 'value-only' and 'none-value-only' elements in the array: ```php array( 'things' => array( array('id' => 'option1', 'value' => 'value1'), array('id' => 'option2', 'value' => 'value2', 'foo' => 'foo2') ) ) ``` The above shall be converted to the following array. ```php array( 'things' => array( 'option1' => 'value1', 'option2' => array('value' => 'value2','foo' => 'foo2') ) ) ``` The 'value' element can also be array: ```php array( 'things' => array( array( 'id' => 'option1', 'value' => array('foo'=>'foo1', 'bar' => 'bar1') ) ) ) ``` The above shall be converted to the following array. ```php array( 'things' => array( 'option1' => array('foo' => 'foo1', 'bar' => 'bar1') ) ) ``` When using VariableNode for value element, it's also possible to mix different types of value elements: ```php array( 'things' => array( array('id' => 'option1', 'value' => array('foo'=>'foo1', 'bar' => 'bar1')), array('id' => 'option2', 'value' => 'value2') ) ) ``` The above shall be converted to the following array. ```php array( 'things' => array( 'option1' => array('foo'=>'foo1', 'bar' => 'bar1'), 'option2' => 'value2' ) ) ``` | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #15270 | License | MIT | Doc PR | n/a --- .../Config/Definition/PrototypedArrayNode.php | 74 +++++++- .../Definition/PrototypedArrayNodeTest.php | 161 ++++++++++++++++++ 2 files changed, 227 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php index ae1a76663a..5c0fc1c603 100644 --- a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php +++ b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php @@ -29,6 +29,10 @@ class PrototypedArrayNode extends ArrayNode protected $minNumberOfElements = 0; protected $defaultValue = array(); protected $defaultChildren; + /** + * @var NodeInterface[] An array of the prototypes of the simplified value children + */ + private $valuePrototypes = array(); /** * Sets the minimum number of elements that a prototype based node must @@ -194,9 +198,9 @@ class PrototypedArrayNode extends ArrayNode } foreach ($value as $k => $v) { - $this->prototype->setName($k); + $prototype = $this->getPrototypeForChild($k); try { - $value[$k] = $this->prototype->finalize($v); + $value[$k] = $prototype->finalize($v); } catch (UnsetKeyException $e) { unset($value[$k]); } @@ -250,8 +254,18 @@ class PrototypedArrayNode extends ArrayNode } // if only "value" is left - if (1 == count($v) && isset($v['value'])) { + if (array_keys($v) === array('value')) { $v = $v['value']; + if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && array_key_exists('value', $children)) { + $valuePrototype = current($this->valuePrototypes) ?: clone($children['value']); + $valuePrototype->parent = $this; + $originalClosures = $this->prototype->normalizationClosures; + if (is_array($originalClosures)) { + $valuePrototypeClosures = $valuePrototype->normalizationClosures; + $valuePrototype->normalizationClosures = is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures; + } + $this->valuePrototypes[$k] = $valuePrototype; + } } } @@ -264,11 +278,11 @@ class PrototypedArrayNode extends ArrayNode } } - $this->prototype->setName($k); + $prototype = $this->getPrototypeForChild($k); if (null !== $this->keyAttribute || $isAssoc) { - $normalized[$k] = $this->prototype->normalize($v); + $normalized[$k] = $prototype->normalize($v); } else { - $normalized[] = $this->prototype->normalize($v); + $normalized[] = $prototype->normalize($v); } } @@ -322,10 +336,54 @@ class PrototypedArrayNode extends ArrayNode continue; } - $this->prototype->setName($k); - $leftSide[$k] = $this->prototype->merge($leftSide[$k], $v); + $prototype = $this->getPrototypeForChild($k); + $leftSide[$k] = $prototype->merge($leftSide[$k], $v); } return $leftSide; } + + /** + * Returns a prototype for the child node that is associated to $key in the value array. + * For general child nodes, this will be $this->prototype. + * But if $this->removeKeyAttribute is true and there are only two keys in the child node: + * one is same as this->keyAttribute and the other is 'value', then the prototype will be different. + * + * For example, assume $this->keyAttribute is 'name' and the value array is as follows: + * array( + * array( + * 'name' => 'name001', + * 'value' => 'value001' + * ) + * ) + * + * Now, the key is 0 and the child node is: + * array( + * 'name' => 'name001', + * 'value' => 'value001' + * ) + * + * When normalizing the value array, the 'name' element will removed from the child node + * and its value becomes the new key of the child node: + * array( + * 'name001' => array('value' => 'value001') + * ) + * + * Now only 'value' element is left in the child node which can be further simplified into a string: + * array('name001' => 'value001') + * + * Now, the key becomes 'name001' and the child node becomes 'value001' and + * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance. + * + * @param string $key The key of the child node + * + * @return mixed The prototype instance + */ + private function getPrototypeForChild($key) + { + $prototype = isset($this->valuePrototypes[$key]) ? $this->valuePrototypes[$key] : $this->prototype; + $prototype->setName($key); + + return $prototype; + } } diff --git a/src/Symfony/Component/Config/Tests/Definition/PrototypedArrayNodeTest.php b/src/Symfony/Component/Config/Tests/Definition/PrototypedArrayNodeTest.php index c343fcfd94..77013a14b2 100644 --- a/src/Symfony/Component/Config/Tests/Definition/PrototypedArrayNodeTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/PrototypedArrayNodeTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Config\Tests\Definition; use Symfony\Component\Config\Definition\PrototypedArrayNode; use Symfony\Component\Config\Definition\ArrayNode; use Symfony\Component\Config\Definition\ScalarNode; +use Symfony\Component\Config\Definition\VariableNode; class PrototypedArrayNodeTest extends \PHPUnit_Framework_TestCase { @@ -177,4 +178,164 @@ class PrototypedArrayNodeTest extends \PHPUnit_Framework_TestCase return $node; } + + /** + * Tests that when a key attribute is mapped, that key is removed from the array. + * And if only 'value' element is left in the array, it will replace its wrapper array. + * + * + * + * + * The above should finally be mapped to an array that looks like this + * (because "id" is the key attribute). + * + * array( + * 'things' => array( + * 'option1' => 'value1' + * ) + * ) + * + * It's also possible to mix 'value-only' and 'non-value-only' elements in the array. + * + * + * + * + * The above should finally be mapped to an array as follows + * + * array( + * 'things' => array( + * 'option1' => 'value1', + * 'option2' => array( + * 'value' => 'value2', + * 'foo' => 'foo2' + * ) + * ) + * ) + * + * The 'value' element can also be ArrayNode: + * + * + * + * + * + * The above should be finally be mapped to an array as follows + * + * array( + * 'things' => array( + * 'option1' => array( + * 'foo' => 'foo1', + * 'bar' => 'bar1' + * ) + * ) + * ) + * + * If using VariableNode for value node, it's also possible to mix different types of value nodes: + * + * + * + * + * + * The above should be finally mapped to an array as follows + * + * array( + * 'things' => array( + * 'option1' => array( + * 'foo' => 'foo1', + * 'bar' => 'bar1' + * ), + * 'option2' => 'value2' + * ) + * ) + * + * + * @dataProvider getDataForKeyRemovedLeftValueOnly + */ + public function testMappedAttributeKeyIsRemovedLeftValueOnly($value, $children, $expected) + { + $node = new PrototypedArrayNode('root'); + $node->setKeyAttribute('id', true); + + // each item under the root is an array, with one scalar item + $prototype = new ArrayNode(null, $node); + $prototype->addChild(new ScalarNode('id')); + $prototype->addChild(new ScalarNode('foo')); + $prototype->addChild($value); + $node->setPrototype($prototype); + + $normalized = $node->normalize($children); + $this->assertEquals($expected, $normalized); + } + + public function getDataForKeyRemovedLeftValueOnly() + { + $scalarValue = new ScalarNode('value'); + + $arrayValue = new ArrayNode('value'); + $arrayValue->addChild(new ScalarNode('foo')); + $arrayValue->addChild(new ScalarNode('bar')); + + $variableValue = new VariableNode('value'); + + return array( + array( + $scalarValue, + array( + array('id' => 'option1', 'value' => 'value1'), + ), + array('option1' => 'value1'), + ), + + array( + $scalarValue, + array( + array('id' => 'option1', 'value' => 'value1'), + array('id' => 'option2', 'value' => 'value2', 'foo' => 'foo2'), + ), + array( + 'option1' => 'value1', + 'option2' => array('value' => 'value2', 'foo' => 'foo2'), + ), + ), + + array( + $arrayValue, + array( + array( + 'id' => 'option1', + 'value' => array('foo' => 'foo1', 'bar' => 'bar1'), + ), + ), + array( + 'option1' => array('foo' => 'foo1', 'bar' => 'bar1'), + ), + ), + + array($variableValue, + array( + array( + 'id' => 'option1', 'value' => array('foo' => 'foo1', 'bar' => 'bar1'), + ), + array('id' => 'option2', 'value' => 'value2'), + ), + array( + 'option1' => array('foo' => 'foo1', 'bar' => 'bar1'), + 'option2' => 'value2', + ), + ), + ); + } } From 92423e77ac35eb9a2af1e3a3c44bca7531cba3cf Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 14 Dec 2016 09:03:29 +0100 Subject: [PATCH 3/4] fixed CS --- src/Symfony/Component/Config/Definition/PrototypedArrayNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php index 5c0fc1c603..1c3c218832 100644 --- a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php +++ b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php @@ -257,7 +257,7 @@ class PrototypedArrayNode extends ArrayNode if (array_keys($v) === array('value')) { $v = $v['value']; if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && array_key_exists('value', $children)) { - $valuePrototype = current($this->valuePrototypes) ?: clone($children['value']); + $valuePrototype = current($this->valuePrototypes) ?: clone $children['value']; $valuePrototype->parent = $this; $originalClosures = $this->prototype->normalizationClosures; if (is_array($originalClosures)) { From ca4c35164fad0ae6977250adf575f494a85cf249 Mon Sep 17 00:00:00 2001 From: Indra Gunawan Date: Wed, 14 Dec 2016 14:53:51 +0700 Subject: [PATCH 4/4] [Validator] add Indonesian translation --- .../Validator/Resources/translations/validators.id.xlf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf index c7061470dc..ce8db48639 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf @@ -310,6 +310,10 @@ This value does not match the expected {{ charset }} charset. Nilai ini tidak memenuhi set karakter {{ charset }} yang diharapkan. + + This is not a valid Business Identifier Code (BIC). + Ini bukan Business Identifier Code (BIC) yang sah. +