Merge branch '5.1'

* 5.1:
  Handle fetch mode deprecation of DBAL 2.11.
  Fixed security-* package dependencies
  Fixed handling of CSRF logout error
  [WebProfilerBundle] changed label of memory usage in time panel (Mb into MiB)
  [DotEnv][WebLink][Templating][ErrorHandler] Updated README with minimal example
This commit is contained in:
Nicolas Grekas 2020-05-28 10:21:24 +02:00
commit 71d1d70945
12 changed files with 215 additions and 13 deletions

View File

@ -56,7 +56,7 @@ class DoctrineTokenProvider implements TokenProviderInterface
$paramValues = ['series' => $series]; $paramValues = ['series' => $series];
$paramTypes = ['series' => \PDO::PARAM_STR]; $paramTypes = ['series' => \PDO::PARAM_STR];
$stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes); $stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes);
$row = $stmt->fetch(\PDO::FETCH_ASSOC); $row = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC);
if ($row) { if ($row) {
return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used'])); return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used']));

View File

@ -0,0 +1,88 @@
<?php
namespace Security\RememberMe;
use Doctrine\DBAL\DriverManager;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider;
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
/**
* @requires extension pdo_sqlite
*/
class DoctrineTokenProviderTest extends TestCase
{
public static function setUpBeforeClass()
{
if (\PHP_VERSION_ID >= 80000) {
self::markTestSkipped('Doctrine DBAL 2.x is incompatible with PHP 8.');
}
}
public function testCreateNewToken()
{
$provider = $this->bootstrapProvider();
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51'));
$provider->createNewToken($token);
$this->assertEquals($provider->loadTokenBySeries('someSeries'), $token);
}
public function testLoadTokenBySeriesThrowsNotFoundException()
{
$provider = $this->bootstrapProvider();
$this->expectException(TokenNotFoundException::class);
$provider->loadTokenBySeries('someSeries');
}
public function testUpdateToken()
{
$provider = $this->bootstrapProvider();
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51'));
$provider->createNewToken($token);
$provider->updateToken('someSeries', 'newValue', $lastUsed = new \DateTime('2014-06-26T22:03:46'));
$token = $provider->loadTokenBySeries('someSeries');
$this->assertEquals('newValue', $token->getTokenValue());
$this->assertEquals($token->getLastUsed(), $lastUsed);
}
public function testDeleteToken()
{
$provider = $this->bootstrapProvider();
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51'));
$provider->createNewToken($token);
$provider->deleteTokenBySeries('someSeries');
$this->expectException(TokenNotFoundException::class);
$provider->loadTokenBySeries('someSeries');
}
/**
* @return DoctrineTokenProvider
*/
private function bootstrapProvider()
{
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'url' => 'sqlite:///:memory:',
]);
$connection->executeUpdate(<<< 'SQL'
CREATE TABLE rememberme_token (
series char(88) UNIQUE PRIMARY KEY NOT NULL,
value char(88) NOT NULL,
lastUsed datetime NOT NULL,
class varchar(100) NOT NULL,
username varchar(200) NOT NULL
);
SQL
);
return new DoctrineTokenProvider($connection);
}
}

View File

