Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[2.8] [Ldap] Added support for LDAP (New Component + integration in the Security Component). #14602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.* ]] && [ "$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 echo "extension = ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LDAP extension is available without any installation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It seems that the LDAP extension is available by default in Travis' PHP build.

See travis-ci/travis-ci#1096

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and this is certain, as I added the skip tests only after checking that they passed on travis first, and because of appveyor launching tests with and without PHP extensions.

- ./phpunit install
- export PHPUNIT="$(readlink -f ./phpunit)"

Expand Down
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ install:
- 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_pdo_sqlite.dll >> php.ini
- IF %PHP_EXT%==1 echo extension=php_ldap.dll >> php.ini
- cd c:\projects\symfony
- php phpunit install
- IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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 <[email protected]>
* @author Charles Sarrazin <[email protected]>
*/
class FormLoginLdapFactory extends FormLoginFactory
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're making it impossible like this to attach a custom authenticator. Can you extend the SimpleFormFactory instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These factories are only to be used with LDAP, and actually override the authentication provider to be the LDAP bind authentication provider. As it already handles authentication, I don't really see why you would want to attach a custom authenticator.

Can you provide a use case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example if a user account is disabled, a certain token is not supplied etc. LDAP should be nothing more than a specific user provider in my opinion. Instead of fetching it from the database, you fetch it from ldap.

For me it's currently impossible to do the following:

  • Custom validation on the user account
  • Restrict logins if a user account is in a certain ldap-group
  • Adding a 3rd required field, for example specifying a second "username", where the uniqueness lies in (for example) vendor + username

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue should start to be addressed in #14673 (with the new guard authentication system), and maybe #14713.

This feature will be adapted for the new guard system, and improved once these PRs. Depending on when (and whether) these two PRs will be merged, a new PR should be made to update the legacy authentication providers. Until then, I will fix the tests, unless there actually is another issue.

Ping @iltar @weaverryan

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the guard PR gets merged and we can build this on top of that, that's awesome! Realistically, since security is so complex, I imagine that we (the community) will make a push on all these security-related PR's at the same time and decide how it will all end up. For now, I agree with making this PR as solid as possible without #14673 and #14713... though I hope that'll change ;).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I keep the PR as is. I'll fix the tests, and that should do it.

Of course, I'm open for any suggestion.

Ping @iltar @stof @weaverryan

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think core features in the Security component should still be built with core features. Guard is more for end-users.

{
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';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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 <[email protected]>
* @author Grégoire Pineau <[email protected]>
* @author Charles Sarrazin <[email protected]>
*/
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';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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 <[email protected]>
* @author Charles Sarrazin <[email protected]>
*/
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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 base_dn + search_dn + search_password should be required (and not empty?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, you can be authenticated anonymously using ldap_bind server.

See http://php.net/manual/en/function.ldap-bind.php:

If bind_rdn and bind_password are not specified, an anonymous bind is attempted.

Regarding the base_dn, it should indeed be required, and non-empty.

->arrayNode('default_roles')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is required, as you are accessing this key in self::create

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed!

->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()
;
}
}
12 changes: 12 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,29 @@
<argument type="service" id="security.token_storage" on-invalid="null" />
</service>


<!-- 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.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.http_utils" class="%security.http_utils.class%" public="false">
<argument type="service" id="router" on-invalid="null" />
<argument type="service" id="router" on-invalid="null" />
</service>


<!-- Validator -->
<service id="security.validator.user_password" class="%security.validator.user_password.class%">
<tag name="validator.constraint_validator" alias="security.validator.user_password" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@
<argument>%security.authentication.hide_user_not_found%</argument>
</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">
<argument /> <!-- Simple Authenticator -->
<argument /> <!-- User Provider -->
Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/SecurityBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
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\HttpBasicLdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpDigestFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory;
Expand All @@ -24,6 +26,7 @@
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory;

/**
* Bundle.
Expand All @@ -38,7 +41,9 @@ public function build(ContainerBuilder $container)

$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new FormLoginFactory());
$extension->addSecurityListenerFactory(new FormLoginLdapFactory());
$extension->addSecurityListenerFactory(new HttpBasicFactory());
$extension->addSecurityListenerFactory(new HttpBasicLdapFactory());
$extension->addSecurityListenerFactory(new HttpDigestFactory());
$extension->addSecurityListenerFactory(new RememberMeFactory());
$extension->addSecurityListenerFactory(new X509Factory());
Expand All @@ -48,6 +53,7 @@ public function build(ContainerBuilder $container)
$extension->addSecurityListenerFactory(new GuardAuthenticationFactory());

$extension->addUserProviderFactory(new InMemoryFactory());
$extension->addUserProviderFactory(new LdapFactory());
$container->addCompilerPass(new AddSecurityVotersPass());
}
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/Ldap/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml
21 changes: 21 additions & 0 deletions src/Symfony/Component/Ldap/Exception/ConnectionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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 <[email protected]>
*/
class ConnectionException extends \RuntimeException
{
}
21 changes: 21 additions & 0 deletions src/Symfony/Component/Ldap/Exception/LdapException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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 <[email protected]>
*/
class LdapException extends \RuntimeException
{
}
19 changes: 19 additions & 0 deletions src/Symfony/Component/Ldap/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
Loading