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

AttributeAddUsersGroups::search()   B

Complexity

Conditions 11
Paths 16

Size

Total Lines 65
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 30
c 1
b 0
f 0
nc 16
nop 1
dl 0
loc 65
rs 7.3166

How to fix   Long Method    Complexity   

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
21
class AttributeAddUsersGroups extends BaseFilter
22
{
23
    /** @var string */
24
    protected string $searchUsername;
25
26
    /** @var string */
27
    protected string $searchPassword;
28
29
    /** @var string */
30
    protected string $product;
31
32
33
    /**
34
     * Initialize this filter.
35
     *
36
     * @param array $config Configuration information about this filter.
37
     * @param mixed $reserved For future use.
38
     */
39
    public function __construct(array $config, $reserved)
40
    {
41
        parent::__construct($config, $reserved);
42
43
        // Get filter specific config options
44
        $this->searchUsername = $this->config->getString('search.username');
45
        $this->searchPassword = $this->config->getString('search.password', null);
46
        $this->product = $this->config->getString('product', 'ActiveDirectory');
47
    }
48
49
50
    /**
51
     * LDAP search filters to be added to the base filters for this authproc-filter.
52
     * It's an array of key => value pairs that will be translated to (key=value) in the ldap query.
53
     *
54
     * @var array
55
     */
56
    protected array $additional_filters;
57
58
59
    /**
60
     * This is run when the filter is processed by SimpleSAML.
61
     * It will attempt to find the current users groups using
62
     * the best method possible for the LDAP product. The groups
63
     * are then added to the request attributes.
64
     *
65
     * @throws \SimpleSAML\Error\Exception
66
     * @param array $state
67
     */
68
    public function process(array &$state): void
69
    {
70
        Assert::keyExists($state, 'Attributes');
71
72
        // Log the process
73
        Logger::debug(sprintf(
74
            '%s : Attempting to get the users groups...',
75
            $this->title
76
        ));
77
78
        $this->additional_filters = $this->config->getArray('additional_filters', []);
79
80
        // Reference the attributes, just to make the names shorter
81
        $attributes = &$state['Attributes'];
82
        $map = &$this->attribute_map;
83
        $distinguishedName = $attributes[$map['dn']][0];
84
85
        // Get the users groups from LDAP
86
        $groups = $this->getGroups($distinguishedName);
87
88
        // Make the array if it is not set already
89
        if (!isset($attributes[$map['groups']])) {
90
            $attributes[$map['groups']] = [];
91
        }
92
93
        // Must be an array, else cannot merge groups
94
        if (!is_array($attributes[$map['groups']])) {
95
            throw new Error\Exception(sprintf(
96
                   '%s : The group attribute [%s] is not an array of group DNs. %s',
97
                    $this->title,
98
                    $map['groups'],
99
                    $this->varExport($attributes[$map['groups']])
100
            ));
101
        }
102
103
        // Add the users group(s)
104
        $group_attribute = &$attributes[$map['groups']];
105
        $group_attribute = array_merge($group_attribute, $groups);
106
        $group_attribute = array_unique($group_attribute);
107
108
        // All done
109
        Logger::debug(sprintf(
110
                '%s : Added users groups to the group attribute[%s]: %s',
111
                $this->title,
112
                $map['groups'],
113
                implode('; ', $groups)
114
        ));
115
    }
116
117
118
    /**
119
     * Will perform a search using the required attribute values from the user to
120
     * get their group membership, recursively.
121
     *
122
     * @throws \SimpleSAML\Error\Exception
123
     * @param array $attributes
124
     * @return array
125
     */
126
    protected function getGroups(array $attributes): array
127
    {
128
        // Reference the map, just to make the name shorter
129
        $map = &$this->attribute_map;
130
131
        // Log the request
132
        Logger::debug(sprintf(
133
            '%s : Checking for groups based on the best method for the LDAP product.',
134
            $this->title
135
        ));
136
137
        $ldapUtils = new LdapUtils();
138
        $ldap = $ldapUtils->bind($this->ldapServers, $this->searchUsername, $this->searchPassword);
139
140
        $options = [
141
            'scope' => $this->config->getString('search.scope', QUERY::SCOPE_SUB),
142
            'timeout' => $this->config->getInteger('timeout', 3),
143
        ];
144
145
        // Based on the directory service, search LDAP for groups
146
        // If any attributes are needed, prepare them before calling search method
147
        switch ($this->product) {
148
            case 'ACTIVEDIRECTORY':
149
                $arrayUtils = new Utils\Arrays();
150
151
                // Log the AD specific search
152
                Logger::debug(sprintf(
153
                    '%s : Searching LDAP using ActiveDirectory specific method.',
154
                    $this->title
155
                ));
156
157
                // Make sure the defined dn attribute exists
158
                if (!isset($attributes[$map['dn']])) {
159
                    throw new Error\Exception(sprintf(
160
                        "%s : The DN attribute [%s] is not defined in the user's Attributes: %s",
161
                        $this->title,
162
                        $map['dn'],
163
                        implode(', ', array_keys($attributes)),
164
                    );
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ';', expecting ',' or ')' on line 164 at column 21
Loading history...
165
                }
166
167
                // DN attribute must have a value
168
                if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) {
169
                    throw new Error\Exception(sprintf(
170
                        '%s : The DN attribute [%s] does not have a [0] value defined. %s',
171
                        $this->title,
172
                        $map['dn']
173
                        $this->varExport($attributes[$map['dn']])
174
                    ));
175
                }
176
177
                // Log the search
178
                Logger::debug(sprintf(
179
                    '%s : Searching ActiveDirectory group membership.'
180
                        . ' DN: %s DN Attribute: %s Member Attribute: %s Type Attribute: %s Type Value: %s Base: %s',
181
                    $this->title,
182
                    $dn,
183
                    $map['dn'],
184
                    $map['member'],
185
                    $map['type'],
186
                    $this->type_map['group'],
187
                    implode('; ', $arrayUtils->arrayize($this->base_dn))
188
                );
189
190
                $filter = sprintf("(%s=%s)(%s=%s)",
191
                    $map['type'],
192
                    $this->type_map['group'],
193
                    $map['member'] . ':1.2.840.113556.1.4.1941:',
194
                    $dn
195
                );
196
//                $groups = $this->getGroupsActiveDirectory($attributes);
197
                break;
198
            case 'OPENLDAP':
199
                // Log the OpenLDAP specific search
200
                Logger::debug(sprintf(
201
                    '%s : Searching LDAP using OpenLDAP specific method.',
202
                    $this->title
203
                ));
204
205
                Logger::debug(sprintf(
206
                    '%s : Searching for groups in base [%s] with filter (%s=%s) and attributes %s',
207
                    $this->title,
208
                    implode(', ', $this->searchBase),
209
                    $map['memberof'],
210
                    $attributes[$map['username']][0],
211
                    $map['member']
212
                ));
213
214
                $filter = sprintf('(%s=%s)', $map['memberof'], $attributes[$map['username']][0]);
215
                break;
216
            default:
217
                Logger::debug(sprintf(
218
                    '%s : Checking DNs for groups. DNs: %s Attributes: %s, %s Group Type: %s',
219
                    $this->title,
220
                    implode('; ', $memberof),
221
                    $map['memberof'],
222
                    $map['type'],
223
                    $this->type_map['group']
224
                ));
225
226
/**
227
228
                // Log the general search
229
                Logger::debug(
230
                    $this->title . 'Searching LDAP using the default search method.'
231
                );
232
233
                // Make sure the defined memberOf attribute exists
234
                if (!isset($attributes[$map['memberof']])) {
235
                    throw new Error\Exception(
236
                        $this->title . 'The memberof attribute [' . $map['memberof'] .
237
                        '] is not defined in the user\'s Attributes: ' . implode(', ', array_keys($attributes))
238
                    );
239
                }
240
241
                // MemberOf must be an array of group DN's
242
                if (!is_array($attributes[$map['memberof']])) {
243
                    throw new Error\Exception(
244
                        $this->title . 'The memberof attribute [' . $map['memberof'] .
245
                        '] is not an array of group DNs. ' . $this->varExport($attributes[$map['memberof']])
246
                    );
247
-                }
248
249
                // Search for the users group membership, recursively
250
                $groups = $this->search($attributes[$map['memberof']]);
251
*/
252
        }
253
254
        $entries = $ldapUtils->searchForMultiple(
255
            $ldap,
256
            $this->searchBase,
257
            $filter,
258
            $options,
259
            true
260
        );
261
262
        // All done
263
        Logger::debug(
264
            $this->title . 'User found to be a member of the groups:' . implode('; ', $groups)
265
        );
266
267
        return $groups;
268
    }
269
270
271
    /**
272
     * OpenLDAP optimized search
273
     * using the required attribute values from the user to
274
     * get their group membership, recursively.
275
     *
276
     * @throws \SimpleSAML\Error\Exception
277
     * @param array $attributes
278
     * @return array
279
    protected function getGroupsOpenLdap(array $attributes): array
280
    {
281
        $groups = [];
282
        try {
283
            // Intention is to filter in 'ou=groups,dc=example,dc=com' for
284
            // '(memberUid = <value of attribute.username>)' and take only the attributes 'cn' (=name of the group)
285
            //
286
            $all_groups = $ldapUtils->searchForMultiple(
287
                $openldap_base,
288
                array_merge(
289
                    [
290
                        $map['memberof'] => $attributes[$map['username']][0]
291
                    ],
292
                    $this->additional_filters
293
                ),
294
                [$map['return']]
295
            );
296
        } catch (Error\UserNotFound $e) {
297
            return $groups; // if no groups found return with empty (still just initialized) groups array
298
        }
299
300
        // run through all groups and add each to our groups array
301
        foreach ($all_groups as $group_entry) {
302
            $groups[] = $group_entry[$map['return']][0];
303
        }
304
305
        return $groups;
306
    }
307
     */
308
309
310
    /**
311
     * Active Directory optimized search
312
     * using the required attribute values from the user to
313
     * get their group membership, recursively.
314
     *
315
     * @throws \SimpleSAML\Error\Exception
316
     * @param array $attributes
317
     * @return array
318
     */
319
    protected function getGroupsActiveDirectory(array $attributes): array
320
    {
321
        // Reference the map, just to make the name shorter
322
        //$map = &$this->attribute_map;
323
/**
324
        // Make sure the defined dn attribute exists
325
        if (!isset($attributes[$map['dn']])) {
326
            throw new Error\Exception(
327
                $this->title . 'The DN attribute [' . $map['dn'] .
328
                '] is not defined in the user\'s Attributes: ' . implode(', ', array_keys($attributes))
329
            );
330
        }
331
332
        // DN attribute must have a value
333
        if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) {
334
            throw new Error\Exception(
335
                $this->title . 'The DN attribute [' . $map['dn'] .
336
                '] does not have a [0] value defined. ' . $this->varExport($attributes[$map['dn']])
337
            );
338
        }
339
*/
340
        // Pass to the AD specific search
341
        return $this->searchActiveDirectory($attributes[$map['dn']][0]);
342
    }
343
344
345
    /**
346
     * Looks for groups from the list of DN's passed. Also
347
     * recursively searches groups for further membership.
348
     * Avoids loops by only searching a DN once. Returns
349
     * the list of groups found.
350
     *
351
     * @param array $memberof
352
     * @return array
353
     */
354
    protected function search(array $memberof): array
355
    {
356
        // Used to determine what DN's have already been searched
357
        static $searched = [];
358
359
        // Init the groups variable
360
        $groups = [];
361
362
        // Shorten the variable name
363
        //$map = &$this->attribute_map;
364
365
        // Log the search
366
/**
367
        Logger::debug(
368
            $this->title . 'Checking DNs for groups.' .
369
            ' DNs: ' . implode('; ', $memberof) .
370
            ' Attributes: ' . $map['memberof'] . ', ' . $map['type'] .
371
            ' Group Type: ' . $this->type_map['group']
372
        );
373
*/
374
        // Work out what attributes to get for a group
375
        $use_group_name = false;
376
        $get_attributes = [$map['memberof'], $map['type']];
377
        if (isset($map['name']) && $map['name']) {
378
            $get_attributes[] = $map['name'];
379
            $use_group_name = true;
380
        }
381
382
        // Check each DN of the passed memberOf
383
        foreach ($memberof as $dn) {
384
            // Avoid infinite loops, only need to check a DN once
385
            if (isset($searched[$dn])) {
386
                continue;
387
            }
388
389
            // Track all DN's that are searched
390
            // Use DN for key as well, isset() is faster than in_array()
391
            $searched[$dn] = $dn;
392
393
            // Query LDAP for the attribute values for the DN
394
            try {
395
                $attributes = $this->getLdap()->getAttributes($dn, $get_attributes);
396
            } catch (Error\AuthSource $e) {
397
                continue; // DN must not exist, just continue. Logged by the LDAP object
398
            }
399
400
            // Only look for groups
401
            if (!in_array($this->type_map['group'], $attributes[$map['type']], true)) {
402
                continue;
403
            }
404
405
            // Add to found groups array
406
            if ($use_group_name && isset($attributes[$map['name']]) && is_array($attributes[$map['name']])) {
407
                $groups[] = $attributes[$map['name']][0];
408
            } else {
409
                $groups[] = $dn;
410
            }
411
412
            // Recursively search "sub" groups
413
            if (!empty($attributes[$map['memberof']])) {
414
                $groups = array_merge($groups, $this->search($attributes[$map['memberof']]));
415
            }
416
        }
417
418
        // Return only the unique group names
419
        return array_unique($groups);
420
    }
421
422
423
    /**
424
     * Searches LDAP using a ActiveDirectory specific filter,
425
     * looking for group membership for the users DN. Returns
426
     * the list of group DNs retrieved.
427
     *
428
     * @param string $dn
429
     * @return array
430
     */
431
    protected function searchActiveDirectory(string $dn): array
432
    {
433
//        $arrayUtils = new Utils\Arrays();
434
435
//        // Shorten the variable name
436
        //$map = &$this->attribute_map;
437
438
        // Log the search
439
/*
440
        Logger::debug(
441
            $this->title . 'Searching ActiveDirectory group membership.' .
442
            ' DN: ' . $dn .
443
            ' DN Attribute: ' . $map['dn'] .
444
            ' Return Attribute: ' . $map['return'] .
445
            ' Member Attribute: ' . $map['member'] .
446
            ' Type Attribute: ' . $map['type'] .
447
            ' Type Value: ' . $this->type_map['group'] .
448
            ' Base: ' . implode('; ', $arrayUtils->arrayize($this->base_dn))
449
        );
450
*/
451
452
        // AD connections should have this set
453
        //$this->getLdap()->setOption(LDAP_OPT_REFERRALS, 0);
454
455
        // Search AD with the specific recursive flag
456
/**
457
        try {
458
            $entries = $this->getLdap()->searchformultiple(
459
                $this->base_dn,
460
                array_merge(
461
                    [
462
                        $map['type'] => $this->type_map['group'],
463
                        $map['member'] . ':1.2.840.113556.1.4.1941:' => $dn
464
                    ],
465
                    $this->additional_filters
466
                ),
467
                [$map['return']]
468
            );
469
470
        // The search may throw an exception if no entries
471
        // are found, unlikely but possible.
472
        } catch (Error\UserNotFound $e) {
473
            return [];
474
        }
475
*/
476
        //Init the groups
477
        $groups = [];
478
479
        // Check each entry..
480
        foreach ($entries as $entry) {
481
            // Check for the DN using the original attribute name
482
            if (isset($entry[$map['return']][0])) {
483
                $groups[] = $entry[$map['return']][0];
484
                continue;
485
            }
486
487
            // Sometimes the returned attribute names are lowercase
488
            if (isset($entry[strtolower($map['return'])][0])) {
489
                $groups[] = $entry[strtolower($map['return'])][0];
490
                continue;
491
            }
492
493
            // AD queries also seem to return the objects dn by default
494
            if (isset($entry['return'])) {
495
                $groups[] = $entry['return'];
496
                continue;
497
            }
498
499
            // Could not find DN, log and continue
500
            Logger::notice(
501
                $this->title . 'The return attribute [' .
502
                implode(', ', [$map['return'], strtolower($map['return'])]) .
503
                '] could not be found in the entry. ' . $this->varExport($entry)
504
            );
505
        }
506
507
        // All done
508
        return $groups;
509
    }
510
}
511