Merge branch '4.1' into 4.2

* 4.1:
  [Routing] dont redirect routes with greedy trailing vars with no explicit slash
  skip native serialize among child and parent serializable objects
  [Routing] backport tests from 4.1
  [MonologBridge] Remove unused local variable
  Remove unreachable code
  Add PackageNameTest to ConfigurationTest also add in the changelog the corresponding entry to this PR
  Support use of hyphen in asset package name
  Remove gendered pronouns
  Replace gender by eye color in tests
  [Security] dont do nested calls to serialize()
This commit is contained in:
Nicolas Grekas 2019-01-29 10:49:29 +01:00
commit adbdec838a
31 changed files with 183 additions and 105 deletions

View File

@ -60,7 +60,6 @@ class HttpCodeActivationStrategy extends ErrorLevelActivationStrategy
continue;
}
$urlBlacklist = null;
if (\count($exclusion['urls'])) {
return !preg_match('{('.implode('|', $exclusion['urls']).')}i', $request->getPathInfo());
}

View File

@ -648,6 +648,7 @@ class Configuration implements ConfigurationInterface
->fixXmlConfig('package')
->children()
->arrayNode('packages')
->normalizeKeys(false)
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('base_url')

View File

@ -102,6 +102,35 @@ class ConfigurationTest extends TestCase
$this->assertEquals($defaultConfig, $config['assets']);
}
/**
* @dataProvider provideValidAssetsPackageNameConfigurationTests
*/
public function testValidAssetsPackageNameConfiguration($packageName)
{
$processor = new Processor();
$configuration = new Configuration(true);
$config = $processor->processConfiguration($configuration, [
[
'assets' => [
'packages' => [
$packageName => [],
],
],
],
]);
$this->assertArrayHasKey($packageName, $config['assets']['packages']);
}
public function provideValidAssetsPackageNameConfigurationTests()
{
return [
['foobar'],
['foo-bar'],
['foo_bar'],
];
}
/**
* @dataProvider provideInvalidAssetConfigurationTests
*/

View File

@ -4,5 +4,5 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization;
class Author
{
public $gender;
public $eyeColor;
}

View File

@ -4,5 +4,5 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization;
class Person
{
public $gender;
public $eyeColor;
}

View File

@ -1,4 +1,4 @@
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization\Author:
attributes:
gender:
eyeColor:
groups: ['group1', 'group2']

View File

@ -5,7 +5,7 @@
http://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
>
<class name="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization\Person">
<attribute name="gender">
<attribute name="eyeColor">
<group>group1</group>
<group>group2</group>
</attribute>

View File

@ -4,5 +4,5 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation;
class Author
{
public $gender;
public $eyeColor;
}

View File

@ -4,5 +4,5 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation;
class Person
{
public $gender;
public $eyeColor;
}

View File

@ -1,4 +1,4 @@
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Author:
properties:
gender:
- Choice: { choices: [male, female, other], message: Choose a valid gender. }
eyeColor:
- Choice: { choices: [brown, green, blue], message: Choose a valid eye color. }

View File

@ -4,14 +4,14 @@
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Person">
<property name="gender">
<property name="eyeColor">
<constraint name="Choice">
<option name="choices">
<value>male</value>
<value>female</value>
<value>other</value>
<value>brown</value>
<value>green</value>
<value>blue</value>
</option>
<option name="message">Choose a valid gender.</option>
<option name="message">Choose a valid eye color.</option>
</constraint>
</property>
</class>

View File

@ -163,9 +163,9 @@ class FormRenderer implements FormRendererInterface
// to implement a custom "choice_widget" block (no matter in which theme),
// or to fallback to the block of the parent type, which would be
// "form_widget" in this example (again, no matter in which theme).
// If the designer wants to explicitly fallback to "form_widget" in his
// custom "choice_widget", for example because he only wants to wrap
// a <div> around the original implementation, he can simply call the
// If the designer wants to explicitly fallback to "form_widget" in their
// custom "choice_widget", for example because they only want to wrap
// a <div> around the original implementation, they can simply call the
// widget() function again to render the block for the parent type.
//
// The second kind is implemented in the following blocks.

View File

@ -470,7 +470,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
// Insert the start tag, the end tag should be rendered by the helper
// Unfortunately this is not valid HTML, because the surrounding table
// tag is missing. If someone renders a form with table layout
// manually, she should call form_rest() explicitly within the <table>
// manually, they should call form_rest() explicitly within the <table>
// tag.
$this->assertMatchesXpath('<form>'.$html,
'/form

View File

@ -783,14 +783,14 @@ class RequestTest extends TestCase
// GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
// PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str.
['him=John%20Doe&her=Jane+Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes spaces in both encodings "%20" and "+"'],
['baz=Foo%20Baz&bar=Foo+Bar', 'bar=Foo%20Bar&baz=Foo%20Baz', 'normalizes spaces in both encodings "%20" and "+"'],
['foo[]=1&foo[]=2', 'foo%5B0%5D=1&foo%5B1%5D=2', 'allows array notation'],
['foo=1&foo=2', 'foo=2', 'merges repeated parameters'],
['pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'],
['0', '0=', 'allows "0"'],
['Jane Doe&John%20Doe', 'Jane_Doe=&John_Doe=', 'normalizes encoding in keys'],
['her=Jane Doe&him=John%20Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes encoding in values'],
['Foo Bar&Foo%20Baz', 'Foo_Bar=&Foo_Baz=', 'normalizes encoding in keys'],
['bar=Foo Bar&baz=Foo%20Baz', 'bar=Foo%20Bar&baz=Foo%20Baz', 'normalizes encoding in values'],
['foo=bar&&&test&&', 'foo=bar&test=', 'removes unneeded delimiters'],
['formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'],

View File

@ -130,21 +130,16 @@ trait PhpMatcherTrait
continue;
}
if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar) {
// no-op
} elseif (preg_match($regex, $this->matchHost ? $host.'.'.$trimmedPathinfo : $trimmedPathinfo, $n) && $m === (int) $n['MARK']) {
$matches = $n;
} else {
$hasTrailingSlash = true;
}
if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && $hasTrailingVar;
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
return $allow = $allowSchemes = [];
}
if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar) {
continue;
}
continue;
}
if ($hasTrailingSlash && $hasTrailingVar && preg_match($regex, $this->matchHost ? $host.'.'.$trimmedPathinfo : $trimmedPathinfo, $n) && $m === (int) $n['MARK']) {
$matches = $n;
}
foreach ($vars as $i => $v) {

View File

@ -156,21 +156,18 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
continue;
}
if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar = preg_match('#\{\w+\}/?$#', $route->getPath())) {
// no-op
} elseif (preg_match($regex, $trimmedPathinfo, $m)) {
$matches = $m;
} else {
$hasTrailingSlash = true;
}
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());
if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) {
return $this->allow = $this->allowSchemes = [];
}
if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar) {
continue;
}
continue;
}
if ($hasTrailingSlash && $hasTrailingVar && preg_match($regex, $trimmedPathinfo, $m)) {
$matches = $m;
}
$hostMatches = [];

View File

@ -711,12 +711,12 @@ class UrlMatcherTest extends TestCase
$this->assertSame(['_route' => 'b'], $matcher->match('/bar/'));
$coll = new RouteCollection();
$coll->add('a', new Route('/dav/{foo<.*>?}', [], [], [], '', [], ['GET', 'OPTIONS']));
$coll->add('a', new Route('/dav/{foo}', [], ['foo' => '.*'], [], '', [], ['GET', 'OPTIONS']));
$matcher = $this->getUrlMatcher($coll, new RequestContext('', 'OPTIONS'));
$expected = [
'_route' => 'a',
'foo' => 'files/bar',
'foo' => 'files/bar/',
];
$this->assertEquals($expected, $matcher->match('/dav/files/bar/'));
}
@ -746,6 +746,17 @@ class UrlMatcherTest extends TestCase
$this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons'));
}
public function testGreedyTrailingRequirement()
{
$coll = new RouteCollection();
$coll->add('a', new Route('/{a}', [], ['a' => '.+']));
$matcher = $this->getUrlMatcher($coll);
$this->assertEquals(['_route' => 'a', 'a' => 'foo'], $matcher->match('/foo'));
$this->assertEquals(['_route' => 'a', 'a' => 'foo/'], $matcher->match('/foo/'));
}
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
return new UrlMatcher($routes, $context ?: new RequestContext());

