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

AttributeAddUsersGroups::getGroups()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 150
Code Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
eloc 78
c 3
b 0
f 0
nc 5
nop 1
dl 0
loc 150
rs 7.8577

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

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