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;
}