@ -23,9 +23,9 @@
"symfony/event-dispatcher": "^5.1", "symfony/event-dispatcher": "^5.1",
"symfony/http-kernel": "^5.0", "symfony/http-kernel": "^5.0",
"symfony/polyfill-php80": "^1.15", "symfony/polyfill-php80": "^1.15",
"symfony/security-core": "^4.4|^5.0", "symfony/security-core": "^5.1",
"symfony/security-csrf": "^4.4|^5.0", "symfony/security-csrf": "^4.4|^5.0",
"symfony/security-guard": "^4.4|^5.0", "symfony/security-guard": "^5.1",
"symfony/security-http": "^5.1" "symfony/security-http": "^5.1"
}, },
"require-dev": { "require-dev": {

View File

@ -94,7 +94,7 @@ class TimelineEngine {
createLabel(name, duration, memory, period) { createLabel(name, duration, memory, period) {
const label = this.renderer.createText(name, period.start * this.scale, this.labelY, 'timeline-label'); const label = this.renderer.createText(name, period.start * this.scale, this.labelY, 'timeline-label');
const sublabel = this.renderer.createTspan(` ${duration} ms / ${memory} Mb`, 'timeline-sublabel'); const sublabel = this.renderer.createTspan(` ${duration} ms / ${memory} MiB`, 'timeline-sublabel');
label.appendChild(sublabel); label.appendChild(sublabel);

View File

@ -219,7 +219,13 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
} }
$stmt->execute(); $stmt->execute();
while ($row = $stmt->fetch(\PDO::FETCH_NUM)) { if (method_exists($stmt, 'iterateNumeric')) {
$stmt = $stmt->iterateNumeric();
} else {
$stmt->setFetchMode(\PDO::FETCH_NUM);
}
foreach ($stmt as $row) {
if (null === $row[1]) { if (null === $row[1]) {
$expired[] = $row[0]; $expired[] = $row[0];
} else { } else {

View File

@ -24,11 +24,11 @@ trait PdoPruneableTrait
$getPdoConn = $o->getMethod('getConnection'); $getPdoConn = $o->getMethod('getConnection');
$getPdoConn->setAccessible(true); $getPdoConn->setAccessible(true);
/** @var \Doctrine\DBAL\Statement $select */ /** @var \Doctrine\DBAL\Statement|\PDOStatement $select */
$select = $getPdoConn->invoke($cache)->prepare('SELECT 1 FROM cache_items WHERE item_id LIKE :id'); $select = $getPdoConn->invoke($cache)->prepare('SELECT 1 FROM cache_items WHERE item_id LIKE :id');
$select->bindValue(':id', sprintf('%%%s', $name)); $select->bindValue(':id', sprintf('%%%s', $name));
$select->execute(); $select->execute();
return 0 === \count($select->fetchAll(\PDO::FETCH_COLUMN)); return 1 !== (int) (method_exists($select, 'fetchOne') ? $select->fetchOne() : $select->fetch(\PDO::FETCH_COLUMN));
} }
} }

View File

@ -4,10 +4,32 @@ Dotenv Component
Symfony Dotenv parses `.env` files to make environment variables stored in them Symfony Dotenv parses `.env` files to make environment variables stored in them
accessible via `$_SERVER` or `$_ENV`. accessible via `$_SERVER` or `$_ENV`.
Getting Started
---------------
```
$ composer require symfony/dotenv
```
```php
use Symfony\Component\Dotenv\Dotenv;
$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/.env');
// you can also load several files
$dotenv->load(__DIR__.'/.env', __DIR__.'/.env.dev');
// overwrites existing env variables
$dotenv->overload(__DIR__.'/.env');
// loads .env, .env.local, and .env.$APP_ENV.local or .env.$APP_ENV
$dotenv->loadEnv(__DIR__.'/.env');
```
Resources Resources
--------- ---------
* [Documentation](https://symfony.com/doc/current/components/dotenv.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [send Pull Requests](https://github.com/symfony/symfony/pulls)

View File

@ -3,6 +3,35 @@ ErrorHandler Component
The ErrorHandler component provides tools to manage errors and ease debugging PHP code. The ErrorHandler component provides tools to manage errors and ease debugging PHP code.
Getting Started
---------------
```
$ composer require symfony/error-handler
```
```php
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\ErrorHandler\DebugClassLoader;
Debug::enable();
// or enable only one feature
//ErrorHandler::register();
//DebugClassLoader::enable();
$data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) {
// if any code executed inside this anonymous function fails, a PHP exception
// will be thrown, even if the code uses the '@' PHP silence operator
$data = json_decode(file_get_contents($filename), true);
$data['read_at'] = date($datetimeFormat);
file_put_contents($filename, json_encode($data));
return $data;
});
```
Resources Resources
--------- ---------

View File

@ -111,7 +111,7 @@ class ExceptionListener
} }
if ($exception instanceof LogoutException) { if ($exception instanceof LogoutException) {
$this->handleLogoutException($exception); $this->handleLogoutException($event, $exception);
return; return;
} }
@ -183,10 +183,12 @@ class ExceptionListener
} }
} }
private function handleLogoutException(LogoutException $exception): void private function handleLogoutException(ExceptionEvent $event, LogoutException $exception): void
{ {
$event->setException(new AccessDeniedHttpException($exception->getMessage(), $exception));
if (null !== $this->logger) { if (null !== $this->logger) {
$this->logger->info('A LogoutException was thrown.', ['exception' => $exception]); $this->logger->info('A LogoutException was thrown; wrapping with AccessDeniedHttpException', ['exception' => $exception]);
} }
} }

