Passed
Pull Request — master (#28)
by Tim
02:30
created

BaseFilter::getLdap()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 20
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 29
rs 9.2888

1 Method

Rating   Name   Duplication   Size   Complexity  
A BaseFilter::varExport() 0 18 6
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
use SimpleSAML\Module\ldap\Utils;
23
24
abstract class BaseFilter extends \SimpleSAML\Auth\ProcessingFilter
25
{
26
    // TODO: Support ldap:LDAPMulti, if possible
27
    protected static array $ldapsources = ['ldap:Ldap', 'authX509:X509userCert'];
28
29
    /**
30
     * List of attribute "alias's" linked to the real attribute
31
     * name. Used for abstraction / configuration of the LDAP
32
     * attribute names, which may change between dir service.
33
     *
34
     * @var array
35
     */
36
    protected array $attribute_map;
37
38
    /**
39
     * The base DN of the LDAP connection. Used when searching
40
     * the LDAP server.
41
     *
42
     * @var string|array
43
     */
44
    protected $base_dn;
45
46
    /**
47
     * The construct method will change the filter config into
48
     * a \SimpleSAML\Configuration object and store it here for
49
     * later use, if needed.
50
     *
51
     * @var \SimpleSAML\Configuration
52
     */
53
    protected Configuration $config;
54
55
    /**
56
     * Array of LDAP connection objects. Stored here to be accessed later during processing.
57
     *
58
     * @var \Symfony\Component\Ldap\Ldap[]
59
     */
60
    private array $ldapServers;
61
62
    /**
63
     * The class "title" used in logging and exception messages.
64
     * This should be prepended to the beginning of the message.
65
     *
66
     * @var string
67
     */
68
    protected string $title = 'ldap:BaseFilter : ';
69
70
    /**
71
     * List of LDAP object types, used to determine the type of
72
     * object that a DN references.
73
     *
74
     * @var array
75
     */
76
    protected array $type_map;
77
78
79
    /**
80
     * Checks the authsource, if defined, for configuration values
81
     * to the LDAP server. Then sets up the LDAP connection for the
82
     * instance/object and stores everything in class members.
83
     *
84
     * @throws \SimpleSAML\Error\Exception
85
     * @param array &$config
86
     * @param mixed $reserved
87
     */
88
    public function __construct(array &$config, $reserved)
89
    {
90
        parent::__construct($config, $reserved);
91
92
        // Change the class $title to match it's true name
93
        // This way if the class is extended the proper name is used
94
        $classname = get_class($this);
95
        $classname = explode('_', $classname);
96
        $this->title = 'ldap:' . end($classname) . ' : ';
97
98
        // Log the construction
99
        Logger::debug($this->title . 'Creating and configuring the filter.');
100
101
        // If an authsource was defined (an not empty string)...
102
        if (isset($config['authsource']) && $config['authsource'] !== '') {
103
            $authconfig = $this->parseAuthSourceConfig($config['authsource']);
104
105
            // Merge the authsource config with the filter config,
106
            // but have the filter config override the authsource config
107
            $config = array_merge($authconfig, $config);
108
109
            // Authsource complete
110
            Logger::debug(
111
                $this->title . 'Retrieved authsource [' . $config['authsource'] .
112
                '] configuration values: ' . $this->varExport($authconfig)
113
            );
114
        }
115
116
        // Convert the config array to a config class,
117
        // that way we can verify type and define defaults.
118
        // Store in the instance in-case needed later, by a child class.
119
        $this->config = Configuration::loadFromArray($config, 'ldap:AuthProcess');
120
121
        // Initialize the Ldap-object
122
        $this->ldapServers = $this->initializeLdap();
123
124
        // Set all the filter values, setting defaults if needed
125
        $this->base_dn = $this->config->getArrayizeString('search.base', '');
126
127
        // Log the member values retrieved above
128
        Logger::debug(
129
            $this->title . 'Configuration values retrieved;' .
130
            ' BaseDN: ' . $this->varExport($this->base_dn)
131
        );
132
133
        // Setup the attribute map which will be used to search LDAP
134
        $this->attribute_map = [
135
            'dn'       => $this->config->getString('attribute.dn', 'distinguishedName'),
136
            'groups'   => $this->config->getString('attribute.groups', 'groups'),
137
            'member'   => $this->config->getString('attribute.member', 'member'),
138
            'memberof' => $this->config->getString('attribute.memberof', 'memberOf'),
139
            'name'     => $this->config->getString('attribute.groupname', 'name'),
140
            'return'   => $this->config->getString('attribute.return', 'distinguishedName'),
141
            'type'     => $this->config->getString('attribute.type', 'objectClass'),
142
            'username' => $this->config->getString('attribute.username', 'sAMAccountName')
143
        ];
144
145
        // Log the attribute map
146
        Logger::debug(
147
            $this->title . 'Attribute map created: ' . $this->varExport($this->attribute_map)
148
        );
149
150
        // Setup the object type map which is used to determine a DNs' type
151
        $this->type_map = [
152
            'group' => $this->config->getString('type.group', 'group'),
153
            'user'  => $this->config->getString('type.user', 'user')
154
        ];
155
156
        // Log the type map
157
        Logger::debug(
158
            $this->title . 'Type map created: ' . $this->varExport($this->type_map)
159
        );
160
    }
161
162
163
    /**
164
     * Parse authsource config
165
     *
166
     * @param string $as The name of the authsource
167
     */
168
    private function parseAuthSourceConfig(string $as) : array
169
    {
170
        // Log the authsource request
171
        Logger::debug(
172
            $this->title . 'Attempting to get configuration values from authsource [' . $as . ']'
173
        );
174
175
        // Get the authsources file, which should contain the config
176
        $authsources = Configuration::getConfig('authsources.php');
177
178
        // Verify that the authsource config exists
179
        if (!$authsources->hasValue($as)) {
180
            throw new Error\Exception(
181
                $this->title . 'Authsource [' . $as . '] defined in filter parameters not found in authsources.php'
182
            );
183
        }
184
185
        // Get just the specified authsource config values
186
        $authsource = $authsources->getArray($as);
187
188
        // Make sure it is an ldap source
189
        if (isset($authsource[0]) && !in_array($authsource[0], self::$ldapsources)) {
190
            throw new Error\Exception(
191
                $this->title . 'Authsource [' . $as . '] specified in filter parameters is not an ldap:LDAP type'
192
            );
193
        }
194
195
        // Build the authsource config
196
        $authconfig = [];
197
        if (isset($authsource['connection_string'])) {
198
            $authconfig['connection_string'] = $authsource['connection_string'];
199
        }
200
        if (isset($authsource['encryption'])) {
201
            $authconfig['encryption'] = $authsource['encryption'];
202
        }
203
        if (isset($authsource['version'])) {
204
            $authconfig['version'] = $authsource['version'];
205
        }
206
        if (isset($authsource['timeout'])) {
207
            $authconfig['timeout'] = $authsource['timeout'];
208
        }
209
        if (isset($authsource['debug'])) {
210
            $authconfig['debug']      = $authsource['debug'];
211
        }
212
        if (isset($authsource['referrals'])) {
213
            $authconfig['referrals']  = $authsource['referrals'];
214
        }
215
216
        // only set when search.enabled = true
217
        if (isset($authsource['search.enable']) && ($authsource['search.enable'] === true)) {
218
            if (isset($authsource['search.base'])) {
219
                $authconfig['search.base'] = $authsource['search.base'];
220
            }
221
            if (isset($authsource['search.scope'])) {
222
                $authconfig['search.scope'] = $authsource['search.scope'];
223
            }
224
            if (isset($authsource['search.username'])) {
225
                $authconfig['search.username']   = $authsource['search.username'];
226
            }
227
            if (isset($authsource['search.password'])) {
228
                $authconfig['search.password']   = $authsource['search.password'];
229
            }
230
231
            // Only set the username attribute if the authsource specifies one attribute
232
            if (
233
                isset($authsource['search.attributes'])
234
                && is_array($authsource['search.attributes'])
235
                && count($authsource['search.attributes']) == 1
236
            ) {
237
                $authconfig['attribute.username'] = reset($authsource['search.attributes']);
238
            }
239
        }
240
241
        // only set when priv.read = true
242
        if (isset($authsource['priv.read']) && $authsource['priv.read']) {
243
            if (isset($authsource['priv.username'])) {
244
                $authconfig['priv.username'] = $authsource['priv.username'];
245
            }
246
            if (isset($authsource['priv.password'])) {
247
                $authconfig['priv.password'] = $authsource['priv.password'];
248
            }
249
        }
250
251
        return $authconfig;
252
    }
253
254
255
    /**
256
     * Initialize the Ldap-object
257
     *
258
     * @return array
259
     */
260
    private function initializeLdap(): array
261
    {
262
        $ldapUtils = new Utils\Ldap();
263
264
        return $ldapUtils->create(
265
            explode(' ', $this->config->getString('connection_string')),
266
            $this->config->getString('encryption', 'ssl'),
267
            $this->config->getInteger('version', 3),
268
            $this->config->getString('extension', 'ext_ldap'),
269
            $this->config->getBoolean('debug', false),
270
            [
271
                'network_timeout' => $this->config->getInteger('timeout', 3),
272
                'referrals' => $this->config->getBoolean('referrals', false),
273
            ]
274
        );
275
    }
276
277
278
    /**
279
     * Local utility function to get details about a variable,
280
     * basically converting it to a string to be used in a log
281
     * message. The var_export() function returns several lines
282
     * so this will remove the new lines and trim each line.
283
     *
284
     * @param mixed $value
285
     * @return string
286
     */
287
    protected function varExport($value): string
288
    {
289
        if (is_array($value)) {
290
            // remove sensitive data
291
            foreach ($value as $key => &$val) {
292
                if ($key === 'search.password') {
293
                    $val = empty($val) ? '' : '********';
294
                }
295
            }
296
            unset($val);
297
        }
298
299
        $export = var_export($value, true);
300
        $lines = explode("\n", $export);
301
        foreach ($lines as &$line) {
302
            $line = trim($line);
303
        }
304
        return implode(' ', $lines);
305
    }
306
}
307