Passed
Pull Request — master (#28)
by Tim
03:08
created

AttributeAddUsersGroups::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 1
nc 1
nop 2
1
<?php
2
3
/**
4
 * Does a reverse membership lookup on the logged in user,
5
 * looking for groups it is a member of and adds them to
6
 * a defined attribute, in DN format.
7
 *
8
 * @package simplesamlphp/simplesamlphp-module-ldap
9
 */
10
11
declare(strict_types=1);
12
13
namespace SimpleSAML\Module\ldap\Auth\Process;
14
15
use SimpleSAML\Assert\Assert;
16
use SimpleSAML\Error;
17
use SimpleSAML\Logger;
18
use SimpleSAML\Module\ldap\Utils\Ldap as LdapUtils;
19
use SimpleSAML\Utils;
20
use Symfony\Component\Ldap\Adapter\ExtLdap\Query;
21
22
class AttributeAddUsersGroups extends BaseFilter
23
{
24
    /** @var string */
25
    protected string $searchUsername;
26
27
    /** @var string */
28
    protected string $searchPassword;
29
30
    /** @var string */
31
    protected string $product;
32
33
34
    /**
35
     * Initialize this filter.
36
     *
37
     * @param array $config Configuration information about this filter.
38
     * @param mixed $reserved For future use.
39
     */
40
    public function __construct(array $config, $reserved)
41
    {
42
        parent::__construct($config, $reserved);
43
44
        // Get filter specific config options
45
        $this->searchUsername = $this->config->getString('search.username');
46
        $this->searchPassword = $this->config->getString('search.password', null);
47
        $this->product = $this->config->getString('product', 'ActiveDirectory');
48
    }
49
50
51
    /**
52
     * LDAP search filters to be added to the base filters for this authproc-filter.
53
     * It's an array of key => value pairs that will be translated to (key=value) in the ldap query.
54
     *
55
     * @var array
56
     */
57
    protected array $additional_filters;
58
59
60
    /**
61
     * This is run when the filter is processed by SimpleSAML.
62
     * It will attempt to find the current users groups using
63
     * the best method possible for the LDAP product. The groups
64
     * are then added to the request attributes.
65
     *
66
     * @throws \SimpleSAML\Error\Exception
67
     * @param array &$state
68
     */
69
    public function process(array &$state): void
70
    {
71
        Assert::keyExists($state, 'Attributes');
72
73
        // Log the process
74
        Logger::debug(sprintf(
75
            '%s : Attempting to get the users groups...',
76
            $this->title
77
        ));
78
79
        $this->additional_filters = $this->config->getArray('additional_filters', []);
80
81
        // Reference the attributes, just to make the names shorter
82
        $attributes = &$state['Attributes'];
83
        $map = &$this->attribute_map;
84
        $distinguishedName = $attributes[$map['dn']][0];
85
86
        // Get the users groups from LDAP
87
        $groups = $this->getGroups($distinguishedName);
88
89
        // If there are none, do not proceed
90
        if (empty($groups)) {
91
            return;
92
        }
93
94
        // Make the array if it is not set already
95
        if (!isset($attributes[$map['groups']])) {
96
            $attributes[$map['groups']] = [];
97
        }
98
99
        // Must be an array, else cannot merge groups
100
        if (!is_array($attributes[$map['groups']])) {
101
            throw new Error\Exception(sprintf(
102
                '%s : The group attribute [%s] is not an array of group DNs. %s',
103
                $this->title,
104
                $map['groups'],
105
                $this->varExport($attributes[$map['groups']])
106
            ));
107
        }
108
109
        // Add the users group(s)
110
        $group_attribute = &$attributes[$map['groups']];
111
        $group_attribute = array_merge($group_attribute, $groups);
112
        $group_attribute = array_unique($group_attribute);
113
114
        // All done
115
        Logger::debug(sprintf(
116
            '%s : Added users groups to the group attribute[%s]: %s',
117
            $this->title,
118
            $map['groups'],
119
            implode('; ', $groups)
120
        ));
121
    }
122
123
124
    /**
125
     * Will perform a search using the required attribute values from the user to
126
     * get their group membership, recursively.
127
     *
128
     * @throws \SimpleSAML\Error\Exception
129
     * @param array $attributes
130
     * @return array
131
     */
132
    protected function getGroups(array $attributes): array
133
    {
134
        // Reference the map, just to make the name shorter
135
        $map = &$this->attribute_map;
136
137
        // Log the request
138
        Logger::debug(sprintf(
139
            '%s : Checking for groups based on the best method for the LDAP product.',
140
            $this->title
141
        ));
142
143
        $ldapUtils = new LdapUtils();
144
        $ldap = $ldapUtils->bind($this->ldapServers, $this->searchUsername, $this->searchPassword);
145
146
        $options = [
147
            'scope' => $this->config->getString('search.scope', Query::SCOPE_SUB),
148
            'timeout' => $this->config->getInteger('timeout', 3),
149
        ];
150
151
        // Based on the directory service, search LDAP for groups
152
        // If any attributes are needed, prepare them before calling search method
153
        switch ($this->product) {
154
            case 'ACTIVEDIRECTORY':
155
                $arrayUtils = new Utils\Arrays();
156
157
                // Log the AD specific search
158
                Logger::debug(sprintf(
159
                    '%s : Searching LDAP using ActiveDirectory specific method.',
160
                    $this->title
161
                ));
162
163
                // Make sure the defined dn attribute exists
164
                if (!isset($attributes[$map['dn']])) {
165
                    throw new Error\Exception(sprintf(
166
                        "%s : The DN attribute [%s] is not defined in the user's Attributes: %s",
167
                        $this->title,
168
                        $map['dn'],
169
                        implode(', ', array_keys($attributes)),
170
                    ));
171
                }
172
173
                // DN attribute must have a value
174
                if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) {
175
                    throw new Error\Exception(sprintf(
176
                        '%s : The DN attribute [%s] does not have a [0] value defined. %s',
177
                        $this->title,
178
                        $map['dn'],
179
                        $this->varExport($attributes[$map['dn']])
180
                    ));
181
                }
182
183
                // Log the search
184
                Logger::debug(sprintf(
185
                    '%s : Searching ActiveDirectory group membership.'
186
                        . ' DN: %s DN Attribute: %s Member Attribute: %s Type Attribute: %s Type Value: %s Base: %s',
187
                    $this->title,
188
                    $attributes[$map['dn']][0],
189
                    $map['dn'],
190
                    $map['member'],
191
                    $map['type'],
192
                    $this->type_map['group'],
193
                    implode('; ', $arrayUtils->arrayize($this->base_dn))
0 ignored issues
show
Bug Best Practice introduced by
The property base_dn does not exist on SimpleSAML\Module\ldap\A...AttributeAddUsersGroups. Did you maybe forget to declare it?
Loading history...
194
                ));
195
196
                $filter = sprintf(
197
                    "(%s=%s)(%s=%s)",
198
                    $map['type'],
199
                    $this->type_map['group'],
200
                    $map['member'] . ':1.2.840.113556.1.4.1941:',
201
                    $attributes[$map['dn']][0]
202
                );
203
//                $groups = $this->getGroupsActiveDirectory($attributes);
204
                break;
205
            case 'OPENLDAP':
206
                // Log the OpenLDAP specific search
207
                Logger::debug(sprintf(
208
                    '%s : Searching LDAP using OpenLDAP specific method.',
209
                    $this->title
210
                ));
211
212
                Logger::debug(sprintf(
213
                    '%s : Searching for groups in base [%s] with filter (%s=%s) and attributes %s',
214
                    $this->title,
215
                    implode(', ', $this->searchBase),
216
                    $map['memberof'],
217
                    $attributes[$map['username']][0],
218
                    $map['member']
219
                ));
220
221
                $filter = sprintf('(%s=%s)', $map['memberof'], $attributes[$map['username']][0]);
222
                break;
223
            default:
224
                Logger::debug(sprintf(
225
                    '%s : Checking DNs for groups. DNs: %s Attributes: %s, %s Group Type: %s',
226
                    $this->title,
227
                    implode('; ', $memberof),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $memberof seems to be never defined.
Loading history...
228
                    $map['memberof'],
229
                    $map['type'],
230
                    $this->type_map['group']
231
                ));
232
233
/**
234
235
                // Log the general search
236
                Logger::debug(
237
                    $this->title . 'Searching LDAP using the default search method.'
238
                );
239
240
                // Make sure the defined memberOf attribute exists
241
                if (!isset($attributes[$map['memberof']])) {
242
                    throw new Error\Exception(
243
                        $this->title . 'The memberof attribute [' . $map['memberof'] .
244
                        '] is not defined in the user\'s Attributes: ' . implode(', ', array_keys($attributes))
245
                    );
246
                }
247
248
                // MemberOf must be an array of group DN's
249
                if (!is_array($attributes[$map['memberof']])) {
250
                    throw new Error\Exception(
251
                        $this->title . 'The memberof attribute [' . $map['memberof'] .
252
                        '] is not an array of group DNs. ' . $this->varExport($attributes[$map['memberof']])
253
                    );
254
-                }
255
256
                // Search for the users group membership, recursively
257
                $groups = $this->search($attributes[$map['memberof']]);
258
*/
259
        }
260
261
        $entries = $ldapUtils->searchForMultiple(
0 ignored issues
show
Unused Code introduced by
The assignment to $entries is dead and can be removed.
Loading history...
262
            $ldap,
263
            $this->searchBase,
264
            $filter,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filter does not seem to be defined for all execution paths leading up to this point.
Loading history...
265
            $options,
266
            true
267
        );
268
269
        // All done
270
        Logger::debug(
271
            $this->title . 'User found to be a member of the groups:' . implode('; ', $groups)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $groups seems to be never defined.
Loading history...
272
        );
273
274
        return $groups;
275
    }
276
277
278
    /**
279
     * OpenLDAP optimized search
280
     * using the required attribute values from the user to
281
     * get their group membership, recursively.
282
     *
283
     * @throws \SimpleSAML\Error\Exception
284
     * @param array $attributes
285
     * @return array
286
    protected function getGroupsOpenLdap(array $attributes): array
287
    {
288
        $groups = [];
289
        try {
290
            // Intention is to filter in 'ou=groups,dc=example,dc=com' for
291
            // '(memberUid = <value of attribute.username>)' and take only the attributes 'cn' (=name of the group)
292
            //
293
            $all_groups = $ldapUtils->searchForMultiple(
294
                $openldap_base,
295
                array_merge(
296
                    [
297
                        $map['memberof'] => $attributes[$map['username']][0]
298
                    ],
299
                    $this->additional_filters
300
                ),
301
                [$map['return']]
302
            );
303
        } catch (Error\UserNotFound $e) {
304
            return $groups; // if no groups found return with empty (still just initialized) groups array
305
        }
306
307
        // run through all groups and add each to our groups array
308
        foreach ($all_groups as $group_entry) {
309
            $groups[] = $group_entry[$map['return']][0];
310
        }
311
312
        return $groups;
313
    }
314
     */
315
316
317
    /**
318
     * Active Directory optimized search
319
     * using the required attribute values from the user to
320
     * get their group membership, recursively.
321
     *
322
     * @throws \SimpleSAML\Error\Exception
323
     * @param array $attributes
324
     * @return array
325
     */
326
    protected function getGroupsActiveDirectory(array $attributes): array
327
    {
328
        // Reference the map, just to make the name shorter
329
        //$map = &$this->attribute_map;
330
/**
331
        // Make sure the defined dn attribute exists
332
        if (!isset($attributes[$map['dn']])) {
333
            throw new Error\Exception(
334
                $this->title . 'The DN attribute [' . $map['dn'] .
335
                '] is not defined in the user\'s Attributes: ' . implode(', ', array_keys($attributes))
336
            );
337
        }
338
339
        // DN attribute must have a value
340
        if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) {
341
            throw new Error\Exception(
342
                $this->title . 'The DN attribute [' . $map['dn'] .
343
                '] does not have a [0] value defined. ' . $this->varExport($attributes[$map['dn']])
344
            );
345
        }
346
*/
347
        // Pass to the AD specific search
348
        return $this->searchActiveDirectory($attributes[$map['dn']][0]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $map seems to be never defined.
Loading history...
349
    }
350
351
352
    /**
353
     * Looks for groups from the list of DN's passed. Also
354
     * recursively searches groups for further membership.
355
     * Avoids loops by only searching a DN once. Returns
356
     * the list of groups found.
357
     *
358
     * @param array $memberof
359
     * @return array
360
     */
361
    protected function search(array $memberof): array
362
    {
363
        // Used to determine what DN's have already been searched
364
        static $searched = [];
365
366
        // Init the groups variable
367
        $groups = [];
368
369
        // Shorten the variable name
370
        //$map = &$this->attribute_map;
371
372
        // Log the search
373
/**
374
        Logger::debug(
375
            $this->title . 'Checking DNs for groups.' .
376
            ' DNs: ' . implode('; ', $memberof) .
377
            ' Attributes: ' . $map['memberof'] . ', ' . $map['type'] .
378
            ' Group Type: ' . $this->type_map['group']
379
        );
380
*/
381
        // Work out what attributes to get for a group
382
        $use_group_name = false;
383
        $get_attributes = [$map['memberof'], $map['type']];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $map seems to be never defined.
Loading history...
384
        if (isset($map['name']) && $map['name']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $map seems to never exist and therefore isset should always be false.
Loading history...
385
            $get_attributes[] = $map['name'];
386
            $use_group_name = true;
387
        }
388
389
        // Check each DN of the passed memberOf
390
        foreach ($memberof as $dn) {
391
            // Avoid infinite loops, only need to check a DN once
392
            if (isset($searched[$dn])) {
393
                continue;
394
            }
395
396
            // Track all DN's that are searched
397
            // Use DN for key as well, isset() is faster than in_array()
398
            $searched[$dn] = $dn;
399
400
            // Query LDAP for the attribute values for the DN
401
            try {
402
                $attributes = $this->getLdap()->getAttributes($dn, $get_attributes);
0 ignored issues
show
Bug introduced by
The method getLdap() does not exist on SimpleSAML\Module\ldap\A...AttributeAddUsersGroups. ( Ignorable by Annotation )

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

402
                $attributes = $this->/** @scrutinizer ignore-call */ getLdap()->getAttributes($dn, $get_attributes);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
403
            } catch (Error\AuthSource $e) {
404
                continue; // DN must not exist, just continue. Logged by the LDAP object
405
            }
406
407
            // Only look for groups
408
            if (!in_array($this->type_map['group'], $attributes[$map['type']], true)) {
409
                continue;
410
            }
411
412
            // Add to found groups array
413
            if ($use_group_name && isset($attributes[$map['name']]) && is_array($attributes[$map['name']])) {
414
                $groups[] = $attributes[$map['name']][0];
415
            } else {
416
                $groups[] = $dn;
417
            }
418
419
            // Recursively search "sub" groups
420
            if (!empty($attributes[$map['memberof']])) {
421
                $groups = array_merge($groups, $this->search($attributes[$map['memberof']]));
422
            }
423
        }
424
425
        // Return only the unique group names
426
        return array_unique($groups);
427
    }
428
429
430
    /**
431
     * Searches LDAP using a ActiveDirectory specific filter,
432
     * looking for group membership for the users DN. Returns
433
     * the list of group DNs retrieved.
434
     *
435
     * @param string $dn
436
     * @return array
437
     */
438
    protected function searchActiveDirectory(string $dn): array
439
    {
440
//        $arrayUtils = new Utils\Arrays();
441
442
//        // Shorten the variable name
443
        //$map = &$this->attribute_map;
444
445
        // Log the search
446
/*
447
        Logger::debug(
448
            $this->title . 'Searching ActiveDirectory group membership.' .
449
            ' DN: ' . $dn .
450
            ' DN Attribute: ' . $map['dn'] .
451
            ' Return Attribute: ' . $map['return'] .
452
            ' Member Attribute: ' . $map['member'] .
453
            ' Type Attribute: ' . $map['type'] .
454
            ' Type Value: ' . $this->type_map['group'] .
455
            ' Base: ' . implode('; ', $arrayUtils->arrayize($this->base_dn))
456
        );
457
*/
458
459
        // AD connections should have this set
460
        //$this->getLdap()->setOption(LDAP_OPT_REFERRALS, 0);
461
462
        // Search AD with the specific recursive flag
463
/**
464
        try {
465
            $entries = $this->getLdap()->searchformultiple(
466
                $this->base_dn,
467
                array_merge(
468
                    [
469
                        $map['type'] => $this->type_map['group'],
470
                        $map['member'] . ':1.2.840.113556.1.4.1941:' => $dn
471
                    ],
472
                    $this->additional_filters
473
                ),
474
                [$map['return']]
475
            );
476
477
        // The search may throw an exception if no entries
478
        // are found, unlikely but possible.
479
        } catch (Error\UserNotFound $e) {
480
            return [];
481
        }
482
*/
483
        //Init the groups
484
        $groups = [];
485
486
        // Check each entry..
487
        foreach ($entries as $entry) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $entries seems to be never defined.
Loading history...
488
            // Check for the DN using the original attribute name
489
            if (isset($entry[$map['return']][0])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $map seems to be never defined.
Loading history...
490
                $groups[] = $entry[$map['return']][0];
491
                continue;
492
            }
493
494
            // Sometimes the returned attribute names are lowercase
495
            if (isset($entry[strtolower($map['return'])][0])) {
496
                $groups[] = $entry[strtolower($map['return'])][0];
497
                continue;
498
            }
499
500
            // AD queries also seem to return the objects dn by default
501
            if (isset($entry['return'])) {
502
                $groups[] = $entry['return'];
503
                continue;
504
            }
505
506
            // Could not find DN, log and continue
507
            Logger::notice(
508
                $this->title . 'The return attribute [' .
509
                implode(', ', [$map['return'], strtolower($map['return'])]) .
510
                '] could not be found in the entry. ' . $this->varExport($entry)
511
            );
512
        }
513
514
        // All done
515
        return $groups;
516
    }
517
}
518