Skip to content

Commit

Permalink
feature #24337 Adding a shortcuts for the main security functionality…
Browse files Browse the repository at this point in the history
… (weaverryan, javiereguiluz)

This PR was squashed before being merged into the 3.4 branch (closes #24337).

Discussion
----------

Adding a shortcuts for the main security functionality

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | none
| License       | MIT
| Doc PR        | Big ol' TODO

I'd like one class that I can inject (especially with autowiring) to get access to the User and `isGranted()` methods. This is *really* important... because to get the User currently, you need to type-hint `TokenStorageInterface`... and there are *two*! That's really bad DX!

Questions:

A) I hi-jacked the existing `Security` class... I wanted a simple class called Security
B) I called the service `security.helper`... for lack of a better id.
C) I did not make `Security` implement the 2 other interfaces (`TokenStorageInterface`, `AuthorizationCheckerInterface`... but I suppose we could?)

Cheers!

Commits
-------

0851189 Adding a shortcuts for the main security functionality
  • Loading branch information
Robin Chalas committed Sep 28, 2017
2 parents 54135cb + 0851189 commit 3b5742e
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
Expand Up @@ -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`
Expand Down
13 changes: 13 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
Expand Up @@ -26,6 +26,19 @@
</service>
<service id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" alias="security.token_storage" />

<service id="security.helper" class="Symfony\Component\Security\Core\Security">
<argument type="service">
<service class="Symfony\Component\DependencyInjection\ServiceLocator">
<tag name="container.service_locator" />
<argument type="collection">
<argument key="security.token_storage" type="service" id="security.token_storage" />
<argument key="security.authorization_checker" type="service" id="security.authorization_checker" />
</argument>
</service>
</argument>
</service>
<service id="Symfony\Component\Security\Core\Security" alias="security.helper" />

<service id="security.user_value_resolver" class="Symfony\Bundle\SecurityBundle\SecurityUserValueResolver">
<argument type="service" id="security.token_storage" />
<tag name="controller.argument_value_resolver" priority="40" />
Expand Down
@@ -0,0 +1,34 @@
<?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\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());
}
}
@@ -0,0 +1,20 @@
<?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.
*/

use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;

return array(
new FrameworkBundle(),
new SecurityBundle(),
new TwigBundle(),
);
@@ -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: ~
1 change: 1 addition & 0 deletions src/Symfony/Component/Security/CHANGELOG.md
Expand Up @@ -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
Expand Down
54 changes: 51 additions & 3 deletions src/Symfony/Component/Security/Core/Security.php
Expand Up @@ -11,15 +11,63 @@

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 <schmittjoh@gmail.com>
* Helper class for commonly-needed security tasks.
*/
final class Security
{
const ACCESS_DENIED_ERROR = '_security.403_error';
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();
}
}
97 changes: 97 additions & 0 deletions src/Symfony/Component/Security/Core/Tests/SecurityTest.php
@@ -0,0 +1,97 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Security\Core\Tests;

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;
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/Security/Core/composer.json
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Security/composer.json
Expand Up @@ -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",
Expand All @@ -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",
Expand Down

0 comments on commit 3b5742e

Please sign in to comment.