feature #14602 [2.8] [Ldap] Added support for LDAP (New Component + integration in the Security Component). (csarrazi, lyrixx)
This PR was merged into the 2.8 branch. Discussion ---------- [2.8] [Ldap] Added support for LDAP (New Component + integration in the Security Component). | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | not yet | Fixed tickets | - | License | MIT | Doc PR | not yet Current state: - [x] Implement logic - [x] Post-review tuning and stabilization - [x] Fix tests This PR is a follow-up to #5189, which was in a stand-still for a few years now. It tries to fix the remaining issues which were mentioned in the discussion. There are still a few issues with the PR, as it is. For example, it introduces two new firewall factories, whereas the base factories (`form_login` and `http_basic`) could simply introduce new configuration options. Also, for a user to use an LDAP server as an authentication provider, he first needs to define a service which should be an instance of `Symfony\Component\Security\Ldap\Ldap`. For example: ```yml services: my_ldap: class: Symfony\Component\Security\Ldap\Ldap arguments: [ "ldap.mydomain.tld" ] ``` Then, in `security.yml`, this service can be used in both the user provider and the firewalls: ```yml security: encoders: Symfony\Component\Security\Core\User\User: plaintext role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] providers: ldap_users: ldap: service: my_ldap base_dn: dc=MyDomain,dc=tld search_dn: CN=My User,OU=Users,DC=MyDomain,DC=tld search_password: p455w0rd filter: (sAMAccountName={username}) default_roles: ROLE_USER firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false demo_login: pattern: ^/login$ security: false api: provider: ldap_users stateless: true pattern: ^/api http_basic_ldap: service: my_ldap dn_string: "{username}@MYDOMAIN" demo_secured_area: provider: ldap_users pattern: ^/ logout: path: logout target: login form_login_ldap: service: my_ldap dn_string: CN={username},OU=Users,DC=MyDomain,DC=tld check_path: login_check login_path: login ``` Commits -------60b9f2e
Implemented LDAP authentication and LDAP user provider1c964b9
Introducing the LDAP component
This commit is contained in:
commit
7d7e07fefa
@ -35,6 +35,7 @@ before_install:
|
|||||||
- if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (pecl install -f memcached-2.1.0 && echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) || echo "Let's continue without memcache extension"; fi;
|
- if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (pecl install -f memcached-2.1.0 && echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) || echo "Let's continue without memcache extension"; fi;
|
||||||
- if [[ "$TRAVIS_PHP_VERSION" = 5.* ]] && [ "$deps" = "no" ]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo "extension = $(pwd)/modules/symfony_debug.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini); fi;
|
- if [[ "$TRAVIS_PHP_VERSION" = 5.* ]] && [ "$deps" = "no" ]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo "extension = $(pwd)/modules/symfony_debug.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini); fi;
|
||||||
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; fi;
|
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; fi;
|
||||||
|
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo "extension = ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;
|
||||||
- ./phpunit install
|
- ./phpunit install
|
||||||
- export PHPUNIT="$(readlink -f ./phpunit)"
|
- export PHPUNIT="$(readlink -f ./phpunit)"
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ install:
|
|||||||
- IF %PHP_EXT%==1 echo extension=php_mbstring.dll >> php.ini
|
- IF %PHP_EXT%==1 echo extension=php_mbstring.dll >> php.ini
|
||||||
- IF %PHP_EXT%==1 echo extension=php_fileinfo.dll >> php.ini
|
- IF %PHP_EXT%==1 echo extension=php_fileinfo.dll >> php.ini
|
||||||
- IF %PHP_EXT%==1 echo extension=php_pdo_sqlite.dll >> php.ini
|
- IF %PHP_EXT%==1 echo extension=php_pdo_sqlite.dll >> php.ini
|
||||||
|
- IF %PHP_EXT%==1 echo extension=php_ldap.dll >> php.ini
|
||||||
- cd c:\projects\symfony
|
- cd c:\projects\symfony
|
||||||
- php phpunit install
|
- php phpunit install
|
||||||
- IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev)
|
- IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev)
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
<?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\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||||
|
|
||||||
|
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||||
|
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FormLoginLdapFactory creates services for form login ldap authentication.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
* @author Charles Sarrazin <charles@sarraz.in>
|
||||||
|
*/
|
||||||
|
class FormLoginLdapFactory extends FormLoginFactory
|
||||||
|
{
|
||||||
|
protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
|
||||||
|
{
|
||||||
|
$provider = 'security.authentication.provider.ldap_bind.'.$id;
|
||||||
|
$container
|
||||||
|
->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.ldap_bind'))
|
||||||
|
->replaceArgument(0, new Reference($userProviderId))
|
||||||
|
->replaceArgument(2, $id)
|
||||||
|
->replaceArgument(3, new Reference($config['service']))
|
||||||
|
->replaceArgument(4, $config['dn_string'])
|
||||||
|
;
|
||||||
|
|
||||||
|
return $provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addConfiguration(NodeDefinition $node)
|
||||||
|
{
|
||||||
|
parent::addConfiguration($node);
|
||||||
|
|
||||||
|
$node
|
||||||
|
->children()
|
||||||
|
->scalarNode('service')->end()
|
||||||
|
->scalarNode('dn_string')->defaultValue('{username}')->end()
|
||||||
|
->end()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey()
|
||||||
|
{
|
||||||
|
return 'form-login-ldap';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
<?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\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||||
|
|
||||||
|
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||||
|
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpBasicFactory creates services for HTTP basic authentication.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
* @author Charles Sarrazin <charles@sarraz.in>
|
||||||
|
*/
|
||||||
|
class HttpBasicLdapFactory extends HttpBasicFactory
|
||||||
|
{
|
||||||
|
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
|
||||||
|
{
|
||||||
|
$provider = 'security.authentication.provider.ldap_bind.'.$id;
|
||||||
|
$container
|
||||||
|
->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.ldap_bind'))
|
||||||
|
->replaceArgument(0, new Reference($userProvider))
|
||||||
|
->replaceArgument(2, $id)
|
||||||
|
->replaceArgument(3, new Reference($config['service']))
|
||||||
|
->replaceArgument(4, $config['dn_string'])
|
||||||
|
;
|
||||||
|
|
||||||
|
// entry point
|
||||||
|
$entryPointId = $this->createEntryPoint($container, $id, $config, $defaultEntryPoint);
|
||||||
|
|
||||||
|
// listener
|
||||||
|
$listenerId = 'security.authentication.listener.basic.'.$id;
|
||||||
|
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.basic'));
|
||||||
|
$listener->replaceArgument(2, $id);
|
||||||
|
$listener->replaceArgument(3, new Reference($entryPointId));
|
||||||
|
|
||||||
|
return array($provider, $listenerId, $entryPointId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addConfiguration(NodeDefinition $node)
|
||||||
|
{
|
||||||
|
parent::addConfiguration($node);
|
||||||
|
|
||||||
|
$node
|
||||||
|
->children()
|
||||||
|
->scalarNode('service')->end()
|
||||||
|
->scalarNode('dn_string')->defaultValue('{username}')->end()
|
||||||
|
->end()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey()
|
||||||
|
{
|
||||||
|
return 'http-basic-ldap';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
<?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\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
|
||||||
|
|
||||||
|
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||||
|
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LdapFactory creates services for Ldap user provider.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
* @author Charles Sarrazin <charles@sarraz.in>
|
||||||
|
*/
|
||||||
|
class LdapFactory implements UserProviderFactoryInterface
|
||||||
|
{
|
||||||
|
public function create(ContainerBuilder $container, $id, $config)
|
||||||
|
{
|
||||||
|
$container
|
||||||
|
->setDefinition($id, new DefinitionDecorator('security.user.provider.ldap'))
|
||||||
|
->replaceArgument(0, new Reference($config['service']))
|
||||||
|
->replaceArgument(1, $config['base_dn'])
|
||||||
|
->replaceArgument(2, $config['search_dn'])
|
||||||
|
->replaceArgument(3, $config['search_password'])
|
||||||
|
->replaceArgument(4, $config['default_roles'])
|
||||||
|
->replaceArgument(5, $config['uid_key'])
|
||||||
|
->replaceArgument(6, $config['filter'])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey()
|
||||||
|
{
|
||||||
|
return 'ldap';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addConfiguration(NodeDefinition $node)
|
||||||
|
{
|
||||||
|
$node
|
||||||
|
->children()
|
||||||
|
->scalarNode('service')->isRequired()->cannotBeEmpty()->end()
|
||||||
|
->scalarNode('base_dn')->isRequired()->cannotBeEmpty()->end()
|
||||||
|
->scalarNode('search_dn')->end()
|
||||||
|
->scalarNode('search_password')->end()
|
||||||
|
->arrayNode('default_roles')
|
||||||
|
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
|
||||||
|
->requiresAtLeastOneElement()
|
||||||
|
->prototype('scalar')->end()
|
||||||
|
->end()
|
||||||
|
->scalarNode('uid_key')->defaultValue('sAMAccountName')->end()
|
||||||
|
->scalarNode('filter')->defaultValue('({uid_key}={username})')->end()
|
||||||
|
->end()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
@ -158,10 +158,21 @@
|
|||||||
<argument type="service" id="security.token_storage" on-invalid="null" />
|
<argument type="service" id="security.token_storage" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
|
||||||
<!-- Provisioning -->
|
<!-- Provisioning -->
|
||||||
<service id="security.user.provider.in_memory" class="%security.user.provider.in_memory.class%" abstract="true" public="false" />
|
<service id="security.user.provider.in_memory" class="%security.user.provider.in_memory.class%" abstract="true" public="false" />
|
||||||
<service id="security.user.provider.in_memory.user" class="%security.user.provider.in_memory.user.class%" abstract="true" public="false" />
|
<service id="security.user.provider.in_memory.user" class="%security.user.provider.in_memory.user.class%" abstract="true" public="false" />
|
||||||
|
|
||||||
|
<service id="security.user.provider.ldap" class="Symfony\Component\Security\Core\User\LdapUserProvider" abstract="true" public="false">
|
||||||
|
<argument /> <!-- security.ldap.ldap -->
|
||||||
|
<argument /> <!-- base dn -->
|
||||||
|
<argument /> <!-- search dn -->
|
||||||
|
<argument /> <!-- search password -->
|
||||||
|
<argument /> <!-- default_roles -->
|
||||||
|
<argument /> <!-- uid key -->
|
||||||
|
<argument /> <!-- filter -->
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="security.user.provider.chain" class="%security.user.provider.chain.class%" abstract="true" public="false" />
|
<service id="security.user.provider.chain" class="%security.user.provider.chain.class%" abstract="true" public="false" />
|
||||||
|
|
||||||
<service id="security.http_utils" class="%security.http_utils.class%" public="false">
|
<service id="security.http_utils" class="%security.http_utils.class%" public="false">
|
||||||
@ -169,6 +180,7 @@
|
|||||||
<argument type="service" id="router" on-invalid="null" />
|
<argument type="service" id="router" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
|
||||||
<!-- Validator -->
|
<!-- Validator -->
|
||||||
<service id="security.validator.user_password" class="%security.validator.user_password.class%">
|
<service id="security.validator.user_password" class="%security.validator.user_password.class%">
|
||||||
<tag name="validator.constraint_validator" alias="security.validator.user_password" />
|
<tag name="validator.constraint_validator" alias="security.validator.user_password" />
|
||||||
|
@ -223,6 +223,15 @@
|
|||||||
<argument>%security.authentication.hide_user_not_found%</argument>
|
<argument>%security.authentication.hide_user_not_found%</argument>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="security.authentication.provider.ldap_bind" class="Symfony\Component\Security\Core\Authentication\Provider\LdapBindAuthenticationProvider" public="false" abstract="true">
|
||||||
|
<argument /> <!-- User Provider -->
|
||||||
|
<argument type="service" id="security.user_checker" />
|
||||||
|
<argument /> <!-- Provider-shared Key -->
|
||||||
|
<argument /> <!-- LDAP -->
|
||||||
|
<argument /> <!-- Base DN -->
|
||||||
|
<argument>%security.authentication.hide_user_not_found%</argument>
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="security.authentication.provider.simple" class="%security.authentication.provider.simple.class%" abstract="true" public="false">
|
<service id="security.authentication.provider.simple" class="%security.authentication.provider.simple.class%" abstract="true" public="false">
|
||||||
<argument /> <!-- Simple Authenticator -->
|
<argument /> <!-- Simple Authenticator -->
|
||||||
<argument /> <!-- User Provider -->
|
<argument /> <!-- User Provider -->
|
||||||
|
@ -15,7 +15,9 @@ use Symfony\Component\HttpKernel\Bundle\Bundle;
|
|||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
|
||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
|
||||||
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
|
||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory;
|
||||||
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicLdapFactory;
|
||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpDigestFactory;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpDigestFactory;
|
||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
|
||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory;
|
||||||
@ -24,6 +26,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimplePre
|
|||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory;
|
||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
|
||||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
|
||||||
|
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bundle.
|
* Bundle.
|
||||||
@ -38,7 +41,9 @@ class SecurityBundle extends Bundle
|
|||||||
|
|
||||||
$extension = $container->getExtension('security');
|
$extension = $container->getExtension('security');
|
||||||
$extension->addSecurityListenerFactory(new FormLoginFactory());
|
$extension->addSecurityListenerFactory(new FormLoginFactory());
|
||||||
|
$extension->addSecurityListenerFactory(new FormLoginLdapFactory());
|
||||||
$extension->addSecurityListenerFactory(new HttpBasicFactory());
|
$extension->addSecurityListenerFactory(new HttpBasicFactory());
|
||||||
|
$extension->addSecurityListenerFactory(new HttpBasicLdapFactory());
|
||||||
$extension->addSecurityListenerFactory(new HttpDigestFactory());
|
$extension->addSecurityListenerFactory(new HttpDigestFactory());
|
||||||
$extension->addSecurityListenerFactory(new RememberMeFactory());
|
$extension->addSecurityListenerFactory(new RememberMeFactory());
|
||||||
$extension->addSecurityListenerFactory(new X509Factory());
|
$extension->addSecurityListenerFactory(new X509Factory());
|
||||||
@ -48,6 +53,7 @@ class SecurityBundle extends Bundle
|
|||||||
$extension->addSecurityListenerFactory(new GuardAuthenticationFactory());
|
$extension->addSecurityListenerFactory(new GuardAuthenticationFactory());
|
||||||
|
|
||||||
$extension->addUserProviderFactory(new InMemoryFactory());
|
$extension->addUserProviderFactory(new InMemoryFactory());
|
||||||
|
$extension->addUserProviderFactory(new LdapFactory());
|
||||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
src/Symfony/Component/Ldap/.gitignore
vendored
Normal file
3
src/Symfony/Component/Ldap/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
vendor/
|
||||||
|
composer.lock
|
||||||
|
phpunit.xml
|
21
src/Symfony/Component/Ldap/Exception/ConnectionException.php
Normal file
21
src/Symfony/Component/Ldap/Exception/ConnectionException.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?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\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConnectionException is throw if binding to ldap can not be established.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
*/
|
||||||
|
class ConnectionException extends \RuntimeException
|
||||||
|
{
|
||||||
|
}
|
21
src/Symfony/Component/Ldap/Exception/LdapException.php
Normal file
21
src/Symfony/Component/Ldap/Exception/LdapException.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?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\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LdapException is throw if php ldap module is not loaded.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
*/
|
||||||
|
class LdapException extends \RuntimeException
|
||||||
|
{
|
||||||
|
}
|
19
src/Symfony/Component/Ldap/LICENSE
Normal file
19
src/Symfony/Component/Ldap/LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2004-2015 Fabien Potencier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
220
src/Symfony/Component/Ldap/LdapClient.php
Normal file
220
src/Symfony/Component/Ldap/LdapClient.php
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Ldap;
|
||||||
|
|
||||||
|
use Symfony\Component\Ldap\Exception\ConnectionException;
|
||||||
|
use Symfony\Component\Ldap\Exception\LdapException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
* @author Francis Besset <francis.besset@gmail.com>
|
||||||
|
* @author Charles Sarrazin <charles@sarraz.in>
|
||||||
|
*/
|
||||||
|
class LdapClient implements LdapClientInterface
|
||||||
|
{
|
||||||
|
private $host;
|
||||||
|
private $port;
|
||||||
|
private $version;
|
||||||
|
private $useSsl;
|
||||||
|
private $useStartTls;
|
||||||
|
private $optReferrals;
|
||||||
|
private $connection;
|
||||||
|
private $charmaps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $host
|
||||||
|
* @param int $port
|
||||||
|
* @param int $version
|
||||||
|
* @param bool $useSsl
|
||||||
|
* @param bool $useStartTls
|
||||||
|
* @param bool $optReferrals
|
||||||
|
*/
|
||||||
|
public function __construct($host = null, $port = 389, $version = 3, $useSsl = false, $useStartTls = false, $optReferrals = false)
|
||||||
|
{
|
||||||
|
if (!extension_loaded('ldap')) {
|
||||||
|
throw new LdapException('The ldap module is needed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->host = $host;
|
||||||
|
$this->port = $port;
|
||||||
|
$this->version = $version;
|
||||||
|
$this->useSsl = (bool) $useSsl;
|
||||||
|
$this->useStartTls = (bool) $useStartTls;
|
||||||
|
$this->optReferrals = (bool) $optReferrals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function bind($dn = null, $password = null)
|
||||||
|
{
|
||||||
|
if (!$this->connection) {
|
||||||
|
$this->connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === @ldap_bind($this->connection, $dn, $password)) {
|
||||||
|
throw new ConnectionException(ldap_error($this->connection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function find($dn, $query, $filter = '*')
|
||||||
|
{
|
||||||
|
if (!is_array($filter)) {
|
||||||
|
$filter = array($filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$search = ldap_search($this->connection, $dn, $query, $filter);
|
||||||
|
$infos = ldap_get_entries($this->connection, $search);
|
||||||
|
|
||||||
|
if (0 === $infos['count']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function escape($subject, $ignore = '', $flags = 0)
|
||||||
|
{
|
||||||
|
if (function_exists('ldap_escape')) {
|
||||||
|
return ldap_escape($subject, $ignore, $flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->doEscape($subject, $ignore, $flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function connect()
|
||||||
|
{
|
||||||
|
if (!$this->connection) {
|
||||||
|
$host = $this->host;
|
||||||
|
|
||||||
|
if ($this->useSsl) {
|
||||||
|
$host = 'ldaps://'.$host;
|
||||||
|
}
|
||||||
|
|
||||||
|
ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $this->version);
|
||||||
|
ldap_set_option($this->connection, LDAP_OPT_REFERRALS, $this->optReferrals);
|
||||||
|
|
||||||
|
$this->connection = ldap_connect($host, $this->port);
|
||||||
|
|
||||||
|
if ($this->useStartTls) {
|
||||||
|
ldap_start_tls($this->connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function disconnect()
|
||||||
|
{
|
||||||
|
if ($this->connection && is_resource($this->connection)) {
|
||||||
|
ldap_unbind($this->connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub implementation of the {@link ldap_escape()} function of the ldap
|
||||||
|
* extension.
|
||||||
|
*
|
||||||
|
* Escape strings for safe use in LDAP filters and DNs.
|
||||||
|
*
|
||||||
|
* @author Chris Wright <ldapi@daverandom.com>
|
||||||
|
*
|
||||||
|
* @param string $subject
|
||||||
|
* @param string $ignore
|
||||||
|
* @param int $flags
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @see http://stackoverflow.com/a/8561604
|
||||||
|
*/
|
||||||
|
private function doEscape($subject, $ignore = '', $flags = 0)
|
||||||
|
{
|
||||||
|
$charMaps = $this->getCharmaps();
|
||||||
|
|
||||||
|
// Create the base char map to escape
|
||||||
|
$flags = (int) $flags;
|
||||||
|
$charMap = array();
|
||||||
|
|
||||||
|
if ($flags & self::LDAP_ESCAPE_FILTER) {
|
||||||
|
$charMap += $charMaps[self::LDAP_ESCAPE_FILTER];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($flags & self::LDAP_ESCAPE_DN) {
|
||||||
|
$charMap += $charMaps[self::LDAP_ESCAPE_DN];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$charMap) {
|
||||||
|
$charMap = $charMaps[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any chars to ignore from the list
|
||||||
|
$ignore = (string) $ignore;
|
||||||
|
|
||||||
|
for ($i = 0, $l = strlen($ignore); $i < $l; ++$i) {
|
||||||
|
unset($charMap[$ignore[$i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the main replacement
|
||||||
|
$result = strtr($subject, $charMap);
|
||||||
|
|
||||||
|
// Encode leading/trailing spaces if LDAP_ESCAPE_DN is passed
|
||||||
|
if ($flags & self::LDAP_ESCAPE_DN) {
|
||||||
|
if ($result[0] === ' ') {
|
||||||
|
$result = '\\20'.substr($result, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result[strlen($result) - 1] === ' ') {
|
||||||
|
$result = substr($result, 0, -1).'\\20';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCharmaps()
|
||||||
|
{
|
||||||
|
if (null !== $this->charmaps) {
|
||||||
|
return $this->charmaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
$charMaps = array(
|
||||||
|
self::LDAP_ESCAPE_FILTER => array('\\', '*', '(', ')', "\x00"),
|
||||||
|
self::LDAP_ESCAPE_DN => array('\\', ',', '=', '+', '<', '>', ';', '"', '#'),
|
||||||
|
);
|
||||||
|
|
||||||
|
$charMaps[0] = array();
|
||||||
|
|
||||||
|
for ($i = 0; $i < 256; ++$i) {
|
||||||
|
$charMaps[0][chr($i)] = sprintf('\\%02x', $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0, $l = count($charMaps[self::LDAP_ESCAPE_FILTER]); $i < $l; ++$i) {
|
||||||
|
$chr = $charMaps[self::LDAP_ESCAPE_FILTER][$i];
|
||||||
|
unset($charMaps[self::LDAP_ESCAPE_FILTER][$i]);
|
||||||
|
$charMaps[self::LDAP_ESCAPE_FILTER][$chr] = $charMaps[0][$chr];
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0, $l = count($charMaps[self::LDAP_ESCAPE_DN]); $i < $l; ++$i) {
|
||||||
|
$chr = $charMaps[self::LDAP_ESCAPE_DN][$i];
|
||||||
|
unset($charMaps[self::LDAP_ESCAPE_DN][$i]);
|
||||||
|
$charMaps[self::LDAP_ESCAPE_DN][$chr] = $charMaps[0][$chr];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->charmaps = $charMaps;
|
||||||
|
|
||||||
|
return $this->charmaps;
|
||||||
|
}
|
||||||
|
}
|
49
src/Symfony/Component/Ldap/LdapClientInterface.php
Normal file
49
src/Symfony/Component/Ldap/LdapClientInterface.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Ldap;
|
||||||
|
|
||||||
|
use Symfony\Component\Ldap\Exception\ConnectionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ldap interface.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
* @author Charles Sarrazin <charles@sarraz.in>
|
||||||
|
*/
|
||||||
|
interface LdapClientInterface
|
||||||
|
{
|
||||||
|
const LDAP_ESCAPE_FILTER = 0x01;
|
||||||
|
const LDAP_ESCAPE_DN = 0x02;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a connection bound to the ldap.
|
||||||
|
*
|
||||||
|
* @param string $dn A LDAP dn
|
||||||
|
* @param string $password A password
|
||||||
|
*
|
||||||
|
* @throws ConnectionException If dn / password could not be bound.
|
||||||
|
*/
|
||||||
|
public function bind($dn = null, $password = null);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find a username into ldap connection.
|
||||||
|
*
|
||||||
|
* @param string $dn
|
||||||
|
* @param string $query
|
||||||
|
* @param mixed $filter
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function find($dn, $query, $filter = '*');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape a string for use in an LDAP filter or DN.
|
||||||
|
*
|
||||||
|
* @param string $subject
|
||||||
|
* @param string $ignore
|
||||||
|
* @param int $flags
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function escape($subject, $ignore = '', $flags = 0);
|
||||||
|
}
|
23
src/Symfony/Component/Ldap/README.md
Normal file
23
src/Symfony/Component/Ldap/README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Ldap Component
|
||||||
|
=============
|
||||||
|
|
||||||
|
A Ldap client for PHP on top of PHP's ldap extension.
|
||||||
|
|
||||||
|
This component also provides a stub for the missing
|
||||||
|
`ldap_escape` function in PHP versions lower than 5.6.
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The documentation for the component can be found [online] [0].
|
||||||
|
|
||||||
|
Resources
|
||||||
|
---------
|
||||||
|
|
||||||
|
You can run the unit tests with the following command:
|
||||||
|
|
||||||
|
$ cd path/to/Symfony/Component/Ldap/
|
||||||
|
$ composer install
|
||||||
|
$ phpunit
|
||||||
|
|
||||||
|
[0]: https://symfony.com/doc/2.8/components/ldap.html
|
56
src/Symfony/Component/Ldap/Tests/LdapClientTest.php
Normal file
56
src/Symfony/Component/Ldap/Tests/LdapClientTest.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Security\Core\Tests\Authentication\Provider;
|
||||||
|
|
||||||
|
use Symfony\Component\Ldap\LdapClient;
|
||||||
|
|
||||||
|
class LdapClientTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
if (!extension_loaded('ldap')) {
|
||||||
|
$this->markTestSkipped('The ldap extension is not available');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideLdapEscapeValues
|
||||||
|
*/
|
||||||
|
public function testLdapEscape($subject, $ignore, $flags, $expected)
|
||||||
|
{
|
||||||
|
$ldap = new LdapClient();
|
||||||
|
$this->assertSame($expected, $ldap->escape($subject, $ignore, $flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides values for the ldap_escape shim. These tests come from the official
|
||||||
|
* extension.
|
||||||
|
*
|
||||||
|
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_dn.phpt
|
||||||
|
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_all.phpt
|
||||||
|
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_both.phpt
|
||||||
|
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_filter.phpt
|
||||||
|
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_ignore.phpt
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function provideLdapEscapeValues()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('foo=bar(baz)*', null, LdapClient::LDAP_ESCAPE_DN, 'foo\3dbar(baz)*'),
|
||||||
|
array('foo=bar(baz)*', null, null, '\66\6f\6f\3d\62\61\72\28\62\61\7a\29\2a'),
|
||||||
|
array('foo=bar(baz)*', null, LdapClient::LDAP_ESCAPE_DN | LdapClient::LDAP_ESCAPE_FILTER, 'foo\3dbar\28baz\29\2a'),
|
||||||
|
array('foo=bar(baz)*', null, LdapClient::LDAP_ESCAPE_FILTER, 'foo=bar\28baz\29\2a'),
|
||||||
|
array('foo=bar(baz)*', 'ao', null, '\66oo\3d\62a\72\28\62a\7a\29\2a'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
src/Symfony/Component/Ldap/composer.json
Normal file
34
src/Symfony/Component/Ldap/composer.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "symfony/ldap",
|
||||||
|
"type": "library",
|
||||||
|
"description": "An abstraction in front of PHP's LDAP functions, compatible with PHP 5.3.9 onwards.",
|
||||||
|
"keywords": ["ldap", "active directory"],
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Charles Sarrazin",
|
||||||
|
"email": "charles@sarraz.in"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.9",
|
||||||
|
"ext-ldap": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/phpunit-bridge": "~2.7|~3.0.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": { "Symfony\\Component\\Ldap\\": "" }
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.8-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/Symfony/Component/Ldap/phpunit.xml.dist
Normal file
28
src/Symfony/Component/Ldap/phpunit.xml.dist
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
||||||
|
backupGlobals="false"
|
||||||
|
colors="true"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
>
|
||||||
|
<php>
|
||||||
|
<ini name="error_reporting" value="-1" />
|
||||||
|
</php>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Symfony Ldap Component Test Suite">
|
||||||
|
<directory>./Tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory>./</directory>
|
||||||
|
<exclude>
|
||||||
|
<directory>./Tests</directory>
|
||||||
|
<directory>./vendor</directory>
|
||||||
|
</exclude>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Security\Core\Authentication\Provider;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||||
|
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
use Symfony\Component\Ldap\LdapClientInterface;
|
||||||
|
use Symfony\Component\Ldap\Exception\ConnectionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LdapBindAuthenticationProvider authenticates a user against an LDAP server.
|
||||||
|
*
|
||||||
|
* The only way to check user credentials is to try to connect the user with its
|
||||||
|
* credentials to the ldap.
|
||||||
|
*
|
||||||
|
* @author Charles Sarrazin <charles@sarraz.in>
|
||||||
|
*/
|
||||||
|
class LdapBindAuthenticationProvider extends UserAuthenticationProvider
|
||||||
|
{
|
||||||
|
private $userProvider;
|
||||||
|
private $ldap;
|
||||||
|
private $dnString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param UserProviderInterface $userProvider A UserProvider
|
||||||
|
* @param UserCheckerInterface $userChecker A UserChecker
|
||||||
|
* @param string $providerKey The provider key
|
||||||
|
* @param LdapClientInterface $ldap An Ldap client
|
||||||
|
* @param string $dnString A string used to create the bind DN
|
||||||
|
* @param bool $hideUserNotFoundExceptions Whether to hide user not found exception or not
|
||||||
|
*/
|
||||||
|
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, LdapClientInterface $ldap, $dnString = '{username}', $hideUserNotFoundExceptions = true)
|
||||||
|
{
|
||||||
|
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
|
||||||
|
|
||||||
|
$this->userProvider = $userProvider;
|
||||||
|
$this->ldap = $ldap;
|
||||||
|
$this->dnString = $dnString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function retrieveUser($username, UsernamePasswordToken $token)
|
||||||
|
{
|
||||||
|
if ('NONE_PROVIDED' === $username) {
|
||||||
|
throw new UsernameNotFoundException('Username can not be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->userProvider->loadUserByUsername($username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
|
||||||
|
{
|
||||||
|
$username = $token->getUsername();
|
||||||
|
$password = $token->getCredentials();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$username = $this->ldap->escape($username, '', LdapClientInterface::LDAP_ESCAPE_DN);
|
||||||
|
$dn = str_replace('{username}', $username, $this->dnString);
|
||||||
|
|
||||||
|
$this->ldap->bind($dn, $password);
|
||||||
|
} catch (ConnectionException $e) {
|
||||||
|
throw new BadCredentialsException('The presented password is invalid.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Security\Core\Tests\Authentication\Provider;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Provider\LdapBindAuthenticationProvider;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||||
|
use Symfony\Component\Security\Core\User\User;
|
||||||
|
use Symfony\Component\Ldap\Exception\ConnectionException;
|
||||||
|
|
||||||
|
class LdapBindAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
|
||||||
|
* @expectedExceptionMessage The presented password is invalid.
|
||||||
|
*/
|
||||||
|
public function testBindFailureShouldThrowAnException()
|
||||||
|
{
|
||||||
|
$userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface');
|
||||||
|
$ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface');
|
||||||
|
$ldap
|
||||||
|
->expects($this->once())
|
||||||
|
->method('bind')
|
||||||
|
->will($this->throwException(new ConnectionException()))
|
||||||
|
;
|
||||||
|
$userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface');
|
||||||
|
|
||||||
|
$provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap);
|
||||||
|
$reflection = new \ReflectionMethod($provider, 'checkAuthentication');
|
||||||
|
$reflection->setAccessible(true);
|
||||||
|
|
||||||
|
$reflection->invoke($provider, new User('foo', null), new UsernamePasswordToken('foo', '', 'key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRetrieveUser()
|
||||||
|
{
|
||||||
|
$userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface');
|
||||||
|
$userProvider
|
||||||
|
->expects($this->once())
|
||||||
|
->method('loadUserByUsername')
|
||||||
|
->with('foo')
|
||||||
|
;
|
||||||
|
$ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface');
|
||||||
|
|
||||||
|
$userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface');
|
||||||
|
|
||||||
|
$provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap);
|
||||||
|
$reflection = new \ReflectionMethod($provider, 'retrieveUser');
|
||||||
|
$reflection->setAccessible(true);
|
||||||
|
|
||||||
|
$reflection->invoke($provider, 'foo', new UsernamePasswordToken('foo', 'bar', 'key'));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Security\Core\Tests\User;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\User\LdapUserProvider;
|
||||||
|
use Symfony\Component\Ldap\Exception\ConnectionException;
|
||||||
|
|
||||||
|
class LdapUserProviderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException
|
||||||
|
*/
|
||||||
|
public function testLoadUserByUsernameFailsIfCantConnectToLdap()
|
||||||
|
{
|
||||||
|
$ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface');
|
||||||
|
$ldap
|
||||||
|
->expects($this->once())
|
||||||
|
->method('bind')
|
||||||
|
->will($this->throwException(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()
|
||||||
|
{
|
||||||
|
$ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface');
|
||||||
|
$ldap
|
||||||
|
->expects($this->once())
|
||||||
|
->method('escape')
|
||||||
|
->will($this->returnValue('foo'))
|
||||||
|
;
|
||||||
|
|
||||||
|
$provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com');
|
||||||
|
$provider->loadUserByUsername('foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException
|
||||||
|
*/
|
||||||
|
public function testLoadUserByUsernameFailsIfMoreThanOneLdapEntry()
|
||||||
|
{
|
||||||
|
$ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface');
|
||||||
|
$ldap
|
||||||
|
->expects($this->once())
|
||||||
|
->method('escape')
|
||||||
|
->will($this->returnValue('foo'))
|
||||||
|
;
|
||||||
|
$ldap
|
||||||
|
->expects($this->once())
|
||||||
|
->method('find')
|
||||||
|
->will($this->returnValue(array(
|
||||||
|
array(),
|
||||||
|
array(),
|
||||||
|
'count' => 2,
|
||||||
|
)))
|
||||||
|
;
|
||||||
|
|
||||||
|
$provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com');
|
||||||
|
$provider->loadUserByUsername('foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSuccessfulLoadUserByUsername()
|
||||||
|
{
|
||||||
|
$ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface');
|
||||||
|
$ldap
|
||||||
|
->expects($this->once())
|
||||||
|
->method('escape')
|
||||||
|
->will($this->returnValue('foo'))
|
||||||
|
;
|
||||||
|
$ldap
|
||||||
|
->expects($this->once())
|
||||||
|
->method('find')
|
||||||
|
->will($this->returnValue(array(
|
||||||
|
array(
|
||||||
|
'sAMAccountName' => 'foo',
|
||||||
|
'userpassword' => 'bar',
|
||||||
|
),
|
||||||
|
'count' => 1,
|
||||||
|
)))
|
||||||
|
;
|
||||||
|
|
||||||
|
$provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com');
|
||||||
|
$this->assertInstanceOf(
|
||||||
|
'Symfony\Component\Security\Core\User\User',
|
||||||
|
$provider->loadUserByUsername('foo')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
109
src/Symfony/Component/Security/Core/User/LdapUserProvider.php
Normal file
109
src/Symfony/Component/Security/Core/User/LdapUserProvider.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Security\Core\User;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||||
|
use Symfony\Component\Ldap\Exception\ConnectionException;
|
||||||
|
use Symfony\Component\Ldap\LdapClientInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LdapUserProvider is a simple user provider on top of ldap.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
* @author Charles Sarrazin <charles@sarraz.in>
|
||||||
|
*/
|
||||||
|
class LdapUserProvider implements UserProviderInterface
|
||||||
|
{
|
||||||
|
private $ldap;
|
||||||
|
private $baseDn;
|
||||||
|
private $searchDn;
|
||||||
|
private $searchPassword;
|
||||||
|
private $defaultRoles;
|
||||||
|
private $defaultSearch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param LdapClientInterface $ldap
|
||||||
|
* @param string $baseDn
|
||||||
|
* @param string $searchDn
|
||||||
|
* @param string $searchPassword
|
||||||
|
* @param array $defaultRoles
|
||||||
|
* @param string $uidKey
|
||||||
|
* @param string $filter
|
||||||
|
*/
|
||||||
|
public function __construct(LdapClientInterface $ldap, $baseDn, $searchDn = null, $searchPassword = null, array $defaultRoles = array(), $uidKey = 'sAMAccountName', $filter = '({uid_key}={username})')
|
||||||
|
{
|
||||||
|
$this->ldap = $ldap;
|
||||||
|
$this->baseDn = $baseDn;
|
||||||
|
$this->searchDn = $searchDn;
|
||||||
|
$this->searchPassword = $searchPassword;
|
||||||
|
$this->defaultRoles = $defaultRoles;
|
||||||
|
$this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadUserByUsername($username)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->ldap->bind($this->searchDn, $this->searchPassword);
|
||||||
|
$username = $this->ldap->escape($username, '', LdapClientInterface::LDAP_ESCAPE_FILTER);
|
||||||
|
$query = str_replace('{username}', $username, $this->defaultSearch);
|
||||||
|
$search = $this->ldap->find($this->baseDn, $query);
|
||||||
|
} catch (ConnectionException $e) {
|
||||||
|
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$search) {
|
||||||
|
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($search['count'] > 1) {
|
||||||
|
throw new UsernameNotFoundException('More than one user found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $search[0];
|
||||||
|
|
||||||
|
return $this->loadUser($username, $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadUser($username, $user)
|
||||||
|
{
|
||||||
|
$password = isset($user['userpassword']) ? $user['userpassword'] : null;
|
||||||
|
|
||||||
|
$roles = $this->defaultRoles;
|
||||||
|
|
||||||
|
return new User($username, $password, $roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function refreshUser(UserInterface $user)
|
||||||
|
{
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new User($user->getUsername(), null, $user->getRoles());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function supportsClass($class)
|
||||||
|
{
|
||||||
|
return $class === 'Symfony\Component\Security\Core\User\User';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,13 +26,15 @@
|
|||||||
"symfony/translation": "~2.0,>=2.0.5|~3.0.0",
|
"symfony/translation": "~2.0,>=2.0.5|~3.0.0",
|
||||||
"symfony/validator": "~2.5,>=2.5.5|~3.0.0",
|
"symfony/validator": "~2.5,>=2.5.5|~3.0.0",
|
||||||
"psr/log": "~1.0",
|
"psr/log": "~1.0",
|
||||||
"ircmaxell/password-compat": "1.0.*"
|
"ircmaxell/password-compat": "1.0.*",
|
||||||
|
"symfony/ldap": "~2.8|~3.0.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"symfony/event-dispatcher": "",
|
"symfony/event-dispatcher": "",
|
||||||
"symfony/http-foundation": "",
|
"symfony/http-foundation": "",
|
||||||
"symfony/validator": "For using the user password constraint",
|
"symfony/validator": "For using the user password constraint",
|
||||||
"symfony/expression-language": "For using the expression voter",
|
"symfony/expression-language": "For using the expression voter",
|
||||||
|
"symfony/ldap": "For using LDAP integration",
|
||||||
"ircmaxell/password-compat": "For using the BCrypt password encoder in PHP <5.5"
|
"ircmaxell/password-compat": "For using the BCrypt password encoder in PHP <5.5"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
"doctrine/dbal": "~2.2",
|
"doctrine/dbal": "~2.2",
|
||||||
"psr/log": "~1.0",
|
"psr/log": "~1.0",
|
||||||
"ircmaxell/password-compat": "~1.0",
|
"ircmaxell/password-compat": "~1.0",
|
||||||
"symfony/expression-language": "~2.6|~3.0.0"
|
"symfony/expression-language": "~2.6|~3.0.0",
|
||||||
|
"symfony/ldap": "~2.8|~3.0.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"symfony/class-loader": "For using the ACL generateSql script",
|
"symfony/class-loader": "For using the ACL generateSql script",
|
||||||
@ -48,7 +49,8 @@
|
|||||||
"symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs",
|
"symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs",
|
||||||
"symfony/expression-language": "For using the expression voter",
|
"symfony/expression-language": "For using the expression voter",
|
||||||
"ircmaxell/password-compat": "For using the BCrypt password encoder in PHP <5.5",
|
"ircmaxell/password-compat": "For using the BCrypt password encoder in PHP <5.5",
|
||||||
"paragonie/random_compat": ""
|
"paragonie/random_compat": "",
|
||||||
|
"symfony/ldap": "For using the LDAP user and authentication providers"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": { "Symfony\\Component\\Security\\": "" }
|
"psr-4": { "Symfony\\Component\\Security\\": "" }
|
||||||
|
Reference in New Issue
Block a user