Passed
Push — master ( b73211...c6bfae )
by Tim
01:54
created

BaseFilter   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 318
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 120
c 4
b 0
f 0
dl 0
loc 318
rs 9.36
wmc 38

4 Methods

Rating   Name   Duplication   Size   Complexity  
B getLdap() 0 36 6
F parseAuthSourceConfig() 0 84 23
A varExport() 0 18 6
A __construct() 0 74 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
        // Set all the filter values, setting defaults if needed
132
        $this->base_dn = $this->config->getArrayizeString('ldap.basedn', '');
133
        $this->product = $this->config->getString('ldap.product', '');
134
135
        // Cleanup the directory service, so that it is easier for
136
        // child classes to determine service name consistently
137
        $this->product = trim($this->product);
138
        $this->product = strtoupper($this->product);
139
140
        // Log the member values retrieved above
141
        Logger::debug(
142
            $this->title . 'Configuration values retrieved;' .
143
            ' BaseDN: ' . $this->varExport($this->base_dn) .
144
            ' Product: ' . $this->varExport($this->product)
145
        );
146
147
        // Setup the attribute map which will be used to search LDAP
148
        $this->attribute_map = [
149
            'dn'       => $this->config->getString('attribute.dn', 'distinguishedName'),
150
            'groups'   => $this->config->getString('attribute.groups', 'groups'),
151
            'member'   => $this->config->getString('attribute.member', 'member'),
152
            'memberof' => $this->config->getString('attribute.memberof', 'memberOf'),
153
            'name'     => $this->config->getString('attribute.groupname', 'name'),
154
            'type'     => $this->config->getString('attribute.type', 'objectClass'),
155
            'username' => $this->config->getString('attribute.username', 'sAMAccountName')
156
        ];
157
158
        // Log the attribute map
159
        Logger::debug(
160
            $this->title . 'Attribute map created: ' . $this->varExport($this->attribute_map)
161
        );
162
163
        // Setup the object type map which is used to determine a DNs' type
164
        $this->type_map = [
165
            'group' => $this->config->getString('type.group', 'group'),
166
            'user'  => $this->config->getString('type.user', 'user')
167
        ];
168
169
        // Log the type map
170
        Logger::debug(
171
            $this->title . 'Type map created: ' . $this->varExport($this->type_map)
172
        );
173
    }
174
175
176
    /**
177
     * Parse authsource config
178
     *
179
     * @param string $as The name of the authsource
180
     */
181
    private function parseAuthSourceConfig(string $as) : array
182
    {
183
        // Log the authsource request
184
        Logger::debug(
185
            $this->title . 'Attempting to get configuration values from authsource [' . $as . ']'
186
        );
187
188
        // Get the authsources file, which should contain the config
189
        $authsources = Configuration::getConfig('authsources.php');
190
191
        // Verify that the authsource config exists
192
        if (!$authsources->hasValue($as)) {
193
            throw new Error\Exception(
194
                $this->title . 'Authsource [' . $as . '] defined in filter parameters not found in authsources.php'
195
            );
196
        }
197
198
        // Get just the specified authsource config values
199
        $authsource = $authsources->getArray($as);
200
201
        // Make sure it is an ldap source
202
        if (isset($authsource[0]) && !in_array($authsource[0], self::$ldapsources)) {
203
            throw new Error\Exception(
204
                $this->title . 'Authsource [' . $as . '] specified in filter parameters is not an ldap:LDAP type'
205
            );
206
        }
207
208
        // Build the authsource config
209
        $authconfig = [];
210
        if (isset($authsource['hostname'])) {
211
            $authconfig['ldap.hostname']   = $authsource['hostname'];
212
        }
213
        if (isset($authsource['enable_tls'])) {
214
            $authconfig['ldap.enable_tls'] = $authsource['enable_tls'];
215
        }
216
        if (isset($authsource['port'])) {
217
            $authconfig['ldap.port']       = $authsource['port'];
218
        }
219
        if (isset($authsource['timeout'])) {
220
            $authconfig['ldap.timeout']    = $authsource['timeout'];
221
        }
222
        if (isset($authsource['debug'])) {
223
            $authconfig['ldap.debug']      = $authsource['debug'];
224
        }
225
        if (isset($authsource['referrals'])) {
226
            $authconfig['ldap.referrals']  = $authsource['referrals'];
227
        }
228
229
        // only set when search.enabled = true
230
        if (isset($authsource['search.enable']) && $authsource['search.enable']) {
231
            if (isset($authsource['search.base'])) {
232
                $authconfig['ldap.basedn'] = $authsource['search.base'];
233
            }
234
            if (isset($authsource['search.scope'])) {
235
                $authconfig['ldap.scope'] = $authsource['search.scope'];
236
            }
237
            if (isset($authsource['search.username'])) {
238
                $authconfig['ldap.username']   = $authsource['search.username'];
239
            }
240
            if (isset($authsource['search.password'])) {
241
                $authconfig['ldap.password']   = $authsource['search.password'];
242
            }
243
244
            // Only set the username attribute if the authsource specifies one attribute
245
            if (
246
                isset($authsource['search.attributes'])
247
                && is_array($authsource['search.attributes'])
248
                && count($authsource['search.attributes']) == 1
249
            ) {
250
                $authconfig['attribute.username'] = reset($authsource['search.attributes']);
251
            }
252
        }
253
254
        // only set when priv.read = true
255
        if (isset($authsource['priv.read']) && $authsource['priv.read']) {
256
            if (isset($authsource['priv.username'])) {
257
                $authconfig['ldap.username'] = $authsource['priv.username'];
258
            }
259
            if (isset($authsource['priv.password'])) {
260
                $authconfig['ldap.password'] = $authsource['priv.password'];
261
            }
262
        }
263
264
        return $authconfig;
265
    }
