diff --git a/.travis.php b/.github/travis.php
similarity index 82%
rename from .travis.php
rename to .github/travis.php
index f0ec7f3df6..6773a6edcc 100644
--- a/.travis.php
+++ b/.github/travis.php
@@ -1,19 +1,23 @@
$_SERVER['argc']) {
- echo "Usage: branch dir1 dir2 ... dirN\n";
+ echo "Usage: branch version dir1 dir2 ... dirN\n";
exit(1);
}
$dirs = $_SERVER['argv'];
array_shift($dirs);
$branch = array_shift($dirs);
+$version = array_shift($dirs);
$packages = array();
$flags = PHP_VERSION_ID >= 50400 ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE : 0;
foreach ($dirs as $dir) {
- if (!`git diff --name-only $branch...HEAD -- $dir`) {
+ if (!system("git diff --name-only $branch...HEAD -- $dir", $exitStatus)) {
+ if ($exitStatus) {
+ exit($exitStatus);
+ }
continue;
}
echo "$dir\n";
@@ -32,7 +36,7 @@ foreach ($dirs as $dir) {
file_put_contents($dir.'/composer.json', $json);
passthru("cd $dir && tar -cf package.tar --exclude='package.tar' *");
- $package->version = 'master' !== $branch ? $branch.'.x-dev' : 'dev-master';
+ $package->version = 'master' !== $version ? $version.'.x-dev' : 'dev-master';
$package->dist['type'] = 'tar';
$package->dist['url'] = 'file://'.__DIR__."/$dir/package.tar";
diff --git a/.travis.yml b/.travis.yml
index 4d8bac1c4b..a6f39ae486 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -72,7 +72,7 @@ before_install:
install:
- if [[ ! $skip ]]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi
# Create local composer packages for each patched components and reference them in composer.json files when cross-testing components
- - if [[ ! $skip && $deps ]]; then php .travis.php $TRAVIS_BRANCH $COMPONENTS; fi
+ - if [[ ! $skip && $deps ]]; then git fetch origin $TRAVIS_BRANCH && php .github/travis.php FETCH_HEAD $TRAVIS_BRANCH $COMPONENTS; fi
# For the master branch when deps=high, the version before master is checked out and tested with the locally patched components
- if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then SYMFONY_VERSION=$(git ls-remote --heads | grep -o '/[1-9].*' | tail -n 1 | sed s/.//); else SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*'); fi
- if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then git fetch origin $SYMFONY_VERSION; git checkout -m FETCH_HEAD; COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); ./phpunit install; fi
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php
new file mode 100644
index 0000000000..a0a7612458
--- /dev/null
+++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
+
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ORM\Entity
+ */
+class AssociationEntity2
+{
+ /**
+ * @var int
+ * @ORM\Id @ORM\GeneratedValue
+ * @ORM\Column(type="integer")
+ */
+ private $id;
+
+ /**
+ * @ORM\ManyToOne(targetEntity="SingleIntIdNoToStringEntity")
+ *
+ * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity
+ */
+ public $single;
+
+ /**
+ * @ORM\ManyToOne(targetEntity="CompositeIntIdEntity")
+ * @ORM\JoinColumns({
+ * @ORM\JoinColumn(name="composite_id1", referencedColumnName="id1"),
+ * @ORM\JoinColumn(name="composite_id2", referencedColumnName="id2")
+ * })
+ *
+ * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity
+ */
+ public $composite;
+}
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
index 0511f8f848..9b3e6e78e4 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
@@ -19,6 +19,8 @@ use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity;
+use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity2;
+use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator;
use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest;
@@ -127,9 +129,11 @@ class UniqueEntityValidatorTest extends AbstractConstraintValidatorTest
$schemaTool = new SchemaTool($em);
$schemaTool->createSchema(array(
$em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'),
+ $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity'),
$em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity'),
$em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity'),
$em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity'),
+ $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity2'),
));
}
@@ -408,6 +412,42 @@ class UniqueEntityValidatorTest extends AbstractConstraintValidatorTest
->assertRaised();
}
+ public function testValidateUniquenessNotToStringEntityWithAssociatedEntity()
+ {
+ $constraint = new UniqueEntity(array(
+ 'message' => 'myMessage',
+ 'fields' => array('single'),
+ 'em' => self::EM_NAME,
+ ));
+
+ $entity1 = new SingleIntIdNoToStringEntity(1, 'foo');
+ $associated = new AssociationEntity2();
+ $associated->single = $entity1;
+ $associated2 = new AssociationEntity2();
+ $associated2->single = $entity1;
+
+ $this->em->persist($entity1);
+ $this->em->persist($associated);
+ $this->em->flush();
+
+ $this->validator->validate($associated, $constraint);
+
+ $this->assertNoViolation();
+
+ $this->em->persist($associated2);
+ $this->em->flush();
+
+ $this->validator->validate($associated2, $constraint);
+
+ $expectedValue = 'Object of class "Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity2" identified by "2"';
+
+ $this->buildViolation('myMessage')
+ ->atPath('property.path.single')
+ ->setParameter('{{ value }}', $expectedValue)
+ ->setInvalidValue($expectedValue)
+ ->assertRaised();
+ }
+
public function testAssociatedEntityWithNull()
{
$constraint = new UniqueEntity(array(
diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
index 1b16f5237e..c85c344b94 100644
--- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
+++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
@@ -127,6 +127,10 @@ class UniqueEntityValidator extends ConstraintValidator
$errorPath = null !== $constraint->errorPath ? $constraint->errorPath : $fields[0];
$invalidValue = isset($criteria[$errorPath]) ? $criteria[$errorPath] : $criteria[$fields[0]];
+ if (is_object($invalidValue) && !method_exists($invalidValue, '__toString')) {
+ $invalidValue = sprintf('Object of class "%s" identified by "%s"', get_class($entity), implode(', ', $class->getIdentifierValues($entity)));
+ }
+
$this->context->buildViolation($constraint->message)
->atPath($errorPath)
->setParameter('{{ value }}', $invalidValue)
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml
index 9d7b893f20..5c1f78b1c4 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml
@@ -61,5 +61,9 @@
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index 77e1e900a7..c30d1dfe9e 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -24,7 +24,7 @@
"symfony/config": "~2.8|~3.0",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/http-foundation": "~3.1",
- "symfony/http-kernel": "~3.1",
+ "symfony/http-kernel": "~3.1.2|~3.2",
"symfony/polyfill-mbstring": "~1.0",
"symfony/filesystem": "~2.8|~3.0",
"symfony/finder": "~2.8|~3.0",
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml
index 738455358b..de157d5182 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_debug.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_debug.xml
index 4312d74741..eb22cbddf5 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_debug.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_debug.xml
@@ -1,8 +1,8 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json
index ea20c7c837..40d2e04741 100644
--- a/src/Symfony/Bundle/SecurityBundle/composer.json
+++ b/src/Symfony/Bundle/SecurityBundle/composer.json
@@ -17,7 +17,7 @@
],
"require": {
"php": ">=5.5.9",
- "symfony/security": "~3.1",
+ "symfony/security": "~3.1,>=3.1.2",
"symfony/http-kernel": "~2.8|~3.0",
"symfony/polyfill-php70": "~1.0"
},
diff --git a/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php b/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php
new file mode 100644
index 0000000000..00096ccf9e
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Validates that the headers and other information indicating the
+ * client IP address of a request are consistent.
+ *
+ * @author Magnus Nordlander
+ */
+class ValidateRequestListener implements EventSubscriberInterface
+{
+ /**
+ * Performs the validation.
+ *
+ * @param GetResponseEvent $event
+ */
+ public function onKernelRequest(GetResponseEvent $event)
+ {
+ if (!$event->isMasterRequest()) {
+ return;
+ }
+ $request = $event->getRequest();
+
+ if ($request::getTrustedProxies()) {
+ // This will throw an exception if the headers are inconsistent.
+ $request->getClientIps();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents()
+ {
+ return array(
+ KernelEvents::REQUEST => array(
+ array('onKernelRequest', 256),
+ ),
+ );
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php
index 573a1e232d..c63a6af832 100644
--- a/src/Symfony/Component/HttpKernel/HttpKernel.php
+++ b/src/Symfony/Component/HttpKernel/HttpKernel.php
@@ -67,6 +67,9 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
try {
return $this->handleRaw($request, $type);
} catch (\Exception $e) {
+ if ($e instanceof ConflictingHeadersException) {
+ $e = new BadRequestHttpException('The request headers contain conflicting information regarding the origin of this request.', $e);
+ }
if (false === $catch) {
$this->finishRequest($request, $type);
@@ -119,13 +122,6 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
*/
private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
- if (self::MASTER_REQUEST === $type && $request::getTrustedProxies()) {
- try {
- $request->getClientIps();
- } catch (ConflictingHeadersException $e) {
- throw new BadRequestHttpException('The request headers contain conflicting information regarding the origin of this request.', $e);
- }
- }
$this->requestStack->push($request);
// request
diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php
new file mode 100644
index 0000000000..842a3869cb
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\Tests\EventListener;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+class ValidateRequestListenerTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @expectedException Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException
+ */
+ public function testListenerThrowsWhenMasterRequestHasInconsistentClientIps()
+ {
+ $dispatcher = new EventDispatcher();
+ $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
+
+ $request = new Request();
+ $request->setTrustedProxies(array('1.1.1.1'));
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $request->headers->set('FORWARDED', '2.2.2.2');
+ $request->headers->set('X_FORWARDED_FOR', '3.3.3.3');
+
+ $dispatcher->addListener(KernelEvents::REQUEST, array(new ValidateRequestListener(), 'onKernelRequest'));
+ $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST);
+
+ $dispatcher->dispatch(KernelEvents::REQUEST, $event);
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php
index 66de833045..8587aa3aae 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php
@@ -307,28 +307,21 @@ class HttpKernelTest extends \PHPUnit_Framework_TestCase
*/
public function testInconsistentClientIpsOnMasterRequests()
{
- $kernel = $this->getHttpKernel(new EventDispatcher());
$request = new Request();
$request->setTrustedProxies(array('1.1.1.1'));
$request->server->set('REMOTE_ADDR', '1.1.1.1');
$request->headers->set('FORWARDED', '2.2.2.2');
$request->headers->set('X_FORWARDED_FOR', '3.3.3.3');
+ $dispatcher = new EventDispatcher();
+ $dispatcher->addListener(KernelEvents::REQUEST, function ($event) {
+ $event->getRequest()->getClientIp();
+ });
+
+ $kernel = $this->getHttpKernel($dispatcher);
$kernel->handle($request, $kernel::MASTER_REQUEST, false);
}
- public function testInconsistentClientIpsOnSubRequests()
- {
- $kernel = $this->getHttpKernel(new EventDispatcher());
- $request = new Request();
- $request->setTrustedProxies(array('1.1.1.1'));
- $request->server->set('REMOTE_ADDR', '1.1.1.1');
- $request->headers->set('FORWARDED', '2.2.2.2');
- $request->headers->set('X_FORWARDED_FOR', '3.3.3.3');
-
- $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $kernel->handle($request, $kernel::SUB_REQUEST, false));
- }
-
private function getHttpKernel(EventDispatcherInterface $eventDispatcher, $controller = null, RequestStack $requestStack = null, array $arguments = array())
{
if (null === $controller) {
diff --git a/src/Symfony/Component/Security/Core/Authorization/DebugAccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/DebugAccessDecisionManager.php
index 540d998206..1a04bc1f60 100644
--- a/src/Symfony/Component/Security/Core/Authorization/DebugAccessDecisionManager.php
+++ b/src/Symfony/Component/Security/Core/Authorization/DebugAccessDecisionManager.php
@@ -26,17 +26,19 @@ class DebugAccessDecisionManager implements AccessDecisionManagerInterface
{
private $manager;
private $strategy;
- private $voters;
+ private $voters = array();
private $decisionLog = array();
- public function __construct(AccessDecisionManager $manager)
+ public function __construct(AccessDecisionManagerInterface $manager)
{
$this->manager = $manager;
- // The strategy is stored in a private property of the decorated service
- $reflection = new \ReflectionProperty($manager, 'strategy');
- $reflection->setAccessible(true);
- $this->strategy = $reflection->getValue($manager);
+ if ($this->manager instanceof AccessDecisionManager) {
+ // The strategy is stored in a private property of the decorated service
+ $reflection = new \ReflectionProperty(AccessDecisionManager::class, 'strategy');
+ $reflection->setAccessible(true);
+ $this->strategy = $reflection->getValue($manager);
+ }
}
/**
@@ -60,6 +62,10 @@ class DebugAccessDecisionManager implements AccessDecisionManagerInterface
*/
public function setVoters(array $voters)
{
+ if (!$this->manager instanceof AccessDecisionManager) {
+ return;
+ }
+
$this->voters = $voters;
$this->manager->setVoters($voters);
}
@@ -72,7 +78,7 @@ class DebugAccessDecisionManager implements AccessDecisionManagerInterface
// The $strategy property is misleading because it stores the name of its
// method (e.g. 'decideAffirmative') instead of the original strategy name
// (e.g. 'affirmative')
- return strtolower(substr($this->strategy, 6));
+ return null === $this->strategy ? '-' : strtolower(substr($this->strategy, 6));
}
/**