Passed
Pull Request — master (#28)
by Tim
01:57
created

AttributeAddUsersGroups::getGroups()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 143
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 74
c 1
b 0
f 0
dl 0
loc 143
rs 7.945
cc 6
nc 5
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
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
        // Make the array if it is not set already
90
        if (!isset($attributes[$map['groups']])) {
91
            $attributes[$map['groups']] = [];
92
        }
93
94
        // Must be an array, else cannot merge groups
95
        if (!is_array($attributes[$map['groups']])) {
96
            throw new Error\Exception(sprintf(
97
                   '%s : The group attribute [%s] is not an array of group DNs. %s',
98
                    $this->title,
99
                    $map['groups'],
100
                    $this->varExport($attributes[$map['groups']])
101
            ));
102
        }
103
104
        // Add the users group(s)
105
        $group_attribute = &$attributes[$map['groups']];
106
        $group_attribute = array_merge($group_attribute, $groups);
107
        $group_attribute = array_unique($group_attribute);
108
109
        // All done
110
        Logger::debug(sprintf(
111
                '%s : Added users groups to the group attribute[%s]: %s',
112
                $this->title,
113
                $map['groups'],
114
                implode('; ', $groups)
115
        ));
116
    }
117
118
119
    /**
120
     * Will perform a search using the required attribute values from the user to
121
     * get their group membership, recursively.
122
     *
123
     * @throws \SimpleSAML\Error\Exception
124
     * @param array $attributes
125
     * @return array
126
     */
127
    protected function getGroups(array $attributes): array
128
    {
129
        // Reference the map, just to make the name shorter
130
        $map = &$this->attribute_map;
131
132
        // Log the request
133
        Logger::debug(sprintf(
134
            '%s : Checking for groups based on the best method for the LDAP product.',
135
            $this->title
136
        ));
137
138
        $ldapUtils = new LdapUtils();
139
        $ldap = $ldapUtils->bind($this->ldapServers, $this->searchUsername, $this->searchPassword);
140
141
        $options = [
142
            'scope' => $this->config->getString('search.scope', Query::SCOPE_SUB),
143
            'timeout' => $this->config->getInteger('timeout', 3),
144
        ];
145
146
        // Based on the directory service, search LDAP for groups
147
        // If any attributes are needed, prepare them before calling search method
148
        switch ($this->product) {
149
            case 'ACTIVEDIRECTORY':
150
                $arrayUtils = new Utils\Arrays();
151
152
                // Log the AD specific search
153
                Logger::debug(sprintf(
154
                    '%s : Searching LDAP using ActiveDirectory specific method.',
155
                    $this->title
156
                ));
157
158
                // Make sure the defined dn attribute exists
159
                if (!isset($attributes[$map['dn']])) {
160
                    throw new Error\Exception(sprintf(
161
                        "%s : The DN attribute [%s] is not defined in the user's Attributes: %s",
162
                        $this->title,
163
                        $map['dn'],
164
                        implode(', ', array_keys($attributes)),
165
                    ));
166
                }
167
168
                // DN attribute must have a value
169
                if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) {
170
                    throw new Error\Exception(sprintf(
171
                        '%s : The DN attribute [%s] does not have a [0] value defined. %s',
172
                        $this->title,
173
                        $map['dn'],
174
                        $this->varExport($attributes[$map['dn']])
175
                    ));
176
                }
177
178
                // Log the search
179
                Logger::debug(sprintf(
180
                    '%s : Searching ActiveDirectory group membership.'
181
                        . ' DN: %s DN Attribute: %s Member Attribute: %s Type Attribute: %s Type Value: %s Base: %s',
182
                    $this->title,
183
                    $attributes[$map['dn']][0],
184
                    $map['dn'],
185
                    $map['member'],
186
                    $map['type'],
187
                    $this->type_map['group'],
188
                    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...
189
                ));
190
191
                $filter = sprintf(
192
                    "(%s=%s)(%s=%s)",
193
                    $map['type'],
194
                    $this->type_map['group'],
195
                    $map['member'] . ':1.2.840.113556.1.4.1941:',
196
                    $attributes[$map['dn']][0]
197
                );
198
//                $groups = $this->getGroupsActiveDirectory($attributes);
199
                break;
200
            case 'OPENLDAP':
201
                // Log the OpenLDAP specific search
202
                Logger::debug(sprintf(
203
                    '%s : Searching LDAP using OpenLDAP specific method.',
204
                    $this->title
205
                ));
206
207
                Logger::debug(sprintf(
208
                    '%s : Searching for groups in base [%s] with filter (%s=%s) and attributes %s',
209
                    $this->title,
210
                    implode(', ', $this->searchBase),
211
                    $map['memberof'],
212
                    $attributes[$map['username']][0],
213
                    $map['member']
214
                ));
215
216
                $filter = sprintf('(%s=%s)', $map['memberof'], $attributes[$map['username']][0]);
217
                break;
218
            default:
219
                Logger::debug(sprintf(
220
                    '%s : Checking DNs for groups. DNs: %s Attributes: %s, %s Group Type: %s',
221
                    $this->title,
222
                    implode('; ', $memberof),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $memberof seems to be never defined.
Loading history...
223
                    $map['memberof'],
224
                    $map['type'],
225
                    $this->type_map['group']
226
                ));
227
228
/**
229
230
                // Log the general search
231
                Logger::debug(
232
                    $this->title . 'Searching LDAP using the default search method.'
233
                );
234
235
                // Make sure the defined memberOf attribute exists
236
                if (!isset($attributes[$map['memberof']])) {
237
                    throw new Error\Exception(
238
                        $this->title . 'The memberof attribute [' . $map['memberof'] .
239
                        '] is not defined in the user\'s Attributes: ' . implode(', ', array_keys($attributes))
240
                    );
241
                }
242
243
                // MemberOf must be an array of group DN's
244
                if (!is_array($attributes[$map['memberof']])) {
245
                    throw new Error\Exception(
246
                        $this->title . 'The memberof attribute [' . $map['memberof'] .
247
                        '] is not an array of group DNs. ' . $this->varExport($attributes[$map['memberof']])
248
                    );
249
-                }
250
251
                // Search for the users group membership, recursively
252
                $groups = $this->search($attributes[$map['memberof']]);
253
*/
254
        }
255
256
        $entries = $ldapUtils->searchForMultiple(
0 ignored issues
show
Unused Code introduced by
The assignment to $entries is dead and can be removed.
Loading history...
257
            $ldap,
258
            $this->searchBase,
259
            $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...
260
            $options,
261
            true
262
        );
263
264
        // All done
265
        Logger::debug(
266
            $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...
267
        );
268
269
        return $groups;
270
    }
271
272
273
    /**
274
     * OpenLDAP optimized search
275
     * using the required attribute values from the user to
276
     * get their group membership, recursively.
277
     *
278
     * @throws \SimpleSAML\Error\Exception
279
     * @param array $attributes
280
     * @return array
281
    protected function getGroupsOpenLdap(array $attributes): array
282
    {
283
        $groups = [];
284
        try {
285
            // Intention is to filter in 'ou=groups,dc=example,dc=com' for
286
            // '(memberUid = <value of attribute.username>)' and take only the attributes 'cn' (=name of the group)
287
            //
288
            $all_groups = $ldapUtils->searchForMultiple(
289
                $openldap_base,
290
                array_merge(
291
                    [
292
                        $map['memberof'] => $attributes[$map['username']][0]
293
                    ],
294
                    $this->additional_filters
295
                ),
296
                [$map['return']]
297
            );
298
        } catch (Error\UserNotFound $e) {
299
            return $groups; // if no groups found return with empty (still just initialized) groups array
300
        }
301
302
        // run through all groups and add each to our groups array
303
        foreach ($all_groups as $group_entry) {
304
            $groups[] = $group_entry[$map['return']][0];
305
        }
306
307
        return $groups;
308
    }
309
     */
310
311
312
    /**
313
     * Active Directory optimized search
314
     * using the required attribute values from the user to
315
     * get their group membership, recursively.
316
     *
317
     * @throws \SimpleSAML\Error\Exception
318
     * @param array $attributes
319
     * @return array
320
     */
321
    protected function getGroupsActiveDirectory(array $attributes): array
322
    {
323
        // Reference the map, just to make the name shorter
324
        //$map = &$this->attribute_map;
325
/**
326
        // Make sure the defined dn attribute exists
327
        if (!isset($attributes[$map['dn']])) {
328
            throw new Error\Exception(
329
                $this->title . 'The DN attribute [' . $map['dn'] .
330
                '] is not defined in the user\'s Attributes: ' . implode(', ', array_keys($attributes))
331
            );
332
        }
333
334
        // DN attribute must have a value
335
        if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) {
336
            throw new Error\Exception(
337
                $this->title . 'The DN attribute [' . $map['dn'] .
338
                '] does not have a [0] value defined. ' . $this->varExport($attributes[$map['dn']])
339
            );
340
        }
341
*/
342
        // Pass to the AD specific search
343
        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...
344
    }
345
346
347
    /**
348
     * Looks for groups from the list of DN's passed. Also
349
     * recursively searches groups for further membership.
350
     * Avoids loops by only searching a DN once. Returns
351
     * the list of groups found.
352
     *
353
     * @param array $memberof
354
     * @return array
355
     */
356
    protected function search(array $memberof): array
357
    {
358
        // Used to determine what DN's have already been searched
359
        static $searched = [];
360
361
        // Init the groups variable
362
        $groups = [];
363
364
        // Shorten the variable name
365
        //$map = &$this->attribute_map;
366
367
        // Log the search
368
/**
369
        Logger::debug(
370
            $this->title . 'Checking DNs for groups.' .
371
            ' DNs: ' . implode('; ', $memberof) .
372
            ' Attributes: ' . $map['memberof'] . ', ' . $map['type'] .
373
            ' Group Type: ' . $this->type_map['group']
374
        );
375
*/
376
        // Work out what attributes to get for a group
377
        $use_group_name = false;
378
        $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...
379
        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...
380
            $get_attributes[] = $map['name'];
381
            $use_group_name = true;
382
        }
383
384
        // Check each DN of the passed memberOf
385
        foreach ($memberof as $dn) {
386
            // Avoid infinite loops, only need to check a DN once
387
            if (isset($searched[$dn])) {
388
                continue;
389
            }
390
391
            // Track all DN's that are searched
392
            // Use DN for key as well, isset() is faster than in_array()
393
            $searched[$dn] = $dn;
394
395
            // Query LDAP for the attribute values for the DN
396
            try {
397
                $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

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