View File

@ -136,14 +136,9 @@ abstract class AbstractToken implements TokenInterface
*/
public function serialize()
{
return serialize(
[
\is_object($this->user) ? clone $this->user : $this->user,
$this->authenticated,
array_map(function ($role) { return clone $role; }, $this->roles),
$this->attributes,
]
);
$serialized = [$this->user, $this->authenticated, $this->roles, $this->attributes];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -151,7 +146,7 @@ abstract class AbstractToken implements TokenInterface
*/
public function unserialize($serialized)
{
list($this->user, $this->authenticated, $this->roles, $this->attributes) = unserialize($serialized);
list($this->user, $this->authenticated, $this->roles, $this->attributes) = \is_array($serialized) ? $serialized : unserialize($serialized);
}
/**
@ -231,6 +226,19 @@ abstract class AbstractToken implements TokenInterface
return sprintf('%s(user="%s", authenticated=%s, roles="%s")', $class, $this->getUsername(), json_encode($this->authenticated), implode(', ', $roles));
}
/**
* @internal
*/
protected function doSerialize($serialized, $isCalledFromOverridingMethod)
{
if (null === $isCalledFromOverridingMethod) {
$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 3);
$isCalledFromOverridingMethod = isset($trace[2]['function'], $trace[2]['object']) && 'serialize' === $trace[2]['function'] && $this === $trace[2]['object'];
}
return $isCalledFromOverridingMethod ? $serialized : serialize($serialized);
}
private function hasUserChanged(UserInterface $user)
{
if (!($this->user instanceof UserInterface)) {

View File

@ -59,7 +59,9 @@ class AnonymousToken extends AbstractToken
*/
public function serialize()
{
return serialize([$this->secret, parent::serialize()]);
$serialized = [$this->secret, parent::serialize(true)];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -67,7 +69,7 @@ class AnonymousToken extends AbstractToken
*/
public function unserialize($serialized)
{
list($this->secret, $parentStr) = unserialize($serialized);
list($this->secret, $parentStr) = \is_array($serialized) ? $serialized : unserialize($serialized);
parent::unserialize($parentStr);
}
}

View File

@ -79,7 +79,9 @@ class PreAuthenticatedToken extends AbstractToken
*/
public function serialize()
{
return serialize([$this->credentials, $this->providerKey, parent::serialize()]);
$serialized = [$this->credentials, $this->providerKey, parent::serialize(true)];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -87,7 +89,7 @@ class PreAuthenticatedToken extends AbstractToken
*/
public function unserialize($str)
{
list($this->credentials, $this->providerKey, $parentStr) = unserialize($str);
list($this->credentials, $this->providerKey, $parentStr) = \is_array($str) ? $str : unserialize($str);
parent::unserialize($parentStr);
}
}

View File

@ -94,11 +94,9 @@ class RememberMeToken extends AbstractToken
*/
public function serialize()
{
return serialize([
$this->secret,
$this->providerKey,
parent::serialize(),
]);
$serialized = [$this->secret, $this->providerKey, parent::serialize(true)];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -106,7 +104,7 @@ class RememberMeToken extends AbstractToken
*/
public function unserialize($serialized)
{
list($this->secret, $this->providerKey, $parentStr) = unserialize($serialized);
list($this->secret, $this->providerKey, $parentStr) = \is_array($serialized) ? $serialized : unserialize($serialized);
parent::unserialize($parentStr);
}
}

View File

@ -91,7 +91,9 @@ class UsernamePasswordToken extends AbstractToken
*/
public function serialize()
{
return serialize([$this->credentials, $this->providerKey, parent::serialize()]);
$serialized = [$this->credentials, $this->providerKey, parent::serialize(true)];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -99,7 +101,7 @@ class UsernamePasswordToken extends AbstractToken
*/
public function unserialize($serialized)
{
list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized);
list($this->credentials, $this->providerKey, $parentStr) = \is_array($serialized) ? $serialized : unserialize($serialized);
parent::unserialize($parentStr);
}
}

View File

@ -44,10 +44,9 @@ abstract class AccountStatusException extends AuthenticationException
*/
public function serialize()
{
return serialize([
$this->user,
parent::serialize(),
]);
$serialized = [$this->user, parent::serialize(true)];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -55,7 +54,7 @@ abstract class AccountStatusException extends AuthenticationException
*/
public function unserialize($str)
{
list($this->user, $parentData) = unserialize($str);
list($this->user, $parentData) = \is_array($str) ? $str : unserialize($str);
parent::unserialize($parentData);
}

View File

@ -38,15 +38,33 @@ class AuthenticationException extends RuntimeException implements \Serializable
$this->token = $token;
}
/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize([
$serialized = [
$this->token,
$this->code,
$this->message,
$this->file,
$this->line,
]);
];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
* @internal
*/
protected function doSerialize($serialized, $isCalledFromOverridingMethod)
{
if (null === $isCalledFromOverridingMethod) {
$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 3);
$isCalledFromOverridingMethod = isset($trace[2]['function'], $trace[2]['object']) && 'serialize' === $trace[2]['function'] && $this === $trace[2]['object'];
}
return $isCalledFromOverridingMethod ? $serialized : serialize($serialized);
}
public function unserialize($str)
@ -57,7 +75,7 @@ class AuthenticationException extends RuntimeException implements \Serializable
$this->message,
$this->file,
$this->line
) = unserialize($str);
) = \is_array($str) ? $str : unserialize($str);
}
/**

View File

@ -60,11 +60,9 @@ class CustomUserMessageAuthenticationException extends AuthenticationException
*/
public function serialize()
{
return serialize([
parent::serialize(),
$this->messageKey,
$this->messageData,
]);
return serialize([parent::serialize(true), $this->messageKey, $this->messageData]);
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -72,7 +70,7 @@ class CustomUserMessageAuthenticationException extends AuthenticationException
*/
public function unserialize($str)
{
list($parentData, $this->messageKey, $this->messageData) = unserialize($str);
list($parentData, $this->messageKey, $this->messageData) = \is_array($str) ? $str : unserialize($str);
parent::unserialize($parentData);
}

View File

@ -54,10 +54,9 @@ class UsernameNotFoundException extends AuthenticationException
*/
public function serialize()
{
return serialize([
$this->username,
parent::serialize(),
]);
$serialized = [$this->username, parent::serialize(true)];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -65,7 +64,7 @@ class UsernameNotFoundException extends AuthenticationException
*/
public function unserialize($str)
{
list($this->username, $parentData) = unserialize($str);
list($this->username, $parentData) = \is_array($str) ? $str : unserialize($str);
parent::unserialize($parentData);
}

View File

@ -43,9 +43,14 @@ class ConcreteToken extends AbstractToken
$this->setUser($user);
}
/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize([$this->credentials, parent::serialize()]);
$serialized = [$this->credentials, parent::serialize(true)];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
public function unserialize($serialized)

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Core\Tests\Exception;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
class CustomUserMessageAuthenticationExceptionTest extends TestCase
@ -24,4 +25,18 @@ class CustomUserMessageAuthenticationExceptionTest extends TestCase
$this->assertEquals(['foo' => true], $e->getMessageData());
$this->assertEquals('SAFE MESSAGE', $e->getMessage());
}
public function testSharedSerializedData()
{
$token = new AnonymousToken('foo', 'bar');
$exception = new CustomUserMessageAuthenticationException();
$exception->setToken($token);
$exception->setSafeMessage('message', ['token' => $token]);
$processed = unserialize(serialize($exception));
$this->assertEquals($token, $processed->getToken());
$this->assertEquals($token, $processed->getMessageData()['token']);
$this->assertSame($processed->getToken(), $processed->getMessageData()['token']);
}
}