266
267
268
    /**
269
     * Getter for the LDAP connection object. Created this getter
270
     * rather than setting in the constructor to avoid unnecessarily
271
     * connecting to LDAP when it might not be needed.
272
     *
273
     * @return \SimpleSAML\Module\ldap\Auth\Ldap
274
     */
275
    protected function getLdap(): Ldap
276
    {
277
        // Check if already connected
278
        if (isset($this->ldap)) {
279
            return $this->ldap;
280
        }
281
282
        // Get the connection specific options
283
        $hostname   = $this->config->getString('ldap.hostname');
284
        $port       = $this->config->getInteger('ldap.port', 389);
285
        $enable_tls = $this->config->getBoolean('ldap.enable_tls', false);
286
        $debug      = $this->config->getBoolean('ldap.debug', false);
287
        $referrals  = $this->config->getBoolean('ldap.referrals', true);
288
        $timeout    = $this->config->getInteger('ldap.timeout', 0);
289
        $username   = $this->config->getString('ldap.username', null);
290
        $password   = $this->config->getString('ldap.password', null);
291
292
        // Log the LDAP connection
293
        Logger::debug(
294
            $this->title . 'Connecting to LDAP server;' .
295
            ' Hostname: ' . $hostname .
296
            ' Port: ' . $port .
297
            ' Enable TLS: ' . ($enable_tls ? 'Yes' : 'No') .
298
            ' Debug: ' . ($debug ? 'Yes' : 'No') .
299
            ' Referrals: ' . ($referrals ? 'Yes' : 'No') .
300
            ' Timeout: ' . $timeout .
301
            ' Username: ' . $username .
302
            ' Password: ' . (empty($password) ? '' : '********')
303
        );
304
305
        // Connect to the LDAP server to be queried during processing
306
        $this->ldap = new Ldap($hostname, $enable_tls, $debug, $timeout, $port, $referrals);
307
        $this->ldap->bind($username, $password);
308
309
        // All done
310
        return $this->ldap;
311
    }
312
313
314
    /**
315
     * Local utility function to get details about a variable,
316
     * basically converting it to a string to be used in a log
317
     * message. The var_export() function returns several lines
318
     * so this will remove the new lines and trim each line.
319
     *
320
     * @param mixed $value
321
     * @return string
322
     */
323
    protected function varExport($value): string
324
    {
325
        if (is_array($value)) {
326
            // remove sensitive data
327
            foreach ($value as $key => &$val) {
328
                if ($key === 'ldap.password') {
329
                    $val = empty($val) ? '' : '********';
330
                }
331
            }
332
            unset($val);
333
        }
334
335
        $export = var_export($value, true);
336
        $lines = explode("\n", $export);
337
        foreach ($lines as &$line) {
338
            $line = trim($line);
339
        }
340
        return implode(' ', $lines);
341
    }
342
}
343