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

AttributeAddUsersGroups::getGroups()   C

Complexity

Conditions 10
Paths 17

Size

Total Lines 202
Code Lines 128

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 10
eloc 128
c 7
b 0
f 0
nc 17
nop 1
dl 0
loc 202
rs 6.1333

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/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('ldap.product', null);
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
        $ldapUtils->bind($this->ldapObject, $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
150
151
        // All map-properties are guaranteed to exist and have a default value
152
        $dn_attribute = $map['dn'];
153
        $return_attribute = $map['return'];
154
155
        // Based on the directory service, search LDAP for groups
156
        // If any attributes are needed, prepare them before calling search method
157
        switch ($this->product) {
158
            case 'ActiveDirectory':
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
                $arrayUtils = new Utils\Arrays();
191
                Logger::debug(sprintf(
192
                    '%s : Searching ActiveDirectory group membership.'
193
                        . ' DN: %s DN Attribute: %s Member Attribute: %s Type Attribute: %s Type Value: %s Base: %s',
194
                    $this->title,
195
                    $attributes[$dn_attribute][0],
196
                    $dn_attribute,
197
                    $map['member'],
198
                    $map['type'],
199
                    $this->type_map['group'],
200
                    implode('; ', $arrayUtils->arrayize($this->searchBase))
201
                ));
202
203
                $filter = sprintf(
204
                    "(&(%s=%s)(%s=%s))",
205
                    $map['type'],
206
                    $this->type_map['group'],
207
                    $map['member'] . ':1.2.840.113556.1.4.1941:',
208
                    $attributes[$dn_attribute][0],
209
                );
210
211
                $entries = $ldapUtils->searchForMultiple(
212
                    $this->ldapObject,
213
                    $this->searchBase,
214
                    $filter,
215
                    $options,
216
                    true
217
                );
218
219
                break;
220
            case 'OpenLDAP':
221
                // Log the OpenLDAP specific search
222
                Logger::debug(sprintf(
223
                    '%s : Searching LDAP using OpenLDAP specific method.',
224
                    $this->title
225
                ));
226
227
                Logger::debug(sprintf(
228
                    '%s : Searching for groups in base [%s] with filter (%s=%s) and attributes %s',
229
                    $this->title,
230
                    implode(', ', $this->searchBase),
231
                    $map['memberOf'],
232
                    $attributes[$map['username']][0],
233
                    $map['member']
234
                ));
235
236
                $filter = sprintf(
237
                    '(&(%s=%s))',
238
                    $map['memberOf'],
239
                    $attributes[$map['username']][0]
240
                );
241
242
                $entries = $ldapUtils->searchForMultiple(
243
                    $this->ldapObject,
244
                    $this->searchBase,
245
                    $filter,
246
                    $options,
247
                    true
248
                );
249
250
                break;
251
            default:
252
                // Log the generic search
253
                Logger::debug(
254
                    $this->title . 'Searching LDAP using the generic search method.'
255
                );
256
257
                // Make sure the defined memberOf attribute exists
258
                Assert::keyExists(
259
                    $attributes,
260
                    $map['memberOf'],
261
                    sprintf(
262
                        "%s : The memberOf attribute [%s] is not defined in the user's attributes: [%s]",
263
                        $this->title,
264
                        $map['memberOf'],
265
                        implode(', ', array_keys($attributes))
266
                    ),
267
                    Error\Exception::class,
268
                );
269
270
                // MemberOf must be an array of group DN's
271
                Assert::isArray(
272
                    $attributes[$map['memberOf']],
273
                    sprintf(
274
                        '%s : The memberOf attribute [%s] is not an array of group DNs;  %s',
275
                        $this->title,
276
                        $map['memberOf'],
277
                        $this->varExport($attributes[$map['memberOf']]),
278
                    ),
279
                    Error\Exception::class,
280
                );
281
282
                Logger::debug(sprintf(
283
                    '%s : Checking DNs for groups. DNs: %s Attributes: %s, %s Group Type: %s',
284
                    $this->title,
285
                    implode('; ', $attributes[$map['memberOf']]),
286
                    $map['memberOf'],
287
                    $map['type'],
288
                    $this->type_map['group']
289
                ));
290
291
                // Search for the users group membership, recursively
292
                $entries = $this->search($attributes[$map['memberOf']], $options);
293
        }
294
295
        $groups = [];
296
        foreach ($entries as $entry) {
297
            if ($entry->hasAttribute($return_attribute)) {
298
                /** @psalm-var array $values */
299
                $values = $entry->getAttribute($return_attribute);
300
                $groups[] = array_pop($values);
301
                continue;
302
            } elseif ($entry->hasAttribute(strtolower($return_attribute))) {
303
                // Some backends return lowercase attributes
304
                /** @psalm-var array $values */
305
                $values = $entry->getAttribute(strtolower($return_attribute));
306
                $groups[] = array_pop($values);
307
                continue;
308
            } elseif ($entry->hasAttribute('dn')) {
309
                // AD queries also seem to return the objects dn by default
310
                /** @psalm-var array $values */
311
                $values = $entry->getAttribute('dn');
312
                $groups[] = array_pop($values);
313
                continue;
314
            }
315
316
            // Could not find DN, log and continue
317
            Logger::notice(sprintf(
318
                '%s : The return attribute [%s] could not be found in the entry. %s',
319
                $this->title,
320
                implode(', ', [$map['return'], strtolower($map['return']), 'dn']),
321
                $this->varExport($entry),
322
            ));
323
        }
324
325
        // All done
326
        Logger::debug(sprintf(
327
            '%s : User found to be a member of the groups: %s',
328
            $this->title,
329
            implode('; ', $groups),
330
        ));
331
332
        return $groups;
333
    }
334
335
336
    /**
337
     * Looks for groups from the list of DN's passed. Also
338
     * recursively searches groups for further membership.
339
     * Avoids loops by only searching a DN once. Returns
340
     * the list of groups found.
341
     *
342
     * @param array $memberOf
343
     * @param array $options
344
     * @return array
345
     */
346
    protected function search(array $memberOf, array $options): array
347
    {
348
        // Shorten the variable name
349
        $map = &$this->attribute_map;
350
351
        // Used to determine what DN's have already been searched
352
        static $searched = [];
353
354
        // Init the groups variable
355
        $entries = [];
356
        $ldapUtils = new LdapUtils();
357
358
        // Check each DN of the passed memberOf
359
        foreach ($memberOf as $dn) {
360
            // Avoid infinite loops, only need to check a DN once
361
            if (isset($searched[$dn])) {
362
                continue;
363
            }
364
365
            // Track all DN's that are searched
366
            // Use DN for key as well, isset() is faster than in_array()
367
            $searched[$dn] = $dn;
368
369
            // Query LDAP for the attribute values for the DN
370
            $entry = $ldapUtils->search(
371
                $this->ldapObject,
372
                $this->searchBase,
373
                sprintf("(&(%s=%s)(distinguishedName=%s))", $map['type'], $this->type_map['group'], $dn),
374
                $options,
375
                true,
376
            );
377
378
            if ($entry === null) {
379
                // Probably the DN does not exist within the given search base
380
                continue;
381
            }
382
383
            // Add to found groups array
384
            $entries[] = $entry;
385
386
            // Recursively search "sub" groups
387
            $subGroups = $entry->getAttribute($map['memberOf']);
388
            if (!empty($subGroups)) {
389
                $entries = array_merge($entries, $this->search($subGroups, $options));
390
            }
391
        }
392
393
        return $entries;
394
    }
395
}
396