View File

@ -76,7 +76,9 @@ class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenIn
*/
public function serialize()
{
return serialize([$this->providerKey, parent::serialize()]);
$serialized = [$this->providerKey, parent::serialize(true)];
return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null);
}
/**
@ -84,7 +86,7 @@ class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenIn
*/
public function unserialize($serialized)
{
list($this->providerKey, $parentStr) = unserialize($serialized);
list($this->providerKey, $parentStr) = \is_array($serialized) ? $serialized : unserialize($serialized);
parent::unserialize($parentStr);
}
}

View File

@ -193,11 +193,11 @@ XML;
{
$array = [
'#' => 'Paul',
'@gender' => 'm',
'@eye-color' => 'brown',
];
$expected = '<?xml version="1.0"?>'."\n".
'<response gender="m">Paul</response>'."\n";
'<response eye-color="brown">Paul</response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
@ -206,11 +206,11 @@ XML;
{
$array = [
'firstname' => 'Paul',
'@gender' => 'm',
'@eye-color' => 'brown',
];
$expected = '<?xml version="1.0"?>'."\n".
'<response gender="m"><firstname>Paul</firstname></response>'."\n";
'<response eye-color="brown"><firstname>Paul</firstname></response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
@ -230,11 +230,11 @@ XML;
public function testEncodeScalarWithAttribute()
{
$array = [
'person' => ['@gender' => 'M', '#' => 'Peter'],
'person' => ['@eye-color' => 'brown', '#' => 'Peter'],
];
$expected = '<?xml version="1.0"?>'."\n".
'<response><person gender="M">Peter</person></response>'."\n";
'<response><person eye-color="brown">Peter</person></response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
@ -333,11 +333,11 @@ XML;
$this->encoder->setSerializer($serializer);
$array = [
'person' => ['@gender' => 'M', '#' => 'Peter'],
'person' => ['@eye-color' => 'brown', '#' => 'Peter'],
];
$expected = '<?xml version="1.0"?>'."\n".
'<test><person gender="M">Peter</person></test>'."\n";
'<test><person eye-color="brown">Peter</person></test>'."\n";
$this->assertEquals($expected, $serializer->serialize($array, 'xml', $options));
}
@ -401,10 +401,10 @@ XML;
public function testDecodeScalarWithAttribute()
{
$source = '<?xml version="1.0"?>'."\n".
'<response><person gender="M">Peter</person></response>'."\n";
'<response><person eye-color="brown">Peter</person></response>'."\n";
$expected = [
'person' => ['@gender' => 'M', '#' => 'Peter'],
'person' => ['@eye-color' => 'brown', '#' => 'Peter'],
];
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
@ -413,11 +413,11 @@ XML;
public function testDecodeScalarRootAttributes()
{
$source = '<?xml version="1.0"?>'."\n".
'<person gender="M">Peter</person>'."\n";
'<person eye-color="brown">Peter</person>'."\n";
$expected = [
'#' => 'Peter',
'@gender' => 'M',
'@eye-color' => 'brown',
];
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
@ -426,12 +426,12 @@ XML;
public function testDecodeRootAttributes()
{
$source = '<?xml version="1.0"?>'."\n".
'<person gender="M"><firstname>Peter</firstname><lastname>Mac Calloway</lastname></person>'."\n";
'<person eye-color="brown"><firstname>Peter</firstname><lastname>Mac Calloway</lastname></person>'."\n";
$expected = [
'firstname' => 'Peter',
'lastname' => 'Mac Calloway',
'@gender' => 'M',
'@eye-color' => 'brown',
];
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));

View File

@ -91,7 +91,5 @@ class Connection
} finally {
restore_error_handler();
}
return $socket;
}
}