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

BaseFilter::initializeLdap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 13
rs 9.9666
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * This base LDAP filter class can be extended to enable real filter classes direct access
5
 * access to the authsource ldap config and connects to the ldap server.
6
 *
7
 * @package simplesamlphp/simplesamlphp-module-ldap
8
 */
9
10
declare(strict_types=1);
11
12
namespace SimpleSAML\Module\ldap\Auth\Process;
13
14
use SimpleSAML\Configuration;
15
use SimpleSAML\Error;
16
use SimpleSAML\Logger;
17
use SimpleSAML\Module\ldap\Auth\Ldap;
18
use SimpleSAML\Module\ldap\Utils;
19
20
abstract class BaseFilter extends \SimpleSAML\Auth\ProcessingFilter
21
{
22
    // TODO: Support ldap:LDAPMulti, if possible
23
    protected static array $ldapsources = ['ldap:Ldap', 'authX509:X509userCert'];
24
25
    /**
26
     * List of attribute "alias's" linked to the real attribute
27
     * name. Used for abstraction / configuration of the LDAP
28
     * attribute names, which may change between dir service.
29
     *
30
     * @var array
31
     */
32
    protected array $attribute_map;
33
34
    /**
35
     * The base DN of the LDAP connection. Used when searching the LDAP server.
36
     *
37
     * @var array
38
     */
39
    protected array $searchBase;
40
41
    /**
42
     * The construct method will change the filter config into
43
     * a \SimpleSAML\Configuration object and store it here for
44
     * later use, if needed.
45
     *
46
     * @var \SimpleSAML\Configuration
47
     */
48
    protected Configuration $config;
49
50
    /**
51
     * Array of LDAP connection objects. Stored here to be accessed later during processing.
52
     *
53
     * @var \Symfony\Component\Ldap\Ldap[]
54
     */
55
    protected array $ldapServers;
56
57
    /**
58
     * The class "title" used in logging and exception messages.
59
     * This should be prepended to the beginning of the message.
60
     *
61
     * @var string
62
     */
63
    protected string $title = 'ldap:BaseFilter';
64
65
    /**
66
     * List of LDAP object types, used to determine the type of
67
     * object that a DN references.
68
     *
69
     * @var array
70
     */
71
    protected array $type_map;
72
73
74
    /**
75
     * Checks the authsource, if defined, for configuration values
76
     * to the LDAP server. Then sets up the LDAP connection for the
77
     * instance/object and stores everything in class members.
78
     *
79
     * @throws \SimpleSAML\Error\Exception
80
     * @param array &$config
81
     * @param mixed $reserved
82
     */
83
    public function __construct(array &$config, $reserved)
84
    {
85
        parent::__construct($config, $reserved);
86
87
        // Change the class $title to match it's true name
88
        // This way if the class is extended the proper name is used
89
        $classname = get_class($this);
90
        $classname = explode('_', $classname);
91
        $this->title = 'ldap:' . end($classname);
92
93
        // Log the construction
94
        Logger::debug(sprintf('%s : Creating and configuring the filter.', $this->title));
95
96
        // If an authsource was defined (an not empty string)...
97
        if (isset($config['authsource']) && $config['authsource'] !== '') {
98
            $authconfig = $this->parseAuthSourceConfig($config['authsource']);
99
100
            // Merge the authsource config with the filter config,
101
            // but have the filter config override the authsource config
102
            $config = array_merge($authconfig, $config);
103
104
            // Authsource complete
105
            Logger::debug(sprintf(
106
                '%s : Retrieved authsource [%s] configuration values: %s',
107
                $this->title,
108
                $config['authsource'],
109
                $this->varExport($authconfig)
110
            ));
111
        }
112
113
        // Convert the config array to a config class,
114
        // that way we can verify type and define defaults.
115
        // Store in the instance in-case needed later, by a child class.
116
        $this->config = Configuration::loadFromArray($config, 'ldap:AuthProcess');
117
118
        // Initialize the Ldap-object
119
        $this->ldapServers = $this->initializeLdap();
120
121
        // Set all the filter values, setting defaults if needed
122
        $this->searchBase = $this->config->getArrayizeString('search.base', '');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->config->getArrayi...ring('search.base', '') can also be of type string. However, the property $searchBase is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
123
124
        // Log the member values retrieved above
125
        Logger::debug(sprintf(
126
            '%s : Configuration values retrieved; BaseDN: %s',
127
            $this->title,
128
            $this->varExport($this->searchBase)
129
        ));
130
131
        // Setup the attribute map which will be used to search LDAP
132
        $this->attribute_map = [
133
            'dn'       => $this->config->getString('attribute.dn', 'distinguishedName'),
134
            'groups'   => $this->config->getString('attribute.groups', 'groups'),
135
            'member'   => $this->config->getString('attribute.member', 'member'),
136
            'memberof' => $this->config->getString('attribute.memberof', 'memberOf'),
137
            'name'     => $this->config->getString('attribute.groupname', 'name'),
138
            'return'   => $this->config->getString('attribute.return', 'distinguishedName'),
139
            'type'     => $this->config->getString('attribute.type', 'objectClass'),
140
            'username' => $this->config->getString('attribute.username', 'sAMAccountName')
141
        ];
142
143
        // Log the attribute map
144
        Logger::debug(sprintf(
145
            '%s : Attribute map created: $s',
146
            $this->title,
147
            $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(sprintf(
158
            '%s : Type map created: %s',
159
            $this->title,
160
            $this->varExport($this->type_map)
161
        ));
162
    }
163
164
165
    /**
166
     * Parse authsource config
167
     *
168
     * @param string $as The name of the authsource
169
     */
170
    private function parseAuthSourceConfig(string $as): array
171
    {
172
        // Log the authsource request
173
        Logger::debug(sprintf(
174
            '%s : Attempting to get configuration values from authsource [%s]',
175
            $this->title,
176
            $as
177
        ));
178
179
        // Get the authsources file, which should contain the config
180
        $authsources = Configuration::getConfig('authsources.php');
181
182
        // Verify that the authsource config exists
183
        if (!$authsources->hasValue($as)) {
184
            throw new Error\Exception(sprintf(
185
                '%s : Authsource [%s] defined in filter parameters not found in authsources.php',
186
                $this->title,
187
                $as
188
            ));
189
        }
190
191
        // Get just the specified authsource config values
192
        $authsource = $authsources->getArray($as);
193
194
        // Make sure it is an ldap source
195
        if (isset($authsource[0]) && !in_array($authsource[0], self::$ldapsources)) {
196
            throw new Error\Exception(sprintf(
197
                '%s : Authsource [%s] specified in filter parameters is not an ldap:LDAP type',
198
                $this->title,
199
                $as
200
            ));
201
        }
202
203
        // Build the authsource config
204
        $authconfig = [];
205
        if (isset($authsource['connection_string'])) {
206
            $authconfig['connection_string'] = $authsource['connection_string'];
207
        }
208
        if (isset($authsource['encryption'])) {
209
            $authconfig['encryption'] = $authsource['encryption'];
210
        }
211
        if (isset($authsource['version'])) {
212
            $authconfig['version'] = $authsource['version'];
213
        }
214
        if (isset($authsource['timeout'])) {
215
            $authconfig['timeout'] = $authsource['timeout'];
216
        }
217
        if (isset($authsource['debug'])) {
218
            $authconfig['debug']      = $authsource['debug'];
219
        }
220
        if (isset($authsource['referrals'])) {
221
            $authconfig['referrals']  = $authsource['referrals'];
222
        }
223
224
        // only set when search.enabled = true
225
        if (isset($authsource['search.enable']) && ($authsource['search.enable'] === true)) {
226
            if (isset($authsource['search.base'])) {
227
                $authconfig['search.base'] = $authsource['search.base'];
228
            }
229
            if (isset($authsource['search.scope'])) {
230
                $authconfig['search.scope'] = $authsource['search.scope'];
231
            }
232
            if (isset($authsource['search.username'])) {
233
                $authconfig['search.username']   = $authsource['search.username'];
234
            }
235
            if (isset($authsource['search.password'])) {
236
                $authconfig['search.password']   = $authsource['search.password'];
237
            }
238
239
            // Only set the username attribute if the authsource specifies one attribute
240
            if (
241
                isset($authsource['search.attributes'])
242
                && is_array($authsource['search.attributes'])
243
                && count($authsource['search.attributes']) == 1
244
            ) {
245
                $authconfig['attribute.username'] = reset($authsource['search.attributes']);
246
            }
247
        }
248
249
        // only set when priv.read = true
250
        if (isset($authsource['priv.read']) && $authsource['priv.read']) {
251
            if (isset($authsource['priv.username'])) {
252
                $authconfig['priv.username'] = $authsource['priv.username'];
253
            }
254
            if (isset($authsource['priv.password'])) {
255
                $authconfig['priv.password'] = $authsource['priv.password'];
256
            }
257
        }
258
259
        return $authconfig;
260
    }
261
262
263
    /**
264
     * Initialize the Ldap-object
265
     *
266
     * @return array
267
     */
268
    private function initializeLdap(): array
269
    {
270
        $ldapUtils = new Utils\Ldap();
271
272
        return $ldapUtils->create(
273
            explode(' ', $this->config->getString('connection_string')),
274
            $this->config->getString('encryption', 'ssl'),
275
            $this->config->getInteger('version', 3),
276
            $this->config->getString('extension', 'ext_ldap'),
277
            $this->config->getBoolean('debug', false),
278
            [
279
                'network_timeout' => $this->config->getInteger('timeout', 3),
280
                'referrals' => $this->config->getBoolean('referrals', false),
281
            ]
282
        );
283
    }
284
285
286
    /**
287
     * Local utility function to get details about a variable,
288
     * basically converting it to a string to be used in a log
289
     * message. The var_export() function returns several lines
290
     * so this will remove the new lines and trim each line.
291
     *
292
     * @param mixed $value
293
     * @return string
294
     */
295
    protected function varExport($value): string
296
    {
297
        if (is_array($value)) {
298
            // remove sensitive data
299
            foreach ($value as $key => &$val) {
300
                if ($key === 'search.password') {
301
                    $val = empty($val) ? '' : '********';
302
                }
303
            }
304
            unset($val);
305
        }
306
307
        $export = var_export($value, true);
308
        $lines = explode("\n", $export);
309
        foreach ($lines as &$line) {
310
            $line = trim($line);
311
        }
312
        return implode(' ', $lines);
313
    }
314
}
315