Merge branch '4.4'
* 4.4: fix case [Messenger] Removed named parameters and replaced with `?` placeholders for sqlsrv compatibility [FrameworkBundle] Detect indirect env vars in routing [Form] type cannot be a FormTypeInterface anymore [HttpClient] use "idle" instead of "inactivity" when telling about the timeout option Create mailBody with only attachments part present Remove calls to deprecated function assertAttributeX [PhpUnitBridge] make the bridge act as a polyfill for newest PHPUnit features [Intl] Order alpha2 to alpha3 mapping [Routing] added a warning about the getRouteCollection() method Allow sutFqcnResolver to return array [Messenger] Fix incompatibility with FrameworkBundle <4.3.1 Created alias to FlattenException to avoid BC break [Ldap] Add security LdapUser and provider [HttpFoundation] Revert getClientIp @return docblock
This commit is contained in:
commit
2d594d513b
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
-----
|
||||
|
||||
|
@ -76,7 +76,7 @@ class CoverageListenerTrait
|
||||
$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);
|
||||
|
@ -1233,7 +1233,7 @@ class Configuration implements ConfigurationInterface
|
||||
->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.')
|
||||
|
@ -82,7 +82,7 @@
|
||||
</service>
|
||||
|
||||
<service id="console.command.messenger_consume_messages" class="Symfony\Component\Messenger\Command\ConsumeMessagesCommand">
|
||||
<argument type="service" id="messenger.routable_message_bus" />
|
||||
<argument /> <!-- Routable message bus -->
|
||||
<argument type="service" id="messenger.receiver_locator" />
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
<argument type="collection" /> <!-- Receiver names -->
|
||||
|
@ -160,7 +160,7 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI
|
||||
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 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI
|
||||
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)));
|
||||
|
@ -16,6 +16,7 @@ use Psr\Container\ContainerInterface;
|
||||
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 @@ class RouterTest extends TestCase
|
||||
$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 @@ class RouterTest extends TestCase
|
||||
$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();
|
||||
|
@ -170,7 +170,7 @@
|
||||
<deprecated>The "%service_id%" service is deprecated since Symfony 4.1.</deprecated>
|
||||
</service>
|
||||
|
||||
<service id="security.user.provider.ldap" class="Symfony\Component\Security\Core\User\LdapUserProvider" abstract="true">
|
||||
<service id="security.user.provider.ldap" class="Symfony\Component\Ldap\Security\LdapUserProvider" abstract="true">
|
||||
<argument /> <!-- security.ldap.ldap -->
|
||||
<argument /> <!-- base dn -->
|
||||
<argument /> <!-- search dn -->
|
||||
|
@ -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\\": "" },
|
||||
|
@ -364,3 +364,18 @@ class FlattenException
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -61,14 +61,14 @@ class GenericEventTest extends TestCase
|
||||
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 @@ class GenericEventTest extends TestCase
|
||||
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()
|
||||
|
@ -69,9 +69,6 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
|
||||
*
|
||||
* 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 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
|
||||
*
|
||||
* 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 = [])
|
||||
|
@ -848,8 +848,8 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
|
||||
|
||||
$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
|
||||
|
@ -66,8 +66,8 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
|
||||
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
|
||||
|
@ -120,13 +120,6 @@ class FormBuilderTest extends TestCase
|
||||
$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');
|
||||
|
@ -30,7 +30,7 @@ class ErrorChunk implements ChunkInterface
|
||||
{
|
||||
$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.';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -795,7 +795,11 @@ class Request
|
||||
* 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
|
||||
|
@ -317,15 +317,15 @@ class PdoSessionHandlerTest extends TestCase
|
||||
public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null)
|
||||
{
|
||||
$storage = new PdoSessionHandler($url);
|
||||
$reflection = new \ReflectionClass(PdoSessionHandler::class);
|
||||
|
||||
$this->assertAttributeEquals($expectedDsn, 'dsn', $storage);
|
||||
|
||||
if (null !== $expectedUser) {
|
||||
$this->assertAttributeEquals($expectedUser, 'username', $storage);
|
||||
}
|
||||
|
||||
if (null !== $expectedPassword) {
|
||||
$this->assertAttributeEquals($expectedPassword, 'password', $storage);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,6 +205,8 @@ class LanguageDataGenerator extends AbstractDataGenerator
|
||||
}
|
||||
}
|
||||
|
||||
asort($alpha2ToAlpha3);
|
||||
|
||||
return $alpha2ToAlpha3;
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
91
src/Symfony/Component/Ldap/Security/LdapUser.php
Normal file
91
src/Symfony/Component/Ldap/Security/LdapUser.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Ldap\Security;
|
||||
|
||||
use Symfony\Component\Ldap\Entry;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
155
src/Symfony/Component/Ldap/Security/LdapUserProvider.php
Normal file
155
src/Symfony/Component/Ldap/Security/LdapUserProvider.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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 <lyrixx@lyrixx.info>
|
||||
* @author Charles Sarrazin <charles@sarraz.in>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
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];
|
||||
}
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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'));
|
||||
}
|
||||
}
|
@ -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"
|
||||
},
|
||||
|
@ -251,14 +251,19 @@ class MessengerPass implements CompilerPassInterface
|
||||
$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')) {
|
||||
|
@ -109,19 +109,19 @@ class Connection
|
||||
$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 @@ class Connection
|
||||
|
||||
$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 @@ class Connection
|
||||
}
|
||||
|
||||
$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 @@ class Connection
|
||||
$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'],
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -421,12 +421,12 @@ class Email extends Message
|
||||
*/
|
||||
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 @@ class Email extends Message
|
||||
}
|
||||
|
||||
if ($attachmentParts) {
|
||||
$part = new MixedPart($part, ...$attachmentParts);
|
||||
if ($part) {
|
||||
$part = new MixedPart($part, ...$attachmentParts);
|
||||
} else {
|
||||
$part = new MixedPart(...$attachmentParts);
|
||||
}
|
||||
}
|
||||
|
||||
return $part;
|
||||
|
@ -284,6 +284,10 @@ class EmailTest extends TestCase
|
||||
$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');
|
||||
|
@ -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();
|
||||
|
@ -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`
|
||||
|
||||
|
@ -20,6 +20,7 @@ use Symfony\Component\Ldap\LdapInterface;
|
||||
use Symfony\Component\Security\Core\User\LdapUserProvider;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
* @requires extension ldap
|
||||
*/
|
||||
class LdapUserProviderTest extends TestCase
|
||||
|
@ -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 <lyrixx@lyrixx.info>
|
||||
* @author Charles Sarrazin <charles@sarraz.in>
|
||||
*
|
||||
* @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 @@ class LdapUserProvider implements UserProviderInterface
|
||||
*/
|
||||
protected function loadUser(string $username, Entry $entry)
|
||||
{
|
||||
$password = null;
|
||||
$extraFields = [];
|
||||
$ldapUser = parent::loadUser($username, $entry);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
return $values[0];
|
||||
return new User($ldapUser->getUsername(), $ldapUser->getPassword(), $ldapUser->getRoles(), true, true, true, true, $ldapUser->getExtraFields());
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -27,7 +27,7 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
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 @@ interface ChunkInterface
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
@ -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 @@ interface HttpClientInterface
|
||||
* 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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user