3
votes

I struggled to get a LDAP custom authentification working on my Symfony2 project. I first tried to use Fr3dLdapBundle and others but it's impossible to get it working + it doesn't support security groups.

I finally got it working with those two links : - https://groups.google.com/forum/#!topic/symfony2/RUyl15zaH8A - http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html

But now I have to check when someone logs in if he's in the security group "AdminMyWebsite" for example. And if he is, I have to set his role to ROLE_ADMIN. But I don't know how to do this.

LdapProvider :

<?php

namespace EspaceApprenti\UserBundle\Security\Authentication\Provider;

use EspaceApprenti\UserBundle\Security\Authentication\Token\LdapToken;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class LdapProvider implements AuthenticationProviderInterface {

private $userProvider;
private $providerKey;
private $logger;
private $server;
private $port;
private $domaine;
private $dn;

public function __construct(UserProviderInterface $userProvider, $providerKey, $logger, $server, $port, $domaine, $dn) {
    $this->userProvider = $userProvider;
    $this->providerKey = $providerKey;
    $this->logger = $logger;

    $this->server = $server;
    $this->port = $port;
    $this->domaine = $domaine;
    $this->dn = $dn;
}

public function authenticate(TokenInterface $token) {

    if (!$this->supports($token)) {
        return null;
    }

    $user = $this->userProvider->loadUserByUsername($token->getUsername());

    $username = $token->getUsername();
    $password = $token->getCredentials();

    $this->logger->info(' -- LdapProvider -- ' . $username);
    if ($password) {
        if (!$this->ldapBind($username, $password)) {
            throw new AuthenticationException('Authentication failed ! ');
        }
    }

    $authenticatedToken = new LdapToken($user, $password, $this->providerKey, $user->getRoles());
    $authenticatedToken->setAttributes($token->getAttributes());

    return $authenticatedToken;
}

private function ldapBind($username, $password) {
    // returns true or false        
    $this->logger->info(' -- LdapProvider -> ldapBind() Serveur -- ' . $this->server);
    $this->logger->info(' -- LdapProvider -> ldapBind() Username -- ' . $username);
    $ds = ldap_connect($this->server, $this->port);
    if ($ds) {
        try {
            if (ldap_bind($ds, $this->domaine . '\\' . $username, $password)) {
                return true;
            } else {
                return false;
            }
        } catch (ContextErrorException $e) {
            $this->logger->error(' -- LdapProvider -> ldapBind() Unable to bind to server: Invalid credentials -- ' . $e->getMessage());
        }
    } else {
        $this->logger->info(' -- LdapProvider -> ldapBind() Unable to connect to LAPP Server -- ' . $this->server);
    }

}

public function supports(TokenInterface $token) {
    return $token instanceof LdapToken;
}

}

LdapFactory

<?php

namespace EspaceApprenti\UserBundle\DependencyInjection\Security\Factory;

use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;



class LdapFactory extends AbstractFactory
{
    protected function getListenerId()
{
    return 'security.authentication.listener.ldap';
}

protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
{
    $providerId = 'security.authentication.provider.ldap.'.$id;

    $container
        ->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.ldap'))
        ->replaceArgument(0, new Reference($userProviderId))                
        ->replaceArgument(1, $id);

    return $providerId;
}

protected function createEntryPoint($container, $id, $config, $defaultEntryPoint)
{
    $entryPointId = 'security.authentication.ldap_entry_point.'.$id;
    $container
        ->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.ldap_entry_point'))
        ->addArgument(new Reference('security.http_utils'))
        ->addArgument($config['login_path'])
        ->addArgument($config['use_forward'])
    ;

    return $entryPointId;
}    

public function getPosition()
{
    return 'form';
}

public function getKey()
{
    return 'ldap';
}

}

LdapListener

<?php

namespace EspaceApprenti\UserBundle\Security\Firewall;

use EspaceApprenti\UserBundle\Security\Authentication\Token\LdapToken;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;

class LdapListener extends AbstractAuthenticationListener
{
private $csrfProvider;
protected $logger;

/**
 * {@inheritdoc}
 */
public function __construct(SecurityContextInterface $securityContext, 
        AuthenticationManagerInterface $authenticationManager, 
        SessionAuthenticationStrategyInterface $sessionStrategy, 
        HttpUtils $httpUtils, 
        $providerKey, 
        AuthenticationSuccessHandlerInterface $successHandler, 
        AuthenticationFailureHandlerInterface $failureHandler, 
        array $options = array(), 
        LoggerInterface $logger = null, 
        EventDispatcherInterface $dispatcher = null, 
        CsrfProviderInterface $csrfProvider = null)
{
    parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
        'username_parameter' => '_username',
        'password_parameter' => '_password',
        'csrf_parameter'     => '_csrf_token',
        'intention'          => 'authenticate',
        'post_only'          => true,
    ), $options), $logger, $dispatcher);

    $this->csrfProvider = $csrfProvider;
    $this->logger = $logger;
}

/**
 * {@inheritdoc}
 */
