diff options
32 files changed, 1265 insertions, 303 deletions
@@ -1,4 +1,3 @@ vendor/ composer.lock phpunit.xml - diff --git a/Acl/Permission/BasicPermissionMap.php b/Acl/Permission/BasicPermissionMap.php index b2bcf65..97157f1 100644 --- a/Acl/Permission/BasicPermissionMap.php +++ b/Acl/Permission/BasicPermissionMap.php @@ -28,58 +28,63 @@ class BasicPermissionMap implements PermissionMapInterface const PERMISSION_MASTER = 'MASTER'; const PERMISSION_OWNER = 'OWNER'; - private $map = array( - self::PERMISSION_VIEW => array( - MaskBuilder::MASK_VIEW, - MaskBuilder::MASK_EDIT, - MaskBuilder::MASK_OPERATOR, - MaskBuilder::MASK_MASTER, - MaskBuilder::MASK_OWNER, - ), + protected $map; - self::PERMISSION_EDIT => array( - MaskBuilder::MASK_EDIT, - MaskBuilder::MASK_OPERATOR, - MaskBuilder::MASK_MASTER, - MaskBuilder::MASK_OWNER, - ), + public function __construct() + { + $this->map = array( + self::PERMISSION_VIEW => array( + MaskBuilder::MASK_VIEW, + MaskBuilder::MASK_EDIT, + MaskBuilder::MASK_OPERATOR, + MaskBuilder::MASK_MASTER, + MaskBuilder::MASK_OWNER, + ), + + self::PERMISSION_EDIT => array( + MaskBuilder::MASK_EDIT, + MaskBuilder::MASK_OPERATOR, + MaskBuilder::MASK_MASTER, + MaskBuilder::MASK_OWNER, + ), - self::PERMISSION_CREATE => array( - MaskBuilder::MASK_CREATE, - MaskBuilder::MASK_OPERATOR, - MaskBuilder::MASK_MASTER, - MaskBuilder::MASK_OWNER, - ), + self::PERMISSION_CREATE => array( + MaskBuilder::MASK_CREATE, + MaskBuilder::MASK_OPERATOR, + MaskBuilder::MASK_MASTER, + MaskBuilder::MASK_OWNER, + ), - self::PERMISSION_DELETE => array( - MaskBuilder::MASK_DELETE, - MaskBuilder::MASK_OPERATOR, - MaskBuilder::MASK_MASTER, - MaskBuilder::MASK_OWNER, - ), + self::PERMISSION_DELETE => array( + MaskBuilder::MASK_DELETE, + MaskBuilder::MASK_OPERATOR, + MaskBuilder::MASK_MASTER, + MaskBuilder::MASK_OWNER, + ), - self::PERMISSION_UNDELETE => array( - MaskBuilder::MASK_UNDELETE, - MaskBuilder::MASK_OPERATOR, - MaskBuilder::MASK_MASTER, - MaskBuilder::MASK_OWNER, - ), + self::PERMISSION_UNDELETE => array( + MaskBuilder::MASK_UNDELETE, + MaskBuilder::MASK_OPERATOR, + MaskBuilder::MASK_MASTER, + MaskBuilder::MASK_OWNER, + ), - self::PERMISSION_OPERATOR => array( - MaskBuilder::MASK_OPERATOR, - MaskBuilder::MASK_MASTER, - MaskBuilder::MASK_OWNER, - ), + self::PERMISSION_OPERATOR => array( + MaskBuilder::MASK_OPERATOR, + MaskBuilder::MASK_MASTER, + MaskBuilder::MASK_OWNER, + ), - self::PERMISSION_MASTER => array( - MaskBuilder::MASK_MASTER, - MaskBuilder::MASK_OWNER, - ), + self::PERMISSION_MASTER => array( + MaskBuilder::MASK_MASTER, + MaskBuilder::MASK_OWNER, + ), - self::PERMISSION_OWNER => array( - MaskBuilder::MASK_OWNER, - ), - ); + self::PERMISSION_OWNER => array( + MaskBuilder::MASK_OWNER, + ), + ); + } /** * {@inheritDoc} diff --git a/CHANGELOG.md b/CHANGELOG.md index 82c4312..92f8073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +2.3.0 +----- + + * [BC BREAK] the BCrypt encoder constructor signature has changed (the first argument was removed) + To use the BCrypt encoder, you now need PHP 5.5 or "ircmaxell/password-compat" as a composer dependency + * [BC BREAK] return 401 instead of 500 when using use_forward during for form authentication + * added a `require_previous_session` option to `AbstractAuthenticationListener` + 2.2.0 ----- diff --git a/Core/Authentication/RememberMe/TokenProviderInterface.php b/Core/Authentication/RememberMe/TokenProviderInterface.php index 39a4ebe..93ed8d3 100644 --- a/Core/Authentication/RememberMe/TokenProviderInterface.php +++ b/Core/Authentication/RememberMe/TokenProviderInterface.php @@ -23,11 +23,11 @@ interface TokenProviderInterface /** * Loads the active token for the given series. * - * @throws TokenNotFoundException if the token is not found - * * @param string $series * * @return PersistentTokenInterface + * + * @throws TokenNotFoundException if the token is not found */ public function loadTokenBySeries($series); @@ -44,6 +44,7 @@ interface TokenProviderInterface * @param string $series * @param string $tokenValue * @param \DateTime $lastUsed + * @throws TokenNotFoundException if the token is not found */ public function updateToken($series, $tokenValue, \DateTime $lastUsed); diff --git a/Core/Authentication/Token/AbstractToken.php b/Core/Authentication/Token/AbstractToken.php index f21aa76..1d65819 100644 --- a/Core/Authentication/Token/AbstractToken.php +++ b/Core/Authentication/Token/AbstractToken.php @@ -91,7 +91,7 @@ abstract class AbstractToken implements TokenInterface public function setUser($user) { if (!($user instanceof UserInterface || (is_object($user) && method_exists($user, '__toString')) || is_string($user))) { - throw new \InvalidArgumentException('$user must be an instanceof of UserInterface, an object implementing a __toString method, or a primitive string.'); + throw new \InvalidArgumentException('$user must be an instanceof UserInterface, an object implementing a __toString method, or a primitive string.'); } if (null === $this->user) { @@ -190,7 +190,7 @@ abstract class AbstractToken implements TokenInterface } /** - * Returns a attribute value. + * Returns an attribute value. * * @param string $name The attribute name * diff --git a/Core/Encoder/BCryptPasswordEncoder.php b/Core/Encoder/BCryptPasswordEncoder.php index 1b7572d..a355421 100644 --- a/Core/Encoder/BCryptPasswordEncoder.php +++ b/Core/Encoder/BCryptPasswordEncoder.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Security\Core\Encoder; use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder; -use Symfony\Component\Security\Core\Util\SecureRandomInterface; /** * @author Elnur Abdurrakhimov <elnur@elnur.pro> @@ -21,128 +20,64 @@ use Symfony\Component\Security\Core\Util\SecureRandomInterface; class BCryptPasswordEncoder extends BasePasswordEncoder { /** - * @var SecureRandomInterface - */ - private $secureRandom; - - /** * @var string */ private $cost; - private static $prefix = null; - /** * Constructor. * - * @param SecureRandomInterface $secureRandom A SecureRandomInterface instance - * @param integer $cost The algorithmic cost that should be used + * @param integer $cost The algorithmic cost that should be used * * @throws \InvalidArgumentException if cost is out of range */ - public function __construct(SecureRandomInterface $secureRandom, $cost) + public function __construct($cost) { - $this->secureRandom = $secureRandom; + if (!function_exists('password_hash')) { + throw new \RuntimeException('To use the BCrypt encoder, you need to upgrade to PHP 5.5 or install the "ircmaxell/password-compat" via Composer.'); + } $cost = (int) $cost; if ($cost < 4 || $cost > 31) { throw new \InvalidArgumentException('Cost must be in the range of 4-31.'); } - $this->cost = sprintf('%02d', $cost); - - if (!self::$prefix) { - self::$prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=') ? '2y' : '2a').'$'; - } - } - - /** - * {@inheritdoc} - */ - public function encodePassword($raw, $salt) - { - if (function_exists('password_hash')) { - return password_hash($raw, PASSWORD_BCRYPT, array('cost' => $this->cost)); - } - - $salt = self::$prefix.$this->cost.'$'.$this->encodeSalt($this->getRawSalt()); - $encoded = crypt($raw, $salt); - if (!is_string($encoded) || strlen($encoded) <= 13) { - return false; - } - - return $encoded; - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid($encoded, $raw, $salt) - { - if (function_exists('password_verify')) { - return password_verify($raw, $encoded); - } - $crypted = crypt($raw, $encoded); - if (strlen($crypted) <= 13) { - return false; - } - - return $this->comparePasswords($encoded, $crypted); + $this->cost = sprintf('%02d', $cost); } /** - * Encodes the salt to be used by Bcrypt. + * Encodes the raw password. * - * The blowfish/bcrypt algorithm used by PHP crypt expects a different - * set and order of characters than the usual base64_encode function. - * Regular b64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ - * Bcrypt b64: ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 - * We care because the last character in our encoded string will - * only represent 2 bits. While two known implementations of - * bcrypt will happily accept and correct a salt string which - * has the 4 unused bits set to non-zero, we do not want to take - * chances and we also do not want to waste an additional byte - * of entropy. + * It doesn't work with PHP versions lower than 5.3.7, since + * the password compat library uses CRYPT_BLOWFISH hash type with + * the "$2y$" salt prefix (which is not available in the early PHP versions). + * @see https://github.com/ircmaxell/password_compat/issues/10#issuecomment-11203833 * - * @param bytes $random a string of 16 random bytes + * It is almost best to **not** pass a salt and let PHP generate one for you. * - * @return string Properly encoded salt to use with php crypt function + * @param string $raw The password to encode + * @param string $salt The salt * - * @throws \InvalidArgumentException if string of random bytes is too short + * @return string The encoded password + * + * @link http://lxr.php.net/xref/PHP_5_5/ext/standard/password.c#111 */ - protected function encodeSalt($random) + public function encodePassword($raw, $salt) { - $len = strlen($random); - if ($len < 16) { - throw new \InvalidArgumentException('The bcrypt salt needs 16 random bytes.'); - } - if ($len > 16) { - $random = substr($random, 0, 16); - } + $options = array('cost' => $this->cost); - $base64raw = str_replace('+', '.', base64_encode($random)); - $salt128bit = substr($base64raw, 0, 21); - $lastchar = substr($base64raw, 21, 1); - $lastchar = strtr($lastchar, 'AQgw', '.Oeu'); - $salt128bit .= $lastchar; + if ($salt) { + $options['salt'] = $salt; + } - return $salt128bit; + return password_hash($raw, PASSWORD_BCRYPT, $options); } /** - * @return bytes 16 random bytes to be used in the salt + * {@inheritdoc} */ - protected function getRawSalt() + public function isPasswordValid($encoded, $raw, $salt) { - $rawSalt = false; - $numBytes = 16; - if (function_exists('mcrypt_create_iv')) { - $rawSalt = mcrypt_create_iv($numBytes, MCRYPT_DEV_URANDOM); - } - if (!$rawSalt) { - $rawSalt = $this->secureRandom->nextBytes($numBytes); - } - - return $rawSalt; + return password_verify($raw, $encoded); } } diff --git a/Core/Encoder/Pbkdf2PasswordEncoder.php b/Core/Encoder/Pbkdf2PasswordEncoder.php index 656545f..4f37ba3 100644 --- a/Core/Encoder/Pbkdf2PasswordEncoder.php +++ b/Core/Encoder/Pbkdf2PasswordEncoder.php @@ -82,7 +82,7 @@ class Pbkdf2PasswordEncoder extends BasePasswordEncoder $digest = ''; for ($i = 1; $i <= $blocks; $i++) { - $ib = $block = hash_hmac($algorithm, $salt . pack('N', $i), $password, true); + $ib = $block = hash_hmac($algorithm, $salt.pack('N', $i), $password, true); // Iterations for ($j = 1; $j < $iterations; $j++) { diff --git a/Core/User/ChainUserProvider.php b/Core/User/ChainUserProvider.php index 3ff1ea9..fc72074 100644 --- a/Core/User/ChainUserProvider.php +++ b/Core/User/ChainUserProvider.php @@ -32,6 +32,14 @@ class ChainUserProvider implements UserProviderInterface } /** + * @return array + */ + public function getProviders() + { + return $this->providers; + } + + /** * {@inheritDoc} */ public function loadUserByUsername($username) diff --git a/Core/Validator/Constraint/UserPassword.php b/Core/Validator/Constraint/UserPassword.php deleted file mode 100644 index 93ca24d..0000000 --- a/Core/Validator/Constraint/UserPassword.php +++ /dev/null @@ -1,29 +0,0 @@ -<?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\Validator\Constraint; - -use Symfony\Component\Security\Core\Validator\Constraints\UserPassword as BaseUserPassword; - -/** - * @Annotation - * - * @deprecated Deprecated since version 2.2, to be removed in 2.3. - */ -class UserPassword extends BaseUserPassword -{ - public function __construct($options = null) - { - trigger_error('UserPassword class in Symfony\Component\Security\Core\Validator\Constraint namespace is deprecated since version 2.2 and will be removed in 2.3. Use the Symfony\Component\Security\Core\Validator\Constraints\UserPassword class instead.', E_USER_DEPRECATED); - - parent::__construct($options); - } -} diff --git a/Core/Validator/Constraint/UserPasswordValidator.php b/Core/Validator/Constraint/UserPasswordValidator.php deleted file mode 100644 index 0195fe5..0000000 --- a/Core/Validator/Constraint/UserPasswordValidator.php +++ /dev/null @@ -1,29 +0,0 @@ -<?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\Validator\Constraint; - -use Symfony\Component\Security\Core\SecurityContextInterface; -use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator as BaseUserPasswordValidator; - -/** - * @deprecated Deprecated since version 2.2, to be removed in 2.3. - */ -class UserPasswordValidator extends BaseUserPasswordValidator -{ - public function __construct(SecurityContextInterface $securityContext, EncoderFactoryInterface $encoderFactory) - { - trigger_error('UserPasswordValidator class in Symfony\Component\Security\Core\Validator\Constraint namespace is deprecated since version 2.2 and will be removed in 2.3. Use the Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator class instead.', E_USER_DEPRECATED); - - parent::__construct($securityContext, $encoderFactory); - } -} diff --git a/Http/EntryPoint/FormAuthenticationEntryPoint.php b/Http/EntryPoint/FormAuthenticationEntryPoint.php index 2170e9e..3eaae82 100644 --- a/Http/EntryPoint/FormAuthenticationEntryPoint.php +++ b/Http/EntryPoint/FormAuthenticationEntryPoint.php @@ -53,7 +53,12 @@ class FormAuthenticationEntryPoint implements AuthenticationEntryPointInterface if ($this->useForward) { $subRequest = $this->httpUtils->createRequest($request, $this->loginPath); - return $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + $response = $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + if (200 === $response->getStatusCode()) { + $response->headers->set('X-Status-Code', 401); + } + + return $response; } return $this->httpUtils->createRedirectResponse($request, $this->loginPath); diff --git a/Http/Firewall/AbstractAuthenticationListener.php b/Http/Firewall/AbstractAuthenticationListener.php index b16e70c..7fa991c 100644 --- a/Http/Firewall/AbstractAuthenticationListener.php +++ b/Http/Firewall/AbstractAuthenticationListener.php @@ -93,6 +93,14 @@ abstract class AbstractAuthenticationListener implements ListenerInterface $this->failureHandler = $failureHandler; $this->options = array_merge(array( 'check_path' => '/login_check', + 'login_path' => '/login', + 'always_use_default_target_path' => false, + 'default_target_path' => '/', + 'target_path_parameter' => '_target_path', + 'use_referer' => false, + 'failure_path' => null, + 'failure_forward' => false, + 'require_previous_session' => true, ), $options); $this->logger = $logger; $this->dispatcher = $dispatcher; @@ -130,7 +138,7 @@ abstract class AbstractAuthenticationListener implements ListenerInterface } try { - if (!$request->hasPreviousSession()) { + if ($this->options['require_previous_session'] && !$request->hasPreviousSession()) { throw new SessionUnavailableException('Your session has timed out, or you have disabled cookies.'); } diff --git a/Http/Firewall/AnonymousAuthenticationListener.php b/Http/Firewall/AnonymousAuthenticationListener.php index af2213b..59f05ff 100644 --- a/Http/Firewall/AnonymousAuthenticationListener.php +++ b/Http/Firewall/AnonymousAuthenticationListener.php @@ -49,7 +49,7 @@ class AnonymousAuthenticationListener implements ListenerInterface $this->context->setToken(new AnonymousToken($this->key, 'anon.', array())); if (null !== $this->logger) { - $this->logger->info(sprintf('Populated SecurityContext with an anonymous Token')); + $this->logger->info('Populated SecurityContext with an anonymous Token'); } } } diff --git a/Http/Firewall/ExceptionListener.php b/Http/Firewall/ExceptionListener.php index b2c8862..abbb460 100644 --- a/Http/Firewall/ExceptionListener.php +++ b/Http/Firewall/ExceptionListener.php @@ -185,7 +185,7 @@ class ExceptionListener { // session isn't required when using http basic authentication mechanism for example if ($request->hasSession() && $request->isMethodSafe()) { - $request->getSession()->set('_security.' . $this->providerKey . '.target_path', $request->getUri()); + $request->getSession()->set('_security.'.$this->providerKey.'.target_path', $request->getUri()); } } } @@ -11,7 +11,7 @@ Resources Documentation: -http://symfony.com/doc/2.2/book/security.html +http://symfony.com/doc/2.3/book/security.html Resources --------- diff --git a/Resources/translations/security.el.xlf b/Resources/translations/security.el.xlf new file mode 100644 index 0000000..07eabe7 --- /dev/null +++ b/Resources/translations/security.el.xlf @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> + <file source-language="en" datatype="plaintext" original="file.ext"> + <body> + <trans-unit id="1"> + <source>An authentication exception occurred.</source> + <target>Συνέβη ένα σφάλμα πιστοποίησης.</target> + </trans-unit> + <trans-unit id="2"> + <source>Authentication credentials could not be found.</source> + <target>Τα στοιχεία πιστοποίησης δε βρέθηκαν.</target> + </trans-unit> + <trans-unit id="3"> + <source>Authentication request could not be processed due to a system problem.</source> + <target>Το αίτημα πιστοποίησης δε μπορεί να επεξεργαστεί λόγω σφάλματος του συστήματος.</target> + </trans-unit> + <trans-unit id="4"> + <source>Invalid credentials.</source> + <target>Λανθασμένα στοιχεία σύνδεσης.</target> + </trans-unit> + <trans-unit id="5"> + <source>Cookie has already been used by someone else.</source> + <target>Το Cookie έχει ήδη χρησιμοποιηθεί από κάποιον άλλο.</target> + </trans-unit> + <trans-unit id="6"> + <source>Not privileged to request the resource.</source> + <target>Δεν είστε εξουσιοδοτημένος για πρόσβαση στο συγκεκριμένο περιεχόμενο.</target> + </trans-unit> + <trans-unit id="7"> + <source>Invalid CSRF token.</source> + <target>Μη έγκυρο CSRF token.</target> + </trans-unit> + <trans-unit id="8"> + <source>Digest nonce has expired.</source> + <target>Το digest nonce έχει λήξει.</target> + </trans-unit> + <trans-unit id="9"> + <source>No authentication provider found to support the authentication token.</source> + <target>Δε βρέθηκε κάποιος πάροχος πιστοποίησης που να υποστηρίζει το token πιστοποίησης.</target> + </trans-unit> + <trans-unit id="10"> + <source>No session available, it either timed out or cookies are not enabled.</source> + <target>Δεν υπάρχει ενεργή σύνοδος (session), είτε έχει λήξει ή τα cookies δεν είναι ενεργοποιημένα.</target> + </trans-unit> + <trans-unit id="11"> + <source>No token could be found.</source> + <target>Δεν ήταν δυνατόν να βρεθεί κάποιο token.</target> + </trans-unit> + <trans-unit id="12"> + <source>Username could not be found.</source> + <target>Το Username δε βρέθηκε.</target> + </trans-unit> + <trans-unit id="13"> + <source>Account has expired.</source> + <target>Ο λογαριασμός έχει λήξει.</target> + </trans-unit> + <trans-unit id="14"> + <source>Credentials have expired.</source> + <target>Τα στοιχεία σύνδεσης έχουν λήξει.</target> + </trans-unit> + <trans-unit id="15"> + <source>Account is disabled.</source> + <target>Ο λογαριασμός είναι απενεργοποιημένος.</target> + </trans-unit> + <trans-unit id="16"> + <source>Account is locked.</source> + <target>Ο λογαριασμός είναι κλειδωμένος.</target> + </trans-unit> + </body> + </file> +</xliff> diff --git a/Tests/Core/Authentication/Token/AbstractTokenTest.php b/Tests/Core/Authentication/Token/AbstractTokenTest.php index 4df6068..783c27e 100644 --- a/Tests/Core/Authentication/Token/AbstractTokenTest.php +++ b/Tests/Core/Authentication/Token/AbstractTokenTest.php @@ -116,9 +116,9 @@ class AbstractTokenTest extends \PHPUnit_Framework_TestCase $token->setAttributes($attributes); $this->assertEquals($attributes, $token->getAttributes(), '->getAttributes() returns the token attributes'); - $this->assertEquals('bar', $token->getAttribute('foo'), '->getAttribute() returns the value of a attribute'); + $this->assertEquals('bar', $token->getAttribute('foo'), '->getAttribute() returns the value of an attribute'); $token->setAttribute('foo', 'foo'); - $this->assertEquals('foo', $token->getAttribute('foo'), '->setAttribute() changes the value of a attribute'); + $this->assertEquals('foo', $token->getAttribute('foo'), '->setAttribute() changes the value of an attribute'); $this->assertTrue($token->hasAttribute('foo'), '->hasAttribute() returns true if the attribute is defined'); $this->assertFalse($token->hasAttribute('oof'), '->hasAttribute() returns false if the attribute is not defined'); diff --git a/Tests/Core/Encoder/BCryptPasswordEncoderTest.php b/Tests/Core/Encoder/BCryptPasswordEncoderTest.php index bfaf5fc..49c1051 100644 --- a/Tests/Core/Encoder/BCryptPasswordEncoderTest.php +++ b/Tests/Core/Encoder/BCryptPasswordEncoderTest.php @@ -22,30 +22,12 @@ class BCryptPasswordEncoderTest extends \PHPUnit_Framework_TestCase const BYTES = '0123456789abcdef'; const VALID_COST = '04'; - const SECURE_RANDOM_INTERFACE = 'Symfony\Component\Security\Core\Util\SecureRandomInterface'; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $secureRandom; - - protected function setUp() - { - $this->secureRandom = $this->getMock(self::SECURE_RANDOM_INTERFACE); - - $this->secureRandom - ->expects($this->any()) - ->method('nextBytes') - ->will($this->returnValue(self::BYTES)) - ; - } - /** * @expectedException \InvalidArgumentException */ public function testCostBelowRange() { - new BCryptPasswordEncoder($this->secureRandom, 3); + new BCryptPasswordEncoder(3); } /** @@ -53,60 +35,39 @@ class BCryptPasswordEncoderTest extends \PHPUnit_Framework_TestCase */ public function testCostAboveRange() { - new BCryptPasswordEncoder($this->secureRandom, 32); + new BCryptPasswordEncoder(32); } public function testCostInRange() { for ($cost = 4; $cost <= 31; $cost++) { - new BCryptPasswordEncoder($this->secureRandom, $cost); + new BCryptPasswordEncoder($cost); } } public function testResultLength() { - $encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST); + $this->skipIfPhpVersionIsNotSupported(); + + $encoder = new BCryptPasswordEncoder(self::VALID_COST); $result = $encoder->encodePassword(self::PASSWORD, null); $this->assertEquals(60, strlen($result)); } public function testValidation() { - $encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST); + $this->skipIfPhpVersionIsNotSupported(); + + $encoder = new BCryptPasswordEncoder(self::VALID_COST); $result = $encoder->encodePassword(self::PASSWORD, null); $this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null)); $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); } - public function testValidationKnownPassword() + private function skipIfPhpVersionIsNotSupported() { - $encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST); - $prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=') - ? '2y' : '2a').'$'; - - $encrypted = $prefix.'04$ABCDEFGHIJKLMNOPQRSTU.uTmwd4KMSHxbUsG7bng8x7YdA0PM1iq'; - $this->assertTrue($encoder->isPasswordValid($encrypted, self::PASSWORD, null)); - } - - public function testSecureRandomIsUsed() - { - if (function_exists('mcrypt_create_iv')) { - return; + if (version_compare(phpversion(), '5.3.7', '<')) { + $this->markTestSkipped('Requires PHP >= 5.3.7'); } - - $this->secureRandom - ->expects($this->atLeastOnce()) - ->method('nextBytes') - ; - - $encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST); - $result = $encoder->encodePassword(self::PASSWORD, null); - - $prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=') - ? '2y' : '2a').'$'; - $salt = 'MDEyMzQ1Njc4OWFiY2RlZe'; - $expected = crypt(self::PASSWORD, $prefix . self::VALID_COST . '$' . $salt); - - $this->assertEquals($expected, $result); } } diff --git a/Tests/Http/AccessMapTest.php b/Tests/Http/AccessMapTest.php new file mode 100644 index 0000000..653152a --- /dev/null +++ b/Tests/Http/AccessMapTest.php @@ -0,0 +1,58 @@ +<?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\Tests\Http; + +use Symfony\Component\Security\Http\AccessMap; + +class AccessMapTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testReturnsFirstMatchedPattern() + { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $requestMatcher1 = $this->getRequestMatcher($request, false); + $requestMatcher2 = $this->getRequestMatcher($request, true); + + $map = new AccessMap(); + $map->add($requestMatcher1, array('ROLE_ADMIN'), 'http'); + $map->add($requestMatcher2, array('ROLE_USER'), 'https'); + + $this->assertSame(array(array('ROLE_USER'), 'https'), $map->getPatterns($request)); + } + + public function testReturnsEmptyPatternIfNoneMatched() + { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $requestMatcher = $this->getRequestMatcher($request, false); + + $map = new AccessMap(); + $map->add($requestMatcher, array('ROLE_USER'), 'https'); + + $this->assertSame(array(null, null), $map->getPatterns($request)); + } + + private function getRequestMatcher($request, $matches) + { + $requestMatcher = $this->getMock('Symfony\Component\HttpFoundation\RequestMatcherInterface'); + $requestMatcher->expects($this->once()) + ->method('matches')->with($request) + ->will($this->returnValue($matches)); + + return $requestMatcher; + } +} diff --git a/Tests/Http/Authentication/DefaultAuthenticationFailureHandlerTest.php b/Tests/Http/Authentication/DefaultAuthenticationFailureHandlerTest.php new file mode 100644 index 0000000..c51893f --- /dev/null +++ b/Tests/Http/Authentication/DefaultAuthenticationFailureHandlerTest.php @@ -0,0 +1,186 @@ +<?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\Tests\Http; + +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; +use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class DefaultAuthenticationFailureHandlerTest extends \PHPUnit_Framework_TestCase +{ + private $httpKernel = null; + + private $httpUtils = null; + + private $logger = null; + + private $request = null; + + private $session = null; + + private $exception = null; + + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpKernel\HttpKernel')) { + $this->markTestSkipped('The "HttpKernel" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + if (!interface_exists('Psr\Log\LoggerInterface')) { + $this->markTestSkipped('The "LoggerInterface" is not available'); + } + + $this->httpKernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $this->httpUtils = $this->getMock('Symfony\Component\Security\Http\HttpUtils'); + $this->logger = $this->getMock('Psr\Log\LoggerInterface'); + + $this->session = $this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface'); + $this->request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $this->request->expects($this->any())->method('getSession')->will($this->returnValue($this->session)); + $this->exception = $this->getMock('Symfony\Component\Security\Core\Exception\AuthenticationException'); + } + + public function testForward() + { + $options = array('failure_forward' => true); + + $subRequest = $this->getRequest(); + $subRequest->attributes->expects($this->once()) + ->method('set')->with(SecurityContextInterface::AUTHENTICATION_ERROR, $this->exception); + $this->httpUtils->expects($this->once()) + ->method('createRequest')->with($this->request, '/login') + ->will($this->returnValue($subRequest)); + + $response = $this->getMock('Symfony\Component\HttpFoundation\Response'); + $this->httpKernel->expects($this->once()) + ->method('handle')->with($subRequest, HttpKernelInterface::SUB_REQUEST) + ->will($this->returnValue($response)); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, $options, $this->logger); + $result = $handler->onAuthenticationFailure($this->request, $this->exception); + + $this->assertSame($response, $result); + } + + public function testRedirect() + { + $response = $this->getMock('Symfony\Component\HttpFoundation\Response'); + $this->httpUtils->expects($this->once()) + ->method('createRedirectResponse')->with($this->request, '/login') + ->will($this->returnValue($response)); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, array(), $this->logger); + $result = $handler->onAuthenticationFailure($this->request, $this->exception); + + $this->assertSame($response, $result); + } + + public function testExceptionIsPersistedInSession() + { + $this->session->expects($this->once()) + ->method('set')->with(SecurityContextInterface::AUTHENTICATION_ERROR, $this->exception); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, array(), $this->logger); + $handler->onAuthenticationFailure($this->request, $this->exception); + } + + public function testExceptionIsPassedInRequestOnForward() + { + $options = array('failure_forward' => true); + + $subRequest = $this->getRequest(); + $subRequest->attributes->expects($this->once()) + ->method('set')->with(SecurityContextInterface::AUTHENTICATION_ERROR, $this->exception); + + $this->httpUtils->expects($this->once()) + ->method('createRequest')->with($this->request, '/login') + ->will($this->returnValue($subRequest)); + + $this->session->expects($this->never())->method('set'); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, $options, $this->logger); + $handler->onAuthenticationFailure($this->request, $this->exception); + } + + public function testRedirectIsLogged() + { + $this->logger->expects($this->once())->method('debug')->with('Redirecting to /login'); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, array(), $this->logger); + $handler->onAuthenticationFailure($this->request, $this->exception); + } + + public function testForwardIsLogged() + { + $options = array('failure_forward' => true); + + $this->httpUtils->expects($this->once()) + ->method('createRequest')->with($this->request, '/login') + ->will($this->returnValue($this->getRequest())); + + $this->logger->expects($this->once())->method('debug')->with('Forwarding to /login'); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, $options, $this->logger); + $handler->onAuthenticationFailure($this->request, $this->exception); + } + + public function testFailurePathCanBeOverwritten() + { + $options = array('failure_path' => '/auth/login'); + + $this->httpUtils->expects($this->once()) + ->method('createRedirectResponse')->with($this->request, '/auth/login'); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, $options, $this->logger); + $handler->onAuthenticationFailure($this->request, $this->exception); + } + + public function testFailurePathCanBeOverwrittenWithRequest() + { + $this->request->expects($this->once()) + ->method('get')->with('_failure_path', null, true) + ->will($this->returnValue('/auth/login')); + + $this->httpUtils->expects($this->once()) + ->method('createRedirectResponse')->with($this->request, '/auth/login'); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, array(), $this->logger); + $handler->onAuthenticationFailure($this->request, $this->exception); + } + + public function testFailurePathParameterCanBeOverwritten() + { + $options = array('failure_path_parameter' => '_my_failure_path'); + + $this->request->expects($this->once()) + ->method('get')->with('_my_failure_path', null, true) + ->will($this->returnValue('/auth/login')); + + $this->httpUtils->expects($this->once()) + ->method('createRedirectResponse')->with($this->request, '/auth/login'); + + $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, $options, $this->logger); + $handler->onAuthenticationFailure($this->request, $this->exception); + } + + private function getRequest() + { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $request->attributes = $this->getMock('Symfony\Component\HttpFoundation\ParameterBag'); + + return $request; + } +} diff --git a/Tests/Http/Authentication/DefaultAuthenticationSuccessHandlerTest.php b/Tests/Http/Authentication/DefaultAuthenticationSuccessHandlerTest.php new file mode 100644 index 0000000..71d6ad4 --- /dev/null +++ b/Tests/Http/Authentication/DefaultAuthenticationSuccessHandlerTest.php @@ -0,0 +1,173 @@ +<?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\Tests\Http; + +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; + +class DefaultAuthenticationSuccessHandlerTest extends \PHPUnit_Framework_TestCase +{ + private $httpUtils = null; + + private $request = null; + + private $token = null; + + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $this->httpUtils = $this->getMock('Symfony\Component\Security\Http\HttpUtils'); + $this->request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $this->request->headers = $this->getMock('Symfony\Component\HttpFoundation\HeaderBag'); + $this->token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + } + + public function testRequestIsRedirected() + { + $response = $this->expectRedirectResponse('/'); + + $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array()); + $result = $handler->onAuthenticationSuccess($this->request, $this->token); + + $this->assertSame($response, $result); + } + + public function testDefaultTargetPathCanBeForced() + { + $options = array( + 'always_use_default_target_path' => true, + 'default_target_path' => '/dashboard' + ); + + $response = $this->expectRedirectResponse('/dashboard'); + + $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options); + $result = $handler->onAuthenticationSuccess($this->request, $this->token); + + $this->assertSame($response, $result); + } + + public function testTargetPathIsPassedWithRequest() + { + $this->request->expects($this->once()) + ->method('get')->with('_target_path') + ->will($this->returnValue('/dashboard')); + + $response = $this->expectRedirectResponse('/dashboard'); + + $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array()); + $result = $handler->onAuthenticationSuccess($this->request, $this->token); + + $this->assertSame($response, $result); + } + + public function testTargetPathParameterIsCustomised() + { + $options = array('target_path_parameter' => '_my_target_path'); + + $this->request->expects($this->once()) + ->method('get')->with('_my_target_path') + ->will($this->returnValue('/dashboard')); + + $response = $this->expectRedirectResponse('/dashboard'); + + $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options); + $result = $handler->onAuthenticationSuccess($this->request, $this->token); + + $this->assertSame($response, $result); + } + + public function testTargetPathIsTakenFromTheSession() + { + $session = $this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface'); + $session->expects($this->once()) + ->method('get')->with('_security.admin.target_path') + ->will($this->returnValue('/admin/dashboard')); + $session->expects($this->once()) + ->method('remove')->with('_security.admin.target_path'); + + $this->request->expects($this->any()) + ->method('getSession') + ->will($this->returnValue($session)); + + $response = $this->expectRedirectResponse('/admin/dashboard'); + + $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array()); + $handler->setProviderKey('admin'); + + $result = $handler->onAuthenticationSuccess($this->request, $this->token); + + $this->assertSame($response, $result); + } + + public function testTargetPathIsPassedAsReferer() + { + $options = array('use_referer' => true); + + $this->request->headers->expects($this->once()) + ->method('get')->with('Referer') + ->will($this->returnValue('/dashboard')); + + $response = $this->expectRedirectResponse('/dashboard'); + + $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options); + $result = $handler->onAuthenticationSuccess($this->request, $this->token); + + $this->assertSame($response, $result); + } + + public function testRefererHasToBeDifferentThatLoginUrl() + { + $options = array('use_referer' => true); + + $this->request->headers->expects($this->any()) + ->method('get')->with('Referer') + ->will($this->returnValue('/login')); + + $this->httpUtils->expects($this->once()) + ->method('generateUri')->with($this->request, '/login') + ->will($this->returnValue('/login')); + + $response = $this->expectRedirectResponse('/'); + + $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options); + $result = $handler->onAuthenticationSuccess($this->request, $this->token); + + $this->assertSame($response, $result); + } + + public function testRefererTargetPathIsIgnoredByDefault() + { + $this->request->headers->expects($this->never())->method('get'); + + $response = $this->expectRedirectResponse('/'); + + $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array()); + $result = $handler->onAuthenticationSuccess($this->request, $this->token); + + $this->assertSame($response, $result); + } + + private function expectRedirectResponse($path) + { + $response = $this->getMock('Symfony\Component\HttpFoundation\Response'); + + $this->httpUtils->expects($this->once()) + ->method('createRedirectResponse') + ->with($this->request, $path) + ->will($this->returnValue($response)); + + return $response; + } +} diff --git a/Tests/Http/EntryPoint/FormAuthenticationEntryPointTest.php b/Tests/Http/EntryPoint/FormAuthenticationEntryPointTest.php index 1cf2c2d..cbec1bd 100644 --- a/Tests/Http/EntryPoint/FormAuthenticationEntryPointTest.php +++ b/Tests/Http/EntryPoint/FormAuthenticationEntryPointTest.php @@ -50,7 +50,7 @@ class FormAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase { $request = $this->getMock('Symfony\Component\HttpFoundation\Request', array(), array(), '', false, false); $subRequest = $this->getMock('Symfony\Component\HttpFoundation\Request', array(), array(), '', false, false); - $response = $this->getMock('Symfony\Component\HttpFoundation\Response'); + $response = new \Symfony\Component\HttpFoundation\Response('', 200); $httpUtils = $this->getMock('Symfony\Component\Security\Http\HttpUtils'); $httpUtils @@ -70,6 +70,9 @@ class FormAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase $entryPoint = new FormAuthenticationEntryPoint($httpKernel, $httpUtils, '/the/login/path', true); - $this->assertEquals($response, $entryPoint->start($request)); + $entryPointResponse = $entryPoint->start($request); + + $this->assertEquals($response, $entryPointResponse); + $this->assertEquals(401, $entryPointResponse->headers->get('X-Status-Code')); } } diff --git a/Tests/Http/Firewall/AnonymousAuthenticationListenerTest.php b/Tests/Http/Firewall/AnonymousAuthenticationListenerTest.php index 9e7ea90..73ee821 100644 --- a/Tests/Http/Firewall/AnonymousAuthenticationListenerTest.php +++ b/Tests/Http/Firewall/AnonymousAuthenticationListenerTest.php @@ -59,4 +59,21 @@ class AnonymousAuthenticationListenerTest extends \PHPUnit_Framework_TestCase $listener = new AnonymousAuthenticationListener($context, 'TheKey'); $listener->handle($this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false)); } + + public function testHandledEventIsLogged() + { + if (!interface_exists('Psr\Log\LoggerInterface')) { + $this->markTestSkipped('The "LoggerInterface" is not available'); + } + + $context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $logger->expects($this->once()) + ->method('info') + ->with('Populated SecurityContext with an anonymous Token') + ; + + $listener = new AnonymousAuthenticationListener($context, 'TheKey', $logger); + $listener->handle($this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false)); + } } diff --git a/Tests/Http/Firewall/BasicAuthenticationListenerTest.php b/Tests/Http/Firewall/BasicAuthenticationListenerTest.php index 84564bf..7616149 100644 --- a/Tests/Http/Firewall/BasicAuthenticationListenerTest.php +++ b/Tests/Http/Firewall/BasicAuthenticationListenerTest.php @@ -196,6 +196,20 @@ class BasicAuthenticationListenerTest extends \PHPUnit_Framework_TestCase $listener->handle($event); } + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage $providerKey must not be empty + */ + public function testItRequiresProviderKey() + { + new BasicAuthenticationListener( + $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'), + $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface'), + '', + $this->getMock('Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface') + ); + } + public function testHandleWithADifferentAuthenticatedToken() { $request = new Request(array(), array(), array(), array(), array(), array( diff --git a/Tests/Http/Firewall/ContextListenerTest.php b/Tests/Http/Firewall/ContextListenerTest.php index ffe6195..336c333 100644 --- a/Tests/Http/Firewall/ContextListenerTest.php +++ b/Tests/Http/Firewall/ContextListenerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\SecurityContext; use Symfony\Component\Security\Http\Firewall\ContextListener; @@ -48,6 +49,32 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase unset($this->securityContext); } + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage $contextKey must not be empty + */ + public function testItRequiresContextKey() + { + new ContextListener( + $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'), + array(), + '' + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage User provider "stdClass" must implement "Symfony\Component\Security\Core\User\UserProviderInterface + */ + public function testUserProvidersNeedToImplementAnInterface() + { + new ContextListener( + $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'), + array(new \stdClass()), + 'key123' + ); + } + public function testOnKernelResponseWillAddSession() { $session = $this->runSessionOnKernelResponse( @@ -131,9 +158,7 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); - $session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session') - ->disableOriginalConstructor() - ->getMock(); + $session = $this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface'); $event->expects($this->any()) ->method('getRequest') @@ -147,7 +172,7 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase $session->expects($this->any()) ->method('get') ->with('_security_key123') - ->will($this->returnValue(serialize($token))); + ->will($this->returnValue($token)); $context->expects($this->once()) ->method('setToken') ->with(null); @@ -159,11 +184,53 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase public function provideInvalidToken() { return array( - array(new \__PHP_Incomplete_Class()), - array(null), + array(serialize(new \__PHP_Incomplete_Class())), + array(serialize(null)), + array(null) ); } + public function testHandleAddsKernelResponseListener() + { + $context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $listener = new ContextListener($context, array(), 'key123', null, $dispatcher); + + $event->expects($this->any()) + ->method('getRequestType') + ->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST)); + $event->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($this->getMock('Symfony\Component\HttpFoundation\Request'))); + + $dispatcher->expects($this->once()) + ->method('addListener') + ->with(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + + $listener->handle($event); + } + + public function testHandleRemovesTokenIfNoPreviousSessionWasFound() + { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $request->expects($this->any())->method('hasPreviousSession')->will($this->returnValue(false)); + + $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + $event->expects($this->any())->method('getRequest')->will($this->returnValue($request)); + + $context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); + $context->expects($this->once())->method('setToken')->with(null); + + $listener = new ContextListener($context, array(), 'key123'); + $listener->handle($event); + } + protected function runSessionOnKernelResponse($newToken, $original = null) { $session = new Session(new MockArraySessionStorage()); @@ -189,4 +256,5 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase $listener->onKernelResponse($event); return $session; - }} + } +} diff --git a/Tests/Http/Firewall/DigestDataTest.php b/Tests/Http/Firewall/DigestDataTest.php index cfb929c..8b63d9c 100644 --- a/Tests/Http/Firewall/DigestDataTest.php +++ b/Tests/Http/Firewall/DigestDataTest.php @@ -103,10 +103,10 @@ class DigestDataTest extends \PHPUnit_Framework_TestCase { $time = microtime(true); $key = 'ThisIsAKey'; - $nonce = base64_encode($time . ':' . md5($time . ':' . $key)); + $nonce = base64_encode($time.':'.md5($time.':'.$key)); $digestAuth = new DigestData( - 'username="user", realm="Welcome, robot!", nonce="' . $nonce . '", ' . + 'username="user", realm="Welcome, robot!", nonce="'.$nonce.'", ' . 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . 'response="b52938fc9e6d7c01be7702ece9031b42"' ); @@ -143,10 +143,10 @@ class DigestDataTest extends \PHPUnit_Framework_TestCase { $time = microtime(true) + 10; $key = 'ThisIsAKey'; - $nonce = base64_encode($time . ':' . md5($time . ':' . $key)); + $nonce = base64_encode($time.':'.md5($time.':'.$key)); $digestAuth = new DigestData( - 'username="user", realm="Welcome, robot!", nonce="' . $nonce . '", ' . + 'username="user", realm="Welcome, robot!", nonce="'.$nonce.'", ' . 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . 'response="b52938fc9e6d7c01be7702ece9031b42"' ); @@ -164,10 +164,10 @@ class DigestDataTest extends \PHPUnit_Framework_TestCase private function calculateServerDigest($username, $realm, $password, $key, $nc, $cnonce, $qop, $method, $uri) { $time = microtime(true); - $nonce = base64_encode($time . ':' . md5($time . ':' . $key)); + $nonce = base64_encode($time.':'.md5($time.':'.$key)); $response = md5( - md5($username . ':' . $realm . ':' . $password) . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . $qop . ':' . md5($method . ':' . $uri) + md5($username.':'.$realm.':'.$password).':'.$nonce.':'.$nc.':'.$cnonce.':'.$qop.':'.md5($method.':'.$uri) ); $digest = sprintf('username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=%s, qop="%s", response="%s"', diff --git a/Tests/Http/Firewall/SwitchUserListenerTest.php b/Tests/Http/Firewall/SwitchUserListenerTest.php new file mode 100644 index 0000000..f8bb9f6 --- /dev/null +++ b/Tests/Http/Firewall/SwitchUserListenerTest.php @@ -0,0 +1,175 @@ +<?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\Tests\Http\Firewall; + +use Symfony\Component\Security\Http\Firewall\SwitchUserListener; + +class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase +{ + private $securityContext; + + private $userProvider; + + private $userChecker; + + private $accessDecisionManager; + + private $request; + + private $event; + + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpKernel\HttpKernel')) { + $this->markTestSkipped('The "HttpKernel" component is not available'); + } + + $this->securityContext = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); + $this->userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface'); + $this->userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface'); + $this->accessDecisionManager = $this->getMock('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface'); + $this->request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $this->request->server = $this->getMock('Symfony\Component\HttpFoundation\ServerBag'); + $this->event = $this->getEvent($this->request); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage $providerKey must not be empty + */ + public function testProviderKeyIsRequired() + { + new SwitchUserListener($this->securityContext, $this->userProvider, $this->userChecker, '', $this->accessDecisionManager); + } + + public function testEventIsIgnoredIfUsernameIsNotPassedWithTheRequest() + { + $this->request->expects($this->any())->method('get')->with('_switch_user')->will($this->returnValue(null)); + + $this->event->expects($this->never())->method('setResopnse'); + $this->securityContext->expects($this->never())->method('setToken'); + + $listener = new SwitchUserListener($this->securityContext, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager); + $listener->handle($this->event); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException + */ + public function testExitUserThrowsAuthenticationExceptionIfOriginalTokenCannotBeFound() + { + $token = $this->getToken(array($this->getMock('Symfony\Component\Security\Core\Role\RoleInterface'))); + + $this->securityContext->expects($this->any())->method('getToken')->will($this->returnValue($token)); + $this->request->expects($this->any())->method('get')->with('_switch_user')->will($this->returnValue('_exit')); + + $listener = new SwitchUserListener($this->securityContext, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager); + $listener->handle($this->event); + } + + public function testExitUserUpdatesToken() + { + $originalToken = $this->getToken(); + $role = $this->getMockBuilder('Symfony\Component\Security\Core\Role\SwitchUserRole') + ->disableOriginalConstructor() + ->getMock(); + $role->expects($this->any())->method('getSource')->will($this->returnValue($originalToken)); + + $this->securityContext->expects($this->any()) + ->method('getToken') + ->will($this->returnValue($this->getToken(array($role)))); + + $this->request->expects($this->any())->method('get')->with('_switch_user')->will($this->returnValue('_exit')); + $this->request->expects($this->any())->method('getUri')->will($this->returnValue('/')); + $this->request->server->expects($this->once())->method('set')->with('QUERY_STRING', ''); + + $this->securityContext->expects($this->once()) + ->method('setToken')->with($originalToken); + $this->event->expects($this->once()) + ->method('setResponse')->with($this->isInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse')); + + $listener = new SwitchUserListener($this->securityContext, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager); + $listener->handle($this->event); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AccessDeniedException + */ + public function testSwitchUserIsDissallowed() + { + $token = $this->getToken(array($this->getMock('Symfony\Component\Security\Core\Role\RoleInterface'))); + + $this->securityContext->expects($this->any())->method('getToken')->will($this->returnValue($token)); + $this->request->expects($this->any())->method('get')->with('_switch_user')->will($this->returnValue('kuba')); + + $this->accessDecisionManager->expects($this->once()) + ->method('decide')->with($token, array('ROLE_ALLOWED_TO_SWITCH')) + ->will($this->returnValue(false)); + + $listener = new SwitchUserListener($this->securityContext, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager); + $listener->handle($this->event); + } + + public function testSwitchUser() + { + $token = $this->getToken(array($this->getMock('Symfony\Component\Security\Core\Role\RoleInterface'))); + $user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); + $user->expects($this->any())->method('getRoles')->will($this->returnValue(array())); + + $this->securityContext->expects($this->any())->method('getToken')->will($this->returnValue($token)); + $this->request->expects($this->any())->method('get')->with('_switch_user')->will($this->returnValue('kuba')); + $this->request->expects($this->any())->method('getUri')->will($this->returnValue('/')); + $this->request->server->expects($this->once())->method('set')->with('QUERY_STRING', ''); + + $this->accessDecisionManager->expects($this->once()) + ->method('decide')->with($token, array('ROLE_ALLOWED_TO_SWITCH')) + ->will($this->returnValue(true)); + + $this->userProvider->expects($this->once()) + ->method('loadUserByUsername')->with('kuba') + ->will($this->returnValue($user)); + $this->userChecker->expects($this->once()) + ->method('checkPostAuth')->with($user); + $this->securityContext->expects($this->once()) + ->method('setToken')->with($this->isInstanceOf('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken')); + + $listener = new SwitchUserListener($this->securityContext, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager); + $listener->handle($this->event); + } + + private function getEvent($request) + { + $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)); + + return $event; + } + + private function getToken(array $roles = array()) + { + $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + $token->expects($this->any()) + ->method('getRoles') + ->will($this->returnValue($roles)); + + return $token; + } +} diff --git a/Tests/Http/HttpUtilsTest.php b/Tests/Http/HttpUtilsTest.php index bf078da..db1aa4b 100644 --- a/Tests/Http/HttpUtilsTest.php +++ b/Tests/Http/HttpUtilsTest.php @@ -12,8 +12,10 @@ namespace Symfony\Component\Security\Tests\Http; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\Security\Http\HttpUtils; class HttpUtilsTest extends \PHPUnit_Framework_TestCase { @@ -28,21 +30,27 @@ class HttpUtilsTest extends \PHPUnit_Framework_TestCase } } - public function testCreateRedirectResponse() + public function testCreateRedirectResponseWithPath() { $utils = new HttpUtils($this->getUrlGenerator()); - - // absolute path $response = $utils->createRedirectResponse($this->getRequest(), '/foobar'); + $this->assertTrue($response->isRedirect('http://localhost/foobar')); $this->assertEquals(302, $response->getStatusCode()); + } - // absolute URL + public function testCreateRedirectResponseWithAbsoluteUrl() + { + $utils = new HttpUtils($this->getUrlGenerator()); $response = $utils->createRedirectResponse($this->getRequest(), 'http://symfony.com/'); + $this->assertTrue($response->isRedirect('http://symfony.com/')); + } - // route name + public function testCreateRedirectResponseWithRouteName() + { $utils = new HttpUtils($urlGenerator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface')); + $urlGenerator ->expects($this->any()) ->method('generate') @@ -54,25 +62,29 @@ class HttpUtilsTest extends \PHPUnit_Framework_TestCase ->method('getContext') ->will($this->returnValue($this->getMock('Symfony\Component\Routing\RequestContext'))) ; + $response = $utils->createRedirectResponse($this->getRequest(), 'foobar'); + $this->assertTrue($response->isRedirect('http://localhost/foo/bar')); } - public function testCreateRequest() + public function testCreateRequestWithPath() { - $utils = new HttpUtils($this->getUrlGenerator()); - - // absolute path $request = $this->getRequest(); $request->server->set('Foo', 'bar'); + + $utils = new HttpUtils($this->getUrlGenerator()); $subRequest = $utils->createRequest($request, '/foobar'); $this->assertEquals('GET', $subRequest->getMethod()); $this->assertEquals('/foobar', $subRequest->getPathInfo()); $this->assertEquals('bar', $subRequest->server->get('Foo')); + } - // route name + public function testCreateRequestWithRouteName() + { $utils = new HttpUtils($urlGenerator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface')); + $urlGenerator ->expects($this->once()) ->method('generate') @@ -83,14 +95,54 @@ class HttpUtilsTest extends \PHPUnit_Framework_TestCase ->method('getContext') ->will($this->returnValue($this->getMock('Symfony\Component\Routing\RequestContext'))) ; + $subRequest = $utils->createRequest($this->getRequest(), 'foobar'); + $this->assertEquals('/foo/bar', $subRequest->getPathInfo()); + } - // absolute URL + public function testCreateRequestWithAbsoluteUrl() + { + $utils = new HttpUtils($this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface')); $subRequest = $utils->createRequest($this->getRequest(), 'http://symfony.com/'); + $this->assertEquals('/', $subRequest->getPathInfo()); } + public function testCreateRequestPassesSessionToTheNewRequest() + { + $request = $this->getRequest(); + $request->setSession($session = $this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface')); + + $utils = new HttpUtils($this->getUrlGenerator()); + $subRequest = $utils->createRequest($request, '/foobar'); + + $this->assertSame($session, $subRequest->getSession()); + } + + /** + * @dataProvider provideSecurityContextAttributes + */ + public function testCreateRequestPassesSecurityContextAttributesToTheNewRequest($attribute) + { + $request = $this->getRequest(); + $request->attributes->set($attribute, 'foo'); + + $utils = new HttpUtils($this->getUrlGenerator()); + $subRequest = $utils->createRequest($request, '/foobar'); + + $this->assertSame('foo', $subRequest->attributes->get($attribute)); + } + + public function provideSecurityContextAttributes() + { + return array( + array(SecurityContextInterface::AUTHENTICATION_ERROR), + array(SecurityContextInterface::ACCESS_DENIED_ERROR), + array(SecurityContextInterface::LAST_USERNAME) + ); + } + public function testCheckRequestPath() { $utils = new HttpUtils($this->getUrlGenerator()); @@ -102,26 +154,66 @@ class HttpUtilsTest extends \PHPUnit_Framework_TestCase $this->assertTrue($utils->checkRequestPath($this->getRequest('/foo+bar'), '/foo+bar')); // Checking unicode $this->assertTrue($utils->checkRequestPath($this->getRequest(urlencode('/вход')), '/вход')); + } + public function testCheckRequestPathWithUrlMatcherAndResourceNotFound() + { $urlMatcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); $urlMatcher ->expects($this->any()) ->method('match') + ->with('/') ->will($this->throwException(new ResourceNotFoundException())) ; + $utils = new HttpUtils(null, $urlMatcher); $this->assertFalse($utils->checkRequestPath($this->getRequest(), 'foobar')); + } + public function testCheckRequestPathWithUrlMatcherAndMethodNotAllowed() + { + $request = $this->getRequest(); + $urlMatcher = $this->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface'); + $urlMatcher + ->expects($this->any()) + ->method('matchRequest') + ->with($request) + ->will($this->throwException(new MethodNotAllowedException(array()))) + ; + + $utils = new HttpUtils(null, $urlMatcher); + $this->assertFalse($utils->checkRequestPath($request, 'foobar')); + } + + public function testCheckRequestPathWithUrlMatcherAndResourceFoundByUrl() + { $urlMatcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); $urlMatcher ->expects($this->any()) ->method('match') + ->with('/foo/bar') ->will($this->returnValue(array('_route' => 'foobar'))) ; + $utils = new HttpUtils(null, $urlMatcher); $this->assertTrue($utils->checkRequestPath($this->getRequest('/foo/bar'), 'foobar')); } + public function testCheckRequestPathWithUrlMatcherAndResourceFoundByRequest() + { + $request = $this->getRequest(); + $urlMatcher = $this->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface'); + $urlMatcher + ->expects($this->any()) + ->method('matchRequest') + ->with($request) + ->will($this->returnValue(array('_route' => 'foobar'))) + ; + + $utils = new HttpUtils(null, $urlMatcher); + $this->assertTrue($utils->checkRequestPath($request, 'foobar')); + } + /** * @expectedException \RuntimeException */ @@ -133,20 +225,37 @@ class HttpUtilsTest extends \PHPUnit_Framework_TestCase ->method('match') ->will($this->throwException(new \RuntimeException())) ; + $utils = new HttpUtils(null, $urlMatcher); $utils->checkRequestPath($this->getRequest(), 'foobar'); } - public function testGenerateUriRemovesQueryString() + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Matcher must either implement UrlMatcherInterface or RequestMatcherInterface + */ + public function testUrlMatcher() { - $method = new \ReflectionMethod('Symfony\Component\Security\Http\HttpUtils', 'generateUri'); - $method->setAccessible(true); + new HttpUtils($this->getUrlGenerator(), new \stdClass()); + } - $utils = new HttpUtils($this->getUrlGenerator()); - $this->assertEquals('/foo/bar', $method->invoke($utils, new Request(), 'route_name')); + public function testGenerateUriRemovesQueryString() + { + $utils = new HttpUtils($this->getUrlGenerator('/foo/bar')); + $this->assertEquals('/foo/bar', $utils->generateUri(new Request(), 'route_name')); $utils = new HttpUtils($this->getUrlGenerator('/foo/bar?param=value')); - $this->assertEquals('/foo/bar', $method->invoke($utils, new Request(), 'route_name')); + $this->assertEquals('/foo/bar', $utils->generateUri(new Request(), 'route_name')); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You must provide a UrlGeneratorInterface instance to be able to use routes. + */ + public function testUrlGeneratorIsRequiredToGenerateUrl() + { + $utils = new HttpUtils(); + $utils->generateUri(new Request(), 'route_name'); } private function getUrlGenerator($generatedUrl = '/foo/bar') diff --git a/Tests/Http/Logout/DefaultLogoutSuccessHandlerTest.php b/Tests/Http/Logout/DefaultLogoutSuccessHandlerTest.php new file mode 100644 index 0000000..e1b1227 --- /dev/null +++ b/Tests/Http/Logout/DefaultLogoutSuccessHandlerTest.php @@ -0,0 +1,42 @@ +<?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\Tests\Http\Logout; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Http\Logout\DefaultLogoutSuccessHandler; + +class DefaultLogoutSuccessHandlerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testLogout() + { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $response = $this->getMock('Symfony\Component\HttpFoundation\Response'); + + $httpUtils = $this->getMock('Symfony\Component\Security\Http\HttpUtils'); + $httpUtils->expects($this->once()) + ->method('createRedirectResponse') + ->with($request, '/dashboard') + ->will($this->returnValue($response)); + + $handler = new DefaultLogoutSuccessHandler($httpUtils, '/dashboard'); + $result = $handler->onLogoutSuccess($request); + + $this->assertSame($response, $result); + } +} diff --git a/Tests/Http/RememberMe/ResponseListenerTest.php b/Tests/Http/RememberMe/ResponseListenerTest.php new file mode 100644 index 0000000..cbd3f1f --- /dev/null +++ b/Tests/Http/RememberMe/ResponseListenerTest.php @@ -0,0 +1,92 @@ +<?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\Tests\Http\RememberMe; + +use Symfony\Component\Security\Http\RememberMe\ResponseListener; +use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpKernel\KernelEvents; + +class ResponseListenerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testRememberMeCookieIsSentWithResponse() + { + $cookie = new Cookie('rememberme'); + + $request = $this->getRequest(array( + RememberMeServicesInterface::COOKIE_ATTR_NAME => $cookie + )); + + $response = $this->getResponse(); + $response->headers->expects($this->once())->method('setCookie')->with($cookie); + + $listener = new ResponseListener(); + $listener->onKernelResponse($this->getEvent($request, $response)); + } + + public function testRemmeberMeCookieIsNotSendWithResponse() + { + $request = $this->getRequest(); + + $response = $this->getResponse(); + $response->headers->expects($this->never())->method('setCookie'); + + $listener = new ResponseListener(); + $listener->onKernelResponse($this->getEvent($request, $response)); + } + + public function testItSubscribesToTheOnKernelResponseEvent() + { + $listener = new ResponseListener(); + + $this->assertSame(array(KernelEvents::RESPONSE => 'onKernelResponse'), $listener->getSubscribedEvents()); + } + + private function getRequest(array $attributes = array()) + { + $request = new Request(); + + foreach ($attributes as $name => $value) { + $request->attributes->set($name, $value); + } + + return $request; + } + + private function getResponse() + { + $response = $this->getMock('Symfony\Component\HttpFoundation\Response'); + $response->headers = $this->getMock('Symfony\Component\HttpFoundation\ResponseHeaderBag'); + + return $response; + } + + private function getEvent($request, $response) + { + $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\FilterResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->any())->method('getRequest')->will($this->returnValue($request)); + $event->expects($this->any())->method('getResponse')->will($this->returnValue($response)); + + return $event; + } +} diff --git a/Tests/Http/Session/SessionAuthenticationStrategyTest.php b/Tests/Http/Session/SessionAuthenticationStrategyTest.php new file mode 100644 index 0000000..43c52b5 --- /dev/null +++ b/Tests/Http/Session/SessionAuthenticationStrategyTest.php @@ -0,0 +1,80 @@ +<?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\Tests\Http\Session; + +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; + +class SessionAuthenticationStrategyTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testSessionIsNotChanged() + { + $request = $this->getRequest(); + $request->expects($this->never())->method('getSession'); + + $strategy = new SessionAuthenticationStrategy(SessionAuthenticationStrategy::NONE); + $strategy->onAuthentication($request, $this->getToken()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Invalid session authentication strategy "foo" + */ + public function testUnsupportedStrategy() + { + $request = $this->getRequest(); + $request->expects($this->never())->method('getSession'); + + $strategy = new SessionAuthenticationStrategy('foo'); + $strategy->onAuthentication($request, $this->getToken()); + } + + public function testSessionIsMigrated() + { + $session = $this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface'); + $session->expects($this->once())->method('migrate'); + + $strategy = new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE); + $strategy->onAuthentication($this->getRequest($session), $this->getToken()); + } + + public function testSessionIsInvalidated() + { + $session = $this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface'); + $session->expects($this->once())->method('invalidate'); + + $strategy = new SessionAuthenticationStrategy(SessionAuthenticationStrategy::INVALIDATE); + $strategy->onAuthentication($this->getRequest($session), $this->getToken()); + } + + private function getRequest($session = null) + { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + + if (null !== $session) { + $request->expects($this->any())->method('getSession')->will($this->returnValue($session)); + } + + return $request; + } + + private function getToken() + { + return $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + } +} diff --git a/composer.json b/composer.json index 083ce94..826c58b 100644 --- a/composer.json +++ b/composer.json @@ -18,24 +18,26 @@ "require": { "php": ">=5.3.3", "symfony/event-dispatcher": "~2.1", - "symfony/http-foundation": ">=2.1,<2.3-dev", - "symfony/http-kernel": ">=2.1,<=2.3-dev" + "symfony/http-foundation": "~2.1", + "symfony/http-kernel": "~2.1" }, "require-dev": { "symfony/form": "~2.0", - "symfony/routing": ">=2.2,<2.3-dev", - "symfony/validator": ">=2.2,<2.3-dev", + "symfony/routing": "~2.2", + "symfony/validator": "~2.2", "doctrine/common": "~2.2", "doctrine/dbal": "~2.2", - "psr/log": "~1.0" + "psr/log": "~1.0", + "ircmaxell/password-compat": "1.0.*" }, "suggest": { - "symfony/class-loader": "2.2.*", - "symfony/finder": "2.2.*", - "symfony/form": "2.2.*", - "symfony/validator": "2.2.*", - "symfony/routing": "2.2.*", - "doctrine/dbal": "to use the built-in ACL implementation" + "symfony/class-loader": "", + "symfony/finder": "", + "symfony/form": "", + "symfony/validator": "", + "symfony/routing": "", + "doctrine/dbal": "to use the built-in ACL implementation", + "ircmaxell/password-compat": "" }, "autoload": { "psr-0": { "Symfony\\Component\\Security\\": "" } @@ -44,7 +46,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } |