1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Security\Core\User;
13
14use Symfony\Component\Ldap\Entry;
15use Symfony\Component\Ldap\Exception\ConnectionException;
16use Symfony\Component\Ldap\LdapInterface;
17use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
18use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
19use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
20
21/**
22 * LdapUserProvider is a simple user provider on top of ldap.
23 *
24 * @author Grégoire Pineau <lyrixx@lyrixx.info>
25 * @author Charles Sarrazin <charles@sarraz.in>
26 */
27class LdapUserProvider implements UserProviderInterface
28{
29    private $ldap;
30    private $baseDn;
31    private $searchDn;
32    private $searchPassword;
33    private $defaultRoles;
34    private $uidKey;
35    private $defaultSearch;
36    private $passwordAttribute;
37
38    /**
39     * @param string $baseDn
40     * @param string $searchDn
41     * @param string $searchPassword
42     * @param string $uidKey
43     * @param string $filter
44     * @param string $passwordAttribute
45     */
46    public function __construct(LdapInterface $ldap, $baseDn, $searchDn = null, $searchPassword = null, array $defaultRoles = [], $uidKey = 'sAMAccountName', $filter = '({uid_key}={username})', $passwordAttribute = null)
47    {
48        if (null === $uidKey) {
49            $uidKey = 'sAMAccountName';
50        }
51
52        $this->ldap = $ldap;
53        $this->baseDn = $baseDn;
54        $this->searchDn = $searchDn;
55        $this->searchPassword = $searchPassword;
56        $this->defaultRoles = $defaultRoles;
57        $this->uidKey = $uidKey;
58        $this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter);
59        $this->passwordAttribute = $passwordAttribute;
60    }
61
62    /**
63     * {@inheritdoc}
64     */
65    public function loadUserByUsername($username)
66    {
67        try {
68            $this->ldap->bind($this->searchDn, $this->searchPassword);
69            $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER);
70            $query = str_replace('{username}', $username, $this->defaultSearch);
71            $search = $this->ldap->query($this->baseDn, $query);
72        } catch (ConnectionException $e) {
73            throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e);
74        }
75
76        $entries = $search->execute();
77        $count = \count($entries);
78
79        if (!$count) {
80            throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
81        }
82
83        if ($count > 1) {
84            throw new UsernameNotFoundException('More than one user found.');
85        }
86
87        $entry = $entries[0];
88
89        try {
90            if (null !== $this->uidKey) {
91                $username = $this->getAttributeValue($entry, $this->uidKey);
92            }
93        } catch (InvalidArgumentException $e) {
94        }
95
96        return $this->loadUser($username, $entry);
97    }
98
99    /**
100     * {@inheritdoc}
101     */
102    public function refreshUser(UserInterface $user)
103    {
104        if (!$user instanceof User) {
105            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
106        }
107
108        return new User($user->getUsername(), null, $user->getRoles());
109    }
110
111    /**
112     * {@inheritdoc}
113     */
114    public function supportsClass($class)
115    {
116        return 'Symfony\Component\Security\Core\User\User' === $class;
117    }
118
119    /**
120     * Loads a user from an LDAP entry.
121     *
122     * @param string $username
123     *
124     * @return User
125     */
126    protected function loadUser($username, Entry $entry)
127    {
128        $password = null;
129
130        if (null !== $this->passwordAttribute) {
131            $password = $this->getAttributeValue($entry, $this->passwordAttribute);
132        }
133
134        return new User($username, $password, $this->defaultRoles);
135    }
136
137    /**
138     * Fetches a required unique attribute value from an LDAP entry.
139     *
140     * @param Entry|null $entry
141     * @param string     $attribute
142     */
143    private function getAttributeValue(Entry $entry, $attribute)
144    {
145        if (!$entry->hasAttribute($attribute)) {
146            throw new InvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $attribute, $entry->getDn()));
147        }
148
149        $values = $entry->getAttribute($attribute);
150
151        if (1 !== \count($values)) {
152            throw new InvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $attribute));
153        }
154
155        return $values[0];
156    }
157}
158