diff --git a/.travis.yml b/.travis.yml index 19d9f9d2e..52433a15f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ cache: env: global: - PATH="$HOME/.composer/vendor/bin:$PATH" - - SYMFONY_DEPRECATIONS_HELPER=weak matrix: fast_finish: true @@ -31,16 +30,16 @@ matrix: env: SYMFONY_VERSION=2.3.* - php: 5.6 env: SYMFONY_VERSION=2.7.* - - php: 5.6 - env: SYMFONY_VERSION=2.8.* - php: 5.5 - env: SYMFONY_VERSION="3.0.*" ACL_VERSION="dev-master" + env: SYMFONY_VERSION=3.0.* ACL_VERSION="dev-master" - php: 5.6 - env: SYMFONY_VERSION="3.0.*" ACL_VERSION="dev-master" + env: SYMFONY_VERSION=3.0.* ACL_VERSION="dev-master" - php: 7.0 - env: SYMFONY_VERSION="3.0.*" ACL_VERSION="dev-master" + env: SYMFONY_VERSION=3.0.* ACL_VERSION="dev-master" allow_failures: + - php: 7.0 + env: SYMFONY_VERSION=3.0.* ACL_VERSION="dev-master" - php: hhvm before_script: diff --git a/CDN/CloudFront.php b/CDN/CloudFront.php index 9186a5d91..748eed462 100644 --- a/CDN/CloudFront.php +++ b/CDN/CloudFront.php @@ -171,7 +171,7 @@ private function getClient() public function setClient($client) { if (!$client instanceof CloudFrontClient) { - trigger_error('The '.__METHOD__.' expects a CloudFrontClient as parameter.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' expects a CloudFrontClient as parameter.', E_USER_DEPRECATED); } $this->client = $client; diff --git a/DependencyInjection/Compiler/SecurityContextCompilerPass.php b/DependencyInjection/Compiler/SecurityContextCompilerPass.php new file mode 100644 index 000000000..bfd43112b --- /dev/null +++ b/DependencyInjection/Compiler/SecurityContextCompilerPass.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\MediaBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * SecurityContextCompilerPass. + * + * This compiler pass provides compatibility with Syfmony < 2.6 security.context service + * and 2.6+ security.authorization_checker service. This pass may be removed when support + * for Symfony < 2.6 is dropped. + */ +class SecurityContextCompilerPass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + // Prefer the security.authorization_checker service + if ($container->hasDefinition('security.authorization_checker')) { + $security = $container->getDefinition('security.authorization_checker'); + } else { + $security = $container->getDefinition('security.context'); + } + + $container->getDefinition('sonata.media.security.superadmin_strategy') + ->replaceArgument(1, $security); + + $container->getDefinition('sonata.media.security.connected_strategy') + ->replaceArgument(1, $security); + } +} diff --git a/Model/Media.php b/Model/Media.php index 4daa3e237..16e526435 100644 --- a/Model/Media.php +++ b/Model/Media.php @@ -13,7 +13,8 @@ use Imagine\Image\Box; use Sonata\ClassificationBundle\Model\CategoryInterface; -use Symfony\Component\Validator\ExecutionContextInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface; abstract class Media implements MediaInterface { @@ -611,12 +612,23 @@ public function getPreviousProviderReference() } /** - * @param ExecutionContextInterface $context + * @param ExecutionContextInterface|LegacyExecutionContextInterface $context */ - public function isStatusErroneous(ExecutionContextInterface $context) + public function isStatusErroneous($context) { if ($this->getBinaryContent() && $this->getProviderStatus() == self::STATUS_ERROR) { - $context->addViolationAt('binaryContent', 'invalid', array(), null); + // Interface compatibility, the new ExecutionContextInterface should be typehinted when support for Symfony <2.5 is dropped + if (!$context instanceof ExecutionContextInterface && !$context instanceof LegacyExecutionContextInterface) { + throw new \InvalidArgumentException('Argument 1 should be an instance of Symfony\Component\Validator\ExecutionContextInterface or Symfony\Component\Validator\Context\ExecutionContextInterface'); + } + + if ($context instanceof LegacyExecutionContextInterface) { + $context->addViolationAt('binaryContent', 'invalid', array(), null); + } else { + $context->buildViolation('invalid') + ->atPath('binaryContent') + ->addViolation(); + } } } diff --git a/Resources/config/security.xml b/Resources/config/security.xml index f7d9d7933..f0b443599 100644 --- a/Resources/config/security.xml +++ b/Resources/config/security.xml @@ -15,7 +15,7 @@ - + ROLE_SUPER_ADMIN ROLE_ADMIN @@ -24,7 +24,7 @@ - + IS_AUTHENTICATED_FULLY IS_AUTHENTICATED_REMEMBERED diff --git a/Security/RolesDownloadStrategy.php b/Security/RolesDownloadStrategy.php index 57c4c1932..c002f5477 100644 --- a/Security/RolesDownloadStrategy.php +++ b/Security/RolesDownloadStrategy.php @@ -13,6 +13,8 @@ use Sonata\MediaBundle\Model\MediaInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Translation\TranslatorInterface; @@ -24,7 +26,7 @@ class RolesDownloadStrategy implements DownloadStrategyInterface protected $roles; /** - * @var SecurityContextInterface + * @var AuthorizationCheckerInterface|SecurityContextInterface */ protected $security; @@ -34,12 +36,16 @@ class RolesDownloadStrategy implements DownloadStrategyInterface protected $translator; /** - * @param TranslatorInterface $translator - * @param SecurityContextInterface $security - * @param string[] $roles + * @param TranslatorInterface $translator + * @param AuthorizationCheckerInterface|SecurityContextInterface $security + * @param string[] $roles */ - public function __construct(TranslatorInterface $translator, SecurityContextInterface $security, array $roles = array()) + public function __construct(TranslatorInterface $translator, $security, array $roles = array()) { + if (!$security instanceof AuthorizationCheckerInterface && !$security instanceof SecurityContextInterface) { + throw new \InvalidArgumentException('Argument 2 should be an instance of Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface or Symfony\Component\Security\Core\SecurityContextInterface'); + } + $this->roles = $roles; $this->security = $security; $this->translator = $translator; @@ -50,7 +56,12 @@ public function __construct(TranslatorInterface $translator, SecurityContextInte */ public function isGranted(MediaInterface $media, Request $request) { - return $this->security->getToken() && $this->security->isGranted($this->roles); + try { + return $this->security->isGranted($this->roles); + } catch (AuthenticationCredentialsNotFoundException $e) { + // The token is not set in an AuthorizationCheckerInterface object + return false; + } } /** diff --git a/SonataMediaBundle.php b/SonataMediaBundle.php index 26dfaa3d1..6636236e0 100644 --- a/SonataMediaBundle.php +++ b/SonataMediaBundle.php @@ -14,6 +14,7 @@ use Sonata\CoreBundle\Form\FormHelper; use Sonata\MediaBundle\DependencyInjection\Compiler\AddProviderCompilerPass; use Sonata\MediaBundle\DependencyInjection\Compiler\GlobalVariablesCompilerPass; +use Sonata\MediaBundle\DependencyInjection\Compiler\SecurityContextCompilerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -26,6 +27,7 @@ public function build(ContainerBuilder $container) { $container->addCompilerPass(new AddProviderCompilerPass()); $container->addCompilerPass(new GlobalVariablesCompilerPass()); + $container->addCompilerPass(new SecurityContextCompilerPass()); $this->registerFormMapping(); } diff --git a/Tests/CDN/CloudFrontTest.php b/Tests/CDN/CloudFrontTest.php index 1e5ef9ddd..79854ade9 100644 --- a/Tests/CDN/CloudFrontTest.php +++ b/Tests/CDN/CloudFrontTest.php @@ -16,7 +16,10 @@ */ class CloudFrontTest extends \PHPUnit_Framework_TestCase { - public function testCloudFront() + /** + * @group legacy + */ + public function testLegacyCloudFront() { $client = $this->getMock('CloudFrontClientSpy', array('createInvalidation'), array(), '', false); $client->expects($this->exactly(3))->method('createInvalidation')->will($this->returnValue(new CloudFrontResultSpy())); @@ -36,7 +39,10 @@ public function testCloudFront() $cloudFront->flushPaths(array($path)); } - public function testException() + /** + * @group legacy + */ + public function testLegacyException() { $this->setExpectedException('\RuntimeException', 'Unable to flush : '); $client = $this->getMock('CloudFrontClientSpy', array('createInvalidation'), array(), '', false); diff --git a/Tests/Security/RolesDownloadStrategyTest.php b/Tests/Security/RolesDownloadStrategyTest.php index 127def1bf..5b391b89d 100644 --- a/Tests/Security/RolesDownloadStrategyTest.php +++ b/Tests/Security/RolesDownloadStrategyTest.php @@ -15,24 +15,24 @@ class RolesDownloadStrategyTest extends \PHPUnit_Framework_TestCase { - /** - * @group legacy - */ public function testIsGrantedTrue() { $media = $this->getMock('Sonata\MediaBundle\Model\MediaInterface'); $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); - $security = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); $translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); + // Prefer the Syfmony 2.6+ API if available + if (interface_exists('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface')) { + $security = $this->getMock('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'); + } else { + $security = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); + } + $security->expects($this->any()) ->method('isGranted') ->will($this->returnCallback(function (array $roles) { return in_array('ROLE_ADMIN', $roles); })); - $security->expects($this->once()) - ->method('getToken') - ->will(($this->returnValue(true))); $strategy = new RolesDownloadStrategy($translator, $security, array('ROLE_ADMIN')); $this->assertTrue($strategy->isGranted($media, $request)); @@ -44,7 +44,12 @@ public function testIsGrantedFalse() $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); $translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); - $security = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); + // Prefer the Syfmony 2.6+ API if available + if (interface_exists('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface')) { + $security = $this->getMock('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'); + } else { + $security = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); + } $security->expects($this->any()) ->method('isGranted') @@ -52,10 +57,6 @@ public function testIsGrantedFalse() return in_array('FOO', $roles); })); - $security->expects($this->once()) - ->method('getToken') - ->will(($this->returnValue(true))); - $strategy = new RolesDownloadStrategy($translator, $security, array('ROLE_ADMIN')); $this->assertFalse($strategy->isGranted($media, $request)); } diff --git a/Tests/Validator/FormatValidatorTest.php b/Tests/Validator/FormatValidatorTest.php index 087aa3bac..41910f5f5 100644 --- a/Tests/Validator/FormatValidatorTest.php +++ b/Tests/Validator/FormatValidatorTest.php @@ -17,9 +17,6 @@ class FormatValidatorTest extends \PHPUnit_Framework_TestCase { - /** - * @group legacy - */ public function testValidate() { $pool = new Pool('defaultContext'); @@ -29,7 +26,14 @@ public function testValidate() $gallery->expects($this->once())->method('getDefaultFormat')->will($this->returnValue('format1')); $gallery->expects($this->once())->method('getContext')->will($this->returnValue('test')); - $context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false); + // Prefer the Syfmony 2.5+ API if available + if (class_exists('Symfony\Component\Validator\Context\ExecutionContext')) { + $contextClass = 'Symfony\Component\Validator\Context\ExecutionContext'; + } else { + $contextClass = 'Symfony\Component\Validator\ExecutionContext'; + } + + $context = $this->getMock($contextClass, array(), array(), '', false); $context->expects($this->never())->method('addViolation'); $validator = new FormatValidator($pool); @@ -47,7 +51,14 @@ public function testValidateWithValidContext() $gallery->expects($this->once())->method('getDefaultFormat')->will($this->returnValue('format1')); $gallery->expects($this->once())->method('getContext')->will($this->returnValue('test')); - $context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false); + // Prefer the Syfmony 2.5+ API if available + if (class_exists('Symfony\Component\Validator\Context\ExecutionContext')) { + $contextClass = 'Symfony\Component\Validator\Context\ExecutionContext'; + } else { + $contextClass = 'Symfony\Component\Validator\ExecutionContext'; + } + + $context = $this->getMock($contextClass, array(), array(), '', false); $context->expects($this->once())->method('addViolation'); $validator = new FormatValidator($pool); diff --git a/Validator/FormatValidator.php b/Validator/FormatValidator.php index e03324c50..f80f1d782 100644 --- a/Validator/FormatValidator.php +++ b/Validator/FormatValidator.php @@ -15,6 +15,7 @@ use Sonata\MediaBundle\Provider\Pool; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface; class FormatValidator extends ConstraintValidator { @@ -39,7 +40,14 @@ public function validate($value, Constraint $constraint) $formats = $this->pool->getFormatNamesByContext($value->getContext()); if (!$value instanceof GalleryInterface) { - $this->context->addViolationAtPath('defaultFormat', 'Invalid instance, expected GalleryInterface'); + // Interface compatibility, support for LegacyExecutionContextInterface can be removed when support for Symfony <2.5 is dropped + if ($this->context instanceof LegacyExecutionContextInterface) { + $this->context->addViolationAt('defaultFormat', 'Invalid instance, expected GalleryInterface'); + } else { + $this->context->buildViolation('Invalid instance, expected GalleryInterface') + ->atPath('defaultFormat') + ->addViolation(); + } } if (!array_key_exists($value->getDefaultFormat(), $formats)) { diff --git a/composer.json b/composer.json index dac3d1db2..00dbd4581 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ ], "require": { "symfony/framework-bundle": "~2.3|~3.0", - "sonata-project/core-bundle": "~2.3,>=2.3.10", + "sonata-project/core-bundle": "^2.3.10", "sonata-project/notification-bundle": "~2.2", "sonata-project/easy-extends-bundle": "~2.1", "sonata-project/doctrine-extensions": "~1.0", @@ -34,13 +34,14 @@ "sonata-project/admin-bundle": "~2.3", "sonata-project/formatter-bundle": "~2.2", "sonata-project/datagrid-bundle": "~2.2", - "sonata-project/seo-bundle": "~1.1.5|~2.0", + "sonata-project/seo-bundle": "^1.1.5|~2.0", + "sonata-project/block-bundle": "^2.3.7", "aws/aws-sdk-php": "~2.7", "doctrine/mongodb-odm": "~1.0", "jackalope/jackalope-doctrine-dbal": "~1.1", "symfony/phpunit-bridge": "~2.7|~3.0", - "friendsofsymfony/rest-bundle": "~1.1", - "nelmio/api-doc-bundle": "~2.4" + "friendsofsymfony/rest-bundle": "^1.7.4|~2.0", + "nelmio/api-doc-bundle": "~2.11" }, "suggest": { "sonata-project/doctrine-orm-admin-bundle": "~2.3",