Merge branch '4.0' into 4.1

* 4.0:
  remove HHVM code
  [VarDumper] Fix dumping ArrayObject and ArrayIterator instances
  [ProxyManagerBridge] Fixed support of private services
  [Cache] Fix typo in comment.
  Fix bad method call with guard authentication + session migration
This commit is contained in:
Nicolas Grekas 2018-06-15 09:52:42 +02:00
commit f658ed6d17
12 changed files with 167 additions and 33 deletions

View File

@ -54,7 +54,7 @@ class ProxyDumper implements DumperInterface
$instantiation = 'return';
if ($definition->isShared()) {
$instantiation .= sprintf(' $this->%s[\'%s\'] =', $definition->isPublic() || !method_exists(ContainerBuilder::class, 'addClassResource') ? 'services' : 'privates', $id);
$instantiation .= sprintf(' $this->%s[\'%s\'] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', $id);
}
if (null === $factoryCode) {

View File

@ -80,6 +80,34 @@ class ProxyDumperTest extends TestCase
);
}
/**
* @dataProvider getPrivatePublicDefinitions
*/
public function testCorrectAssigning(Definition $definition, $access)
{
$definition->setLazy(true);
$code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)');
$this->assertStringMatchesFormat('%A$this->'.$access.'[\'foo\'] = %A', $code);
}
public function getPrivatePublicDefinitions()
{
return array(
array(
(new Definition(__CLASS__))
->setPublic(false),
'privates',
),
array(
(new Definition(__CLASS__))
->setPublic(true),
'services',
),
);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Missing factory code to construct the service "foo".

View File

@ -45,6 +45,7 @@ class SecurityExtension extends Extension
private $listenerPositions = array('pre_auth', 'form', 'http', 'remember_me');
private $factories = array();
private $userProviderFactories = array();
private $statelessFirewallKeys = array();
public function __construct()
{
@ -104,6 +105,9 @@ class SecurityExtension extends Extension
$this->createAuthorization($config, $container);
$this->createRoleHierarchy($config, $container);
$container->getDefinition('security.authentication.guard_handler')
->replaceArgument(2, $this->statelessFirewallKeys);
if ($config['encoders']) {
$this->createEncoders($config['encoders'], $container);
}
@ -287,6 +291,7 @@ class SecurityExtension extends Extension
$listeners[] = new Reference($this->createContextListener($container, $contextKey));
$sessionStrategyId = 'security.authentication.session_strategy';
} else {
$this->statelessFirewallKeys[] = $id;
$sessionStrategyId = 'security.authentication.session_strategy_noop';
}
$container->setAlias(new Alias('security.authentication.session_strategy.'.$id, false), $sessionStrategyId);

View File

@ -12,6 +12,10 @@
>
<argument type="service" id="security.token_storage" />
<argument type="service" id="event_dispatcher" on-invalid="null" />
<argument /> <!-- stateless firewall keys -->
<call method="setSessionAuthenticationStrategy">
<argument type="service" id="security.authentication.session_strategy" />
</call>
</service>
<service id="Symfony\Component\Security\Guard\GuardAuthenticatorHandler" alias="security.authentication.guard_handler" />

View File

@ -123,6 +123,35 @@ class SecurityExtensionTest extends TestCase
$this->assertFalse($container->hasDefinition('security.access.role_hierarchy_voter'));
}
public function testGuardHandlerIsPassedStatelessFirewalls()
{
$container = $this->getRawContainer();
$container->loadFromExtension('security', array(
'providers' => array(
'default' => array('id' => 'foo'),
),
'firewalls' => array(
'some_firewall' => array(
'pattern' => '^/admin',
'http_basic' => null,
'logout_on_user_change' => true,
),
'stateless_firewall' => array(
'pattern' => '/.*',
'stateless' => true,
'http_basic' => null,
'logout_on_user_change' => true,
),
),
));
$container->compile();
$definition = $container->getDefinition('security.authentication.guard_handler');
$this->assertSame(array('stateless_firewall'), $definition->getArgument(2));
}
public function testSwitchUserNotStatelessOnStatelessFirewall()
{
$container = $this->getRawContainer();

View File

@ -197,7 +197,7 @@ trait RedisTrait
protected function doClear($namespace)
{
// When using a native Redis cluster, clearing the cache is done by versioning in AbstractTrait::clear().
// This means old keys are not really removed until they expire and may need gargage collection.
// This means old keys are not really removed until they expire and may need garbage collection.
$cleared = true;
$hosts = array($this->redis);

View File

@ -126,7 +126,7 @@ class GuardAuthenticationListener implements ListenerInterface
}
// sets the token on the token storage, etc
$this->guardHandler->authenticateWithToken($token, $request);
$this->guardHandler->authenticateWithToken($token, $request, $this->providerKey);
} catch (AuthenticationException $e) {
// oh no! Authentication failed!

View File

@ -37,19 +37,28 @@ class GuardAuthenticatorHandler
private $tokenStorage;
private $dispatcher;
private $sessionStrategy;
private $statelessProviderKeys;
public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null)
/**
* @param array $statelessProviderKeys An array of provider/firewall keys that are "stateless" and so do not need the session migrated on success
*/
public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null, array $statelessProviderKeys = array())
{
$this->tokenStorage = $tokenStorage;
$this->dispatcher = $eventDispatcher;
$this->statelessProviderKeys = $statelessProviderKeys;
}
/**
* Authenticates the given token in the system.
*
* @param string $providerKey The name of the provider/firewall being used for authentication
*/
public function authenticateWithToken(TokenInterface $token, Request $request)
public function authenticateWithToken(TokenInterface $token, Request $request/*, string $providerKey */)
{
$this->migrateSession($request, $token);
$providerKey = \func_num_args() > 2 ? func_get_arg(2) : null;
$this->migrateSession($request, $token, $providerKey);
$this->tokenStorage->setToken($token);
if (null !== $this->dispatcher) {
@ -86,7 +95,7 @@ class GuardAuthenticatorHandler
// create an authenticated token for the User
$token = $authenticator->createAuthenticatedToken($user, $providerKey);
// authenticate this in the system
$this->authenticateWithToken($token, $request);
$this->authenticateWithToken($token, $request, $providerKey);
// return the success metric
return $this->handleAuthenticationSuccess($token, $request, $authenticator, $providerKey);
@ -121,9 +130,9 @@ class GuardAuthenticatorHandler
$this->sessionStrategy = $sessionStrategy;
}
private function migrateSession(Request $request, TokenInterface $token)
private function migrateSession(Request $request, TokenInterface $token, $providerKey)
{
if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) {
if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession() || \in_array($providerKey, $this->statelessProviderKeys, true)) {
return;
}

View File

@ -144,6 +144,18 @@ class GuardAuthenticatorHandlerTest extends TestCase
$handler->authenticateWithToken($this->token, $this->request);
}
public function testSessionStrategyIsNotCalledWhenStateless()
{
$this->configurePreviousSession();
$this->sessionStrategy->expects($this->never())
->method('onAuthentication');
$handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher, array('some_provider_key'));
$handler->setSessionAuthenticationStrategy($this->sessionStrategy);
$handler->authenticateWithToken($this->token, $this->request, 'some_provider_key');
}
protected function setUp()
{
$this->tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock();

View File

@ -29,30 +29,12 @@ class SplCaster
public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
$class = $stub->class;
$flags = $c->getFlags();
return self::castSplArray($c, $a, $stub, $isNested);
}
$b = array(
$prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
$prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
$prefix.'iteratorClass' => new ClassStub($c->getIteratorClass()),
$prefix.'storage' => $c->getArrayCopy(),
);
if ('ArrayObject' === $class) {
$a = $b;
} else {
if (!($flags & \ArrayObject::STD_PROP_LIST)) {
$c->setFlags(\ArrayObject::STD_PROP_LIST);
$a = Caster::castObject($c, $class);
$c->setFlags($flags);
}
$a += $b;
}
return $a;
public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested)
{
return self::castSplArray($c, $a, $stub, $isNested);
}
public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested)
@ -186,7 +168,7 @@ class SplCaster
$clone = clone $c;
foreach ($clone as $obj) {
$storage[spl_object_hash($obj)] = array(
$storage[] = array(
'object' => $obj,
'info' => $clone->getInfo(),
);
@ -205,4 +187,27 @@ class SplCaster
return $a;
}
private static function castSplArray($c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
$class = $stub->class;
$flags = $c->getFlags();
if (!($flags & \ArrayObject::STD_PROP_LIST)) {
$c->setFlags(\ArrayObject::STD_PROP_LIST);
$a = Caster::castObject($c, $class);
$c->setFlags($flags);
}
$a += array(
$prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
$prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
);
if ($c instanceof \ArrayObject) {
$a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass());
}
$a[$prefix.'storage'] = $c->getArrayCopy();
return $a;
}
}

