-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
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 <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'; | ||
} | ||
} |
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 <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'; | ||
} | ||
} |
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 <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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, you can be authenticated anonymously using See http://php.net/manual/en/function.ldap-bind.php:
Regarding the |
||
->arrayNode('default_roles') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is required, as you are accessing this key in There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about adding theses lines ? if ('NONE_PROVIDED' === $uuid) {
throw new UsernameNotFoundException('Username can not be null');
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, I'm not sure about this one. I don't know where we should put this piece of code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By $uuid, you mean $username? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oups. Yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. Rather than using an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know. This method should throw an |
||
} | ||
|
||
/** | ||
* {@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.'); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ;).
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.