From 1ccdce6ca4700d281bc63695c653bef803f18925 Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Sun, 27 Jan 2013 14:18:08 +0100 Subject: [PATCH 1/2] [Security] changed namespaces for the UserPassword and UserPasswordValidator classes to fit the coding convention. Also added a bunch of unit tests to validate the UserPasswordValidator object and introduced a new UserPassword constraint class in the SecurityBundle. The base UserPassword constraint class in the Security component is now decoupled from the SecurityBundle. --- .../Validator/Constraints/UserPassword.php | 32 ++++ .../UserPassword.php | 7 +- .../UserPasswordValidator.php | 4 +- .../Constraints/UserPasswordValidatorTest.php | 161 ++++++++++++++++++ 4 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Validator/Constraints/UserPassword.php rename src/Symfony/Component/Security/Core/Validator/{Constraint => Constraints}/UserPassword.php (72%) rename src/Symfony/Component/Security/Core/Validator/{Constraint => Constraints}/UserPasswordValidator.php (92%) create mode 100644 src/Symfony/Component/Security/Tests/Core/Validator/Constraints/UserPasswordValidatorTest.php diff --git a/src/Symfony/Bundle/SecurityBundle/Validator/Constraints/UserPassword.php b/src/Symfony/Bundle/SecurityBundle/Validator/Constraints/UserPassword.php new file mode 100644 index 0000000000000..760219f5fad42 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Validator/Constraints/UserPassword.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Validator\Constraints; + +use Symfony\Component\Security\Core\Validator\Constraints\UserPassword as BaseUserPassword; + +/** + * This class defines a constraint to validate the current logged-in user's + * password. + * + * @Annotation + * + * @author Hugo Hamon + */ +class UserPassword extends BaseUserPassword +{ + public $service = 'security.validator.user_password'; + + public function validatedBy() + { + return $this->service; + } +} diff --git a/src/Symfony/Component/Security/Core/Validator/Constraint/UserPassword.php b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php similarity index 72% rename from src/Symfony/Component/Security/Core/Validator/Constraint/UserPassword.php rename to src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php index 3279e022d580a..dfafaa132e6c7 100644 --- a/src/Symfony/Component/Security/Core/Validator/Constraint/UserPassword.php +++ b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Security\Core\Validator\Constraint; +namespace Symfony\Component\Security\Core\Validator\Constraints; use Symfony\Component\Validator\Constraint; @@ -19,9 +19,4 @@ class UserPassword extends Constraint { public $message = 'This value should be the user current password.'; - - public function validatedBy() - { - return 'security.validator.user_password'; - } } diff --git a/src/Symfony/Component/Security/Core/Validator/Constraint/UserPasswordValidator.php b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php similarity index 92% rename from src/Symfony/Component/Security/Core/Validator/Constraint/UserPasswordValidator.php rename to src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php index a54906bb74250..76cddd4995d93 100644 --- a/src/Symfony/Component/Security/Core/Validator/Constraint/UserPasswordValidator.php +++ b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Security\Core\Validator\Constraint; +namespace Symfony\Component\Security\Core\Validator\Constraints; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\SecurityContextInterface; @@ -34,7 +34,7 @@ public function validate($password, Constraint $constraint) $user = $this->securityContext->getToken()->getUser(); if (!$user instanceof UserInterface) { - throw new ConstraintDefinitionException('The User must extend UserInterface'); + throw new ConstraintDefinitionException('The User must be an instance of the UserInterface interface.'); } $encoder = $this->encoderFactory->getEncoder($user); diff --git a/src/Symfony/Component/Security/Tests/Core/Validator/Constraints/UserPasswordValidatorTest.php b/src/Symfony/Component/Security/Tests/Core/Validator/Constraints/UserPasswordValidatorTest.php new file mode 100644 index 0000000000000..d4053a4b1e149 --- /dev/null +++ b/src/Symfony/Component/Security/Tests/Core/Validator/Constraints/UserPasswordValidatorTest.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Tests\Core\Validator\Constraints; + +use Symfony\Component\Security\Core\Validator\Constraints\UserPassword; +use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; + +class UserPasswordValidatorTest extends \PHPUnit_Framework_TestCase +{ + const PASSWORD_VALID = true; + const PASSWORD_INVALID = false; + + protected $context; + + protected function setUp() + { + if (!class_exists('Symfony\Component\Validator\Validator', true)) { + $this->markTestSkipped('The Validator component is required for this test.'); + } + + $this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false); + } + + protected function tearDown() + { + $this->context = null; + } + + public function testPasswordIsValid() + { + $user = $this->createUser(); + $securityContext = $this->createSecurityContext($user); + + $encoder = $this->createPasswordEncoder(static::PASSWORD_VALID); + $encoderFactory = $this->createEncoderFactory($encoder); + + $validator = new UserPasswordValidator($securityContext, $encoderFactory); + $validator->initialize($this->context); + + $this + ->context + ->expects($this->never()) + ->method('addViolation') + ; + + $validator->validate('secret', new UserPassword()); + } + + public function testPasswordIsNotValid() + { + $user = $this->createUser(); + $securityContext = $this->createSecurityContext($user); + + $encoder = $this->createPasswordEncoder(static::PASSWORD_INVALID); + $encoderFactory = $this->createEncoderFactory($encoder); + + $validator = new UserPasswordValidator($securityContext, $encoderFactory); + $validator->initialize($this->context); + + $this + ->context + ->expects($this->once()) + ->method('addViolation') + ; + + $validator->validate('secret', new UserPassword()); + } + + public function testUserIsNotValid() + { + $this->setExpectedException('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); + + $user = $this->getMock('Foo\Bar\User'); + $encoderFactory = $this->getMock('Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface'); + $securityContext = $this->createSecurityContext($user); + + $validator = new UserPasswordValidator($securityContext, $encoderFactory); + $validator->initialize($this->context); + $validator->validate('secret', new UserPassword()); + } + + protected function createUser() + { + $mock = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); + + $mock + ->expects($this->once()) + ->method('getPassword') + ->will($this->returnValue('s3Cr3t')) + ; + + $mock + ->expects($this->once()) + ->method('getSalt') + ->will($this->returnValue('^S4lt$')) + ; + + return $mock; + } + + protected function createPasswordEncoder($isPasswordValid = true) + { + $mock = $this->getMock('Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface'); + + $mock + ->expects($this->once()) + ->method('isPasswordValid') + ->will($this->returnValue($isPasswordValid)) + ; + + return $mock; + } + + protected function createEncoderFactory($encoder = null) + { + $mock = $this->getMock('Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface'); + + $mock + ->expects($this->once()) + ->method('getEncoder') + ->will($this->returnValue($encoder)) + ; + + return $mock; + } + + protected function createSecurityContext($user = null) + { + $token = $this->createAuthenticationToken($user); + + $mock = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); + $mock + ->expects($this->once()) + ->method('getToken') + ->will($this->returnValue($token)) + ; + + return $mock; + } + + protected function createAuthenticationToken($user = null) + { + $mock = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + $mock + ->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)) + ; + + return $mock; + } +} From 36ee69a3d7f2a062f9aec4ea7bf8f5e4dad75cad Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Sun, 27 Jan 2013 18:36:58 +0100 Subject: [PATCH 2/2] Updated the UPGRADE file to reflect the BC breaks of the UserPassword class constraint. --- UPGRADE-2.2.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md index 06b5ff5bbb7f2..9a4d624133320 100644 --- a/UPGRADE-2.2.md +++ b/UPGRADE-2.2.md @@ -519,6 +519,52 @@ } ``` +### Security + + * The existing ``UserPassword`` validator constraint class has been modified + to make it decoupled from the ``SecurityBundle``. It's no more validated by + the ``security.validator.user_password`` service and its namespace has been + changed to better fit the Symfony coding convention. + + Before: + + ``` + use Symfony\Component\Security\Core\Validator\Constraint\UserPassword; + ``` + + After: (note the `s` at the end of `Constraint`) + + ``` + use Symfony\Component\Security\Core\Validator\Constraints\UserPassword; + ``` + + * If you were using the ``UserPassword`` validator constraint class in your + Symfony Standard Edition based application to validate the current logged-in + user's password, you must now use the new extended ``UserPassword`` + validator constraint class from the ``SecurityBundle`` instead. This is + because the original ``UserPassword`` constraint class from the ``Security`` + component is no more validated by the ``security.validator.user_password`` + service of the ``SecurityBundle``. + + Before: + + ``` + use Symfony\Component\Security\Core\Validator\Constraint\UserPassword; + ``` + + After: (note the `s` at the end of `Constraint`) + + ``` + use Symfony\Bundle\SecurityBundle\Validator\Constraints\UserPassword; + ``` + +### Serializer + + * All serializer interfaces (Serializer, Normalizer, Encoder) have been + extended with an optional `$context` array. This was necessary to allow for + more complex use-cases that require context information during the + (de)normalization and en-/decoding steps. + ### FrameworkBundle * The `render` method of the `actions` templating helper signature and arguments changed: @@ -563,9 +609,36 @@ trusted_proxies: ['127.0.0.1', '10.0.0.1'] # a list of proxy IPs you trust ``` -### Serializer +### SecurityBundle - * All serializer interfaces (Serializer, Normalizer, Encoder) have been - extended with an optional `$context` array. This was necessary to allow for - more complex use-cases that require context information during the - (de)normalization and en-/decoding steps. + * A ``UserPassword`` validator constraint class has been reintroduced in the + ``SecurityBundle``. It extends the base ``UserPassword`` validator + constraint class located in the ``Security`` component. + + This is the validator constraint class you should now use to validate the + current logged-in user's password while securing your application with the + ``SecurityBundle``. + + Before: + + ``` + use Symfony\Component\Security\Core\Validator\Constraint\UserPassword; + ``` + + After: + + ``` + use Symfony\Bundle\SecurityBundle\Validator\Constraints\UserPassword; + ``` + + * The new ``UserPassword`` validator constraint class now accepts a new + ``service`` option, which allows to specify a custom validator service name + instead to validate the current logged-in user's password. + + ``` + use Symfony\Bundle\SecurityBundle\Validator\Constraints\UserPassword; + + $constraint = new UserPassword(array( + 'service' => 'my.custom.validator.user_password', + )); + ```