View File

@ -95,6 +95,7 @@ abstract class AbstractCloner implements ClonerInterface
'AMQPEnvelope' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'),
'ArrayObject' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'),
'ArrayIterator' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'),
'SplDoublyLinkedList' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'),
'SplFileInfo' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'),
'SplFileObject' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'),

View File

@ -163,4 +163,45 @@ EOTXT;
$this->assertDumpMatchesFormat('%ADateTime%A', $var);
}
public function testCastArrayObject()
{
$var = new \ArrayObject(array(123));
$var->foo = 234;
$expected = <<<EOTXT
ArrayObject {
+"foo": 234
flag::STD_PROP_LIST: false
flag::ARRAY_AS_PROPS: false
iteratorClass: "ArrayIterator"
storage: array:1 [
0 => 123
]
}
EOTXT;
$this->assertDumpEquals($expected, $var);
}
public function testArrayIterator()
{
$var = new MyArrayIterator(array(234));
$expected = <<<EOTXT
Symfony\Component\VarDumper\Tests\Caster\MyArrayIterator {
-foo: 123
flag::STD_PROP_LIST: false
flag::ARRAY_AS_PROPS: false
storage: array:1 [
0 => 234
]
}
EOTXT;
$this->assertDumpEquals($expected, $var);
}
}
class MyArrayIterator extends \ArrayIterator
{
private $foo = 123;
}