Skip to content

Commit

Permalink
feature #35804 [HttpFoundation] Added MarshallingSessionHandler (atai…
Browse files Browse the repository at this point in the history
…louloute)

This PR was merged into the 5.1-dev branch.

Discussion
----------

[HttpFoundation] Added MarshallingSessionHandler

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       |
| License       | MIT
| Doc PR        | TODO

Added `MarshallingSessionHandler`, a decorator for session handlers which uses the cache marshaller in order to encrypt session data.

(This is an alternative solution to #35643)

To use it, we can simply decorate the session marshaller, after that all session data will be encrypted

```yaml
Symfony\Component\Cache\Marshaller\SodiumMarshaller:
    decorates: 'session.marshaller'
    arguments:
        - ['%env(file:resolve:SODIUM_DECRYPTION_FILE)%']
        - '@symfony\Component\Cache\Marshaller\SodiumMarshaller.inner'
```

TODO:
- [x] unit tests

Commits
-------

155d980 [HttpFoundation][Cache] Added MarshallingSessionHandler
  • Loading branch information
fabpot committed Feb 25, 2020
2 parents 82db995 + 155d980 commit 28a249f
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 0 deletions.
@@ -0,0 +1,43 @@
<?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\FrameworkBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
*/
class RemoveUnusedSessionMarshallingHandlerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('session.marshalling_handler')) {
return;
}

$isMarshallerDecorated = false;

foreach ($container->getDefinitions() as $definition) {
$decorated = $definition->getDecoratedService();
if (null !== $decorated && 'session.marshaller' === $decorated[0]) {
$isMarshallerDecorated = true;

break;
}
}

if (!$isMarshallerDecorated) {
$container->removeDefinition('session.marshalling_handler');
}
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Expand Up @@ -18,6 +18,7 @@
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RemoveUnusedSessionMarshallingHandlerPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
Expand Down Expand Up @@ -130,6 +131,7 @@ public function build(ContainerBuilder $container)
$this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class);
$container->addCompilerPass(new RegisterReverseContainerPass(true));
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass());

if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2);
Expand Down
Expand Up @@ -71,5 +71,12 @@

<!-- for BC -->
<service id="session.storage.filesystem" alias="session.storage.mock_file" />

<service id="session.marshaller" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\IdentityMarshaller" />

<service id="session.marshalling_handler" decorates="session.handler" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\MarshallingSessionHandler">
<argument type="service" id="session.marshalling_handler.inner" />
<argument type="service" id="session.marshaller" />
</service>
</services>
</container>
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpFoundation/CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@ CHANGELOG
* added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference
according to [RFC 8674](https://tools.ietf.org/html/rfc8674)
* made the Mime component an optional dependency
* added `MarshallingSessionHandler`, `IdentityMarshaller`

5.0.0
-----
Expand Down
@@ -0,0 +1,42 @@
<?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\HttpFoundation\Session\Storage\Handler;

use Symfony\Component\Cache\Marshaller\MarshallerInterface;

/**
* @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
*/
class IdentityMarshaller implements MarshallerInterface
{
/**
* {@inheritdoc}
*/
public function marshall(array $values, ?array &$failed): array
{
foreach ($values as $key => $value) {
if (!\is_string($value)) {
throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__));
}
}

return $values;
}

/**
* {@inheritdoc}
*/
public function unmarshall(string $value): string
{
return $value;
}
}
@@ -0,0 +1,100 @@
<?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\HttpFoundation\Session\Storage\Handler;

use Symfony\Component\Cache\Marshaller\MarshallerInterface;

/**
* @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
*/
class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
{
private $handler;
private $marshaller;

public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller)
{
$this->handler = $handler;
$this->marshaller = $marshaller;
}

/**
* {@inheritdoc}
*/
public function open($savePath, $name)
{
return $this->handler->open($savePath, $name);
}

/**
* {@inheritdoc}
*/
public function close()
{
return $this->handler->close();
}

/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
return $this->handler->destroy($sessionId);
}

/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
return $this->handler->gc($maxlifetime);
}

/**
* {@inheritdoc}
*/
public function read($sessionId)
{
return $this->marshaller->unmarshall($this->handler->read($sessionId));
}

/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
$failed = [];
$marshalledData = $this->marshaller->marshall(['data' => $data], $failed);

if (isset($failed['data'])) {
return false;
}

return $this->handler->write($sessionId, $marshalledData['data']);
}

/**
* {@inheritdoc}
*/
public function validateId($sessionId)
{
return $this->handler->validateId($sessionId);
}

/**
* {@inheritdoc}
*/
public function updateTimestamp($sessionId, $data)
{
return $this->handler->updateTimestamp($sessionId, $data);
}
}
@@ -0,0 +1,59 @@
<?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\HttpFoundation\Tests\Session\Storage\Handler;

use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\IdentityMarshaller;

/**
* @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
*/
class IdentityMarshallerTest extends Testcase
{
public function testMarshall()
{
$marshaller = new IdentityMarshaller();
$values = ['data' => 'string_data'];
$failed = [];

$this->assertSame($values, $marshaller->marshall($values, $failed));
}

/**
* @dataProvider invalidMarshallDataProvider
*/
public function testMarshallInvalidData($values)
{
$marshaller = new IdentityMarshaller();
$failed = [];

$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Symfony\Component\HttpFoundation\Session\Storage\Handler\IdentityMarshaller::marshall accepts only string as data');

$marshaller->marshall($values, $failed);
}

public function testUnmarshall()
{
$marshaller = new IdentityMarshaller();

$this->assertEquals('data', $marshaller->unmarshall('data'));
}

public function invalidMarshallDataProvider(): iterable
{
return [
[['object' => new \stdClass()]],
[['foo' => ['bar']]],
];
}
}

0 comments on commit 28a249f

Please sign in to comment.