diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 90c620bd6016..c3b9ad868861 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -149,6 +149,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 79c2b3250829..284e942c9aec 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -377,6 +377,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/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index 2fa3eedee829..9c106e3c9f07 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -6,6 +6,12 @@ CHANGELOG * removed `weak_vendor` mode, use `max[self]=0` instead +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 ----- diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php index d68aa2888218..52d28cfbf8c5 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); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 9d56e1dee903..111ba6babad1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1233,7 +1233,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/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 11f261e5d4fc..46909703835c 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/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index f1cca310d2e3..cd614e8ada30 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -160,7 +160,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])); } @@ -173,7 +173,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 d31f69cb58f4..fe2f66fe9cf8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php @@ -16,6 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Routing\Router; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -278,13 +279,13 @@ public function testPatternPlaceholdersWithSfContainer() $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() ); } @@ -313,6 +314,22 @@ public function testEnvPlaceholdersWithSfContainer() $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(); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 80b2f10a53ef..66a81f4ab3b8 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -170,7 +170,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 2c3e39c7039c..143506892fe2 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": "<4.4", "symfony/framework-bundle": "<4.4", - "symfony/console": "<4.4" + "symfony/console": "<4.4", + "symfony/ldap": "<4.4" }, "autoload": { "psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" }, diff --git a/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php b/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php index 2d345aead8d0..d0b8086484df 100644 --- a/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php +++ b/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php @@ -364,3 +364,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 + { + } +} 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/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index 4e69e5e535c4..8089802d1233 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -69,9 +69,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 = []) @@ -84,10 +81,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 21f90a7fd59e..6cc508b38b7b 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -848,8 +848,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'); 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/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 56ffe33c1a70..77f1181c94f5 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -795,7 +795,11 @@ public function getClientIps() * being the original client, and each successive proxy that passed the request * adding the IP address where it received the request from. * - * @return string|null The client IP address + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return string The client IP address * * @see getClientIps() * @see http://en.wikipedia.org/wiki/X-Forwarded-For 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 a295dfb8a1e7..6d1b65044c8d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -317,15 +317,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)); } } diff --git a/src/Symfony/Component/Intl/Data/Generator/LanguageDataGenerator.php b/src/Symfony/Component/Intl/Data/Generator/LanguageDataGenerator.php index e7df4fd2db12..0f0f44538d3c 100644 --- a/src/Symfony/Component/Intl/Data/Generator/LanguageDataGenerator.php +++ b/src/Symfony/Component/Intl/Data/Generator/LanguageDataGenerator.php @@ -205,6 +205,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 d0c0942ac62e..e820b722e7fe 100644 --- a/src/Symfony/Component/Intl/Resources/data/languages/meta.json +++ b/src/Symfony/Component/Intl/Resources/data/languages/meta.json @@ -622,14 +622,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", @@ -637,7 +634,6 @@ "az": "aze", "ba": "bak", "bm": "bam", - "eu": "eus", "be": "bel", "bn": "ben", "bi": "bis", @@ -645,12 +641,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", @@ -660,13 +654,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", @@ -675,8 +668,6 @@ "fr": "fra", "fy": "fry", "ff": "ful", - "om": "orm", - "ka": "kat", "gd": "gla", "ga": "gle", "gl": "glg", @@ -691,31 +682,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", @@ -725,40 +719,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", @@ -769,7 +766,9 @@ "so": "som", "st": "sot", "es": "spa", + "sq": "sqi", "sc": "srd", + "sr": "srp", "ss": "ssw", "su": "sun", "sw": "swa", @@ -799,6 +798,7 @@ "yi": "yid", "yo": "yor", "za": "zha", + "zh": "zho", "zu": "zul" } } 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..95baed2d65df --- /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 fb30acec02dd..ba70fb5bbc85 100644 --- a/src/Symfony/Component/Ldap/composer.json +++ b/src/Symfony/Component/Ldap/composer.json @@ -20,6 +20,9 @@ "symfony/options-resolver": "^4.4|^5.0", "ext-ldap": "*" }, + "require-dev": { + "symfony/security-core": "^4.4" + }, "conflict": { "symfony/options-resolver": "<4.4" }, diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index 2b819503f7bc..f0d37c4e805d 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -251,14 +251,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')) { diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index f7bb2e9afe42..67817a3d86ab 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -109,19 +109,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(); @@ -154,12 +154,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(); @@ -247,10 +247,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); @@ -262,13 +262,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'], ]); } diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index 7022554e1cb6..dace1cfc35eb 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -421,12 +421,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); @@ -440,7 +440,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'); 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(); diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 13af4efc2750..51cb39d89e90 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -32,6 +32,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 6d56c0a255b7..90f74584b67f 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 4d45bf9585bc..1eea0f93041a 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(string $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} */ @@ -121,35 +54,8 @@ public function supportsClass(string $class) */ protected function loadUser(string $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 d2eb938cfa08..e2dff4f1f014 100644 --- a/src/Symfony/Component/Security/Core/composer.json +++ b/src/Symfony/Component/Security/Core/composer.json @@ -31,7 +31,8 @@ }, "conflict": { "symfony/event-dispatcher": "<4.4", - "symfony/security-guard": "<4.4" + "symfony/security-guard": "<4.4", + "symfony/ldap": "<4.4" }, "suggest": { "psr/container-implementation": "To instantiate the Security class", 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; }