diff options
author | Fabien Potencier <fabien.potencier@gmail.com> | 2015-10-05 17:24:55 +0200 |
---|---|---|
committer | Fabien Potencier <fabien.potencier@gmail.com> | 2015-10-05 17:24:55 +0200 |
commit | 4e3ea9f244ad465865c2384f3d9ba2f89361d364 (patch) | |
tree | 1ecfb1dd66e5203b64b8d6341fc76a15499a4350 | |
parent | e7529149fc3d42bab75d5d84f33dfa08bf649079 (diff) | |
parent | 99d73ecb12dedf5c772aab7f00e7d39b60c5f4ed (diff) | |
download | symfony-security-4e3ea9f244ad465865c2384f3d9ba2f89361d364.zip symfony-security-4e3ea9f244ad465865c2384f3d9ba2f89361d364.tar.gz symfony-security-4e3ea9f244ad465865c2384f3d9ba2f89361d364.tar.bz2 |
Merge branch '2.8'
* 2.8: (21 commits)
[Security][bugfix] "Remember me" cookie cleared on logout with custom "secure"/"httponly" config options [1]
[ci] Use current PHP_BINARY when running ./phpunit
Fixed typos
[UPGRADE-3.0] fix bullet indentation
Throw exception if tempnam returns false in ProcessPipes
[DomCrawler] Deprecated using /_root/ in XPath expressions
Pass missing request template variables
Simplify AbstractVoter
[Form] add missing deprecation triggers
Throw exception if tempnam returns false
Fix PropertyAccessor modifying array in object when array key does not exist
[DependencyInjection] Add autowiring capabilities
Fixing typo in variable name
Add a few additional tests for the Crawler
[Form] remove obsolete deprecation comments
Updated the style of the event commands
[Debug] Deprecate providing $fileLinkFormat as second argument
[Form] minor CS fix
Updated PHPDoc of the AbstractVoter class
[Security] InMemoryUserProvider now concerns whether user's password is changed when refreshing
...
-rw-r--r-- | Core/Authorization/Voter/AbstractVoter.php | 26 | ||||
-rw-r--r-- | Core/Tests/Authorization/Voter/AbstractVoterTest.php | 5 | ||||
-rw-r--r-- | Core/Tests/User/InMemoryUserProviderTest.php | 33 | ||||
-rw-r--r-- | Core/User/InMemoryUserProvider.php | 37 | ||||
-rw-r--r-- | Http/RememberMe/AbstractRememberMeServices.php | 2 | ||||
-rw-r--r-- | Http/Tests/RememberMe/AbstractRememberMeServicesTest.php | 32 | ||||
-rw-r--r-- | Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php | 11 | ||||
-rw-r--r-- | Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php | 11 |
8 files changed, 109 insertions, 48 deletions
diff --git a/Core/Authorization/Voter/AbstractVoter.php b/Core/Authorization/Voter/AbstractVoter.php index 71f570f..665d5f1 100644 --- a/Core/Authorization/Voter/AbstractVoter.php +++ b/Core/Authorization/Voter/AbstractVoter.php @@ -41,10 +41,9 @@ abstract class AbstractVoter implements VoterInterface // abstain vote by default in case none of the attributes are supported $vote = self::ACCESS_ABSTAIN; - $class = get_class($object); foreach ($attributes as $attribute) { - if (!$this->supports($attribute, $class)) { + if (!$this->supports($attribute, $object)) { continue; } @@ -61,33 +60,16 @@ abstract class AbstractVoter implements VoterInterface } /** - * Determines if the attribute and class are supported by this voter. - * - * To determine if the passed class is instance of the supported class, the - * isClassInstanceOf() method can be used. + * Determines if the attribute and object are supported by this voter. * * @param string $attribute An attribute - * @param string $class The fully qualified class name of the passed object + * @param string $object The object to secure * - * @return bool True if the attribute and class is supported, false otherwise + * @return bool True if the attribute and object is supported, false otherwise */ abstract protected function supports($attribute, $class); /** - * A helper method to test if the actual class is instanceof or equal - * to the expected class. - * - * @param string $actualClass The actual class name - * @param string $expectedClass The expected class name - * - * @return bool - */ - protected function isClassInstanceOf($actualClass, $expectedClass) - { - return $expectedClass === $actualClass || is_subclass_of($actualClass, $expectedClass); - } - - /** * Perform a single access check operation on a given attribute, object and token. * It is safe to assume that $attribute and $object's class pass supports method call. * diff --git a/Core/Tests/Authorization/Voter/AbstractVoterTest.php b/Core/Tests/Authorization/Voter/AbstractVoterTest.php index aafef5a..537dc4c 100644 --- a/Core/Tests/Authorization/Voter/AbstractVoterTest.php +++ b/Core/Tests/Authorization/Voter/AbstractVoterTest.php @@ -63,9 +63,8 @@ class AbstractVoterTest_Voter extends AbstractVoter return 'EDIT' === $attribute; } - protected function supports($attribute, $class) + protected function supports($attribute, $object) { - return $this->isClassInstanceOf($class, 'stdClass') - && in_array($attribute, array('EDIT', 'CREATE')); + return $object instanceof \stdClass && in_array($attribute, array('EDIT', 'CREATE')); } } diff --git a/Core/Tests/User/InMemoryUserProviderTest.php b/Core/Tests/User/InMemoryUserProviderTest.php index dfc4237..0a1815f 100644 --- a/Core/Tests/User/InMemoryUserProviderTest.php +++ b/Core/Tests/User/InMemoryUserProviderTest.php @@ -18,18 +18,39 @@ class InMemoryUserProviderTest extends \PHPUnit_Framework_TestCase { public function testConstructor() { - $provider = new InMemoryUserProvider(array( + $provider = $this->createProvider(); + + $user = $provider->loadUserByUsername('fabien'); + $this->assertEquals('foo', $user->getPassword()); + $this->assertEquals(array('ROLE_USER'), $user->getRoles()); + $this->assertFalse($user->isEnabled()); + } + + public function testRefresh() + { + $user = new User('fabien', 'bar'); + + $provider = $this->createProvider(); + + $refreshedUser = $provider->refreshUser($user); + $this->assertEquals('foo', $refreshedUser->getPassword()); + $this->assertEquals(array('ROLE_USER'), $refreshedUser->getRoles()); + $this->assertFalse($refreshedUser->isEnabled()); + $this->assertFalse($refreshedUser->isCredentialsNonExpired()); + } + + /** + * @return InMemoryUserProvider + */ + protected function createProvider() + { + return new InMemoryUserProvider(array( 'fabien' => array( 'password' => 'foo', 'enabled' => false, 'roles' => array('ROLE_USER'), ), )); - - $user = $provider->loadUserByUsername('fabien'); - $this->assertEquals('foo', $user->getPassword()); - $this->assertEquals(array('ROLE_USER'), $user->getRoles()); - $this->assertFalse($user->isEnabled()); } public function testCreateUser() diff --git a/Core/User/InMemoryUserProvider.php b/Core/User/InMemoryUserProvider.php index 624eb3d..9aa39ca 100644 --- a/Core/User/InMemoryUserProvider.php +++ b/Core/User/InMemoryUserProvider.php @@ -67,17 +67,9 @@ class InMemoryUserProvider implements UserProviderInterface */ public function loadUserByUsername($username) { - if (!isset($this->users[strtolower($username)])) { - $ex = new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); - $ex->setUsername($username); - - throw $ex; - } + $user = $this->getUser($username); - $user = $this->users[strtolower($username)]; - - return new User($user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled(), $user->isAccountNonExpired(), - $user->isCredentialsNonExpired(), $user->isAccountNonLocked()); + return new User($user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled(), $user->isAccountNonExpired(), $user->isCredentialsNonExpired(), $user->isAccountNonLocked()); } /** @@ -89,7 +81,9 @@ class InMemoryUserProvider implements UserProviderInterface throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); } - return $this->loadUserByUsername($user->getUsername()); + $storedUser = $this->getUser($user->getUsername()); + + return new User($storedUser->getUsername(), $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled(), $storedUser->isAccountNonExpired(), $storedUser->isCredentialsNonExpired() && $storedUser->getPassword() === $user->getPassword(), $storedUser->isAccountNonLocked()); } /** @@ -99,4 +93,25 @@ class InMemoryUserProvider implements UserProviderInterface { return $class === 'Symfony\Component\Security\Core\User\User'; } + + /** + * Returns the user by given username. + * + * @param string $username The username. + * + * @return User + * + * @throws UsernameNotFoundException If user whose given username does not exist. + */ + private function getUser($username) + { + if (!isset($this->users[strtolower($username)])) { + $ex = new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); + $ex->setUsername($username); + + throw $ex; + } + + return $this->users[strtolower($username)]; + } } diff --git a/Http/RememberMe/AbstractRememberMeServices.php b/Http/RememberMe/AbstractRememberMeServices.php index aa2a50d..0352eb4 100644 --- a/Http/RememberMe/AbstractRememberMeServices.php +++ b/Http/RememberMe/AbstractRememberMeServices.php @@ -294,7 +294,7 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface $this->logger->debug('Clearing remember-me cookie.', array('name' => $this->options['name'])); } - $request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null, 1, $this->options['path'], $this->options['domain'])); + $request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null, 1, $this->options['path'], $this->options['domain'], $this->options['secure'], $this->options['httponly'])); } /** diff --git a/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php b/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php index d3daa35..4ea4f5d 100644 --- a/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php +++ b/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php @@ -82,16 +82,35 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase $this->assertSame('fookey', $returnedToken->getProviderKey()); } - public function testLogout() + /** + * @dataProvider provideOptionsForLogout + */ + public function testLogout(array $options) { - $service = $this->getService(null, array('name' => 'foo', 'path' => null, 'domain' => null)); + $service = $this->getService(null, $options); $request = new Request(); $response = new Response(); $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); $service->logout($request, $response, $token); - $this->assertTrue($request->attributes->get(RememberMeServicesInterface::COOKIE_ATTR_NAME)->isCleared()); + $cookie = $request->attributes->get(RememberMeServicesInterface::COOKIE_ATTR_NAME); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Cookie', $cookie); + $this->assertTrue($cookie->isCleared()); + $this->assertSame($options['name'], $cookie->getName()); + $this->assertSame($options['path'], $cookie->getPath()); + $this->assertSame($options['domain'], $cookie->getDomain()); + $this->assertSame($options['secure'], $cookie->isSecure()); + $this->assertSame($options['httponly'], $cookie->isHttpOnly()); + } + + public function provideOptionsForLogout() + { + return array( + array(array('name' => 'foo', 'path' => '/', 'domain' => null, 'secure' => false, 'httponly' => true)), + array(array('name' => 'foo', 'path' => '/bar', 'domain' => 'baz.com', 'secure' => true, 'httponly' => false)), + ); } public function testLoginFail() @@ -267,6 +286,13 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase $userProvider = $this->getProvider(); } + if (!isset($options['secure'])) { + $options['secure'] = false; + } + if (!isset($options['httponly'])) { + $options['httponly'] = true; + } + return $this->getMockForAbstractClass('Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices', array( array($userProvider), 'foosecret', 'fookey', $options, $logger, )); diff --git a/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php b/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php index 889211c..43aaf92 100644 --- a/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php +++ b/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php @@ -180,7 +180,7 @@ class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_Test public function testLogout() { - $service = $this->getService(null, array('name' => 'foo', 'path' => '/foo', 'domain' => 'foodomain.foo')); + $service = $this->getService(null, array('name' => 'foo', 'path' => '/foo', 'domain' => 'foodomain.foo', 'secure' => true, 'httponly' => false)); $request = new Request(); $request->cookies->set('foo', $this->encodeCookie(array('fooseries', 'foovalue'))); $response = new Response(); @@ -201,6 +201,8 @@ class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_Test $this->assertTrue($cookie->isCleared()); $this->assertEquals('/foo', $cookie->getPath()); $this->assertEquals('foodomain.foo', $cookie->getDomain()); + $this->assertTrue($cookie->isSecure()); + $this->assertFalse($cookie->isHttpOnly()); } public function testLogoutSimplyIgnoresNonSetRequestCookie() @@ -311,6 +313,13 @@ class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_Test $userProvider = $this->getProvider(); } + if (!isset($options['secure'])) { + $options['secure'] = false; + } + if (!isset($options['httponly'])) { + $options['httponly'] = true; + } + return new PersistentTokenBasedRememberMeServices(array($userProvider), 'foosecret', 'fookey', $options, $logger, new SecureRandom(sys_get_temp_dir().'/_sf2.seed')); } diff --git a/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php b/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php index 2a892c3..dab811b 100644 --- a/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php +++ b/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php @@ -153,7 +153,7 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase public function testLogout() { - $service = $this->getService(null, array('name' => 'foo', 'path' => null, 'domain' => null)); + $service = $this->getService(null, array('name' => 'foo', 'path' => null, 'domain' => null, 'secure' => true, 'httponly' => false)); $request = new Request(); $response = new Response(); $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); @@ -164,6 +164,8 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase $this->assertTrue($cookie->isCleared()); $this->assertEquals('/', $cookie->getPath()); $this->assertNull($cookie->getDomain()); + $this->assertTrue($cookie->isSecure()); + $this->assertFalse($cookie->isHttpOnly()); } public function testLoginFail() @@ -264,6 +266,13 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase $userProvider = $this->getProvider(); } + if (!isset($options['secure'])) { + $options['secure'] = false; + } + if (!isset($options['httponly'])) { + $options['httponly'] = true; + } + $service = new TokenBasedRememberMeServices(array($userProvider), 'foosecret', 'fookey', $options, $logger); return $service; |