AttributeAddUsersGroups::getGroups()   C
last analyzed

Complexity

Conditions 10
Paths 14

Size

Total Lines 192
Code Lines 122

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 122
c 1
b 0
f 0
dl 0
loc 192
rs 6.1333
cc 10
nc 14
nop 1

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\Utils;
19
use Symfony\Component\Ldap\Adapter\ExtLdap\Query;
20
21
class AttributeAddUsersGroups extends BaseFilter
22
{
23
    /** @var string|null */
24
    protected ?string $searchUsername;
25
26
    /** @var string|null */
27
    protected ?string $searchPassword;
28
29
    /** @var string|null */
30
    protected ?string $product;
31
32
33
    /**
34
     * Initialize this filter.
35
     *
36
     * @param array<mixed> $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->getOptionalString('search.username', null);
45
        $this->searchPassword = $this->config->getOptionalString('search.password', null);
46
        $this->product = $this->config->getOptionalString('ldap.product', null);
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<mixed>
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<mixed> &$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->getOptionalArray('additional_filters', []);
79
80
        // Reference the attributes, just to make the names shorter
81
        $attributes = &$state['Attributes'];
82
        $map = &$this->attribute_map;
83
84
        // Get the users groups from LDAP
85
        $groups = $this->getGroups($attributes);
86
87
        // If there are none, do not proceed
88
        if (empty($groups)) {
89
            return;
90
        }
91
92
        // Make the array if it is not set already
93
        if (!isset($attributes[$map['groups']])) {
94
            $attributes[$map['groups']] = [];
95
        }
96
97
        // Must be an array, else cannot merge groups
98
        if (!is_array($attributes[$map['groups']])) {
99
            throw new Error\Exception(sprintf(
100
                '%s : The group attribute [%s] is not an array of group DNs. %s',
101
                $this->title,
102
                $map['groups'],
103
                $this->varExport($attributes[$map['groups']]),
104
            ));
105
        }
106
107
        // Add the users group(s)
108
        $group_attribute = &$attributes[$map['groups']];
109
        $group_attribute = array_merge($group_attribute, $groups);
110
        $group_attribute = array_unique($group_attribute);
111
112
        // All done
113
        Logger::debug(sprintf(
114
            '%s : Added users groups to the group attribute[%s]: %s',
115
            $this->title,
116
            $map['groups'],
117
            implode('; ', $groups),
118
        ));
119
    }
120
121
122
    /**
123
     * Will perform a search using the required attribute values from the user to
124
     * get their group membership, recursively.
125
     *
126
     * @throws \SimpleSAML\Error\Exception
127
     * @param array<mixed> $attributes
128
     * @return array<mixed>
129
     */
130
    protected function getGroups(array $attributes): array
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
        $this->connector->bind($this->searchUsername, $this->searchPassword);
139
140
        $options = [
141
            'scope' => $this->config->getOptionalString('search.scope', Query::SCOPE_SUB),
142
            'timeout' => $this->config->getOptionalInteger('timeout', 3),
143
        ];
144
145
        // Reference the map, just to make the name shorter
146
        $map = &$this->attribute_map;
147
148
149
        // All map-properties are guaranteed to exist and have a default value
150
        $dn_attribute = $map['dn'];
151
        $return_attribute = $map['return'];
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
                // Log the AD specific search
158
                Logger::debug(sprintf(
159
                    '%s : Searching LDAP using ActiveDirectory specific method.',
160
                    $this->title,
161
                ));
162
163
                // Make sure the defined DN attribute exists
164
                if (!isset($attributes[$dn_attribute])) {
165
                    Logger::warning(sprintf(
166
                        "%s : The DN attribute [%s] is not defined in the user's Attributes: %s",
167
                        $this->title,
168
                        $dn_attribute,
169
                        implode(', ', array_keys($attributes)),
170
                    ));
171
172
                    return [];
173
                }
174
175
                // Make sure the defined DN attribute has a value
176
                if (!isset($attributes[$dn_attribute][0]) || !$attributes[$dn_attribute][0]) {
177
                    Logger::warning(sprintf(
178
                        '%s : The DN attribute [%s] does not have a [0] value defined. %s',
179
                        $this->title,
180
                        $dn_attribute,
181
                        $this->varExport($attributes[$dn_attribute]),
182
                    ));
183
184
                    return [];
185
                }
186
187
                // Log the search
188
                $arrayUtils = new Utils\Arrays();
189
                Logger::debug(sprintf(
190
                    '%s : Searching ActiveDirectory group membership.'
191
                        . ' DN: %s DN Attribute: %s Member Attribute: %s Type Attribute: %s Type Value: %s Base: %s',
192
                    $this->title,
193
                    $attributes[$dn_attribute][0],
194
                    $dn_attribute,
195
                    $map['member'],
196
                    $map['type'],
197
                    $this->type_map['group'],
198
                    implode('; ', $arrayUtils->arrayize($this->searchBase)),
199
                ));
200
201
                $filter = sprintf(
202
                    "(&(%s=%s)(%s=%s))",
203
                    $map['type'],
204
                    $this->type_map['group'],
205
                    $map['member'] . ':1.2.840.113556.1.4.1941:',
206
                    $this->connector->escapeFilterValue($attributes[$dn_attribute][0], true),
0 ignored issues
show
Bug introduced by
The method escapeFilterValue() does not exist on SimpleSAML\Module\ldap\ConnectorInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to SimpleSAML\Module\ldap\ConnectorInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

206
                    $this->connector->/** @scrutinizer ignore-call */ 
207
                                      escapeFilterValue($attributes[$dn_attribute][0], true),
Loading history...
207
                );
208
209
                $entries = $this->connector->searchForMultiple(
210
                    $this->searchBase,
211
                    $filter,
212
                    $options,
213
                    true,
214
                );
215
216
                break;
217
            case 'OpenLDAP':
218
                // Log the OpenLDAP specific search
219
                Logger::debug(sprintf(
220
                    '%s : Searching LDAP using OpenLDAP specific method.',
221
                    $this->title,
222
                ));
223
224
                Logger::debug(sprintf(
225
                    '%s : Searching for groups in base [%s] with filter (%s=%s) and attributes %s',
226
                    $this->title,
227
                    implode(', ', $this->searchBase),
228
                    $map['memberOf'],
229
                    $attributes[$map['username']][0],
230
                    $map['member'],
231
                ));
232
233
                $filter = sprintf(
234
                    '(&(%s=%s))',
235
                    $map['memberOf'],
236
                    $attributes[$map['username']][0],
237
                );
238
239
                $entries = $this->connector->searchForMultiple(
240
                    $this->searchBase,
241
                    $filter,
242
                    $options,
243
                    true,
244
                );
245
246
                break;
247
            default:
248
                // Log the generic search
249
                Logger::debug(
250
                    sprintf('%s : Searching LDAP using the generic search method.', $this->title),
251
                );
252
253
                // Make sure the defined memberOf attribute exists
254
                Assert::keyExists(
255
                    $attributes,
256
                    $map['memberOf'],
257
                    sprintf(
258
                        "%s : The memberOf attribute [%s] is not defined in the user's attributes: [%s]",
259
                        $this->title,
260
                        $map['memberOf'],
261
                        implode(', ', array_keys($attributes)),
262
                    ),
263
                    Error\Exception::class,
264
                );
265
266
                // MemberOf must be an array of group DN's
267
                Assert::isArray(
268
                    $attributes[$map['memberOf']],
269
                    sprintf(
270
                        '%s : The memberOf attribute [%s] is not an array of group DNs;  %s',
271
                        $this->title,
272
                        $map['memberOf'],
273
                        $this->varExport($attributes[$map['memberOf']]),
274
                    ),
275
                    Error\Exception::class,
276
                );
277
278
                Logger::debug(sprintf(
279
                    '%s : Checking DNs for groups. DNs: %s Attributes: %s, %s Group Type: %s',
280
                    $this->title,
281
                    implode('; ', $attributes[$map['memberOf']]),
282
                    $map['memberOf'],
283
                    $map['type'],
284
                    $this->type_map['group'],
285
                ));
286
287
                // Search for the users group membership, recursively
288
                $entries = $this->search($attributes[$map['memberOf']], $options);
289
        }
290
291
        $groups = [];
292
        foreach ($entries as $entry) {
293
            if ($entry->hasAttribute($return_attribute)) {
294
                $values = $entry->getAttribute($return_attribute);
295
                $groups[] = array_pop($values);
296
                continue;
297
            } elseif ($entry->hasAttribute(strtolower($return_attribute))) {
298
                // Some backends return lowercase attributes
299
                $values = $entry->getAttribute(strtolower($return_attribute));
300
                $groups[] = array_pop($values);
301
                continue;
302
            }
303
304
            // Could not find return attribute, log and continue
305
            Logger::debug(sprintf(
306
                '%s : The return attribute [%s] could not be found in entry `%s`.',
307
                $this->title,
308
                implode(', ', array_unique([$map['return'], strtolower($map['return'])])),
309
                $entry->getDn(),
310
            ));
311
            Logger::debug(sprintf('%s : Entry was: %s', $this->title, $this->varExport($entry)));
312
        }
313
314
        // All done
315
        Logger::debug(sprintf(
316
            '%s : User found to be a member of the following groups: %s',
317
            $this->title,
318
            empty($groups) ? 'none' : implode('; ', $groups),
319
        ));
320
321
        return $groups;
322
    }
323
324
325
    /**
326
     * Looks for groups from the list of DN's passed. Also
327
     * recursively searches groups for further membership.
328
     * Avoids loops by only searching a DN once. Returns
329
     * the list of groups found.
330
     *
331
     * @param array<mixed> $memberOf
332
     * @param array<mixed> $options
333
     * @return array<mixed>
334
     */
335
    protected function search(array $memberOf, array $options): array
336
    {
337
        // Shorten the variable name
338
        $map = &$this->attribute_map;
339
340
        // Used to determine what DN's have already been searched
341
        static $searched = [];
342
343
        // Init the groups variable
344
        $entries = [];
345
346
        // Set scope to 'base'
347
        $options['scope'] = Query::SCOPE_BASE;
348
349
        // Check each DN of the passed memberOf
350
        foreach ($memberOf as $dn) {
351
            // Avoid infinite loops, only need to check a DN once
352
            if (isset($searched[$dn])) {
353
                continue;
354
            }
355
356
            // Track all DN's that are searched
357
            // Use DN for key as well, isset() is faster than in_array()
358
            $searched[$dn] = $dn;
359
360
            // Query LDAP for the attribute values for the DN
361
            $entry = $this->connector->search(
362
                [$dn],
363
                sprintf("(%s=%s)", $map['type'], $this->type_map['group']),
364
                $options,
365
                true,
366
            );
367
368
            if ($entry === null) {
369
                // Probably the DN does not exist within the given search base
370
                continue;
371
            }
372
373
            // Add to found groups array
374
            $entries[] = $entry;
375
376
            // Recursively search "sub" groups
377
            $subGroups = $entry->getAttribute($map['memberOf']);
378
            if (!empty($subGroups)) {
379
                $entries = array_merge($entries, $this->search($subGroups, $options));
380
            }
381
        }
382
383
        return $entries;
384
    }
385
}
386