View File

@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverIn
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\LogoutException;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface; use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener;
@ -157,6 +158,17 @@ class ExceptionListenerTest extends TestCase
$this->assertSame(null === $eventException ? $exception : $eventException, $event->getThrowable()->getPrevious()); $this->assertSame(null === $eventException ? $exception : $eventException, $event->getThrowable()->getPrevious());
} }
public function testLogoutException()
{
$event = $this->createEvent(new LogoutException('Invalid CSRF.'));
$listener = $this->createExceptionListener();
$listener->onKernelException($event);
$this->assertEquals('Invalid CSRF.', $event->getException()->getMessage());
$this->assertEquals(403, $event->getException()->getStatusCode());
}
public function getAccessDeniedExceptionProvider() public function getAccessDeniedExceptionProvider()
{ {
return [ return [

View File

@ -9,10 +9,33 @@ for changes. It also provides a concrete template engine implementation using
PHP with additional tools for escaping and separating templates into blocks and PHP with additional tools for escaping and separating templates into blocks and
layouts. layouts.
Getting Started
---------------
```
$ composer require symfony/templating
```
```php
use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\Helper\SlotsHelper;
use Symfony\Component\Templating\TemplateNameParser;
$filesystemLoader = new FilesystemLoader(__DIR__.'/views/%name%');
$templating = new PhpEngine(new TemplateNameParser(), $filesystemLoader);
$templating->set(new SlotsHelper());
echo $templating->render('hello.php', ['firstname' => 'Fabien']);
// hello.php
Hello, <?= $view->escape($firstname) ?>!
```
Resources Resources
--------- ---------
* [Documentation](https://symfony.com/doc/current/components/templating.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [send Pull Requests](https://github.com/symfony/symfony/pulls)

View File

@ -8,10 +8,30 @@ This component implements the [HTML5's Links](https://www.w3.org/TR/html5/links.
and [Resource Hints](https://www.w3.org/TR/resource-hints/) W3C's specifications. and [Resource Hints](https://www.w3.org/TR/resource-hints/) W3C's specifications.
It can also be used with extensions defined in the [HTML5 link type extensions wiki](http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions). It can also be used with extensions defined in the [HTML5 link type extensions wiki](http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions).
Getting Started
---------------
```
$ composer require symfony/web-link
```
```php
use Symfony\Component\WebLink\GenericLinkProvider;
use Symfony\Component\WebLink\HttpHeaderSerializer;
use Symfony\Component\WebLink\Link;
$linkProvider = (new GenericLinkProvider())
->withLink(new Link('preload', '/bootstrap.min.css'));
header('Link: '.(new HttpHeaderSerializer())->serialize($linkProvider->getLinks()));
echo 'Hello';
```
Resources Resources
--------- ---------
* [Documentation](https://symfony.com/doc/current/components/web_link.html) * [Documentation](https://symfony.com/doc/current/web_link.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [send Pull Requests](https://github.com/symfony/symfony/pulls)