Passed
Push — master ( a29976...8f78ad )
by Rubén
09:40
created

LdapConnection::getServerUri()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 4
nop 0
dl 0
loc 14
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * sysPass
4
 *
5
 * @author    nuxsmin
6
 * @link      https://syspass.org
7
 * @copyright 2012-2019, Rubén Domínguez nuxsmin@$syspass.org
8
 *
9
 * This file is part of sysPass.
10
 *
11
 * sysPass is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation, either version 3 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * sysPass is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 *  along with sysPass.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
namespace SP\Providers\Auth\Ldap;
26
27
use SP\Core\Events\Event;
28
use SP\Core\Events\EventDispatcher;
29
use SP\Core\Events\EventMessage;
30
31
32
/**
33
 * Class LdapConnection
34
 *
35
 * @package SP\Providers\Auth\Ldap
36
 */
37
final class LdapConnection implements LdapConnectionInterface
38
{
39
    const TIMEOUT = 10;
40
    /**
41
     * @var resource
42
     */
43
    private $ldapHandler;
44
    /**
45
     * @var LdapParams
46
     */
47
    private $ldapParams;
48
    /**
49
     * @var EventDispatcher
50
     */
51
    private $eventDispatcher;
52
    /**
53
     * @var bool
54
     */
55
    private $isConnected = false;
56
    /**
57
     * @var bool
58
     */
59
    private $isBound = false;
60
    /**
61
     * @var bool
62
     */
63
    private $isTls;
64
    /**
65
     * @var bool
66
     */
67
    private $debug;
68
    /**
69
     * @var string
70
     */
71
    private $server;
72
73
    /**
74
     * LdapBase constructor.
75
     *
76
     * @param LdapParams      $ldapParams
77
     * @param EventDispatcher $eventDispatcher
78
     * @param bool            $debug
79
     */
80
    public function __construct(LdapParams $ldapParams, EventDispatcher $eventDispatcher, $debug = false)
81
    {
82
        $this->ldapParams = $ldapParams;
83
        $this->eventDispatcher = $eventDispatcher;
84
        $this->debug = (bool)$debug;
85
    }
86
87
    /**
88
     * Comprobar la conexión al servidor de LDAP.
89
     *
90
     * @throws LdapException
91
     */
92
    public function checkConnection()
93
    {
94
        try {
95
            $this->connectAndBind();
96
97
            $this->eventDispatcher->notifyEvent('ldap.check.connection',
98
                new Event($this, EventMessage::factory()
99
                    ->addDescription(__u('LDAP connection OK')))
100
            );
101
        } catch (LdapException $e) {
102
            throw $e;
103
        }
104
    }
105
106
    /**
107
     * @return resource
108
     * @throws LdapException
109
     */
110
    public function connectAndBind()
111
    {
112
        if (!$this->isConnected && !$this->isBound) {
113
            $this->isConnected = $this->connect();
114
            $this->isBound = $this->bind();
115
        }
116
117
        return $this->ldapHandler;
118
    }
119
120
    /**
121
     * Realizar la conexión al servidor de LDAP.
122
     *
123
     * @return bool
124
     * @throws LdapException
125
     */
126
    public function connect(): bool
127
    {
128
        if ($this->isConnected) {
129
            return true;
130
        }
131
132
        $this->checkParams();
133
134
        // Habilitar la traza si el modo debug está habilitado
135
        if ($this->debug) {
136
            @ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ldap_set_option(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

136
            /** @scrutinizer ignore-unhandled */ @ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
137
        }
138
139
        $this->ldapHandler = @ldap_connect($this->getServerUri());
140
141
        // Conexión al servidor LDAP
142
        if (!is_resource($this->ldapHandler)) {
143
            $this->eventDispatcher->notifyEvent('ldap.connect',
144
                new Event($this, EventMessage::factory()
145
                    ->addDescription(__u('Unable to connect to LDAP server'))
146
                    ->addDetail(__u('Server'), $this->getServer()))
147
            );
148
149
            throw new LdapException(__u('Unable to connect to LDAP server'));
150
        }
151
152
        @ldap_set_option($this->ldapHandler, LDAP_OPT_NETWORK_TIMEOUT, self::TIMEOUT);
153
        @ldap_set_option($this->ldapHandler, LDAP_OPT_PROTOCOL_VERSION, 3);
154
        @ldap_set_option($this->ldapHandler, LDAP_OPT_REFERRALS, 0);
155
156
        $this->isTls = $this->connectTls();
157
158
        return true;
159
    }
160
161
    /**
162
     * Comprobar si los parámetros necesario de LDAP están establecidos.
163
     *
164
     * @throws LdapException
165
     */
166
    public function checkParams()
167
    {
168
        if (empty($this->ldapParams->getSearchBase())
169
            || empty($this->getServer())
170
            || empty($this->ldapParams->getBindDn())
171
        ) {
172
            $this->eventDispatcher->notifyEvent('ldap.check.params',
173
                new Event($this, EventMessage::factory()
174
                    ->addDescription(__u('LDAP parameters are not set'))));
175
176
            throw new LdapException(__u('LDAP parameters are not set'));
177
        }
178
    }
179
180
    /**
181
     * @return string
182
     */
183
    public function getServer(): string
184
    {
185
        return $this->server ?: $this->ldapParams->getServer();
186
    }
187
188
    /**
189
     * @param string $server
190
     *
191
     * @return LdapConnection
192
     */
193
    public function setServer(string $server)
194
    {
195
        $this->server = $server;
196
197
        return $this;
198
    }
199
200
    /**
201
     * @inheritDoc
202
     */
203
    public function getServerUri(): string
204
    {
205
        $server = $this->getServer();
206
        $port = $this->ldapParams->getPort();
207
208
        if (strpos($server, '://') !== false) {
209
            return $server . ':' . $port;
210
        } elseif ($port === 389 || $port === null) {
211
            return 'ldap://' . $server;
212
        } elseif ($port === 636) {
213
            return 'ldaps://' . $server;
214
        }
215
216
        return 'ldap://' . $server . ':' . $port;
217
    }
218
219
    /**
220
     * Connect through TLS
221
     *
222
     * @throws LdapException
223
     */
224
    private function connectTls(): bool
225
    {
226
        if ($this->ldapParams->isTlsEnabled()) {
227
            $result = @ldap_start_tls($this->ldapHandler);
228
229
            if ($result === false) {
230
                $this->eventDispatcher->notifyEvent('ldap.connect.tls',
231
                    new Event($this, EventMessage::factory()
232
                        ->addDescription(__u('Unable to connect to LDAP server'))
233
                        ->addDetail(__u('Server'), $this->getServer())
234
                        ->addDetail('TLS', __u('ON'))
235
                        ->addDetail('LDAP ERROR', self::getLdapErrorMessage($this->ldapHandler))));
236
237
                throw new LdapException(__u('Unable to connect to LDAP server'));
238
            }
239
240
            return true;
241
        }
242
243
        return false;
244
    }
245
246
    /**
247
     * Registrar error de LDAP y devolver el mensaje de error
248
     *
249
     * @param $ldapHandler
250
     *
251
     * @return string
252
     */
253
    public static function getLdapErrorMessage($ldapHandler)
254
    {
255
        return sprintf('%s (%d)', ldap_error($ldapHandler), ldap_errno($ldapHandler));
256
    }
257
258
    /**
259
     * Realizar la autentificación con el servidor de LDAP.
260
     *
261
     * @param string $bindDn   con el DN del usuario
262
     * @param string $bindPass con la clave del usuario
263
     *
264
     * @return bool
265
     * @throws LdapException
266
     */
267
    public function bind(string $bindDn = null, string $bindPass = null): bool
268
    {
269
        $dn = $bindDn ?: $this->ldapParams->getBindDn();
270
        $pass = $bindPass ?: $this->ldapParams->getBindPass();
271
272
        if (@ldap_bind($this->ldapHandler, $dn, $pass) === false) {
273
            $this->eventDispatcher->notifyEvent('ldap.bind',
274
                new Event($this, EventMessage::factory()
275
                    ->addDescription(__u('Connection error (BIND)'))
276
                    ->addDetail('LDAP ERROR', self::getLdapErrorMessage($this->ldapHandler))
277
                    ->addDetail('LDAP DN', $dn))
278
            );
279
280
            throw new LdapException(
281
                __u('Connection error (BIND)'),
282
                LdapException::ERROR,
283
                self::getLdapErrorMessage($this->ldapHandler),
284
                $this->getErrorCode()
285
            );
286
        }
287
288
        return true;
289
    }
290
291
    /**
292
     * @return int
293
     */
294
    public function getErrorCode()
295
    {
296
        if (is_resource($this->ldapHandler)) {
297
            return ldap_errno($this->ldapHandler);
298
        }
299
300
        return -1;
301
    }
302
303
    /**
304
     * @return bool
305
     */
306
    public function isConnected(): bool
307
    {
308
        return $this->isConnected;
309
    }
310
311
    /**
312
     * @return bool
313
     */
314
    public function isBound(): bool
315
    {
316
        return $this->isBound;
317
    }
318
319
    /**
320
     * @return LdapParams
321
     */
322
    public function getLdapParams(): LdapParams
323
    {
324
        return $this->ldapParams;
325
    }
326
327
    /**
328
     * @return bool
329
     */
330
    public function isDebug(): bool
331
    {
332
        return $this->debug;
333
    }
334
335
    /**
336
     * Realizar la desconexión del servidor de LDAP.
337
     */
338
    public function unbind(): bool
339
    {
340
        if (($this->isConnected || $this->isBound)
341
            && @ldap_unbind($this->ldapHandler) === false
342
        ) {
343
            $this->eventDispatcher->notifyEvent('ldap.unbind',
344
                new Event($this, EventMessage::factory()
345
                    ->addDescription(__u('Error while disconnecting from LDAP server'))
346
                    ->addDetail('LDAP ERROR', self::getLdapErrorMessage($this->ldapHandler)))
347
            );
348
349
            return false;
350
        }
351
352
        return true;
353
    }
354
}