protected function requiresAuthentication(Request $request)
{
    if ($this->options['post_only'] && !$request->isMethod('POST')) {
        return false;
    }

    return parent::requiresAuthentication($request);
}

/**
 * {@inheritdoc}
 */
protected function attemptAuthentication(Request $request)
{
    if (null !== $this->csrfProvider) {
        $csrfToken = $request->get($this->options['csrf_parameter'], null, true);

        if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
            throw new InvalidCsrfTokenException('Invalid CSRF token.');
        }
    }

    if ($this->options['post_only']) {
        $username = trim($request->request->get($this->options['username_parameter'], null, true));
        $password = $request->request->get($this->options['password_parameter'], null, true);
    } else {
        $username = trim($request->get($this->options['username_parameter'], null, true));
        $password = $request->get($this->options['password_parameter'], null, true);
    }
    $this->logger->alert('LdapAuthenticationListener : '. $username);
    $this->logger->alert('LdapAuthenticationListener : '. $this->providerKey);
    $request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username);

    return $this->authenticationManager->authenticate(new LdapToken($username, $password, $this->providerKey));
}
}

LdapToken

<?php

namespace EspaceApprenti\UserBundle\Security\Authentication\Token;

use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;

class LdapToken extends AbstractToken {

private $credentials;
private $providerKey;

/**
 * Constructor.
 *
 * @param string          $user        The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method.
 * @param string          $credentials This usually is the password of the user
 * @param string          $providerKey The provider key
 * @param RoleInterface[] $roles       An array of roles
 *
 * @throws \InvalidArgumentException
 */
public function __construct($user, $credentials, $providerKey, array $roles = array())
{
    parent::__construct($roles);

    if (empty($providerKey)) {
        throw new \InvalidArgumentException('$providerKey must not be empty.');
    }

    $this->setUser($user);
    $this->credentials = $credentials;
    $this->providerKey = $providerKey;

    parent::setAuthenticated(count($roles) > 0);
}

/**
 * {@inheritdoc}
 */
public function setAuthenticated($isAuthenticated)
{
    if ($isAuthenticated) {
        throw new \LogicException('Cannot set this token to trusted after instantiation.');
    }

    parent::setAuthenticated(false);
}

public function getCredentials()
{
    return $this->credentials;
}

public function getProviderKey()
{
    return $this->providerKey;
}

/**
 * {@inheritdoc}
 */
public function eraseCredentials()
{
    parent::eraseCredentials();

    $this->credentials = null;
}

/**
 * {@inheritdoc}
 */
public function serialize()
{
    return serialize(array($this->credentials, $this->providerKey, parent::serialize()));
}

/**
 * {@inheritdoc}
 */
public function unserialize($serialized)
{
    list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized);
    parent::unserialize($parentStr);
}

}

Can anyone help me with this ?

Regards

1
I'm not 100% sure about how to do this in Symfony2 but u need to run an LDAP query to retrieve the users groups (you can use google to get example queries). Then you can check the groups returned to see if they have access to the group or not. I would recommend working out how to run these manually without using symfony2, that way you would understand how the LDAP queries work. I have previously work with adldap.sourceforge.net but that is more geared toward Active Directory, but it is very good and provides the functionality you required. Good luck with this! - mic
Hi, I think Fr3dLdapBundle can manage groups. You can extends the LdapManager and use it. In fact, assign LDAP groups to symfony security groups. You have to create a LDAP group by symfony security group - Perroin Thibault
@PerroinThibault the Fr3dLdapBundle seems to be discontinued since Symfony2.1. You should go with the BorisMorel/LdapBundle instead. - ferdynator
@ferdynator Nice ! this bundle seems to be a very good alternative and more efficient ! Thx a lot - Perroin Thibault
Do you know if BorisMorel bundle support unique authentification ? If so do you have examples ? Thanks ! - Elbbard

1 Answers

1
votes

I solved my problem. I added a ldap_search after the ldap_bind to check if the user was member of the security group

$ds = ldap_connect($this->server, $this->port);
        if ($ds) {
            try {
                if (ldap_bind($ds, $this->domaine . '\\' . $username, $password)){
                    $user = $this->m->getRepository('EspaceApprentiUserBundle:ApprenticeUser')->findOneBy(array('username' => $username));   
                    $filter = "(&(objectCategory=person)(samAccountName=" . $username . "))";
                    $result = ldap_search($ds, $this->dn, $filter);
                    $entries = ldap_get_entries($ds, $result);
                    if (in_array($this->administrator,$entries[0]['memberof']))
                    { 
                        $user->setRoles(array('ROLE_ADMIN'));
                        $this->m->persist($user);
                        $this->m->flush();
                    } else {
                        $user->setRoles(array('ROLE_USER'));
                        $this->m->persist($user);
                        $this->m->flush();
                    }       
                    return true;
                } else {
                    return false;
                }
            } catch (ContextErrorException $e) {
                $this->logger->error(' -- LdapProvider -> ldapBind() Unable to bind to server: Invalid credentials -- ' . $e->getMessage());
            }
        } else {
            $this->logger->info(' -- LdapProvider -> ldapBind() Unable to connect to LAPP Server -- ' . $this->server);
        }