Merge branch '5.4' into 6.0
* 5.4: expand uninitialized session tests [Lock] Release PostgreSqlStore connection lock on failure [DomCrawler] Fix HTML5 parser charset option cs fix [HttpKernel] Do not attempt to register enum arguments in controller service locator [Mime] Fix missing sprintf in DkimSigner [Translation] [LocoProvider] Use rawurlencode and separate tag setting [Security] fix unserializing session payloads from v4 [Cache] Don't lock when doing nested computations [Messenger] fix Redis support on 32b arch [HttpFoundation] Fix notice when HTTP_PHP_AUTH_USER passed without pass [Security] Add getting started example to README
This commit is contained in:
commit
bcc5b4b81b
@ -22,6 +22,8 @@ install:
|
||||
- cd ext
|
||||
- appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_apcu-5.1.21-8.0-ts-vs16-x86.zip
|
||||
- 7z x php_apcu-5.1.21-8.0-ts-vs16-x86.zip -y >nul
|
||||
- appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_redis-5.3.5-8.0-ts-vs16-x86.zip
|
||||
- 7z x php_redis-5.3.5-8.0-ts-vs16-x86.zip -y >nul
|
||||
- cd ..
|
||||
- copy /Y php.ini-development php.ini-min
|
||||
- echo memory_limit=-1 >> php.ini-min
|
||||
@ -37,6 +39,7 @@ install:
|
||||
- echo opcache.enable_cli=1 >> php.ini-max
|
||||
- echo extension=php_openssl.dll >> php.ini-max
|
||||
- echo extension=php_apcu.dll >> php.ini-max
|
||||
- echo extension=php_redis.dll >> php.ini-max
|
||||
- echo apc.enable_cli=1 >> php.ini-max
|
||||
- echo extension=php_intl.dll >> php.ini-max
|
||||
- echo extension=php_mbstring.dll >> php.ini-max
|
||||
@ -55,6 +58,7 @@ install:
|
||||
- SET COMPOSER_ROOT_VERSION=%SYMFONY_VERSION%.x-dev
|
||||
- php composer.phar update --no-progress --ansi
|
||||
- php phpunit install
|
||||
- choco install memurai-developer
|
||||
|
||||
test_script:
|
||||
- SET X=0
|
||||
|
5
.github/workflows/integration-tests.yml
vendored
5
.github/workflows/integration-tests.yml
vendored
@ -157,7 +157,6 @@ jobs:
|
||||
- name: Run tests
|
||||
run: ./phpunit --group integration -v
|
||||
env:
|
||||
REDIS_HOST: localhost
|
||||
REDIS_CLUSTER_HOSTS: 'localhost:7000 localhost:7001 localhost:7002 localhost:7003 localhost:7004 localhost:7005'
|
||||
REDIS_SENTINEL_HOSTS: 'localhost:26379'
|
||||
REDIS_SENTINEL_SERVICE: redis_sentinel
|
||||
@ -165,10 +164,6 @@ jobs:
|
||||
MESSENGER_AMQP_DSN: amqp://localhost/%2f/messages
|
||||
MESSENGER_SQS_DSN: "sqs://localhost:9494/messages?sslmode=disable&poll_timeout=0.01"
|
||||
MESSENGER_SQS_FIFO_QUEUE_DSN: "sqs://localhost:9494/messages.fifo?sslmode=disable&poll_timeout=0.01"
|
||||
MEMCACHED_HOST: localhost
|
||||
LDAP_HOST: localhost
|
||||
LDAP_PORT: 3389
|
||||
MONGODB_HOST: localhost
|
||||
KAFKA_BROKER: 127.0.0.1:9092
|
||||
POSTGRES_HOST: localhost
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
<env name="LDAP_HOST" value="localhost" />
|
||||
<env name="LDAP_PORT" value="3389" />
|
||||
<env name="REDIS_HOST" value="localhost" />
|
||||
<env name="MESSENGER_REDIS_DSN" value="redis://localhost/messages" />
|
||||
<env name="MEMCACHED_HOST" value="localhost" />
|
||||
<env name="MONGODB_HOST" value="localhost" />
|
||||
<env name="ZOOKEEPER_HOST" value="localhost" />
|
||||
|
@ -90,7 +90,7 @@ final class LockRegistry
|
||||
|
||||
$key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1;
|
||||
|
||||
if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) {
|
||||
if ($key < 0 || self::$lockedFiles || !$lock = self::open($key)) {
|
||||
return $callback($item, $save);
|
||||
}
|
||||
|
||||
|
@ -1053,7 +1053,7 @@ class Crawler implements \Countable, \IteratorAggregate
|
||||
|
||||
private function parseHtml5(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument
|
||||
{
|
||||
return $this->html5Parser->parse($this->convertToHtmlEntities($htmlContent, $charset), [], $charset);
|
||||
return $this->html5Parser->parse($this->convertToHtmlEntities($htmlContent, $charset));
|
||||
}
|
||||
|
||||
private function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument
|
||||
|
@ -87,7 +87,7 @@ class ServerBag extends ParameterBag
|
||||
|
||||
// PHP_AUTH_USER/PHP_AUTH_PW
|
||||
if (isset($headers['PHP_AUTH_USER'])) {
|
||||
$headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
|
||||
$headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.($headers['PHP_AUTH_PW'] ?? ''));
|
||||
} elseif (isset($headers['PHP_AUTH_DIGEST'])) {
|
||||
$headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
|
||||
}
|
||||
|
@ -57,6 +57,16 @@ class ServerBagTest extends TestCase
|
||||
], $bag->getHeaders());
|
||||
}
|
||||
|
||||
public function testHttpPasswordIsOptionalWhenPassedWithHttpPrefix()
|
||||
{
|
||||
$bag = new ServerBag(['HTTP_PHP_AUTH_USER' => 'foo']);
|
||||
|
||||
$this->assertEquals([
|
||||
'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
|
||||
'PHP_AUTH_USER' => 'foo',
|
||||
], $bag->getHeaders());
|
||||
}
|
||||
|
||||
public function testHttpBasicAuthWithPhpCgi()
|
||||
{
|
||||
$bag = new ServerBag(['HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar')]);
|
||||
|
@ -123,6 +123,11 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
|
||||
$type = ltrim($target = (string) ProxyHelper::getTypeHint($r, $p), '\\');
|
||||
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
|
||||
|
||||
if (is_subclass_of($type, \UnitEnum::class)) {
|
||||
// do not attempt to register enum typed arguments
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($arguments[$r->name][$p->name])) {
|
||||
$target = $arguments[$r->name][$p->name];
|
||||
if ('?' !== $target[0]) {
|
||||
|
@ -24,6 +24,7 @@ use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
use Symfony\Component\DependencyInjection\TypedReference;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\Suit;
|
||||
|
||||
class RegisterControllerArgumentLocatorsPassTest extends TestCase
|
||||
{
|
||||
@ -400,6 +401,25 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase
|
||||
$this->assertEqualsCanonicalizing([RegisterTestController::class.'::fooAction', 'foo::fooAction'], array_keys($locator));
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8.1
|
||||
*/
|
||||
public function testEnumArgumentIsIgnored()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$resolver = $container->register('argument_resolver.service')->addArgument([]);
|
||||
|
||||
$container->register('foo', NonNullableEnumArgumentWithDefaultController::class)
|
||||
->addTag('controller.service_arguments')
|
||||
;
|
||||
|
||||
$pass = new RegisterControllerArgumentLocatorsPass();
|
||||
$pass->process($container);
|
||||
|
||||
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
|
||||
$this->assertEmpty(array_keys($locator), 'enum typed argument is ignored');
|
||||
}
|
||||
|
||||
public function testBindWithTarget()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
@ -479,6 +499,13 @@ class ArgumentWithoutTypeController
|
||||
}
|
||||
}
|
||||
|
||||
class NonNullableEnumArgumentWithDefaultController
|
||||
{
|
||||
public function fooAction(Suit $suit = Suit::Spades)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class WithTarget
|
||||
{
|
||||
public function fooAction(
|
||||
|
@ -308,17 +308,18 @@ class SessionListenerTest extends TestCase
|
||||
$this->assertSame('123456', $cookies[0]->getValue());
|
||||
}
|
||||
|
||||
public function testUninitializedSession()
|
||||
public function testUninitializedSessionUsingSessionFromRequest()
|
||||
{
|
||||
$kernel = $this->createMock(HttpKernelInterface::class);
|
||||
$response = new Response();
|
||||
$response->setSharedMaxAge(60);
|
||||
$response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true');
|
||||
|
||||
$container = new Container();
|
||||
$request = new Request();
|
||||
$request->setSession(new Session());
|
||||
|
||||
$listener = new SessionListener($container);
|
||||
$listener->onKernelResponse(new ResponseEvent($kernel, new Request(), HttpKernelInterface::MAIN_REQUEST, $response));
|
||||
$listener = new SessionListener(new Container());
|
||||
$listener->onKernelResponse(new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $response));
|
||||
$this->assertFalse($response->headers->has('Expires'));
|
||||
$this->assertTrue($response->headers->hasCacheControlDirective('public'));
|
||||
$this->assertFalse($response->headers->hasCacheControlDirective('private'));
|
||||
|
20
src/Symfony/Component/HttpKernel/Tests/Fixtures/Suit.php
Normal file
20
src/Symfony/Component/HttpKernel/Tests/Fixtures/Suit.php
Normal file
@ -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.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\Fixtures;
|
||||
|
||||
enum Suit: string
|
||||
{
|
||||
case Hearts = 'H';
|
||||
case Diamonds = 'D';
|
||||
case Clubs = 'C';
|
||||
case Spades = 'S';
|
||||
}
|
@ -72,18 +72,28 @@ class PostgreSqlStore implements BlockingSharedLockStoreInterface, BlockingStore
|
||||
// prevent concurrency within the same connection
|
||||
$this->getInternalStore()->save($key);
|
||||
|
||||
$sql = 'SELECT pg_try_advisory_lock(:key)';
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->bindValue(':key', $this->getHashedKey($key));
|
||||
$result = $stmt->execute();
|
||||
$lockAcquired = false;
|
||||
|
||||
// Check if lock is acquired
|
||||
if (true === $stmt->fetchColumn()) {
|
||||
$key->markUnserializable();
|
||||
// release sharedLock in case of promotion
|
||||
$this->unlockShared($key);
|
||||
try {
|
||||
$sql = 'SELECT pg_try_advisory_lock(:key)';
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->bindValue(':key', $this->getHashedKey($key));
|
||||
$result = $stmt->execute();
|
||||
|
||||
return;
|
||||
// Check if lock is acquired
|
||||
if (true === $stmt->fetchColumn()) {
|
||||
$key->markUnserializable();
|
||||
// release sharedLock in case of promotion
|
||||
$this->unlockShared($key);
|
||||
|
||||
$lockAcquired = true;
|
||||
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
if (!$lockAcquired) {
|
||||
$this->getInternalStore()->delete($key);
|
||||
}
|
||||
}
|
||||
|
||||
throw new LockConflictedException();
|
||||
@ -94,19 +104,29 @@ class PostgreSqlStore implements BlockingSharedLockStoreInterface, BlockingStore
|
||||
// prevent concurrency within the same connection
|
||||
$this->getInternalStore()->saveRead($key);
|
||||
|
||||
$sql = 'SELECT pg_try_advisory_lock_shared(:key)';
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$lockAcquired = false;
|
||||
|
||||
$stmt->bindValue(':key', $this->getHashedKey($key));
|
||||
$result = $stmt->execute();
|
||||
try {
|
||||
$sql = 'SELECT pg_try_advisory_lock_shared(:key)';
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
|
||||
// Check if lock is acquired
|
||||
if (true === $stmt->fetchColumn()) {
|
||||
$key->markUnserializable();
|
||||
// release lock in case of demotion
|
||||
$this->unlock($key);
|
||||
$stmt->bindValue(':key', $this->getHashedKey($key));
|
||||
$result = $stmt->execute();
|
||||
|
||||
return;
|
||||
// Check if lock is acquired
|
||||
if (true === $stmt->fetchColumn()) {
|
||||
$key->markUnserializable();
|
||||
// release lock in case of demotion
|
||||
$this->unlock($key);
|
||||
|
||||
$lockAcquired = true;
|
||||
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
if (!$lockAcquired) {
|
||||
$this->getInternalStore()->delete($key);
|
||||
}
|
||||
}
|
||||
|
||||
throw new LockConflictedException();
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\PersistingStoreInterface;
|
||||
use Symfony\Component\Lock\Store\PostgreSqlStore;
|
||||
@ -50,4 +51,31 @@ class PostgreSqlStoreTest extends AbstractStoreTest
|
||||
$this->expectExceptionMessage('The adapter "Symfony\Component\Lock\Store\PostgreSqlStore" does not support');
|
||||
$store->exists(new Key('foo'));
|
||||
}
|
||||
|
||||
public function testSaveAfterConflict()
|
||||
{
|
||||
$store1 = $this->getStore();
|
||||
$store2 = $this->getStore();
|
||||
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$store1->save($key);
|
||||
$this->assertTrue($store1->exists($key));
|
||||
|
||||
$lockConflicted = false;
|
||||
|
||||
try {
|
||||
$store2->save($key);
|
||||
} catch (LockConflictedException $lockConflictedException) {
|
||||
$lockConflicted = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($lockConflicted);
|
||||
$this->assertFalse($store2->exists($key));
|
||||
|
||||
$store1->delete($key);
|
||||
|
||||
$store2->save($key);
|
||||
$this->assertTrue($store2->exists($key));
|
||||
}
|
||||
}
|
||||
|
@ -51,10 +51,10 @@ class Connection
|
||||
private bool $autoSetup;
|
||||
private int $maxEntries;
|
||||
private int $redeliverTimeout;
|
||||
private int $nextClaim = 0;
|
||||
private mixed $claimInterval;
|
||||
private mixed $deleteAfterAck;
|
||||
private mixed $deleteAfterReject;
|
||||
private float $nextClaim = 0.0;
|
||||
private float $claimInterval;
|
||||
private bool $deleteAfterAck;
|
||||
private bool $deleteAfterReject;
|
||||
private bool $couldHavePendingMessages = true;
|
||||
|
||||
public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis|\RedisCluster $redis = null)
|
||||
@ -104,7 +104,7 @@ class Connection
|
||||
$this->deleteAfterAck = $configuration['delete_after_ack'] ?? self::DEFAULT_OPTIONS['delete_after_ack'];
|
||||
$this->deleteAfterReject = $configuration['delete_after_reject'] ?? self::DEFAULT_OPTIONS['delete_after_reject'];
|
||||
$this->redeliverTimeout = ($configuration['redeliver_timeout'] ?? self::DEFAULT_OPTIONS['redeliver_timeout']) * 1000;
|
||||
$this->claimInterval = $configuration['claim_interval'] ?? self::DEFAULT_OPTIONS['claim_interval'];
|
||||
$this->claimInterval = ($configuration['claim_interval'] ?? self::DEFAULT_OPTIONS['claim_interval']) / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -320,7 +320,7 @@ class Connection
|
||||
}
|
||||
}
|
||||
|
||||
$this->nextClaim = $this->getCurrentTimeInMilliseconds() + $this->claimInterval;
|
||||
$this->nextClaim = microtime(true) + $this->claimInterval;
|
||||
}
|
||||
|
||||
public function get(): ?array
|
||||
@ -328,36 +328,32 @@ class Connection
|
||||
if ($this->autoSetup) {
|
||||
$this->setup();
|
||||
}
|
||||
$now = microtime();
|
||||
$now = substr($now, 11).substr($now, 2, 3);
|
||||
|
||||
try {
|
||||
$queuedMessageCount = $this->connection->zcount($this->queue, 0, $this->getCurrentTimeInMilliseconds());
|
||||
} catch (\RedisException $e) {
|
||||
throw new TransportException($e->getMessage(), 0, $e);
|
||||
}
|
||||
$queuedMessageCount = $this->rawCommand('ZCOUNT', 0, $now);
|
||||
|
||||
if ($queuedMessageCount) {
|
||||
for ($i = 0; $i < $queuedMessageCount; ++$i) {
|
||||
try {
|
||||
$queuedMessages = $this->connection->zpopmin($this->queue, 1);
|
||||
} catch (\RedisException $e) {
|
||||
throw new TransportException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
foreach ($queuedMessages as $queuedMessage => $time) {
|
||||
$decodedQueuedMessage = json_decode($queuedMessage, true);
|
||||
// if a futured placed message is actually popped because of a race condition with
|
||||
// another running message consumer, the message is readded to the queue by add function
|
||||
// else its just added stream and will be available for all stream consumers
|
||||
$this->add(
|
||||
\array_key_exists('body', $decodedQueuedMessage) ? $decodedQueuedMessage['body'] : $queuedMessage,
|
||||
$decodedQueuedMessage['headers'] ?? [],
|
||||
$time - $this->getCurrentTimeInMilliseconds()
|
||||
);
|
||||
}
|
||||
while ($queuedMessageCount--) {
|
||||
if (![$queuedMessage, $expiry] = $this->rawCommand('ZPOPMIN', 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (\strlen($expiry) === \strlen($now) ? $expiry > $now : \strlen($expiry) < \strlen($now)) {
|
||||
// if a future-placed message is popped because of a race condition with
|
||||
// another running consumer, the message is readded to the queue
|
||||
|
||||
if (!$this->rawCommand('ZADD', 'NX', $expiry, $queuedMessage)) {
|
||||
throw new TransportException('Could not add a message to the redis stream.');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$decodedQueuedMessage = json_decode($queuedMessage, true);
|
||||
$this->add(\array_key_exists('body', $decodedQueuedMessage) ? $decodedQueuedMessage['body'] : $queuedMessage, $decodedQueuedMessage['headers'] ?? [], 0);
|
||||
}
|
||||
|
||||
if (!$this->couldHavePendingMessages && $this->nextClaim <= $this->getCurrentTimeInMilliseconds()) {
|
||||
if (!$this->couldHavePendingMessages && $this->nextClaim <= microtime(true)) {
|
||||
$this->claimOldPendingMessages();
|
||||
}
|
||||
|
||||
@ -448,7 +444,7 @@ class Connection
|
||||
}
|
||||
|
||||
try {
|
||||
if ($delayInMs > 0) { // the delay could be smaller 0 in a queued message
|
||||
if ($delayInMs > 0) { // the delay is <= 0 for queued messages
|
||||
$message = json_encode([
|
||||
'body' => $body,
|
||||
'headers' => $headers,
|
||||
@ -460,8 +456,18 @@ class Connection
|
||||
throw new TransportException(json_last_error_msg());
|
||||
}
|
||||
|
||||
$score = $this->getCurrentTimeInMilliseconds() + $delayInMs;
|
||||
$added = $this->connection->zadd($this->queue, ['NX'], $score, $message);
|
||||
$now = explode(' ', microtime(), 2);
|
||||
$now[0] = str_pad($delayInMs + substr($now[0], 2, 3), 3, '0', \STR_PAD_LEFT);
|
||||
if (3 < \strlen($now[0])) {
|
||||
$now[1] += substr($now[0], 0, -3);
|
||||
$now[0] = substr($now[0], -3);
|
||||
|
||||
if (\is_float($now[1])) {
|
||||
throw new TransportException("Message delay is too big: {$delayInMs}ms.");
|
||||
}
|
||||
}
|
||||
|
||||
$added = $this->rawCommand('ZADD', 'NX', $now[1].$now[0], $message);
|
||||
} else {
|
||||
$message = json_encode([
|
||||
'body' => $body,
|
||||
@ -542,6 +548,28 @@ class Connection
|
||||
$this->connection->del($this->stream, $this->queue);
|
||||
}
|
||||
}
|
||||
|
||||
private function rawCommand(string $command, ...$arguments): mixed
|
||||
{
|
||||
try {
|
||||
if ($this->connection instanceof \RedisCluster || $this->connection instanceof RedisClusterProxy) {
|
||||
$result = $this->connection->rawCommand($this->queue, $command, $this->queue, ...$arguments);
|
||||
} else {
|
||||
$result = $this->connection->rawCommand($command, $this->queue, ...$arguments);
|
||||
}
|
||||
} catch (\RedisException $e) {
|
||||
throw new TransportException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
if (false === $result) {
|
||||
if ($error = $this->connection->getLastError() ?: null) {
|
||||
$this->connection->clearLastError();
|
||||
}
|
||||
throw new TransportException($error ?? sprintf('Could not run "%s" on Redis queue.', $command));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\Connection::class, false)) {
|
||||
|
@ -62,7 +62,7 @@ final class DkimSigner
|
||||
{
|
||||
$options += $this->defaultOptions;
|
||||
if (!\in_array($options['algorithm'], [self::ALGO_SHA256, self::ALGO_ED25519], true)) {
|
||||
throw new InvalidArgumentException('Invalid DKIM signing algorithm "%s".', $options['algorithm']);
|
||||
throw new InvalidArgumentException(sprintf('Invalid DKIM signing algorithm "%s".', $options['algorithm']));
|
||||
}
|
||||
$headersToIgnore['return-path'] = true;
|
||||
$headersToIgnore['x-transport'] = true;
|
||||
@ -202,7 +202,7 @@ final class DkimSigner
|
||||
}
|
||||
|
||||
// Add trailing Line return if last line is non empty
|
||||
if (\strlen($currentLine) > 0) {
|
||||
if ('' !== $currentLine) {
|
||||
hash_update($hash, "\r\n");
|
||||
$length += \strlen("\r\n");
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use Symfony\Bridge\PhpUnit\ClockMock;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Crypto\DkimSigner;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Mime\Message;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
@ -90,6 +91,21 @@ EOF;
|
||||
];
|
||||
}
|
||||
|
||||
public function testSignWithUnsupportedAlgorithm()
|
||||
{
|
||||
$message = $this->createMock(Message::class);
|
||||
|
||||
$signer = new DkimSigner(self::$pk, 'testdkim.symfony.net', 'sf', [
|
||||
'algorithm' => 'unsupported-value',
|
||||
]);
|
||||
|
||||
$this->expectExceptionObject(
|
||||
new \LogicException('Invalid DKIM signing algorithm "unsupported-value".')
|
||||
);
|
||||
|
||||
$signer->sign($message, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getCanonicalizeHeaderData
|
||||
*/
|
||||
|
@ -3,8 +3,40 @@ Security Component - Core
|
||||
|
||||
Security provides an infrastructure for sophisticated authorization systems,
|
||||
which makes it possible to easily separate the actual authorization logic from
|
||||
so called user providers that hold the users credentials. It is inspired by
|
||||
the Java Spring framework.
|
||||
so called user providers that hold the users credentials.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
```
|
||||
$ composer require symfony/security-core
|
||||
```
|
||||
|
||||
```php
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Core\Role\RoleHierarchy;
|
||||
|
||||
$accessDecisionManager = new AccessDecisionManager([
|
||||
new AuthenticatedVoter(new AuthenticationTrustResolver()),
|
||||
new RoleVoter(),
|
||||
new RoleHierarchyVoter(new RoleHierarchy([
|
||||
'ROLE_ADMIN' => ['ROLE_USER'],
|
||||
]))
|
||||
]);
|
||||
|
||||
$user = new \App\Entity\User(...);
|
||||
$token = new UsernamePasswordToken($user, 'main', $user->getRoles());
|
||||
|
||||
if (!$accessDecisionManager->decide($token, ['ROLE_ADMIN'])) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
```
|
||||
|
||||
Sponsor
|
||||
-------
|
||||
|
31
src/Symfony/Component/Security/Core/Role/Role.php
Normal file
31
src/Symfony/Component/Security/Core/Role/Role.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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\Role;
|
||||
|
||||
/**
|
||||
* Allows migrating session payloads from v4.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Role
|
||||
{
|
||||
private $role;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->role;
|
||||
}
|
||||
}
|
23
src/Symfony/Component/Security/Core/Role/SwitchUserRole.php
Normal file
23
src/Symfony/Component/Security/Core/Role/SwitchUserRole.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?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\Role;
|
||||
|
||||
/**
|
||||
* Allows migrating session payloads from v4.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SwitchUserRole extends Role
|
||||
{
|
||||
private $deprecationTriggered;
|
||||
private $source;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?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\Role;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
|
||||
class LegacyRoleTest extends TestCase
|
||||
{
|
||||
public function testPayloadFromV4CanBeUnserialized()
|
||||
{
|
||||
$serialized = 'C:74:"Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken":236:{a:3:{i:0;N;i:1;s:4:"main";i:2;a:5:{i:0;s:2:"sf";i:1;b:1;i:2;a:1:{i:0;O:41:"Symfony\Component\Security\Core\Role\Role":1:{s:47:"Symfony\Component\Security\Core\Role\Role'."\0".'role'."\0".'";s:9:"ROLE_USER";}}i:3;a:0:{}i:4;a:1:{i:0;s:9:"ROLE_USER";}}}}';
|
||||
|
||||
$token = unserialize($serialized);
|
||||
|
||||
$this->assertInstanceOf(UsernamePasswordToken::class, $token);
|
||||
$this->assertSame(['ROLE_USER'], $token->getRoleNames());
|
||||
}
|
||||
}
|
@ -1,10 +1,16 @@
|
||||
Security Component - HTTP Integration
|
||||
=====================================
|
||||
|
||||
Security provides an infrastructure for sophisticated authorization systems,
|
||||
which makes it possible to easily separate the actual authorization logic from
|
||||
so called user providers that hold the users credentials. It is inspired by
|
||||
the Java Spring framework.
|
||||
The Security HTTP component provides an HTTP integration of the Security Core
|
||||
component. It allows securing (parts of) your application using firewalls and
|
||||
provides authenticators to authenticate visitors.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
```
|
||||
$ composer require symfony/security-http
|
||||
```
|
||||
|
||||
Sponsor
|
||||
-------
|
||||
|
@ -138,7 +138,7 @@ final class LocoProvider implements ProviderInterface
|
||||
|
||||
foreach (array_keys($catalogue->all()) as $domain) {
|
||||
foreach ($this->getAssetsIds($domain) as $id) {
|
||||
$responses[$id] = $this->client->request('DELETE', sprintf('assets/%s.json', $id));
|
||||
$responses[$id] = $this->client->request('DELETE', sprintf('assets/%s.json', rawurlencode($id)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,7 +200,7 @@ final class LocoProvider implements ProviderInterface
|
||||
$responses = [];
|
||||
|
||||
foreach ($translations as $id => $message) {
|
||||
$responses[$id] = $this->client->request('POST', sprintf('translations/%s/%s', $id, $locale), [
|
||||
$responses[$id] = $this->client->request('POST', sprintf('translations/%s/%s', rawurlencode($id), rawurlencode($locale)), [
|
||||
'body' => $message,
|
||||
]);
|
||||
}
|
||||
@ -218,13 +218,35 @@ final class LocoProvider implements ProviderInterface
|
||||
$this->createTag($tag);
|
||||
}
|
||||
|
||||
$response = $this->client->request('POST', sprintf('tags/%s.json', $tag), [
|
||||
'body' => implode(',', $ids),
|
||||
// Separate ids with and without comma.
|
||||
$idsWithComma = $idsWithoutComma = [];
|
||||
foreach ($ids as $id) {
|
||||
if (false !== strpos($id, ',')) {
|
||||
$idsWithComma[] = $id;
|
||||
} else {
|
||||
$idsWithoutComma[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
// Set tags for all ids without comma.
|
||||
$response = $this->client->request('POST', sprintf('tags/%s.json', rawurlencode($tag)), [
|
||||
'body' => implode(',', $idsWithoutComma),
|
||||
]);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
$this->logger->error(sprintf('Unable to tag assets with "%s" on Loco: "%s".', $tag, $response->getContent(false)));
|
||||
}
|
||||
|
||||
// Set tags for each id with comma one by one.
|
||||
foreach ($idsWithComma as $id) {
|
||||
$response = $this->client->request('POST', sprintf('assets/%s/tags', rawurlencode($id)), [
|
||||
'body' => ['name' => $tag],
|
||||
]);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
$this->logger->error(sprintf('Unable to tag asset "%s" with "%s" on Loco: "%s".', $id, $tag, $response->getContent(false)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createTag(string $tag): void
|
||||
|
Reference in New Issue
Block a user