From 7568d3452d80f68a834580dd99b7b153a357858c Mon Sep 17 00:00:00 2001 From: Gocha Ossinkine Date: Tue, 23 Jul 2019 19:02:38 +0500 Subject: [PATCH 01/15] [HttpFoundation] Revert getClientIp @return docblock --- src/Symfony/Component/HttpFoundation/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 7185d75e9220..0f7f46fff0ea 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -912,7 +912,7 @@ public function getClientIps() * ("Client-Ip" for instance), configure it via the $trustedHeaderSet * argument of the Request::setTrustedProxies() method instead. * - * @return string|null The client IP address + * @return string The client IP address * * @see getClientIps() * @see http://en.wikipedia.org/wiki/X-Forwarded-For From 6736cdfec386ec2347fa5b3b4de2a6b57dd78b8d Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 31 Jul 2019 04:17:21 +0200 Subject: [PATCH 02/15] [Ldap] Add security LdapUser and provider --- UPGRADE-4.4.md | 1 + UPGRADE-5.0.md | 1 + .../Resources/config/security.xml | 2 +- .../Bundle/SecurityBundle/composer.json | 3 +- .../Component/Ldap/Security/LdapUser.php | 91 +++++ .../Ldap/Security/LdapUserProvider.php | 155 ++++++++ .../Security/User/LdapUserProviderTest.php | 339 ++++++++++++++++++ src/Symfony/Component/Ldap/composer.json | 3 + src/Symfony/Component/Security/CHANGELOG.md | 1 + .../Core/Tests/User/LdapUserProviderTest.php | 1 + .../Security/Core/User/LdapUserProvider.php | 113 +----- .../Component/Security/Core/composer.json | 5 +- src/Symfony/Component/Security/composer.json | 5 +- 13 files changed, 609 insertions(+), 111 deletions(-) create mode 100644 src/Symfony/Component/Ldap/Security/LdapUser.php create mode 100644 src/Symfony/Component/Ldap/Security/LdapUserProvider.php create mode 100644 src/Symfony/Component/Ldap/Tests/Security/User/LdapUserProviderTest.php diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 93854c80739e..f5bee4d81558 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -141,6 +141,7 @@ Routing Security -------- + * The `LdapUserProvider` class has been deprecated, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead. * Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` should add a new `needsRehash()` method Stopwatch diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index d41330e3fd3d..3d6e92c5d511 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -376,6 +376,7 @@ Routing Security -------- + * The `LdapUserProvider` class has been removed, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead. * Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` must have a new `needsRehash()` method * The `Role` and `SwitchUserRole` classes have been removed. * The `getReachableRoles()` method of the `RoleHierarchy` class has been removed. It has been replaced by the new diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 021acccb2a14..b1263b057b31 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -175,7 +175,7 @@ The "%service_id%" service is deprecated since Symfony 4.1. - + diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index b31b1de8ca4c..99881f56ba49 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -51,7 +51,8 @@ "symfony/twig-bundle": "<4.4", "symfony/var-dumper": "<3.4", "symfony/framework-bundle": "<4.4", - "symfony/console": "<3.4" + "symfony/console": "<3.4", + "symfony/ldap": "<4.4" }, "autoload": { "psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" }, diff --git a/src/Symfony/Component/Ldap/Security/LdapUser.php b/src/Symfony/Component/Ldap/Security/LdapUser.php new file mode 100644 index 000000000000..42c6dbcdf889 --- /dev/null +++ b/src/Symfony/Component/Ldap/Security/LdapUser.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Security; + +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * @author Robin Chalas + * + * @final + */ +class LdapUser implements UserInterface +{ + private $entry; + private $username; + private $password; + private $roles; + private $extraFields; + + public function __construct(Entry $entry, string $username, ?string $password, array $roles = [], array $extraFields = []) + { + if (!$username) { + throw new \InvalidArgumentException('The username cannot be empty.'); + } + + $this->entry = $entry; + $this->username = $username; + $this->password = $password; + $this->roles = $roles; + $this->extraFields = $extraFields; + } + + public function getEntry(): Entry + { + return $this->entry; + } + + /** + * {@inheritdoc} + */ + public function getRoles() + { + return $this->roles; + } + + /** + * {@inheritdoc} + */ + public function getPassword() + { + return $this->password; + } + + /** + * {@inheritdoc} + */ + public function getSalt() + { + } + + /** + * {@inheritdoc} + */ + public function getUsername() + { + return $this->username; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + $this->password = null; + } + + public function getExtraFields(): array + { + return $this->extraFields; + } +} diff --git a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php new file mode 100644 index 000000000000..f371ad07fd6f --- /dev/null +++ b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Security; + +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\ConnectionException; +use Symfony\Component\Ldap\LdapInterface; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * LdapUserProvider is a simple user provider on top of ldap. + * + * @author Grégoire Pineau + * @author Charles Sarrazin + * @author Robin Chalas + */ +class LdapUserProvider implements UserProviderInterface +{ + private $ldap; + private $baseDn; + private $searchDn; + private $searchPassword; + private $defaultRoles; + private $uidKey; + private $defaultSearch; + private $passwordAttribute; + private $extraFields; + + public function __construct(LdapInterface $ldap, string $baseDn, string $searchDn = null, string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = []) + { + if (null === $uidKey) { + $uidKey = 'sAMAccountName'; + } + + if (null === $filter) { + $filter = '({uid_key}={username})'; + } + + $this->ldap = $ldap; + $this->baseDn = $baseDn; + $this->searchDn = $searchDn; + $this->searchPassword = $searchPassword; + $this->defaultRoles = $defaultRoles; + $this->uidKey = $uidKey; + $this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter); + $this->passwordAttribute = $passwordAttribute; + $this->extraFields = $extraFields; + } + + /** + * {@inheritdoc} + */ + public function loadUserByUsername($username) + { + try { + $this->ldap->bind($this->searchDn, $this->searchPassword); + $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER); + $query = str_replace('{username}', $username, $this->defaultSearch); + $search = $this->ldap->query($this->baseDn, $query); + } catch (ConnectionException $e) { + throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e); + } + + $entries = $search->execute(); + $count = \count($entries); + + if (!$count) { + throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); + } + + if ($count > 1) { + throw new UsernameNotFoundException('More than one user found'); + } + + $entry = $entries[0]; + + try { + if (null !== $this->uidKey) { + $username = $this->getAttributeValue($entry, $this->uidKey); + } + } catch (InvalidArgumentException $e) { + } + + return $this->loadUser($username, $entry); + } + + /** + * {@inheritdoc} + */ + public function refreshUser(UserInterface $user) + { + if (!$user instanceof LdapUser) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user))); + } + + return new LdapUser($user->getEntry(), $user->getUsername(), $user->getPassword(), $user->getRoles()); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + return LdapUser::class === $class; + } + + /** + * Loads a user from an LDAP entry. + * + * @return LdapUser + */ + protected function loadUser($username, Entry $entry) + { + $password = null; + $extraFields = []; + + if (null !== $this->passwordAttribute) { + $password = $this->getAttributeValue($entry, $this->passwordAttribute); + } + + foreach ($this->extraFields as $field) { + $extraFields[$field] = $this->getAttributeValue($entry, $field); + } + + return new LdapUser($entry, $username, $password, $this->defaultRoles, $extraFields); + } + + private function getAttributeValue(Entry $entry, string $attribute) + { + if (!$entry->hasAttribute($attribute)) { + throw new InvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $attribute, $entry->getDn())); + } + + $values = $entry->getAttribute($attribute); + + if (1 !== \count($values)) { + throw new InvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $attribute)); + } + + return $values[0]; + } +} diff --git a/src/Symfony/Component/Ldap/Tests/Security/User/LdapUserProviderTest.php b/src/Symfony/Component/Ldap/Tests/Security/User/LdapUserProviderTest.php new file mode 100644 index 000000000000..75839db4d38a --- /dev/null +++ b/src/Symfony/Component/Ldap/Tests/Security/User/LdapUserProviderTest.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Tests\Security\User; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Ldap\Adapter\CollectionInterface; +use Symfony\Component\Ldap\Adapter\QueryInterface; +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\ConnectionException; +use Symfony\Component\Ldap\LdapInterface; +use Symfony\Component\Ldap\Security\LdapUser; +use Symfony\Component\Ldap\Security\LdapUserProvider; + +/** + * @group legacy + * @requires extension ldap + */ +class LdapUserProviderTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException + */ + public function testLoadUserByUsernameFailsIfCantConnectToLdap() + { + $ldap = $this->createMock(LdapInterface::class); + $ldap + ->expects($this->once()) + ->method('bind') + ->willThrowException(new ConnectionException()) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $provider->loadUserByUsername('foo'); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException + */ + public function testLoadUserByUsernameFailsIfNoLdapEntries() + { + $result = $this->createMock(CollectionInterface::class); + $query = $this->createMock(QueryInterface::class); + $query + ->expects($this->once()) + ->method('execute') + ->willReturn($result) + ; + $result + ->expects($this->once()) + ->method('count') + ->willReturn(0) + ; + $ldap = $this->createMock(LdapInterface::class); + $ldap + ->expects($this->once()) + ->method('escape') + ->willReturn('foo') + ; + $ldap + ->expects($this->once()) + ->method('query') + ->willReturn($query) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $provider->loadUserByUsername('foo'); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException + */ + public function testLoadUserByUsernameFailsIfMoreThanOneLdapEntry() + { + $result = $this->createMock(CollectionInterface::class); + $query = $this->createMock(QueryInterface::class); + $query + ->expects($this->once()) + ->method('execute') + ->willReturn($result) + ; + $result + ->expects($this->once()) + ->method('count') + ->willReturn(2) + ; + $ldap = $this->createMock(LdapInterface::class); + $ldap + ->expects($this->once()) + ->method('escape') + ->willReturn('foo') + ; + $ldap + ->expects($this->once()) + ->method('query') + ->willReturn($query) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $provider->loadUserByUsername('foo'); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\InvalidArgumentException + */ + public function testLoadUserByUsernameFailsIfMoreThanOneLdapPasswordsInEntry() + { + $result = $this->createMock(CollectionInterface::class); + $query = $this->createMock(QueryInterface::class); + $query + ->expects($this->once()) + ->method('execute') + ->willReturn($result) + ; + $ldap = $this->createMock(LdapInterface::class); + $result + ->expects($this->once()) + ->method('offsetGet') + ->with(0) + ->willReturn(new Entry('foo', [ + 'sAMAccountName' => ['foo'], + 'userpassword' => ['bar', 'baz'], + ])) + ; + $result + ->expects($this->once()) + ->method('count') + ->willReturn(1) + ; + $ldap + ->expects($this->once()) + ->method('escape') + ->willReturn('foo') + ; + $ldap + ->expects($this->once()) + ->method('query') + ->willReturn($query) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword'); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + } + + public function testLoadUserByUsernameShouldNotFailIfEntryHasNoUidKeyAttribute() + { + $result = $this->createMock(CollectionInterface::class); + $query = $this->createMock(QueryInterface::class); + $query + ->expects($this->once()) + ->method('execute') + ->willReturn($result) + ; + $ldap = $this->createMock(LdapInterface::class); + $result + ->expects($this->once()) + ->method('offsetGet') + ->with(0) + ->willReturn(new Entry('foo', [])) + ; + $result + ->expects($this->once()) + ->method('count') + ->willReturn(1) + ; + $ldap + ->expects($this->once()) + ->method('escape') + ->willReturn('foo') + ; + $ldap + ->expects($this->once()) + ->method('query') + ->willReturn($query) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})'); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\InvalidArgumentException + */ + public function testLoadUserByUsernameFailsIfEntryHasNoPasswordAttribute() + { + $result = $this->createMock(CollectionInterface::class); + $query = $this->createMock(QueryInterface::class); + $query + ->expects($this->once()) + ->method('execute') + ->willReturn($result) + ; + $ldap = $this->createMock(LdapInterface::class); + $result + ->expects($this->once()) + ->method('offsetGet') + ->with(0) + ->willReturn(new Entry('foo', ['sAMAccountName' => ['foo']])) + ; + $result + ->expects($this->once()) + ->method('count') + ->willReturn(1) + ; + $ldap + ->expects($this->once()) + ->method('escape') + ->willReturn('foo') + ; + $ldap + ->expects($this->once()) + ->method('query') + ->willReturn($query) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword'); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + } + + public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttribute() + { + $result = $this->createMock(CollectionInterface::class); + $query = $this->createMock(QueryInterface::class); + $query + ->expects($this->once()) + ->method('execute') + ->willReturn($result) + ; + $ldap = $this->createMock(LdapInterface::class); + $result + ->expects($this->once()) + ->method('offsetGet') + ->with(0) + ->willReturn(new Entry('foo', ['sAMAccountName' => ['foo']])) + ; + $result + ->expects($this->once()) + ->method('count') + ->willReturn(1) + ; + $ldap + ->expects($this->once()) + ->method('escape') + ->willReturn('foo') + ; + $ldap + ->expects($this->once()) + ->method('query') + ->willReturn($query) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + } + + public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttributeAndWrongCase() + { + $result = $this->createMock(CollectionInterface::class); + $query = $this->createMock(QueryInterface::class); + $query + ->expects($this->once()) + ->method('execute') + ->willReturn($result) + ; + $ldap = $this->createMock(LdapInterface::class); + $result + ->expects($this->once()) + ->method('offsetGet') + ->with(0) + ->willReturn(new Entry('foo', ['sAMAccountName' => ['foo']])) + ; + $result + ->expects($this->once()) + ->method('count') + ->willReturn(1) + ; + $ldap + ->expects($this->once()) + ->method('escape') + ->willReturn('Foo') + ; + $ldap + ->expects($this->once()) + ->method('query') + ->willReturn($query) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $this->assertSame('foo', $provider->loadUserByUsername('Foo')->getUsername()); + } + + public function testLoadUserByUsernameIsSuccessfulWithPasswordAttribute() + { + $result = $this->createMock(CollectionInterface::class); + $query = $this->createMock(QueryInterface::class); + $query + ->expects($this->once()) + ->method('execute') + ->willReturn($result) + ; + $ldap = $this->createMock(LdapInterface::class); + $result + ->expects($this->once()) + ->method('offsetGet') + ->with(0) + ->willReturn(new Entry('foo', [ + 'sAMAccountName' => ['foo'], + 'userpassword' => ['bar'], + 'email' => ['elsa@symfony.com'], + ])) + ; + $result + ->expects($this->once()) + ->method('count') + ->willReturn(1) + ; + $ldap + ->expects($this->once()) + ->method('escape') + ->willReturn('foo') + ; + $ldap + ->expects($this->once()) + ->method('query') + ->willReturn($query) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword', ['email']); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + } +} diff --git a/src/Symfony/Component/Ldap/composer.json b/src/Symfony/Component/Ldap/composer.json index c302be83ec7a..d36fcb140cb0 100644 --- a/src/Symfony/Component/Ldap/composer.json +++ b/src/Symfony/Component/Ldap/composer.json @@ -20,6 +20,9 @@ "symfony/options-resolver": "^4.2|^5.0", "ext-ldap": "*" }, + "require-dev": { + "symfony/security-core": "^4.4" + }, "conflict": { "symfony/options-resolver": "<4.2" }, diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 982d753af509..3ac23ef992cc 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.4.0 ----- + * Deprecated class `LdapUserProvider`, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead * Added method `needsRehash()` to `PasswordEncoderInterface` and `UserPasswordEncoderInterface` * Added `MigratingPasswordEncoder` diff --git a/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php b/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php index bca36c7b7f84..d0d13b7bf74e 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Security\Core\User\LdapUserProvider; /** + * @group legacy * @requires extension ldap */ class LdapUserProviderTest extends TestCase diff --git a/src/Symfony/Component/Security/Core/User/LdapUserProvider.php b/src/Symfony/Component/Security/Core/User/LdapUserProvider.php index 1820de31a548..406d141c47bb 100644 --- a/src/Symfony/Component/Security/Core/User/LdapUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/LdapUserProvider.php @@ -11,89 +11,22 @@ namespace Symfony\Component\Security\Core\User; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', LdapUserProvider::class, BaseLdapUserProvider::class), E_USER_DEPRECATED); + use Symfony\Component\Ldap\Entry; -use Symfony\Component\Ldap\Exception\ConnectionException; -use Symfony\Component\Ldap\LdapInterface; -use Symfony\Component\Security\Core\Exception\InvalidArgumentException; +use Symfony\Component\Ldap\Security\LdapUserProvider as BaseLdapUserProvider; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; /** * LdapUserProvider is a simple user provider on top of ldap. * * @author Grégoire Pineau * @author Charles Sarrazin + * + * @deprecated since Symfony 4.4, use "Symfony\Component\Ldap\Security\LdapUserProvider" instead */ -class LdapUserProvider implements UserProviderInterface +class LdapUserProvider extends BaseLdapUserProvider { - private $ldap; - private $baseDn; - private $searchDn; - private $searchPassword; - private $defaultRoles; - private $uidKey; - private $defaultSearch; - private $passwordAttribute; - private $extraFields; - - public function __construct(LdapInterface $ldap, string $baseDn, string $searchDn = null, string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = []) - { - if (null === $uidKey) { - $uidKey = 'sAMAccountName'; - } - - if (null === $filter) { - $filter = '({uid_key}={username})'; - } - - $this->ldap = $ldap; - $this->baseDn = $baseDn; - $this->searchDn = $searchDn; - $this->searchPassword = $searchPassword; - $this->defaultRoles = $defaultRoles; - $this->uidKey = $uidKey; - $this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter); - $this->passwordAttribute = $passwordAttribute; - $this->extraFields = $extraFields; - } - - /** - * {@inheritdoc} - */ - public function loadUserByUsername($username) - { - try { - $this->ldap->bind($this->searchDn, $this->searchPassword); - $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER); - $query = str_replace('{username}', $username, $this->defaultSearch); - $search = $this->ldap->query($this->baseDn, $query); - } catch (ConnectionException $e) { - throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e); - } - - $entries = $search->execute(); - $count = \count($entries); - - if (!$count) { - throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); - } - - if ($count > 1) { - throw new UsernameNotFoundException('More than one user found'); - } - - $entry = $entries[0]; - - try { - if (null !== $this->uidKey) { - $username = $this->getAttributeValue($entry, $this->uidKey); - } - } catch (InvalidArgumentException $e) { - } - - return $this->loadUser($username, $entry); - } - /** * {@inheritdoc} */ @@ -117,42 +50,12 @@ public function supportsClass($class) /** * Loads a user from an LDAP entry. * - * @param string $username - * @param Entry $entry - * * @return User */ protected function loadUser($username, Entry $entry) { - $password = null; - $extraFields = []; - - if (null !== $this->passwordAttribute) { - $password = $this->getAttributeValue($entry, $this->passwordAttribute); - } - - foreach ($this->extraFields as $field) { - $extraFields[$field] = $this->getAttributeValue($entry, $field); - } - - return new User($username, $password, $this->defaultRoles, true, true, true, true, $extraFields); - } - - /** - * Fetches a required unique attribute value from an LDAP entry. - */ - private function getAttributeValue(Entry $entry, string $attribute) - { - if (!$entry->hasAttribute($attribute)) { - throw new InvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $attribute, $entry->getDn())); - } - - $values = $entry->getAttribute($attribute); - - if (1 !== \count($values)) { - throw new InvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $attribute)); - } + $ldapUser = parent::loadUser($username, $entry); - return $values[0]; + return new User($ldapUser->getUsername(), $ldapUser->getPassword(), $ldapUser->getRoles(), true, true, true, true, $ldapUser->getExtraFields()); } } diff --git a/src/Symfony/Component/Security/Core/composer.json b/src/Symfony/Component/Security/Core/composer.json index 73f8078ab5ea..9819a096aec7 100644 --- a/src/Symfony/Component/Security/Core/composer.json +++ b/src/Symfony/Component/Security/Core/composer.json @@ -25,13 +25,14 @@ "symfony/event-dispatcher": "^4.3", "symfony/expression-language": "^3.4|^4.0|^5.0", "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/ldap": "^3.4|^4.0|^5.0", + "symfony/ldap": "^4.4|^5.0", "symfony/validator": "^3.4|^4.0|^5.0", "psr/log": "~1.0" }, "conflict": { "symfony/event-dispatcher": "<4.3|>=5", - "symfony/security-guard": "<4.3" + "symfony/security-guard": "<4.3", + "symfony/ldap": "<4.4" }, "suggest": { "psr/container-implementation": "To instantiate the Security class", diff --git a/src/Symfony/Component/Security/composer.json b/src/Symfony/Component/Security/composer.json index 20b29bb106a1..a08ed207b18c 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/src/Symfony/Component/Security/composer.json @@ -37,11 +37,12 @@ "symfony/routing": "^3.4|^4.0|^5.0", "symfony/validator": "^3.4|^4.0|^5.0", "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/ldap": "^3.4|^4.0|^5.0", + "symfony/ldap": "^4.4|^5.0", "psr/log": "~1.0" }, "conflict": { - "symfony/event-dispatcher": ">=5" + "symfony/event-dispatcher": ">=5", + "symfony/ldap": "<4.4" }, "suggest": { "psr/container-implementation": "To instantiate the Security class", From 44b0e7d58cdab721ca4bd02d7f99fc6aad4c9ef1 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Thu, 1 Aug 2019 15:26:08 -0400 Subject: [PATCH 03/15] Created alias to FlattenException to avoid BC break --- .../ErrorRenderer/Exception/FlattenException.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php b/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php index 9ec583f7b7f2..154a1450a108 100644 --- a/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php +++ b/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php @@ -374,3 +374,18 @@ public function getAsString() return rtrim($message); } } + +namespace Symfony\Component\Debug\Exception; + +if (!class_exists(FlattenException::class, false)) { + class_alias(\Symfony\Component\ErrorRenderer\Exception\FlattenException::class, FlattenException::class); +} + +if (false) { + /** + * @deprecated since Symfony 4.4, use Symfony\Component\ErrorRenderer\Exception\FlattenException instead. + */ + class FlattenException extends \Symfony\Component\ErrorRenderer\Exception\FlattenException + { + } +} From 5d739704f2a0ad0de0ac34db9730681da99da4d5 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sun, 28 Jul 2019 22:26:18 +0200 Subject: [PATCH 04/15] [Messenger] Fix incompatibility with FrameworkBundle <4.3.1 --- .../FrameworkBundle/Resources/config/console.xml | 2 +- .../Messenger/Command/ConsumeMessagesCommand.php | 2 ++ .../Messenger/DependencyInjection/MessengerPass.php | 11 ++++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index ebd7d6ce46a6..c0d185a00f31 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -82,7 +82,7 @@ - + diff --git a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php index be6f4c1733b2..79c4aa359779 100644 --- a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php +++ b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php @@ -57,6 +57,8 @@ public function __construct($routableBus, ContainerInterface $receiverLocator, L // to be deprecated in 4.4 if ($routableBus instanceof ContainerInterface) { $routableBus = new RoutableMessageBus($routableBus); + } elseif (!$routableBus instanceof RoutableMessageBus) { + throw new \TypeError(sprintf('The first argument must be an instance of "%s".', RoutableMessageBus::class)); } if (\is_array($retryStrategyLocator)) { diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index b83272792f4d..8836c4ac83e5 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -253,14 +253,19 @@ private function registerReceivers(ContainerBuilder $container, array $busIds) $buses[$busId] = new Reference($busId); } - if ($container->hasDefinition('messenger.routable_message_bus')) { + if ($hasRoutableMessageBus = $container->hasDefinition('messenger.routable_message_bus')) { $container->getDefinition('messenger.routable_message_bus') ->replaceArgument(0, ServiceLocatorTagPass::register($container, $buses)); } if ($container->hasDefinition('console.command.messenger_consume_messages')) { - $container->getDefinition('console.command.messenger_consume_messages') - ->replaceArgument(3, array_values($receiverNames)); + $consumeCommandDefinition = $container->getDefinition('console.command.messenger_consume_messages'); + + if ($hasRoutableMessageBus) { + $consumeCommandDefinition->replaceArgument(0, new Reference('messenger.routable_message_bus')); + } + + $consumeCommandDefinition->replaceArgument(3, array_values($receiverNames)); } if ($container->hasDefinition('console.command.messenger_setup_transports')) { From 1451c0b9158f779756c4f3017da197a862084dc7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 3 Aug 2019 14:01:57 +0200 Subject: [PATCH 05/15] Allow sutFqcnResolver to return array --- src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php index 8e9bdbe92ed4..1f9aabc5195a 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php @@ -76,7 +76,7 @@ public function startTest($test) $cache = $r->getValue(); $cache = array_replace_recursive($cache, array( \get_class($test) => array( - 'covers' => array($sutFqcn), + 'covers' => \is_array($sutFqcn) ? $sutFqcn : array($sutFqcn), ), )); $r->setValue($testClass, $cache); From 8863f0675d9db3ed366d487f165204bd0fba66a4 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 4 Aug 2019 04:46:49 +0200 Subject: [PATCH 06/15] [Routing] added a warning about the getRouteCollection() method --- src/Symfony/Component/Routing/RouterInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Symfony/Component/Routing/RouterInterface.php b/src/Symfony/Component/Routing/RouterInterface.php index a10ae34e0745..8a3e33dc2243 100644 --- a/src/Symfony/Component/Routing/RouterInterface.php +++ b/src/Symfony/Component/Routing/RouterInterface.php @@ -26,6 +26,9 @@ interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface /** * Gets the RouteCollection instance associated with this Router. * + * WARNING: This method should never be used at runtime as it is SLOW. + * You might use it in a cache warmer though. + * * @return RouteCollection A RouteCollection instance */ public function getRouteCollection(); From 724f1f524f99c6679dbbc15b4720087099ad2371 Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sun, 4 Aug 2019 08:07:53 +0200 Subject: [PATCH 07/15] [Intl] Order alpha2 to alpha3 mapping --- .../Data/Generator/LanguageDataGenerator.php | 2 + .../Intl/Resources/data/languages/meta.json | 42 +++++++++---------- .../AbstractLanguageDataProviderTest.php | 38 ++++++++--------- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/Symfony/Component/Intl/Data/Generator/LanguageDataGenerator.php b/src/Symfony/Component/Intl/Data/Generator/LanguageDataGenerator.php index e8695e19319d..3198d7b164db 100644 --- a/src/Symfony/Component/Intl/Data/Generator/LanguageDataGenerator.php +++ b/src/Symfony/Component/Intl/Data/Generator/LanguageDataGenerator.php @@ -193,6 +193,8 @@ private function generateAlpha2ToAlpha3Mapping(ArrayAccessibleResourceBundle $me } } + asort($alpha2ToAlpha3); + return $alpha2ToAlpha3; } } diff --git a/src/Symfony/Component/Intl/Resources/data/languages/meta.json b/src/Symfony/Component/Intl/Resources/data/languages/meta.json index c2eba4aa1a92..7c03bf0c6489 100644 --- a/src/Symfony/Component/Intl/Resources/data/languages/meta.json +++ b/src/Symfony/Component/Intl/Resources/data/languages/meta.json @@ -626,14 +626,11 @@ "Alpha2ToAlpha3": { "aa": "aar", "ab": "abk", - "dz": "dzo", "af": "afr", "ak": "aka", - "sq": "sqi", "am": "amh", "ar": "ara", "an": "arg", - "hy": "hye", "as": "asm", "av": "ava", "ae": "ave", @@ -641,7 +638,6 @@ "az": "aze", "ba": "bak", "bm": "bam", - "eu": "eus", "be": "bel", "bn": "ben", "bi": "bis", @@ -649,12 +645,10 @@ "bs": "bos", "br": "bre", "bg": "bul", - "my": "mya", "ca": "cat", "cs": "ces", "ch": "cha", "ce": "che", - "zh": "zho", "cu": "chu", "cv": "chv", "kw": "cor", @@ -664,13 +658,12 @@ "da": "dan", "de": "deu", "dv": "div", - "mn": "mon", - "nl": "nld", - "et": "est", + "dz": "dzo", "el": "ell", "en": "eng", "eo": "epo", - "ik": "ipk", + "et": "est", + "eu": "eus", "ee": "ewe", "fo": "fao", "fa": "fas", @@ -679,8 +672,6 @@ "fr": "fra", "fy": "fry", "ff": "ful", - "om": "orm", - "ka": "kat", "gd": "gla", "ga": "gle", "gl": "glg", @@ -695,31 +686,34 @@ "ho": "hmo", "hr": "hrv", "hu": "hun", + "hy": "hye", "ig": "ibo", - "is": "isl", "io": "ido", "ii": "iii", "iu": "iku", "ie": "ile", "ia": "ina", "id": "ind", + "ik": "ipk", + "is": "isl", "it": "ita", "jv": "jav", "ja": "jpn", "kl": "kal", "kn": "kan", "ks": "kas", + "ka": "kat", "kr": "kau", "kk": "kaz", "km": "khm", "ki": "kik", "rw": "kin", "ky": "kir", - "ku": "kur", - "kg": "kon", "kv": "kom", + "kg": "kon", "ko": "kor", "kj": "kua", + "ku": "kur", "lo": "lao", "la": "lat", "lv": "lav", @@ -729,40 +723,43 @@ "lb": "ltz", "lu": "lub", "lg": "lug", - "mk": "mkd", "mh": "mah", "ml": "mal", - "mi": "mri", "mr": "mar", - "ms": "msa", + "mk": "mkd", "mg": "mlg", "mt": "mlt", - "ro": "ron", + "mn": "mon", + "mi": "mri", + "ms": "msa", + "my": "mya", "na": "nau", "nv": "nav", "nr": "nbl", "nd": "nde", "ng": "ndo", "ne": "nep", + "nl": "nld", "nn": "nno", "nb": "nob", "ny": "nya", "oc": "oci", "oj": "oji", "or": "ori", + "om": "orm", "os": "oss", "pa": "pan", - "ps": "pus", "pi": "pli", "pl": "pol", "pt": "por", + "ps": "pus", "qu": "que", "rm": "roh", + "ro": "ron", "rn": "run", "ru": "rus", "sg": "sag", "sa": "san", - "sr": "srp", "si": "sin", "sk": "slk", "sl": "slv", @@ -773,7 +770,9 @@ "so": "som", "st": "sot", "es": "spa", + "sq": "sqi", "sc": "srd", + "sr": "srp", "ss": "ssw", "su": "sun", "sw": "swa", @@ -803,6 +802,7 @@ "yi": "yid", "yo": "yor", "za": "zha", + "zh": "zho", "zu": "zul" } } diff --git a/src/Symfony/Component/Intl/Tests/Data/Provider/AbstractLanguageDataProviderTest.php b/src/Symfony/Component/Intl/Tests/Data/Provider/AbstractLanguageDataProviderTest.php index 2c8ce876a845..917f6f0dd1e6 100644 --- a/src/Symfony/Component/Intl/Tests/Data/Provider/AbstractLanguageDataProviderTest.php +++ b/src/Symfony/Component/Intl/Tests/Data/Provider/AbstractLanguageDataProviderTest.php @@ -649,11 +649,9 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'ab' => 'abk', 'af' => 'afr', 'ak' => 'aka', - 'sq' => 'sqi', 'am' => 'amh', 'ar' => 'ara', 'an' => 'arg', - 'hy' => 'hye', 'as' => 'asm', 'av' => 'ava', 'ae' => 'ave', @@ -661,7 +659,6 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'az' => 'aze', 'ba' => 'bak', 'bm' => 'bam', - 'eu' => 'eus', 'be' => 'bel', 'bn' => 'ben', 'bi' => 'bis', @@ -669,12 +666,10 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'bs' => 'bos', 'br' => 'bre', 'bg' => 'bul', - 'my' => 'mya', 'ca' => 'cat', 'cs' => 'ces', 'ch' => 'cha', 'ce' => 'che', - 'zh' => 'zho', 'cu' => 'chu', 'cv' => 'chv', 'kw' => 'cor', @@ -684,13 +679,12 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'da' => 'dan', 'de' => 'deu', 'dv' => 'div', - 'nl' => 'nld', 'dz' => 'dzo', - 'et' => 'est', 'el' => 'ell', 'en' => 'eng', 'eo' => 'epo', - 'ik' => 'ipk', + 'et' => 'est', + 'eu' => 'eus', 'ee' => 'ewe', 'fo' => 'fao', 'fa' => 'fas', @@ -699,8 +693,6 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'fr' => 'fra', 'fy' => 'fry', 'ff' => 'ful', - 'om' => 'orm', - 'ka' => 'kat', 'gd' => 'gla', 'ga' => 'gle', 'gl' => 'glg', @@ -715,32 +707,34 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'ho' => 'hmo', 'hr' => 'hrv', 'hu' => 'hun', + 'hy' => 'hye', 'ig' => 'ibo', - 'is' => 'isl', 'io' => 'ido', 'ii' => 'iii', 'iu' => 'iku', 'ie' => 'ile', 'ia' => 'ina', 'id' => 'ind', + 'ik' => 'ipk', + 'is' => 'isl', 'it' => 'ita', 'jv' => 'jav', 'ja' => 'jpn', 'kl' => 'kal', 'kn' => 'kan', 'ks' => 'kas', + 'ka' => 'kat', 'kr' => 'kau', 'kk' => 'kaz', - 'mn' => 'mon', 'km' => 'khm', 'ki' => 'kik', 'rw' => 'kin', 'ky' => 'kir', - 'ku' => 'kur', - 'kg' => 'kon', 'kv' => 'kom', + 'kg' => 'kon', 'ko' => 'kor', 'kj' => 'kua', + 'ku' => 'kur', 'lo' => 'lao', 'la' => 'lat', 'lv' => 'lav', @@ -750,32 +744,36 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'lb' => 'ltz', 'lu' => 'lub', 'lg' => 'lug', - 'mk' => 'mkd', 'mh' => 'mah', 'ml' => 'mal', - 'mi' => 'mri', 'mr' => 'mar', - 'ms' => 'msa', + 'mk' => 'mkd', 'mg' => 'mlg', 'mt' => 'mlt', + 'mn' => 'mon', + 'mi' => 'mri', + 'ms' => 'msa', + 'my' => 'mya', 'na' => 'nau', 'nv' => 'nav', 'nr' => 'nbl', 'nd' => 'nde', 'ng' => 'ndo', 'ne' => 'nep', + 'nl' => 'nld', 'nn' => 'nno', 'nb' => 'nob', 'ny' => 'nya', 'oc' => 'oci', 'oj' => 'oji', 'or' => 'ori', + 'om' => 'orm', 'os' => 'oss', 'pa' => 'pan', - 'ps' => 'pus', 'pi' => 'pli', 'pl' => 'pol', 'pt' => 'por', + 'ps' => 'pus', 'qu' => 'que', 'rm' => 'roh', 'ro' => 'ron', @@ -783,7 +781,6 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'ru' => 'rus', 'sg' => 'sag', 'sa' => 'san', - 'sr' => 'srp', 'si' => 'sin', 'sk' => 'slk', 'sl' => 'slv', @@ -794,7 +791,9 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'so' => 'som', 'st' => 'sot', 'es' => 'spa', + 'sq' => 'sqi', 'sc' => 'srd', + 'sr' => 'srp', 'ss' => 'ssw', 'su' => 'sun', 'sw' => 'swa', @@ -824,6 +823,7 @@ abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest 'yi' => 'yid', 'yo' => 'yor', 'za' => 'zha', + 'zh' => 'zho', 'zu' => 'zul', ]; From 271211b1f601cb3d24fe609428eaa0032356daef Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 4 Aug 2019 09:53:03 +0200 Subject: [PATCH 08/15] [PhpUnitBridge] make the bridge act as a polyfill for newest PHPUnit features --- src/Symfony/Bridge/PhpUnit/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index 8cd3c372f229..92907ea25cc2 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.4.0 +----- + + * made the bridge act as a polyfill for newest PHPUnit features + * added `SetUpTearDownTrait` to allow working around the `void` return-type added by PHPUnit 8 + 4.3.0 ----- From d098c1153980f477b1a639ba3025d84b37b30987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sun, 4 Aug 2019 11:16:42 +0200 Subject: [PATCH 09/15] Remove calls to deprecated function assertAttributeX --- .../EventDispatcher/Tests/GenericEventTest.php | 8 ++++---- .../Storage/Handler/PdoSessionHandlerTest.php | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php b/src/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php index 0628d8518665..f0f0d71f29a0 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php @@ -61,14 +61,14 @@ public function testGetArguments() public function testSetArguments() { $result = $this->event->setArguments(['foo' => 'bar']); - $this->assertAttributeSame(['foo' => 'bar'], 'arguments', $this->event); + $this->assertSame(['foo' => 'bar'], $this->event->getArguments()); $this->assertSame($this->event, $result); } public function testSetArgument() { $result = $this->event->setArgument('foo2', 'bar2'); - $this->assertAttributeSame(['name' => 'Event', 'foo2' => 'bar2'], 'arguments', $this->event); + $this->assertSame(['name' => 'Event', 'foo2' => 'bar2'], $this->event->getArguments()); $this->assertEquals($this->event, $result); } @@ -97,13 +97,13 @@ public function testOffsetGet() public function testOffsetSet() { $this->event['foo2'] = 'bar2'; - $this->assertAttributeSame(['name' => 'Event', 'foo2' => 'bar2'], 'arguments', $this->event); + $this->assertSame(['name' => 'Event', 'foo2' => 'bar2'], $this->event->getArguments()); } public function testOffsetUnset() { unset($this->event['name']); - $this->assertAttributeSame([], 'arguments', $this->event); + $this->assertSame([], $this->event->getArguments()); } public function testOffsetIsset() diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index ff513b7efae0..123b605d2860 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -324,15 +324,15 @@ public function testGetConnectionConnectsIfNeeded() public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null) { $storage = new PdoSessionHandler($url); - - $this->assertAttributeEquals($expectedDsn, 'dsn', $storage); - - if (null !== $expectedUser) { - $this->assertAttributeEquals($expectedUser, 'username', $storage); - } - - if (null !== $expectedPassword) { - $this->assertAttributeEquals($expectedPassword, 'password', $storage); + $reflection = new \ReflectionClass(PdoSessionHandler::class); + + foreach (['dsn' => $expectedDsn, 'username' => $expectedUser, 'password' => $expectedPassword] as $property => $expectedValue) { + if (!isset($expectedValue)) { + continue; + } + $property = $reflection->getProperty($property); + $property->setAccessible(true); + $this->assertSame($expectedValue, $property->getValue($storage)); } } From b500f929219742d78c0223b577e5d3318661832b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kowalewski?= Date: Tue, 30 Jul 2019 16:40:17 +0200 Subject: [PATCH 10/15] Create mailBody with only attachments part present --- src/Symfony/Component/Mime/Email.php | 12 ++++++++---- src/Symfony/Component/Mime/Tests/EmailTest.php | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index 3fbebc461f1e..1bcdc8a1cd3b 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -423,12 +423,12 @@ public function getBody(): AbstractPart */ private function generateBody(): AbstractPart { - if (null === $this->text && null === $this->html) { - throw new LogicException('A message must have a text and/or an HTML part.'); + [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); + if (null === $this->text && null === $this->html && !$attachmentParts) { + throw new LogicException('A message must have a text or an HTML part or attachments.'); } $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset); - [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); if (null !== $htmlPart) { if (null !== $part) { $part = new AlternativePart($part, $htmlPart); @@ -442,7 +442,11 @@ private function generateBody(): AbstractPart } if ($attachmentParts) { - $part = new MixedPart($part, ...$attachmentParts); + if ($part) { + $part = new MixedPart($part, ...$attachmentParts); + } else { + $part = new MixedPart(...$attachmentParts); + } } return $part; diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index 1d45cab9f49f..d8a982add725 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -284,6 +284,10 @@ public function testGenerateBody() $e->html('html content'); $this->assertEquals(new MixedPart($html, $att), $e->getBody()); + $e = new Email(); + $e->attach($file); + $this->assertEquals(new MixedPart($att), $e->getBody()); + $e = new Email(); $e->html('html content'); $e->text('text content'); From d2c4bf0da81a1970b871c60f5d09a500deb09314 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 4 Aug 2019 17:48:42 +0200 Subject: [PATCH 11/15] [HttpClient] use "idle" instead of "inactivity" when telling about the timeout option --- .../FrameworkBundle/DependencyInjection/Configuration.php | 2 +- src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php | 2 +- src/Symfony/Contracts/HttpClient/ChunkInterface.php | 8 ++++---- src/Symfony/Contracts/HttpClient/HttpClientInterface.php | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 488d56aac09c..b4ce6a2c6a74 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1348,7 +1348,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode) ->info('A comma separated list of hosts that do not require a proxy to be reached.') ->end() ->floatNode('timeout') - ->info('Defaults to "default_socket_timeout" ini parameter.') + ->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.') ->end() ->scalarNode('bindto') ->info('A network interface name, IP address, a host name or a UNIX socket to bind to.') diff --git a/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php b/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php index 6b1e44f69f38..d2a69bc38a15 100644 --- a/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php +++ b/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php @@ -30,7 +30,7 @@ public function __construct(int $offset, \Throwable $error = null) { $this->offset = $offset; $this->error = $error; - $this->errorMessage = null !== $error ? $error->getMessage() : 'Reading from the response stream reached the inactivity timeout.'; + $this->errorMessage = null !== $error ? $error->getMessage() : 'Reading from the response stream reached the idle timeout.'; } /** diff --git a/src/Symfony/Contracts/HttpClient/ChunkInterface.php b/src/Symfony/Contracts/HttpClient/ChunkInterface.php index bbec2cdfa606..d6fd73d8946f 100644 --- a/src/Symfony/Contracts/HttpClient/ChunkInterface.php +++ b/src/Symfony/Contracts/HttpClient/ChunkInterface.php @@ -27,7 +27,7 @@ interface ChunkInterface { /** - * Tells when the inactivity timeout has been reached. + * Tells when the idle timeout has been reached. * * @throws TransportExceptionInterface on a network error */ @@ -36,21 +36,21 @@ public function isTimeout(): bool; /** * Tells when headers just arrived. * - * @throws TransportExceptionInterface on a network error or when the inactivity timeout is reached + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached */ public function isFirst(): bool; /** * Tells when the body just completed. * - * @throws TransportExceptionInterface on a network error or when the inactivity timeout is reached + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached */ public function isLast(): bool; /** * Returns the content of the response chunk. * - * @throws TransportExceptionInterface on a network error or when the inactivity timeout is reached + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached */ public function getContent(): string; diff --git a/src/Symfony/Contracts/HttpClient/HttpClientInterface.php b/src/Symfony/Contracts/HttpClient/HttpClientInterface.php index 6636af7a239b..c974ba97e30b 100644 --- a/src/Symfony/Contracts/HttpClient/HttpClientInterface.php +++ b/src/Symfony/Contracts/HttpClient/HttpClientInterface.php @@ -52,7 +52,7 @@ interface HttpClientInterface 'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution 'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored 'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached - 'timeout' => null, // float - the inactivity timeout - defaults to ini_get('default_socket_timeout') + 'timeout' => null, // float - the idle timeout - defaults to ini_get('default_socket_timeout') 'bindto' => '0', // string - the interface or the local socket to bind to 'verify_peer' => true, // see https://php.net/context.ssl for the following options 'verify_host' => true, @@ -85,7 +85,7 @@ public function request(string $method, string $url, array $options = []): Respo * Yields responses chunk by chunk as they complete. * * @param ResponseInterface|ResponseInterface[]|iterable $responses One or more responses created by the current HTTP client - * @param float|null $timeout The inactivity timeout before exiting the iterator + * @param float|null $timeout The idle timeout before yielding timeout chunks */ public function stream($responses, float $timeout = null): ResponseStreamInterface; } From 6ee0d53c31db757e38a2f54c298a0a3b0a868d17 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Sun, 4 Aug 2019 21:26:13 +0200 Subject: [PATCH 12/15] [Form] type cannot be a FormTypeInterface anymore --- src/Symfony/Component/Form/ButtonBuilder.php | 7 ------- src/Symfony/Component/Form/Form.php | 4 ++-- src/Symfony/Component/Form/FormBuilder.php | 4 ++-- src/Symfony/Component/Form/Tests/FormBuilderTest.php | 7 ------- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index 01f886a0fc29..33bbbdc36de5 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -79,9 +79,6 @@ public function __construct(?string $name, array $options = []) * * This method should not be invoked. * - * @param string|FormBuilderInterface $child - * @param string|FormTypeInterface $type - * * @throws BadMethodCallException */ public function add($child, $type = null, array $options = []) @@ -94,10 +91,6 @@ public function add($child, $type = null, array $options = []) * * This method should not be invoked. * - * @param string $name - * @param string|FormTypeInterface $type - * @param array $options - * * @throws BadMethodCallException */ public function create($name, $type = null, array $options = []) diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index aadab4d75228..e7691cdbc1b4 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -849,8 +849,8 @@ public function add($child, $type = null, array $options = []) $child = (string) $child; - if (null !== $type && !\is_string($type) && !$type instanceof FormTypeInterface) { - throw new UnexpectedTypeException($type, 'string or Symfony\Component\Form\FormTypeInterface'); + if (null !== $type && !\is_string($type)) { + throw new UnexpectedTypeException($type, 'string or null'); } // Never initialize child forms automatically diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 6b6a1728b219..87b4485a6f11 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -66,8 +66,8 @@ public function add($child, $type = null, array $options = []) throw new UnexpectedTypeException($child, 'string or Symfony\Component\Form\FormBuilderInterface'); } - if (null !== $type && !\is_string($type) && !$type instanceof FormTypeInterface) { - throw new UnexpectedTypeException($type, 'string or Symfony\Component\Form\FormTypeInterface'); + if (null !== $type && !\is_string($type)) { + throw new UnexpectedTypeException($type, 'string or null'); } // Add to "children" to maintain order diff --git a/src/Symfony/Component/Form/Tests/FormBuilderTest.php b/src/Symfony/Component/Form/Tests/FormBuilderTest.php index 38d29a28d813..d831e0f3f295 100644 --- a/src/Symfony/Component/Form/Tests/FormBuilderTest.php +++ b/src/Symfony/Component/Form/Tests/FormBuilderTest.php @@ -120,13 +120,6 @@ public function testMaintainOrderOfLazyAndExplicitChildren() $this->assertSame(['foo', 'bar', 'baz'], array_keys($children)); } - public function testAddFormType() - { - $this->assertFalse($this->builder->has('foo')); - $this->builder->add('foo', $this->getMockBuilder('Symfony\Component\Form\FormTypeInterface')->getMock()); - $this->assertTrue($this->builder->has('foo')); - } - public function testRemove() { $this->builder->add('foo', 'Symfony\Component\Form\Extension\Core\Type\TextType'); From ceaa1b33d04013716388825c6f7e99781cd8751c Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Wed, 31 Jul 2019 19:45:44 +0200 Subject: [PATCH 13/15] [FrameworkBundle] Detect indirect env vars in routing --- .../Bundle/FrameworkBundle/Routing/Router.php | 4 ++-- .../Tests/Routing/RouterTest.php | 21 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index a846536a40a3..ebb5e145c4b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -147,7 +147,7 @@ private function resolve($value) return '%%'; } - if (preg_match('/^env\(\w+\)$/', $match[1])) { + if (preg_match('/^env\((?:\w++:)*+\w++\)$/', $match[1])) { throw new RuntimeException(sprintf('Using "%%%s%%" is not allowed in routing configuration.', $match[1])); } @@ -156,7 +156,7 @@ private function resolve($value) if (\is_string($resolved) || is_numeric($resolved)) { $this->collectedParameters[$match[1]] = $resolved; - return (string) $resolved; + return (string) $this->resolve($resolved); } throw new RuntimeException(sprintf('The container parameter "%s", used in the route configuration value "%s", must be a string or numeric, but it is of type %s.', $match[1], $value, \gettype($resolved))); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php index 9fe45527cffe..024e7e6dd75b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Routing\Router; use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -122,13 +123,13 @@ public function testPatternPlaceholders() $routes->add('foo', new Route('/before/%parameter.foo%/after/%%escaped%%')); $sc = $this->getServiceContainer($routes); - $sc->setParameter('parameter.foo', 'foo'); + $sc->setParameter('parameter.foo', 'foo-%%escaped%%'); $router = new Router($sc, 'foo'); $route = $router->getRouteCollection()->get('foo'); $this->assertEquals( - '/before/foo/after/%escaped%', + '/before/foo-%escaped%/after/%escaped%', $route->getPath() ); } @@ -147,6 +148,22 @@ public function testEnvPlaceholders() $router->getRouteCollection(); } + public function testIndirectEnvPlaceholders() + { + $routes = new RouteCollection(); + + $routes->add('foo', new Route('/%foo%')); + + $router = new Router($container = $this->getServiceContainer($routes), 'foo'); + $container->setParameter('foo', 'foo-%bar%'); + $container->setParameter('bar', '%env(string:FOO)%'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Using "%env(string:FOO)%" is not allowed in routing configuration.'); + + $router->getRouteCollection(); + } + public function testHostPlaceholders() { $routes = new RouteCollection(); From fc0e4baf2fd34079e84d287694727b2f0095d018 Mon Sep 17 00:00:00 2001 From: David Legatt Date: Wed, 31 Jul 2019 11:11:19 -0500 Subject: [PATCH 14/15] [Messenger] Removed named parameters and replaced with `?` placeholders for sqlsrv compatibility --- .../Transport/Doctrine/Connection.php | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index d86707b81b7f..41cf70bbf061 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -111,19 +111,19 @@ public function send(string $body, array $headers, int $delay = 0): string $queryBuilder = $this->driverConnection->createQueryBuilder() ->insert($this->configuration['table_name']) ->values([ - 'body' => ':body', - 'headers' => ':headers', - 'queue_name' => ':queue_name', - 'created_at' => ':created_at', - 'available_at' => ':available_at', + 'body' => '?', + 'headers' => '?', + 'queue_name' => '?', + 'created_at' => '?', + 'available_at' => '?', ]); $this->executeQuery($queryBuilder->getSQL(), [ - ':body' => $body, - ':headers' => json_encode($headers), - ':queue_name' => $this->configuration['queue_name'], - ':created_at' => self::formatDateTime($now), - ':available_at' => self::formatDateTime($availableAt), + $body, + json_encode($headers), + $this->configuration['queue_name'], + self::formatDateTime($now), + self::formatDateTime($availableAt), ]); return $this->driverConnection->lastInsertId(); @@ -156,12 +156,12 @@ public function get(): ?array $queryBuilder = $this->driverConnection->createQueryBuilder() ->update($this->configuration['table_name']) - ->set('delivered_at', ':delivered_at') - ->where('id = :id'); + ->set('delivered_at', '?') + ->where('id = ?'); $now = new \DateTime(); $this->executeQuery($queryBuilder->getSQL(), [ - ':id' => $doctrineEnvelope['id'], - ':delivered_at' => self::formatDateTime($now), + self::formatDateTime($now), + $doctrineEnvelope['id'], ]); $this->driverConnection->commit(); @@ -249,10 +249,10 @@ public function find($id): ?array } $queryBuilder = $this->createQueryBuilder() - ->where('m.id = :id'); + ->where('m.id = ?'); $data = $this->executeQuery($queryBuilder->getSQL(), [ - 'id' => $id, + $id, ])->fetch(); return false === $data ? null : $this->decodeEnvelopeHeaders($data); @@ -264,13 +264,13 @@ private function createAvailableMessagesQueryBuilder(): QueryBuilder $redeliverLimit = (clone $now)->modify(sprintf('-%d seconds', $this->configuration['redeliver_timeout'])); return $this->createQueryBuilder() - ->where('m.delivered_at is null OR m.delivered_at < :redeliver_limit') - ->andWhere('m.available_at <= :now') - ->andWhere('m.queue_name = :queue_name') + ->where('m.delivered_at is null OR m.delivered_at < ?') + ->andWhere('m.available_at <= ?') + ->andWhere('m.queue_name = ?') ->setParameters([ - ':now' => self::formatDateTime($now), - ':queue_name' => $this->configuration['queue_name'], - ':redeliver_limit' => self::formatDateTime($redeliverLimit), + self::formatDateTime($redeliverLimit), + self::formatDateTime($now), + $this->configuration['queue_name'], ]); } From 3e523ddd512986ab324334ac2e7a748b966e364a Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 5 Aug 2019 08:30:47 +0200 Subject: [PATCH 15/15] fix case --- src/Symfony/Component/Ldap/Security/LdapUserProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php index f371ad07fd6f..95baed2d65df 100644 --- a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php +++ b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php @@ -21,7 +21,7 @@ use Symfony\Component\Security\Core\User\UserProviderInterface; /** - * LdapUserProvider is a simple user provider on top of ldap. + * LdapUserProvider is a simple user provider on top of LDAP. * * @author Grégoire Pineau * @author Charles Sarrazin