Ldap   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 25
eloc 93
c 6
b 0
f 0
dl 0
loc 241
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getAdapter() 0 3 1
B search() 0 45 6
A searchForMultiple() 0 30 4
A __construct() 0 33 2
A whoami() 0 7 2
A bind() 0 12 3
A saslBind() 0 23 4
A resolveBindException() 0 3 1
A updateEntry() 0 8 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\ldap\Connector;
6
7
use SimpleSAML\Assert\Assert;
8
use SimpleSAML\Error;
9
use SimpleSAML\Logger;
10
use SimpleSAML\Module\ldap\ConnectorInterface;
11
use Symfony\Component\Ldap\Adapter\AdapterInterface;
12
use Symfony\Component\Ldap\Adapter\ExtLdap\Adapter;
13
use Symfony\Component\Ldap\Entry;
14
use Symfony\Component\Ldap\Exception\InvalidCredentialsException;
15
use Symfony\Component\Ldap\Exception\LdapException;
16
use Symfony\Component\Ldap\Ldap as LdapObject;
17
18
use function array_merge;
19
use function array_pop;
20
use function explode;
21
use function implode;
22
use function ini_get;
23
use function sprintf;
24
use function var_export;
25
26
class Ldap implements ConnectorInterface
27
{
28
    use LdapHelpers;
29
30
31
    /**
32
     * @var \Symfony\Component\Ldap\Adapter\AdapterInterface
33
     */
34
    protected AdapterInterface $adapter;
35
36
    /**
37
     * @var \Symfony\Component\Ldap\Ldap
38
     */
39
    protected LdapObject $connection;
40
41
42
    /**
43
     * @param string $connection_strings
44
     * @param string $encryption
45
     * @param int $version
46
     * @param string $extension
47
     * @param bool $debug
48
     * @param array<mixed> $options
49
     */
50
    public function __construct(
51
        string $connection_strings,
52
        string $encryption = 'ssl',
53
        int $version = 3,
54
        string $extension = 'ext_ldap',
0 ignored issues
show
Unused Code introduced by
The parameter $extension is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

54
        /** @scrutinizer ignore-unused */ string $extension = 'ext_ldap',

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
55
        bool $debug = false,
56
        array $options = ['referrals' => false, 'network_timeout' => 3],
57
    ) {
58
        foreach (explode(' ', $connection_strings) as $connection_string) {
59
            Assert::regex($connection_string, '#^ldap[s]?:\/\/#');
60
        }
61
62
        Logger::debug(sprintf(
63
            "Setting up LDAP connection: host='%s', encryption=%s, version=%d, debug=%s, timeout=%d, referrals=%s.",
64
            $connection_strings,
65
            $encryption,
66
            $version,
67
            var_export($debug, true),
68
            $options['timeout'] ?? ini_get('default_socket_timeout'),
69
            var_export($options['referrals'] ?? false, true),
70
        ));
71
72
        $this->adapter = new Adapter(
73
            [
74
                'connection_string' => $connection_strings,
75
                'encryption'        => $encryption,
76
                'version'           => $version,
77
                'debug'             => $debug,
78
                'options'           => $options,
79
            ],
80
        );
81
82
        $this->connection = new LdapObject($this->adapter);
83
    }
84
85
86
    /**
87
     * @return \Symfony\Component\Ldap\Adapter\AdapterInterface
88
     */
89
    public function getAdapter(): AdapterInterface
90
    {
91
        return $this->adapter;
92
    }
93
94
95
    /**
96
     * @inheritDoc
97
     */
98
    public function bind(?string $username, #[\SensitiveParameter]?string $password): void
99
    {
100
        try {
101
            $this->connection->bind($username, strval($password));
102
        } catch (InvalidCredentialsException $e) {
103
            throw new Error\Error($this->resolveBindException());
104
        }
105
106
        if ($username === null) {
107
            Logger::debug("LDAP bind(): Anonymous bind succesful.");
108
        } else {
109
            Logger::debug(sprintf("LDAP bind(): Bind successful for DN '%s'.", $username));
110
        }
111
    }
112
113
114
    /**
115
     * @inheritDoc
116
     */
117
    public function saslBind(
118
        ?string $username,
119
        #[\SensitiveParameter]?string $password,
120
        ?string $mech,
121
        ?string $realm,
122
        ?string $authcId,
123
        ?string $authzId,
124
        ?string $props,
125
    ): void {
126
        if (!method_exists($this->connection, 'saslBind')) {
127
            throw new Error\Error("SASL not implemented");
128
        }
129
130
        try {
131
            $this->connection->saslBind($username, strval($password), $mech, $realm, $authcId, $authzId, $props);
0 ignored issues
show
Bug introduced by
The method saslBind() does not exist on Symfony\Component\Ldap\Ldap. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

131
            $this->connection->/** @scrutinizer ignore-call */ 
132
                               saslBind($username, strval($password), $mech, $realm, $authcId, $authzId, $props);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
132
        } catch (InvalidCredentialsException $e) {
133
            throw new Error\Error($this->resolveBindException());
134
        }
135
136
        if ($username === null) {
137
            Logger::debug("LDAP bind(): Anonymous bind succesful.");
138
        } else {
139
            Logger::debug(sprintf("LDAP bind(): Bind successful for DN '%s'.", $username));
140
        }
141
    }
142
143
144
    /**
145
     * @inheritDoc
146
     */
147
    public function whoami(): string
148
    {
149
        if (!method_exists($this->connection, 'whoami')) {
150
            throw new Error\Error("SASL not implemented");
151
        }
152
153
        return $this->connection->whoami();
0 ignored issues
show
Bug introduced by
The method whoami() does not exist on Symfony\Component\Ldap\Ldap. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

153
        return $this->connection->/** @scrutinizer ignore-call */ whoami();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
154
    }
155
156
157
    /**
158
     * @inheritDoc
159
     */
160
    public function search(
161
        array $searchBase,
162
        string $filter,
163
        array $options,
164
        bool $allowMissing,
165
    ): ?Entry {
166
        $entry = null;
167
168
        foreach ($searchBase as $base) {
169
            $query  = $this->connection->query($base, $filter, $options);
170
            $result = $query->execute()->toArray();
171
172
            if (count($result) > 1) {
173
                throw new Error\Exception(sprintf(
174
                    "LDAP search(): Found %d entries searching base '%s' for '%s'",
175
                    count($result),
176
                    $base,
177
                    $filter,
178
                ));
179
            } elseif (count($result) === 1) {
180
                $entry = array_pop($result);
181
                Logger::debug(sprintf(
182
                    "LDAP search(): Found 1 entry searching base '%s' for '%s'",
183
                    $base,
184
                    $filter,
185
                ));
186
                break;
187
            } else {
188
                Logger::debug(sprintf(
189
                    "LDAP search(): Found no entries searching base '%s' for '%s'",
190
                    $base,
191
                    $filter,
192
                ));
193
            }
194
        }
195
196
        if ($entry === null && $allowMissing === false) {
197
            throw new Error\Exception(sprintf(
198
                "Object not found using search base [%s] and filter '%s'",
199
                implode(', ', $searchBase),
200
                $filter,
201
            ));
202
        }
203
204
        return $entry;
205
    }
206
207
208
    /**
209
     * @inheritDoc
210
     */
211
    public function searchForMultiple(
212
        array $searchBase,
213
        string $filter,
214
        array $options,
215
        bool $allowMissing,
216
    ): array {
217
        $results = [];
218
219
        foreach ($searchBase as $base) {
220
            $query   = $this->connection->query($base, $filter, $options);
221
            $result  = $query->execute()->toArray();
222
            $results = array_merge($results, $result);
223
224
            Logger::debug(sprintf(
225
                "Library - LDAP search(): Found %d entries searching base '%s' for '%s'",
226
                count($result),
227
                $base,
228
                $filter,
229
            ));
230
        }
231
232
        if (empty($results) && ($allowMissing === false)) {
233
            throw new Error\Exception(sprintf(
234
                "No Objects found using search base [%s] and filter '%s'",
235
                implode(', ', $searchBase),
236
                $filter,
237
            ));
238
        }
239
240
        return $results;
241
    }
242
243
244
    /**
245
     * Resolve the message to a UI exception
246
     *
247
     * @return string
248
     */
249
    protected function resolveBindException(): string
250
    {
251
        return self::ERR_WRONG_PASS;
252
    }
253
254
255
    /**
256
     * @param \Symfony\Component\Ldap\Entry $entry
257
     * @return bool
258
     */
259
    public function updateEntry(Entry $entry): bool
260
    {
261
        try {
262
            $this->adapter->getEntryManager()->update($entry);
263
            return true;
264
        } catch (LdapException $e) {
265
            Logger::warning($e->getMessage());
266
            return false;
267
        }
268
    }
269
}
270