diff options
-rw-r--r-- | Core/Authorization/AccessDecisionManager.php | 4 | ||||
-rw-r--r-- | Core/Authorization/Voter/AbstractVoter.php | 37 | ||||
-rw-r--r-- | Core/Tests/Authorization/Voter/AbstractVoterTest.php | 38 | ||||
-rw-r--r-- | Core/Tests/Authorization/Voter/LegacyAbstractVoterTest.php | 9 | ||||
-rw-r--r-- | Core/Util/SecureRandom.php | 8 | ||||
-rw-r--r-- | Guard/Firewall/GuardAuthenticationListener.php | 20 | ||||
-rw-r--r-- | Guard/Tests/Firewall/GuardAuthenticationListenerTest.php | 36 | ||||
-rw-r--r-- | Guard/Token/GuardTokenInterface.php | 4 | ||||
-rw-r--r-- | composer.json | 6 |
9 files changed, 116 insertions, 46 deletions
diff --git a/Core/Authorization/AccessDecisionManager.php b/Core/Authorization/AccessDecisionManager.php index ef942b8..7cefef1 100644 --- a/Core/Authorization/AccessDecisionManager.php +++ b/Core/Authorization/AccessDecisionManager.php @@ -77,7 +77,7 @@ class AccessDecisionManager implements AccessDecisionManagerInterface */ public function supportsAttribute($attribute) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.'); + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); foreach ($this->voters as $voter) { if ($voter->supportsAttribute($attribute)) { @@ -93,7 +93,7 @@ class AccessDecisionManager implements AccessDecisionManagerInterface */ public function supportsClass($class) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.'); + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); foreach ($this->voters as $voter) { if ($voter->supportsClass($class)) { diff --git a/Core/Authorization/Voter/AbstractVoter.php b/Core/Authorization/Voter/AbstractVoter.php index 2cafc5f..12b54db 100644 --- a/Core/Authorization/Voter/AbstractVoter.php +++ b/Core/Authorization/Voter/AbstractVoter.php @@ -26,7 +26,7 @@ abstract class AbstractVoter implements VoterInterface */ public function supportsAttribute($attribute) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.'); + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); return in_array($attribute, $this->getSupportedAttributes()); } @@ -36,7 +36,7 @@ abstract class AbstractVoter implements VoterInterface */ public function supportsClass($class) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.'); + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); foreach ($this->getSupportedClasses() as $supportedClass) { if ($supportedClass === $class || is_subclass_of($class, $supportedClass)) { @@ -70,12 +70,6 @@ abstract class AbstractVoter implements VoterInterface $vote = self::ACCESS_ABSTAIN; $class = get_class($object); - $reflector = new \ReflectionMethod($this, 'voteOnAttribute'); - $isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter'; - if (!$isNewOverwritten) { - @trigger_error(sprintf("The AbstractVoter::isGranted method is deprecated since 2.8 and won't be called anymore in 3.0. Override voteOnAttribute() instead.", $reflector->class), E_USER_DEPRECATED); - } - foreach ($attributes as $attribute) { if (!$this->supports($attribute, $class)) { continue; @@ -84,16 +78,9 @@ abstract class AbstractVoter implements VoterInterface // as soon as at least one attribute is supported, default is to deny access $vote = self::ACCESS_DENIED; - if ($isNewOverwritten) { - if ($this->voteOnAttribute($attribute, $object, $token)) { - // grant access as soon as at least one voter returns a positive response - return self::ACCESS_GRANTED; - } - } else { - if ($this->isGranted($attribute, $object, $token->getUser())) { - // grant access as soon as at least one voter returns a positive response - return self::ACCESS_GRANTED; - } + if ($this->voteOnAttribute($attribute, $object, $token)) { + // grant access as soon as at least one voter returns a positive response + return self::ACCESS_GRANTED; } } @@ -115,7 +102,7 @@ abstract class AbstractVoter implements VoterInterface */ protected function supports($attribute, $class) { - @trigger_error('The getSupportedClasses and getSupportedAttributes methods are deprecated since version 2.8 and will be removed in version 3.0. Overwrite supports instead.'); + @trigger_error('The getSupportedClasses and getSupportedAttributes methods are deprecated since version 2.8 and will be removed in version 3.0. Overwrite supports instead.', E_USER_DEPRECATED); $classIsSupported = false; foreach ($this->getSupportedClasses() as $supportedClass) { @@ -159,7 +146,7 @@ abstract class AbstractVoter implements VoterInterface */ protected function getSupportedClasses() { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.'); + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); } /** @@ -171,7 +158,7 @@ abstract class AbstractVoter implements VoterInterface */ protected function getSupportedAttributes() { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.'); + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); } /** @@ -191,7 +178,8 @@ abstract class AbstractVoter implements VoterInterface */ protected function isGranted($attribute, $object, $user = null) { - return false; + // forces isGranted() or voteOnAttribute() to be overridden + throw new \BadMethodCallException(sprintf('You must override the voteOnAttribute() method in "%s".', get_class($this))); } /** @@ -211,6 +199,9 @@ abstract class AbstractVoter implements VoterInterface */ protected function voteOnAttribute($attribute, $object, TokenInterface $token) { - return false; + // the user should override this method, and not rely on the deprecated isGranted() + @trigger_error(sprintf("The AbstractVoter::isGranted() method is deprecated since 2.8 and won't be called anymore in 3.0. Override voteOnAttribute() in %s instead.", get_class($this)), E_USER_DEPRECATED); + + return $this->isGranted($attribute, $object, $token->getUser()); } } diff --git a/Core/Tests/Authorization/Voter/AbstractVoterTest.php b/Core/Tests/Authorization/Voter/AbstractVoterTest.php index 44da147..ea72e75 100644 --- a/Core/Tests/Authorization/Voter/AbstractVoterTest.php +++ b/Core/Tests/Authorization/Voter/AbstractVoterTest.php @@ -19,17 +19,10 @@ use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; */ class AbstractVoterTest extends \PHPUnit_Framework_TestCase { - /** - * @var AbstractVoter - */ - private $voter; - private $token; protected function setUp() { - $this->voter = new VoterFixture(); - $tokenMock = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); $tokenMock ->expects($this->any()) @@ -44,7 +37,9 @@ class AbstractVoterTest extends \PHPUnit_Framework_TestCase */ public function testVote($expectedVote, $object, $attributes, $message) { - $this->assertEquals($expectedVote, $this->voter->vote($this->token, $object, $attributes), $message); + $voter = new VoterFixture(); + + $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); } /** @@ -58,6 +53,16 @@ class AbstractVoterTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); } + /** + * @group legacy + * @expectedException \BadMethodCallException + */ + public function testNoOverriddenMethodsThrowsException() + { + $voter = new DeprecatedVoterNothingImplementedFixture(); + $voter->vote($this->token, new ObjectFixture(), array('foo')); + } + public function getData() { return array( @@ -113,6 +118,23 @@ class DeprecatedVoterFixture extends AbstractVoter } } +class DeprecatedVoterNothingImplementedFixture extends AbstractVoter +{ + protected function getSupportedClasses() + { + return array( + 'Symfony\Component\Security\Core\Tests\Authorization\Voter\ObjectFixture', + ); + } + + protected function getSupportedAttributes() + { + return array('foo', 'bar', 'baz'); + } + + // this is a bad voter that hasn't overridden isGranted or voteOnAttribute +} + class ObjectFixture { } diff --git a/Core/Tests/Authorization/Voter/LegacyAbstractVoterTest.php b/Core/Tests/Authorization/Voter/LegacyAbstractVoterTest.php index 3a0cf1e..b49f23f 100644 --- a/Core/Tests/Authorization/Voter/LegacyAbstractVoterTest.php +++ b/Core/Tests/Authorization/Voter/LegacyAbstractVoterTest.php @@ -1,5 +1,14 @@ <?php +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; diff --git a/Core/Util/SecureRandom.php b/Core/Util/SecureRandom.php index f4167e4..65722ce 100644 --- a/Core/Util/SecureRandom.php +++ b/Core/Util/SecureRandom.php @@ -43,9 +43,9 @@ final class SecureRandom implements SecureRandomInterface $this->logger = $logger; // determine whether to use OpenSSL - if (!function_exists('openssl_random_pseudo_bytes')) { + if (!function_exists('random_bytes') && !function_exists('openssl_random_pseudo_bytes')) { if (null !== $this->logger) { - $this->logger->notice('It is recommended that you enable the "openssl" extension for random number generation.'); + $this->logger->notice('It is recommended that you install the "paragonie/random_compat" library or enable the "openssl" extension for random number generation.'); } $this->useOpenSsl = false; } else { @@ -58,6 +58,10 @@ final class SecureRandom implements SecureRandomInterface */ public function nextBytes($nbBytes) { + if (function_exists('random_bytes')) { + return random_bytes($nbBytes); + } + // try OpenSSL if ($this->useOpenSsl) { $bytes = openssl_random_pseudo_bytes($nbBytes, $strong); diff --git a/Guard/Firewall/GuardAuthenticationListener.php b/Guard/Firewall/GuardAuthenticationListener.php index 6140be0..0ac7c12 100644 --- a/Guard/Firewall/GuardAuthenticationListener.php +++ b/Guard/Firewall/GuardAuthenticationListener.php @@ -66,7 +66,7 @@ class GuardAuthenticationListener implements ListenerInterface public function handle(GetResponseEvent $event) { if (null !== $this->logger) { - $this->logger->info('Checking for guard authentication credentials.', array('firewall_key' => $this->providerKey, 'authenticators' => count($this->guardAuthenticators))); + $this->logger->debug('Checking for guard authentication credentials.', array('firewall_key' => $this->providerKey, 'authenticators' => count($this->guardAuthenticators))); } foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { @@ -75,6 +75,12 @@ class GuardAuthenticationListener implements ListenerInterface $uniqueGuardKey = $this->providerKey.'_'.$key; $this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event); + + if ($event->hasResponse()) { + $this->logger->debug(sprintf('The "%s" authenticator set the response. Any later authenticator will not be called', get_class($guardAuthenticator))); + + break; + } } } @@ -83,7 +89,7 @@ class GuardAuthenticationListener implements ListenerInterface $request = $event->getRequest(); try { if (null !== $this->logger) { - $this->logger->info('Calling getCredentials on guard configurator.', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator))); + $this->logger->debug('Calling getCredentials() on guard configurator.', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator))); } // allow the authenticator to fetch authentication info from the request @@ -98,7 +104,7 @@ class GuardAuthenticationListener implements ListenerInterface $token = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey); if (null !== $this->logger) { - $this->logger->info('Passing guard token information to the GuardAuthenticationProvider', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator))); + $this->logger->debug('Passing guard token information to the GuardAuthenticationProvider', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator))); } // pass the token into the AuthenticationManager system // this indirectly calls GuardAuthenticationProvider::authenticate() @@ -130,13 +136,13 @@ class GuardAuthenticationListener implements ListenerInterface $response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey); if ($response instanceof Response) { if (null !== $this->logger) { - $this->logger->info('Guard authenticator set success response.', array('response' => $response, 'authenticator' => get_class($guardAuthenticator))); + $this->logger->debug('Guard authenticator set success response.', array('response' => $response, 'authenticator' => get_class($guardAuthenticator))); } $event->setResponse($response); } else { if (null !== $this->logger) { - $this->logger->info('Guard authenticator set no success response: request continues.', array('authenticator' => get_class($guardAuthenticator))); + $this->logger->debug('Guard authenticator set no success response: request continues.', array('authenticator' => get_class($guardAuthenticator))); } } @@ -167,7 +173,7 @@ class GuardAuthenticationListener implements ListenerInterface { if (null === $this->rememberMeServices) { if (null !== $this->logger) { - $this->logger->info('Remember me skipped: it is not configured for the firewall.', array('authenticator' => get_class($guardAuthenticator))); + $this->logger->debug('Remember me skipped: it is not configured for the firewall.', array('authenticator' => get_class($guardAuthenticator))); } return; @@ -175,7 +181,7 @@ class GuardAuthenticationListener implements ListenerInterface if (!$guardAuthenticator->supportsRememberMe()) { if (null !== $this->logger) { - $this->logger->info('Remember me skipped: your authenticator does not support it.', array('authenticator' => get_class($guardAuthenticator))); + $this->logger->debug('Remember me skipped: your authenticator does not support it.', array('authenticator' => get_class($guardAuthenticator))); } return; diff --git a/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php b/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php index 8fab399..3224fee 100644 --- a/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php +++ b/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php @@ -79,6 +79,36 @@ class GuardAuthenticationListenerTest extends \PHPUnit_Framework_TestCase $listener->handle($this->event); } + public function testHandleSuccessStopsAfterResponseIsSet() + { + $authenticator1 = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface'); + $authenticator2 = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface'); + + // mock the first authenticator to fail, and set a Response + $authenticator1 + ->expects($this->once()) + ->method('getCredentials') + ->willThrowException(new AuthenticationException()); + $this->guardAuthenticatorHandler + ->expects($this->once()) + ->method('handleAuthenticationFailure') + ->willReturn(new Response()); + // the second authenticator should *never* be called + $authenticator2 + ->expects($this->never()) + ->method('getCredentials'); + + $listener = new GuardAuthenticationListener( + $this->guardAuthenticatorHandler, + $this->authenticationManager, + 'my_firewall', + array($authenticator1, $authenticator2), + $this->logger + ); + + $listener->handle($this->event); + } + public function testHandleSuccessWithRememberMe() { $authenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface'); @@ -201,7 +231,10 @@ class GuardAuthenticationListenerTest extends \PHPUnit_Framework_TestCase $this->request = new Request(array(), array(), array(), array(), array(), array()); - $this->event = $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false); + $this->event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->setMethods(array('getRequest')) + ->getMock(); $this->event ->expects($this->any()) ->method('getRequest') @@ -218,5 +251,6 @@ class GuardAuthenticationListenerTest extends \PHPUnit_Framework_TestCase $this->event = null; $this->logger = null; $this->request = null; + $this->rememberMeServices = null; } } diff --git a/Guard/Token/GuardTokenInterface.php b/Guard/Token/GuardTokenInterface.php index f0db250..063ffd3 100644 --- a/Guard/Token/GuardTokenInterface.php +++ b/Guard/Token/GuardTokenInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Guard\Token; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + /** * A marker interface that both guard tokens implement. * @@ -20,6 +22,6 @@ namespace Symfony\Component\Security\Guard\Token; * * @author Ryan Weaver <ryan@knpuniversity.com> */ -interface GuardTokenInterface +interface GuardTokenInterface extends TokenInterface { } diff --git a/composer.json b/composer.json index 3bea5b8..547c1ce 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "replace": { "symfony/security-core": "self.version", "symfony/security-csrf": "self.version", + "symfony/security-guard": "self.version", "symfony/security-http": "self.version" }, "require-dev": { @@ -41,10 +42,11 @@ "suggest": { "symfony/class-loader": "For using the ACL generateSql script", "symfony/finder": "For using the ACL generateSql script", + "symfony/form": "", "symfony/validator": "For using the user password constraint", "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs", - "doctrine/dbal": "For using the built-in ACL implementation", - "symfony/expression-language": "For using the expression voter" + "symfony/expression-language": "For using the expression voter", + "paragonie/random_compat": "" }, "autoload": { "psr-4": { "Symfony\\Component\\Security\\": "" } |