Passed
Push — master ( c6bfae...1c5fce )
by Tim
02:26
created

BaseFilter   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 125
c 5
b 0
f 0
dl 0
loc 334
rs 9.36
wmc 38

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getLdap() 0 29 5
F parseAuthSourceConfig() 0 84 23
A initializeLdap() 0 12 1
A varExport() 0 18 6
A __construct() 0 77 3
1
<?php
2
3
/**
4
 * This base LDAP filter class can be extended to enable real
5
 * filter classes direct access to the authsource ldap config
6
 * and connects to the ldap server.
7
 *
8
 * Updated: 20161223 Remy Blom
9
 *          - Wrapped the building of authsource config with issets
10
 *
11
 * @package SimpleSAMLphp
12
 */
13
14
declare(strict_types=1);
15
16
namespace SimpleSAML\Module\ldap\Auth\Process;
17
18
use SimpleSAML\Configuration;
19
use SimpleSAML\Error;
20
use SimpleSAML\Logger;
21
use SimpleSAML\Module\ldap\Auth\Ldap;
22
23
abstract class BaseFilter extends \SimpleSAML\Auth\ProcessingFilter
24
{
25
    // TODO: Support ldap:LDAPMulti, if possible
26
    protected static array $ldapsources = ['ldap:Ldap', 'authX509:X509userCert'];
27
28
    /**
29
     * List of attribute "alias's" linked to the real attribute
30
     * name. Used for abstraction / configuration of the LDAP
31
     * attribute names, which may change between dir service.
32
     *
33
     * @var array
34
     */
35
    protected array $attribute_map;
36
37
    /**
38
     * The base DN of the LDAP connection. Used when searching
39
     * the LDAP server.
40
     *
41
     * @var string|array
42
     */
43
    protected $base_dn;
44
45
    /**
46
     * The construct method will change the filter config into
47
     * a \SimpleSAML\Configuration object and store it here for
48
     * later use, if needed.
49
     *
50
     * @var \SimpleSAML\Configuration
51
     */
52
    protected Configuration $config;
53
54
    /**
55
     * Instance, object of the ldap connection. Stored here to
56
     * be access later during processing.
57
     *
58
     * @var \SimpleSAML\Module\ldap\Auth\Ldap
59
     */
60
    private Ldap $ldap;
61
62
    /**
63
     * Many times a LDAP product specific query can be used to
64
     * speed up or reduce the filter process. This helps the
65
     * child classes determine the product used to optimize
66
     * those queries.
67
     *
68
     * @var string
69
     */
70
    protected string $product;
71
72
    /**
73
     * The class "title" used in logging and exception messages.
74
     * This should be prepended to the beginning of the message.
75
     *
76
     * @var string
77
     */
78
    protected string $title = 'ldap:BaseFilter : ';
79
80
    /**
81
     * List of LDAP object types, used to determine the type of
82
     * object that a DN references.
83
     *
84
     * @var array
85
     */
86
    protected array $type_map;
87
88
89
    /**
90
     * Checks the authsource, if defined, for configuration values
91
     * to the LDAP server. Then sets up the LDAP connection for the
92
     * instance/object and stores everything in class members.
93
     *
94
     * @throws \SimpleSAML\Error\Exception
95
     * @param array &$config
96
     * @param mixed $reserved
97
     */
98
    public function __construct(array &$config, $reserved)
99
    {
100
        parent::__construct($config, $reserved);
101
102
        // Change the class $title to match it's true name
103
        // This way if the class is extended the proper name is used
104
        $classname = get_class($this);
105
        $classname = explode('_', $classname);
106
        $this->title = 'ldap:' . end($classname) . ' : ';
107
108
        // Log the construction
109
        Logger::debug($this->title . 'Creating and configuring the filter.');
110
111
        // Convert the config array to a config class,
112
        // that way we can verify type and define defaults.
113
        // Store in the instance in-case needed later, by a child class.
114
        $this->config = Configuration::loadFromArray($config, 'ldap:AuthProcess');
115
116
        // If an authsource was defined (an not empty string)...
117
        if (isset($config['authsource']) && $config['authsource'] !== '') {
118
            $authconfig = $this->parseAuthSourceConfig($config['authsource']);
119
120
            // Merge the authsource config with the filter config,
121
            // but have the filter config override the authsource config
122
            $config = array_merge($authconfig, $config);
123
124
            // Authsource complete
125
            Logger::debug(
126
                $this->title . 'Retrieved authsource [' . $config['authsource'] .
127
                '] configuration values: ' . $this->varExport($authconfig)
128
            );
129
        }
130
131
        // Initialize the Ldap-object
132
        $this->initializeLdap();
133
134
        // Set all the filter values, setting defaults if needed
135
        $this->base_dn = $this->config->getArrayizeString('ldap.basedn', '');
136
        $this->product = $this->config->getString('ldap.product', '');
137
138
        // Cleanup the directory service, so that it is easier for
139
        // child classes to determine service name consistently
140
        $this->product = trim($this->product);
141
        $this->product = strtoupper($this->product);
142
143
        // Log the member values retrieved above
144
        Logger::debug(
145
            $this->title . 'Configuration values retrieved;' .
146
            ' BaseDN: ' . $this->varExport($this->base_dn) .
147
            ' Product: ' . $this->varExport($this->product)
148
        );
149
150
        // Setup the attribute map which will be used to search LDAP
151
        $this->attribute_map = [
152
            'dn'       => $this->config->getString('attribute.dn', 'distinguishedName'),
153
            'groups'   => $this->config->getString('attribute.groups', 'groups'),
154
            'member'   => $this->config->getString('attribute.member', 'member'),
155
            'memberof' => $this->config->getString('attribute.memberof', 'memberOf'),
156
            'name'     => $this->config->getString('attribute.groupname', 'name'),
157
            'type'     => $this->config->getString('attribute.type', 'objectClass'),
158
            'username' => $this->config->getString('attribute.username', 'sAMAccountName')
159
        ];
160
161
        // Log the attribute map
162
        Logger::debug(
163
            $this->title . 'Attribute map created: ' . $this->varExport($this->attribute_map)
164
        );
165
166
        // Setup the object type map which is used to determine a DNs' type
167
        $this->type_map = [
168
            'group' => $this->config->getString('type.group', 'group'),
169
            'user'  => $this->config->getString('type.user', 'user')
170
        ];
171
172
        // Log the type map
173
        Logger::debug(
174
            $this->title . 'Type map created: ' . $this->varExport($this->type_map)
175
        );
176
    }
177
178
179
    /**
180
     * Parse authsource config
181
     *
182
     * @param string $as The name of the authsource
183
     */
184
    private function parseAuthSourceConfig(string $as) : array
185
    {
186
        // Log the authsource request
187
        Logger::debug(
188
            $this->title . 'Attempting to get configuration values from authsource [' . $as . ']'
189
        );
190
191
        // Get the authsources file, which should contain the config
192
        $authsources = Configuration::getConfig('authsources.php');
193
194
        // Verify that the authsource config exists
195
        if (!$authsources->hasValue($as)) {
196
            throw new Error\Exception(
197
                $this->title . 'Authsource [' . $as . '] defined in filter parameters not found in authsources.php'
198
            );
199
        }
200
201
        // Get just the specified authsource config values
202
        $authsource = $authsources->getArray($as);
203
204
        // Make sure it is an ldap source
205
        if (isset($authsource[0]) && !in_array($authsource[0], self::$ldapsources)) {
206
            throw new Error\Exception(
207
                $this->title . 'Authsource [' . $as . '] specified in filter parameters is not an ldap:LDAP type'
208
            );
209
        }
210
211
        // Build the authsource config
212
        $authconfig = [];
213
        if (isset($authsource['hostname'])) {
214
            $authconfig['ldap.hostname']   = $authsource['hostname'];
215
        }
216
        if (isset($authsource['enable_tls'])) {
217
            $authconfig['ldap.enable_tls'] = $authsource['enable_tls'];
218
        }
219
        if (isset($authsource['port'])) {
220
            $authconfig['ldap.port']       = $authsource['port'];
221
        }
222
        if (isset($authsource['timeout'])) {
223
            $authconfig['ldap.timeout']    = $authsource['timeout'];
224
        }
225
        if (isset($authsource['debug'])) {
226
            $authconfig['ldap.debug']      = $authsource['debug'];
227
        }
228
        if (isset($authsource['referrals'])) {
229
            $authconfig['ldap.referrals']  = $authsource['referrals'];
230
        }
231
232
        // only set when search.enabled = true
233
        if (isset($authsource['search.enable']) && $authsource['search.enable']) {
234
            if (isset($authsource['search.base'])) {
235
                $authconfig['ldap.basedn'] = $authsource['search.base'];
236
            }
237
            if (isset($authsource['search.scope'])) {
238
                $authconfig['ldap.scope'] = $authsource['search.scope'];
239
            }
240
            if (isset($authsource['search.username'])) {
241
                $authconfig['ldap.username']   = $authsource['search.username'];
242
            }
243
            if (isset($authsource['search.password'])) {
244
                $authconfig['ldap.password']   = $authsource['search.password'];
245
            }
246
247
            // Only set the username attribute if the authsource specifies one attribute
248
            if (
249
                isset($authsource['search.attributes'])
250
                && is_array($authsource['search.attributes'])
251
                && count($authsource['search.attributes']) == 1
252
            ) {
253
                $authconfig['attribute.username'] = reset($authsource['search.attributes']);
254
            }
255
        }
256
257
        // only set when priv.read = true
258
        if (isset($authsource['priv.read']) && $authsource['priv.read']) {
259
            if (isset($authsource['priv.username'])) {
260
                $authconfig['ldap.username'] = $authsource['priv.username'];
261
            }
262
            if (isset($authsource['priv.password'])) {
263
                $authconfig['ldap.password'] = $authsource['priv.password'];
264
            }
265
        }
266
267
        return $authconfig;
268
    }
269
270
271
    /**
272
     * Getter for the LDAP connection object. Created this getter
273
     * rather than setting in the constructor to avoid unnecessarily
274
     * connecting to LDAP when it might not be needed.
275
     *
276
     * @return \SimpleSAML\Module\ldap\Auth\Ldap
277
     */
278
    protected function getLdap(): Ldap
279
    {
280
        // Get the connection specific options
281
        $hostname   = $this->config->getString('ldap.hostname');
282
        $port       = $this->config->getInteger('ldap.port', 389);
283
        $enable_tls = $this->config->getBoolean('ldap.enable_tls', false);
284
        $debug      = $this->config->getBoolean('ldap.debug', false);
285
        $referrals  = $this->config->getBoolean('ldap.referrals', true);
286
        $timeout    = $this->config->getInteger('ldap.timeout', 0);
287
        $username   = $this->config->getString('ldap.username', null);
288
        $password   = $this->config->getString('ldap.password', null);
289
290
        // Log the LDAP connection
291
        Logger::debug(
292
            $this->title . 'Connecting to LDAP server;' .
293
            ' Hostname: ' . $hostname .
294
            ' Port: ' . $port .
295
            ' Enable TLS: ' . ($enable_tls ? 'Yes' : 'No') .
296
            ' Debug: ' . ($debug ? 'Yes' : 'No') .
297
            ' Referrals: ' . ($referrals ? 'Yes' : 'No') .
298
            ' Timeout: ' . $timeout .
299
            ' Username: ' . $username .
300
            ' Password: ' . (empty($password) ? '' : '********')
301
        );
302
303
        $this->ldap->bind($username, $password);
304
305
        // All done
306
        return $this->ldap;
307
    }
308
309
310
    /**
311
     * Initialize the Ldap-object
312
     *
313
     * @return void
314
     */
315
    private function initializeLdap(): void
316
    {
317
        // Get the connection specific options
318
        $hostname   = $this->config->getString('ldap.hostname');
319
        $port       = $this->config->getInteger('ldap.port', 389);
320
        $enable_tls = $this->config->getBoolean('ldap.enable_tls', false);
321
        $debug      = $this->config->getBoolean('ldap.debug', false);
322
        $referrals  = $this->config->getBoolean('ldap.referrals', true);
323
        $timeout    = $this->config->getInteger('ldap.timeout', 0);
324
325
        // Connect to the LDAP server to be queried during processing
326
        $this->ldap = new Ldap($hostname, $enable_tls, $debug, $timeout, $port, $referrals);
327
    }
328
329
330
    /**
331
     * Local utility function to get details about a variable,
332
     * basically converting it to a string to be used in a log
333
     * message. The var_export() function returns several lines
334
     * so this will remove the new lines and trim each line.
335
     *
336
     * @param mixed $value
337
     * @return string
338
     */
339
    protected function varExport($value): string
340
    {
341
        if (is_array($value)) {
342
            // remove sensitive data
343
            foreach ($value as $key => &$val) {
344
                if ($key === 'ldap.password') {
345
                    $val = empty($val) ? '' : '********';
346
                }
347
            }
348
            unset($val);
349
        }
350
351
        $export = var_export($value, true);
352
        $lines = explode("\n", $export);
353
        foreach ($lines as &$line) {
354
            $line = trim($line);
355
        }
356
        return implode(' ', $lines);
357
    }
358
}
359