diff --git a/DependencyInjection/AeFeatureExtension.php b/DependencyInjection/AeFeatureExtension.php
index d6fd3ff..a5992c9 100644
--- a/DependencyInjection/AeFeatureExtension.php
+++ b/DependencyInjection/AeFeatureExtension.php
@@ -30,5 +30,6 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('services.xml');
$container->setAlias('ae_feature.cache', $config['cache']);
+ $container->setParameter('ae_feature.provider_key', $config['provider_key']);
}
}
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 99cc2a5..c9a5fe7 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -20,6 +20,10 @@ public function getConfigTreeBuilder()
->scalarNode('cache')
->defaultValue('ae_feature.default_cache')
->end()
+ ->scalarNode('provider_key')
+ ->defaultValue('main')
+ ->cannotBeEmpty()
+ ->end()
->end();
return $treeBuilder;
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
index e5dc713..ea9e5a3 100644
--- a/Resources/config/services.xml
+++ b/Resources/config/services.xml
@@ -18,6 +18,8 @@
+
+ %ae_feature.provider_key%
diff --git a/Resources/doc/configuration-reference.rst b/Resources/doc/configuration-reference.rst
index 7811bc2..d0e1b43 100644
--- a/Resources/doc/configuration-reference.rst
+++ b/Resources/doc/configuration-reference.rst
@@ -6,3 +6,4 @@ Default Bundle Configuration
# app/config/config.yml
ae_feature:
cache: ae_feature.default_cache # default cache is of type ArrayCache
+ provider_key: main
diff --git a/Resources/doc/usage.rst b/Resources/doc/usage.rst
index ed38fdf..2d00b76 100644
--- a/Resources/doc/usage.rst
+++ b/Resources/doc/usage.rst
@@ -21,5 +21,11 @@ or from the service directly:
throw new Exception();
}
+ // $user instanceof Symfony\Component\Security\Core\User\UserInterface
+ // after the check the previous token will be restored
+ if (!$featureService->isGrantedForUser('feature', 'group', $user)) {
+ throw new Exception();
+ }
+
If the feature or the parent feature has at least a role, the `ae_feature.feature`
service will require a token defined in the `security.token_storage`.
diff --git a/Security/FeatureSecurity.php b/Security/FeatureSecurity.php
index 70988c1..193d4be 100644
--- a/Security/FeatureSecurity.php
+++ b/Security/FeatureSecurity.php
@@ -3,7 +3,10 @@
namespace Ae\FeatureBundle\Security;
use Ae\FeatureBundle\Entity\Feature;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
/**
* Controls access to a Feature.
@@ -13,16 +16,33 @@
class FeatureSecurity
{
/**
- * @param AuthorizationCheckerInterface|null
+ * @param AuthorizationCheckerInterface
*/
protected $context;
+ /**
+ * @param TokenStorageInterface
+ */
+ private $storage;
+
+ /**
+ * @param string
+ */
+ private $providerKey;
+
/**
* @param AuthorizationCheckerInterface $context
+ * @param TokenStorageInterface $storage
+ * @param string $providerKey
*/
- public function __construct(AuthorizationCheckerInterface $context = null)
- {
+ public function __construct(
+ AuthorizationCheckerInterface $context,
+ TokenStorageInterface $storage,
+ string $providerKey
+ ) {
$this->context = $context;
+ $this->storage = $storage;
+ $this->providerKey = $providerKey;
}
/**
@@ -38,10 +58,6 @@ public function isGranted(Feature $feature)
return $feature->isEnabled();
}
- if (null === $this->context) {
- return false;
- }
-
if (!$feature->isEnabled()) {
return false;
}
@@ -60,4 +76,22 @@ public function isGranted(Feature $feature)
return true;
}
+
+ public function isGrantedForUser(Feature $feature, UserInterface $user): bool
+ {
+ $oldToken = $this->storage->getToken();
+
+ $this->storage->setToken(new UsernamePasswordToken(
+ $user,
+ null,
+ $this->providerKey,
+ $user->getRoles()
+ ));
+
+ $granted = $this->isGranted($feature);
+
+ $this->storage->setToken($oldToken);
+
+ return $granted;
+ }
}
diff --git a/Service/Feature.php b/Service/Feature.php
index e492c3f..49f4bc1 100644
--- a/Service/Feature.php
+++ b/Service/Feature.php
@@ -2,9 +2,11 @@
namespace Ae\FeatureBundle\Service;
+use Ae\FeatureBundle\Entity\Feature as FeatureEntity;
use Ae\FeatureBundle\Entity\FeatureManager;
use Ae\FeatureBundle\Security\FeatureSecurity;
use Exception;
+use Symfony\Component\Security\Core\User\UserInterface;
/**
* @author Carlo Forghieri
@@ -51,4 +53,15 @@ public function isGranted($name, $parent)
return false;
}
}
+
+ public function isGrantedForUser(string $name, string $parent, UserInterface $user): bool
+ {
+ $feature = $this->manager->find($name, $parent);
+
+ if (!$feature instanceof FeatureEntity) {
+ return false;
+ }
+
+ return $this->security->isGrantedForUser($feature, $user);
+ }
}
diff --git a/Service/FeatureInterface.php b/Service/FeatureInterface.php
index eca2d68..f0ab644 100644
--- a/Service/FeatureInterface.php
+++ b/Service/FeatureInterface.php
@@ -2,6 +2,8 @@
namespace Ae\FeatureBundle\Service;
+use Symfony\Component\Security\Core\User\UserInterface;
+
/**
* @author Simone Di Maulo
*/
@@ -16,4 +18,6 @@ interface FeatureInterface
* @return bool
*/
public function isGranted($name, $parent);
+
+ public function isGrantedForUser(string $name, string $parent, UserInterface $user): bool;
}
diff --git a/Tests/DependencyInjection/AeFeatureExtensionTest.php b/Tests/DependencyInjection/AeFeatureExtensionTest.php
index 34dce35..b1f045e 100644
--- a/Tests/DependencyInjection/AeFeatureExtensionTest.php
+++ b/Tests/DependencyInjection/AeFeatureExtensionTest.php
@@ -26,29 +26,6 @@ protected function getContainerExtensions()
];
}
- /**
- * Test parameters "alias" to migrate from CreativeWeb to AdEspresso.
- *
- * @param string $parameterName
- * @param string $expectedParameterValue
- *
- * @dataProvider parametersProvider
- * @group legacy
- */
- public function testLegacyParameters(
- $parameterName,
- $expectedParameterValue
- ) {
- $this->load();
- $this->compile();
-
- $parameterName = 'cw'.substr($parameterName, 2);
- $this->assertContainerBuilderHasParameter(
- $parameterName,
- $expectedParameterValue
- );
- }
-
/**
* Test parameters.
*
@@ -81,6 +58,7 @@ public function parametersProvider()
'ae_feature.twig.extension.feature.class',
FeatureExtension::class,
],
+ ['ae_feature.provider_key', 'main'],
];
}
@@ -164,4 +142,15 @@ public function testCacheProviderHasDefinedAlias()
$this->assertContainerBuilderHasAlias('ae_feature.cache', 'service');
}
+
+ public function testKeyProviderCanBeDefined()
+ {
+ $providerKey = 'test_'.sha1(mt_rand());
+
+ $this->load([
+ 'provider_key' => $providerKey,
+ ]);
+
+ $this->assertContainerBuilderHasParameter('ae_feature.provider_key', $providerKey);
+ }
}
diff --git a/Tests/Security/FeatureSecurityTest.php b/Tests/Security/FeatureSecurityTest.php
index 98af58c..75205d8 100644
--- a/Tests/Security/FeatureSecurityTest.php
+++ b/Tests/Security/FeatureSecurityTest.php
@@ -5,7 +5,11 @@
use Ae\FeatureBundle\Entity\Feature;
use Ae\FeatureBundle\Security\FeatureSecurity;
use PHPUnit_Framework_TestCase;
+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\User\UserInterface;
/**
* @author Carlo Forghieri
@@ -13,22 +17,35 @@
*/
class FeatureSecurityTest extends PHPUnit_Framework_TestCase
{
- protected $security;
+ /**
+ * @var AuthorizationCheckerInterface
+ */
+ private $context;
+
+ /**
+ * @var TokenStorageInterface
+ */
+ private $storage;
+
+ /**
+ * @var FeatureSecurity
+ */
+ private $security;
protected function setUp()
{
- $context = $this
- ->getMockBuilder(AuthorizationCheckerInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
- $context
+ $this->context = $this->createMock(AuthorizationCheckerInterface::class);
+ $this->storage = $this->createMock(TokenStorageInterface::class);
+
+ $this->context
->expects($this->any())
->method('isGranted')
->will($this->returnValueMap([
['ROLE_USER', null, true],
['ROLE_ADMIN', null, false],
]));
- $this->security = new FeatureSecurity($context);
+
+ $this->security = new FeatureSecurity($this->context, $this->storage, 'test');
}
/**
@@ -39,6 +56,78 @@ public function testIsGranted($feature, $expected)
$this->assertEquals($expected, $this->security->isGranted($feature));
}
+ /**
+ * @dataProvider getTokenDataProvider
+ */
+ public function testIsGrantedForUser($token)
+ {
+ $expected = (bool) mt_rand(0, 1);
+ $roles = [
+ 'ROLE_USER',
+ ];
+ $providerKey = 'test';
+
+ $feature = $this->createMock(Feature::class);
+
+ $security = $this
+ ->getMockBuilder(FeatureSecurity::class)
+ ->setConstructorArgs([
+ $this->context,
+ $this->storage,
+ $providerKey,
+ ])
+ ->setMethods(['isGranted'])
+ ->getMock();
+
+ $this->storage
+ ->expects($this->at(0))
+ ->method('getToken')
+ ->willReturn($token);
+
+ $user = $this->createMock(UserInterface::class);
+ $user
+ ->expects($this->once())
+ ->method('getRoles')
+ ->willReturn($roles);
+
+ $security
+ ->expects($this->once())
+ ->method('isGranted')
+ ->with($feature)
+ ->willReturn($expected);
+
+ $this->storage
+ ->expects($this->at(1))
+ ->method('setToken')
+ ->with($this->callback(function ($argument) use ($user, $roles, $providerKey) {
+ return $argument instanceof UsernamePasswordToken
+ && $argument->getUser() === $user
+ && $argument->getProviderKey() === $providerKey;
+ }));
+
+ $this->storage
+ ->expects($this->at(2))
+ ->method('setToken')
+ ->with($token);
+
+ $this->assertEquals($expected, $security->isGrantedForUser($feature, $user));
+ }
+
+ /**
+ * @return array
+ */
+ public function getTokenDataProvider()
+ {
+ return [
+ 'web request' => [
+ $this->createMock(TokenInterface::class),
+ ],
+ 'server process' => [
+ null,
+ ],
+ ];
+ }
+
/**
* @return array
*/
@@ -116,10 +205,13 @@ public function getFeatures()
public function testFeaturesWithoutRolesEnabled()
{
$context = $this->createMock(AuthorizationCheckerInterface::class);
+ $storage = $this->createMock(TokenStorageInterface::class);
+ $providerKey = 'test';
+
$context
->expects($this->never())
->method('isGranted');
- $security = new FeatureSecurity($context);
+ $security = new FeatureSecurity($context, $storage, $providerKey);
$feature = $this->createMock(Feature::class);
$feature
@@ -138,10 +230,13 @@ public function testFeaturesWithoutRolesEnabled()
public function testFeaturesWithoutRolesDisabled()
{
$context = $this->createMock(AuthorizationCheckerInterface::class);
+ $storage = $this->createMock(TokenStorageInterface::class);
+ $providerKey = 'test';
+
$context
->expects($this->never())
->method('isGranted');
- $security = new FeatureSecurity($context);
+ $security = new FeatureSecurity($context, $storage, $providerKey);
$feature = $this->createMock(Feature::class);
$feature
diff --git a/Tests/Security/LegacyFeatureSecurityTest.php b/Tests/Security/LegacyFeatureSecurityTest.php
deleted file mode 100644
index 441541c..0000000
--- a/Tests/Security/LegacyFeatureSecurityTest.php
+++ /dev/null
@@ -1,29 +0,0 @@
-
- * @covers \Ae\FeatureBundle\Security\FeatureSecurity
- */
-class LegacyFeatureSecurityTest extends FeatureSecurityTest
-{
- protected function setUp()
- {
- $context = $this
- ->getMockBuilder(SecurityContextInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
- $context
- ->expects($this->any())
- ->method('isGranted')
- ->will($this->returnValueMap([
- ['ROLE_USER', null, true],
- ['ROLE_ADMIN', null, false],
- ]));
- $this->security = new FeatureSecurity($context);
- }
-}
diff --git a/Tests/Service/FeatureTest.php b/Tests/Service/FeatureTest.php
index 7be5e6c..33c4f38 100644
--- a/Tests/Service/FeatureTest.php
+++ b/Tests/Service/FeatureTest.php
@@ -7,6 +7,7 @@
use Ae\FeatureBundle\Security\FeatureSecurity;
use Ae\FeatureBundle\Service\Feature as FeatureService;
use PHPUnit_Framework_TestCase;
+use Symfony\Component\Security\Core\User\UserInterface;
/**
* @author Carlo Forghieri
@@ -72,4 +73,40 @@ public function testIsGrantedFalse()
$this->assertFalse($this->service->isGranted('featureB', 'group'));
}
+
+ /**
+ * @dataProvider isGrantedForUserDataProvider
+ */
+ public function testIsGrantedForUser($expected, $feature)
+ {
+ $name = sha1(mt_rand());
+ $parent = sha1(mt_rand());
+ $user = $this->createMock(UserInterface::class);
+
+ $this->manager
+ ->expects($this->once())
+ ->method('find')
+ ->with($name, $parent)
+ ->willReturn($feature);
+
+ $this->security
+ ->expects($expected ? $this->once() : $this->any())
+ ->method('isGrantedForUser')
+ ->with($feature, $user)
+ ->willReturn($expected);
+
+ $this->assertSame($expected, $this->service->isGrantedForUser($name, $parent, $user));
+ }
+
+ /**
+ * @return array
+ */
+ public function isGrantedForUserDataProvider()
+ {
+ return [
+ 'granted' => [true, $this->createMock(Feature::class)],
+ 'not granted' => [false, $this->createMock(Feature::class)],
+ 'no feature' => [false, null],
+ ];
+ }
}