From 0851189daaaba5d2e8267ef5534daf721a16974e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 26 Sep 2017 15:22:45 -0400 Subject: [PATCH] Adding a shortcuts for the main security functionality --- .../Bundle/SecurityBundle/CHANGELOG.md | 2 + .../Resources/config/security.xml | 13 +++ .../Tests/Functional/SecurityTest.php | 34 +++++++ .../Functional/app/SecurityHelper/bundles.php | 20 ++++ .../Functional/app/SecurityHelper/config.yml | 18 ++++ src/Symfony/Component/Security/CHANGELOG.md | 1 + .../Component/Security/Core/Security.php | 54 ++++++++++- .../Security/Core/Tests/SecurityTest.php | 97 +++++++++++++++++++ .../Component/Security/Core/composer.json | 2 + src/Symfony/Component/Security/composer.json | 2 + 10 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml create mode 100644 src/Symfony/Component/Security/Core/Tests/SecurityTest.php diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index b759b693cd27..269eefd43e91 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 3.4.0 ----- + * Added new `security.helper` service that is an instance of `Symfony\Component\Security\Core\Security` + and provides shortcuts for common security tasks. * Tagging voters with the `security.voter` tag without implementing the `VoterInterface` on the class is now deprecated and will be removed in 4.0. * [BC BREAK] `FirewallContext::getListeners()` now returns `\Traversable|array` diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 049d67f5a05b..0225df0ee47a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -26,6 +26,19 @@ + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php new file mode 100644 index 000000000000..bcf8a0d620c9 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional; + +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\User\User; + +class SecurityTest extends WebTestCase +{ + public function testServiceIsFunctional() + { + $kernel = self::createKernel(array('test_case' => 'SecurityHelper', 'root_config' => 'config.yml')); + $kernel->boot(); + $container = $kernel->getContainer(); + + // put a token into the storage so the final calls can function + $user = new User('foo', 'pass'); + $token = new UsernamePasswordToken($user, '', 'provider', array('ROLE_USER')); + $container->get('security.token_storage')->setToken($token); + + $security = $container->get('functional_test.security.helper'); + $this->assertTrue($security->isGranted('ROLE_USER')); + $this->assertSame($token, $security->getToken()); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php new file mode 100644 index 000000000000..2a8e7a2ff88d --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\TwigBundle\TwigBundle; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; + +return array( + new FrameworkBundle(), + new SecurityBundle(), + new TwigBundle(), +); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml new file mode 100644 index 000000000000..d7b8ac97d977 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml @@ -0,0 +1,18 @@ +imports: + - { resource: ./../config/default.yml } + +services: + # alias the service so we can access it in the tests + functional_test.security.helper: + alias: security.helper + public: true + +security: + providers: + in_memory: + memory: + users: [] + + firewalls: + default: + anonymous: ~ diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 5ac97ca8b20b..e0ac7afe2f50 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 3.4.0 ----- + * Added `getUser`, `getToken` and `isGranted` methods to `Security`. * added a `setToken()` method to the `SwitchUserEvent` class to allow to replace the created token while switching users when custom token generation is required by application. * Using voters that do not implement the `VoterInterface`is now deprecated in diff --git a/src/Symfony/Component/Security/Core/Security.php b/src/Symfony/Component/Security/Core/Security.php index 84cc77dcf7f3..5f25b41ccade 100644 --- a/src/Symfony/Component/Security/Core/Security.php +++ b/src/Symfony/Component/Security/Core/Security.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Security\Core; +use Psr\Container\ContainerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\User\UserInterface; + /** - * This class holds security information. - * - * @author Johannes M. Schmitt + * Helper class for commonly-needed security tasks. */ final class Security { @@ -22,4 +24,50 @@ final class Security const AUTHENTICATION_ERROR = '_security.last_error'; const LAST_USERNAME = '_security.last_username'; const MAX_USERNAME_LENGTH = 4096; + + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @return UserInterface|null + */ + public function getUser() + { + if (!$token = $this->getToken()) { + return null; + } + + $user = $token->getUser(); + if (!is_object($user)) { + return null; + } + + return $user; + } + + /** + * Checks if the attributes are granted against the current authentication token and optionally supplied subject. + * + * @param mixed $attributes + * @param mixed $subject + * + * @return bool + */ + public function isGranted($attributes, $subject = null) + { + return $this->container->get('security.authorization_checker') + ->isGranted($attributes, $subject); + } + + /** + * @return TokenInterface|null + */ + public function getToken() + { + return $this->container->get('security.token_storage')->getToken(); + } } diff --git a/src/Symfony/Component/Security/Core/Tests/SecurityTest.php b/src/Symfony/Component/Security/Core/Tests/SecurityTest.php new file mode 100644 index 000000000000..b2ba5d0d5fa1 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Tests/SecurityTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\User\User; + +class SecurityTest extends TestCase +{ + public function testGetToken() + { + $token = new UsernamePasswordToken('foo', 'bar', 'provider'); + $tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); + + $tokenStorage->expects($this->once()) + ->method('getToken') + ->will($this->returnValue($token)); + + $container = $this->createContainer('security.token_storage', $tokenStorage); + + $security = new Security($container); + $this->assertSame($token, $security->getToken()); + } + + /** + * @dataProvider getUserTests + */ + public function testGetUser($userInToken, $expectedUser) + { + $token = $this->getMockBuilder(TokenInterface::class)->getMock(); + $token->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($userInToken)); + $tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); + + $tokenStorage->expects($this->once()) + ->method('getToken') + ->will($this->returnValue($token)); + + $container = $this->createContainer('security.token_storage', $tokenStorage); + + $security = new Security($container); + $this->assertSame($expectedUser, $security->getUser()); + } + + public function getUserTests() + { + yield array(null, null); + + yield array('string_username', null); + + $user = new User('nice_user', 'foo'); + yield array($user, $user); + } + + public function testIsGranted() + { + $authorizationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); + + $authorizationChecker->expects($this->once()) + ->method('isGranted') + ->with('SOME_ATTRIBUTE', 'SOME_SUBJECT') + ->will($this->returnValue(true)); + + $container = $this->createContainer('security.authorization_checker', $authorizationChecker); + + $security = new Security($container); + $this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT')); + } + + private function createContainer($serviceId, $serviceObject) + { + $container = $this->getMockBuilder(ContainerInterface::class)->getMock(); + + $container->expects($this->atLeastOnce()) + ->method('get') + ->with($serviceId) + ->will($this->returnValue($serviceObject)); + + return $container; + } +} diff --git a/src/Symfony/Component/Security/Core/composer.json b/src/Symfony/Component/Security/Core/composer.json index 2bba025ae4b5..b70ef54004ff 100644 --- a/src/Symfony/Component/Security/Core/composer.json +++ b/src/Symfony/Component/Security/Core/composer.json @@ -20,6 +20,7 @@ "symfony/polyfill-php56": "~1.0" }, "require-dev": { + "psr/container": "^1.0", "symfony/event-dispatcher": "~2.8|~3.0|~4.0", "symfony/expression-language": "~2.8|~3.0|~4.0", "symfony/http-foundation": "~2.8|~3.0|~4.0", @@ -28,6 +29,7 @@ "psr/log": "~1.0" }, "suggest": { + "psr/container": "To instantiate the Security class", "symfony/event-dispatcher": "", "symfony/http-foundation": "", "symfony/validator": "For using the user password constraint", diff --git a/src/Symfony/Component/Security/composer.json b/src/Symfony/Component/Security/composer.json index e53bf6d53783..bc72e8469bf0 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/src/Symfony/Component/Security/composer.json @@ -32,6 +32,7 @@ "symfony/security-http": "self.version" }, "require-dev": { + "psr/container": "^1.0", "symfony/finder": "~2.8|~3.0|~4.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/routing": "~2.8|~3.0|~4.0", @@ -41,6 +42,7 @@ "psr/log": "~1.0" }, "suggest": { + "psr/container": "To instantiate the Security class", "symfony/form": "", "symfony/validator": "For using the user password constraint", "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs",