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