Total Complexity | 240 |
Total Lines | 1337 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Wizard often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Wizard, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
48 | class Wizard extends LDAPUtility { |
||
49 | protected static ?IL10N $l = null; |
||
50 | protected Access $access; |
||
51 | /** @var resource|\LDAP\Connection|null */ |
||
|
|||
52 | protected $cr; |
||
53 | protected Configuration $configuration; |
||
54 | protected WizardResult $result; |
||
55 | protected LoggerInterface $logger; |
||
56 | |||
57 | public const LRESULT_PROCESSED_OK = 2; |
||
58 | public const LRESULT_PROCESSED_INVALID = 3; |
||
59 | public const LRESULT_PROCESSED_SKIP = 4; |
||
60 | |||
61 | public const LFILTER_LOGIN = 2; |
||
62 | public const LFILTER_USER_LIST = 3; |
||
63 | public const LFILTER_GROUP_LIST = 4; |
||
64 | |||
65 | public const LFILTER_MODE_ASSISTED = 2; |
||
66 | public const LFILTER_MODE_RAW = 1; |
||
67 | |||
68 | public const LDAP_NW_TIMEOUT = 4; |
||
69 | |||
70 | public function __construct( |
||
71 | Configuration $configuration, |
||
72 | ILDAPWrapper $ldap, |
||
73 | Access $access |
||
74 | ) { |
||
75 | parent::__construct($ldap); |
||
76 | $this->configuration = $configuration; |
||
77 | if (is_null(static::$l)) { |
||
78 | static::$l = \OC::$server->get(IL10NFactory::class)->get('user_ldap'); |
||
79 | } |
||
80 | $this->access = $access; |
||
81 | $this->result = new WizardResult(); |
||
82 | $this->logger = \OC::$server->get(LoggerInterface::class); |
||
83 | } |
||
84 | |||
85 | public function __destruct() { |
||
86 | if ($this->result->hasChanges()) { |
||
87 | $this->configuration->saveConfiguration(); |
||
88 | } |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * counts entries in the LDAP directory |
||
93 | * |
||
94 | * @param string $filter the LDAP search filter |
||
95 | * @param string $type a string being either 'users' or 'groups'; |
||
96 | * @throws \Exception |
||
97 | */ |
||
98 | public function countEntries(string $filter, string $type): int { |
||
99 | $reqs = ['ldapHost', 'ldapBase']; |
||
100 | if (!$this->configuration->usesLdapi()) { |
||
101 | $reqs[] = 'ldapPort'; |
||
102 | } |
||
103 | if ($type === 'users') { |
||
104 | $reqs[] = 'ldapUserFilter'; |
||
105 | } |
||
106 | if (!$this->checkRequirements($reqs)) { |
||
107 | throw new \Exception('Requirements not met', 400); |
||
108 | } |
||
109 | |||
110 | $attr = ['dn']; // default |
||
111 | $limit = 1001; |
||
112 | if ($type === 'groups') { |
||
113 | $result = $this->access->countGroups($filter, $attr, $limit); |
||
114 | } elseif ($type === 'users') { |
||
115 | $result = $this->access->countUsers($filter, $attr, $limit); |
||
116 | } elseif ($type === 'objects') { |
||
117 | $result = $this->access->countObjects($limit); |
||
118 | } else { |
||
119 | throw new \Exception('Internal error: Invalid object type', 500); |
||
120 | } |
||
121 | |||
122 | return (int)$result; |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * @return WizardResult|false |
||
127 | */ |
||
128 | public function countGroups() { |
||
129 | $filter = $this->configuration->ldapGroupFilter; |
||
130 | |||
131 | if (empty($filter)) { |
||
132 | $output = self::$l->n('%n group found', '%n groups found', 0); |
||
133 | $this->result->addChange('ldap_group_count', $output); |
||
134 | return $this->result; |
||
135 | } |
||
136 | |||
137 | try { |
||
138 | $groupsTotal = $this->countEntries($filter, 'groups'); |
||
139 | } catch (\Exception $e) { |
||
140 | //400 can be ignored, 500 is forwarded |
||
141 | if ($e->getCode() === 500) { |
||
142 | throw $e; |
||
143 | } |
||
144 | return false; |
||
145 | } |
||
146 | |||
147 | if ($groupsTotal > 1000) { |
||
148 | $output = self::$l->t('> 1000 groups found'); |
||
149 | } else { |
||
150 | $output = self::$l->n( |
||
151 | '%n group found', |
||
152 | '%n groups found', |
||
153 | $groupsTotal |
||
154 | ); |
||
155 | } |
||
156 | $this->result->addChange('ldap_group_count', $output); |
||
157 | return $this->result; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * @throws \Exception |
||
162 | */ |
||
163 | public function countUsers(): WizardResult { |
||
164 | $filter = $this->access->getFilterForUserCount(); |
||
165 | |||
166 | $usersTotal = $this->countEntries($filter, 'users'); |
||
167 | if ($usersTotal > 1000) { |
||
168 | $output = self::$l->t('> 1000 users found'); |
||
169 | } else { |
||
170 | $output = self::$l->n( |
||
171 | '%n user found', |
||
172 | '%n users found', |
||
173 | $usersTotal |
||
174 | ); |
||
175 | } |
||
176 | $this->result->addChange('ldap_user_count', $output); |
||
177 | return $this->result; |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * counts any objects in the currently set base dn |
||
182 | * |
||
183 | * @throws \Exception |
||
184 | */ |
||
185 | public function countInBaseDN(): WizardResult { |
||
186 | // we don't need to provide a filter in this case |
||
187 | $total = $this->countEntries('', 'objects'); |
||
188 | $this->result->addChange('ldap_test_base', $total); |
||
189 | return $this->result; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * counts users with a specified attribute |
||
194 | * @return int|false |
||
195 | */ |
||
196 | public function countUsersWithAttribute(string $attr, bool $existsCheck = false) { |
||
197 | $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; |
||
198 | if (!$this->configuration->usesLdapi()) { |
||
199 | $reqs[] = 'ldapPort'; |
||
200 | } |
||
201 | if (!$this->checkRequirements($reqs)) { |
||
202 | return false; |
||
203 | } |
||
204 | |||
205 | $filter = $this->access->combineFilterWithAnd([ |
||
206 | $this->configuration->ldapUserFilter, |
||
207 | $attr . '=*' |
||
208 | ]); |
||
209 | |||
210 | $limit = $existsCheck ? null : 1; |
||
211 | |||
212 | return $this->access->countUsers($filter, ['dn'], $limit); |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * detects the display name attribute. If a setting is already present that |
||
217 | * returns at least one hit, the detection will be canceled. |
||
218 | * @return WizardResult|false |
||
219 | * @throws \Exception |
||
220 | */ |
||
221 | public function detectUserDisplayNameAttribute() { |
||
222 | $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; |
||
223 | if (!$this->configuration->usesLdapi()) { |
||
224 | $reqs[] = 'ldapPort'; |
||
225 | } |
||
226 | if (!$this->checkRequirements($reqs)) { |
||
227 | return false; |
||
228 | } |
||
229 | |||
230 | $attr = $this->configuration->ldapUserDisplayName; |
||
231 | if ($attr !== '' && $attr !== 'displayName') { |
||
232 | // most likely not the default value with upper case N, |
||
233 | // verify it still produces a result |
||
234 | $count = (int)$this->countUsersWithAttribute($attr, true); |
||
235 | if ($count > 0) { |
||
236 | //no change, but we sent it back to make sure the user interface |
||
237 | //is still correct, even if the ajax call was cancelled meanwhile |
||
238 | $this->result->addChange('ldap_display_name', $attr); |
||
239 | return $this->result; |
||
240 | } |
||
241 | } |
||
242 | |||
243 | // first attribute that has at least one result wins |
||
244 | $displayNameAttrs = ['displayname', 'cn']; |
||
245 | foreach ($displayNameAttrs as $attr) { |
||
246 | $count = (int)$this->countUsersWithAttribute($attr, true); |
||
247 | |||
248 | if ($count > 0) { |
||
249 | $this->applyFind('ldap_display_name', $attr); |
||
250 | return $this->result; |
||
251 | } |
||
252 | } |
||
253 | |||
254 | throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.')); |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * detects the most often used email attribute for users applying to the |
||
259 | * user list filter. If a setting is already present that returns at least |
||
260 | * one hit, the detection will be canceled. |
||
261 | * @return WizardResult|bool |
||
262 | */ |
||
263 | public function detectEmailAttribute() { |
||
264 | $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; |
||
265 | if (!$this->configuration->usesLdapi()) { |
||
266 | $reqs[] = 'ldapPort'; |
||
267 | } |
||
268 | if (!$this->checkRequirements($reqs)) { |
||
269 | return false; |
||
270 | } |
||
271 | |||
272 | $attr = $this->configuration->ldapEmailAttribute; |
||
273 | if ($attr !== '') { |
||
274 | $count = (int)$this->countUsersWithAttribute($attr, true); |
||
275 | if ($count > 0) { |
||
276 | return false; |
||
277 | } |
||
278 | $writeLog = true; |
||
279 | } else { |
||
280 | $writeLog = false; |
||
281 | } |
||
282 | |||
283 | $emailAttributes = ['mail', 'mailPrimaryAddress']; |
||
284 | $winner = ''; |
||
285 | $maxUsers = 0; |
||
286 | foreach ($emailAttributes as $attr) { |
||
287 | $count = $this->countUsersWithAttribute($attr); |
||
288 | if ($count > $maxUsers) { |
||
289 | $maxUsers = $count; |
||
290 | $winner = $attr; |
||
291 | } |
||
292 | } |
||
293 | |||
294 | if ($winner !== '') { |
||
295 | $this->applyFind('ldap_email_attr', $winner); |
||
296 | if ($writeLog) { |
||
297 | $this->logger->info( |
||
298 | 'The mail attribute has automatically been reset, '. |
||
299 | 'because the original value did not return any results.', |
||
300 | ['app' => 'user_ldap'] |
||
301 | ); |
||
302 | } |
||
303 | } |
||
304 | |||
305 | return $this->result; |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * @return WizardResult|false |
||
310 | * @throws \Exception |
||
311 | */ |
||
312 | public function determineAttributes() { |
||
313 | $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; |
||
314 | if (!$this->configuration->usesLdapi()) { |
||
315 | $reqs[] = 'ldapPort'; |
||
316 | } |
||
317 | if (!$this->checkRequirements($reqs)) { |
||
318 | return false; |
||
319 | } |
||
320 | |||
321 | $attributes = $this->getUserAttributes(); |
||
322 | |||
323 | if (!is_array($attributes)) { |
||
324 | throw new \Exception('Failed to determine user attributes'); |
||
325 | } |
||
326 | |||
327 | natcasesort($attributes); |
||
328 | $attributes = array_values($attributes); |
||
329 | |||
330 | $this->result->addOptions('ldap_loginfilter_attributes', $attributes); |
||
331 | |||
332 | $selected = $this->configuration->ldapLoginFilterAttributes; |
||
333 | if (is_array($selected) && !empty($selected)) { |
||
334 | $this->result->addChange('ldap_loginfilter_attributes', $selected); |
||
335 | } |
||
336 | |||
337 | return $this->result; |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * detects the available LDAP attributes |
||
342 | * @return array|false |
||
343 | * @throws \Exception |
||
344 | */ |
||
345 | private function getUserAttributes() { |
||
346 | $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; |
||
347 | if (!$this->configuration->usesLdapi()) { |
||
348 | $reqs[] = 'ldapPort'; |
||
349 | } |
||
350 | if (!$this->checkRequirements($reqs)) { |
||
351 | return false; |
||
352 | } |
||
353 | $cr = $this->getConnection(); |
||
354 | if (!$cr) { |
||
355 | throw new \Exception('Could not connect to LDAP'); |
||
356 | } |
||
357 | |||
358 | $base = $this->configuration->ldapBase[0]; |
||
359 | $filter = $this->configuration->ldapUserFilter; |
||
360 | $rr = $this->ldap->search($cr, $base, $filter, [], 1, 1); |
||
361 | if (!$this->ldap->isResource($rr)) { |
||
362 | return false; |
||
363 | } |
||
364 | /** @var resource|\LDAP\Result $rr */ |
||
365 | $er = $this->ldap->firstEntry($cr, $rr); |
||
366 | $attributes = $this->ldap->getAttributes($cr, $er); |
||
367 | if ($attributes === false) { |
||
368 | return false; |
||
369 | } |
||
370 | $pureAttributes = []; |
||
371 | for ($i = 0; $i < $attributes['count']; $i++) { |
||
372 | $pureAttributes[] = $attributes[$i]; |
||
373 | } |
||
374 | |||
375 | return $pureAttributes; |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * detects the available LDAP groups |
||
380 | * @return WizardResult|false the instance's WizardResult instance |
||
381 | */ |
||
382 | public function determineGroupsForGroups() { |
||
383 | return $this->determineGroups('ldap_groupfilter_groups', |
||
384 | 'ldapGroupFilterGroups', |
||
385 | false); |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * detects the available LDAP groups |
||
390 | * @return WizardResult|false the instance's WizardResult instance |
||
391 | */ |
||
392 | public function determineGroupsForUsers() { |
||
393 | return $this->determineGroups('ldap_userfilter_groups', |
||
394 | 'ldapUserFilterGroups'); |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * detects the available LDAP groups |
||
399 | * @return WizardResult|false the instance's WizardResult instance |
||
400 | * @throws \Exception |
||
401 | */ |
||
402 | private function determineGroups(string $dbKey, string $confKey, bool $testMemberOf = true) { |
||
403 | $reqs = ['ldapHost', 'ldapBase']; |
||
404 | if (!$this->configuration->usesLdapi()) { |
||
405 | $reqs[] = 'ldapPort'; |
||
406 | } |
||
407 | if (!$this->checkRequirements($reqs)) { |
||
408 | return false; |
||
409 | } |
||
410 | $cr = $this->getConnection(); |
||
411 | if (!$cr) { |
||
412 | throw new \Exception('Could not connect to LDAP'); |
||
413 | } |
||
414 | |||
415 | $this->fetchGroups($dbKey, $confKey); |
||
416 | |||
417 | if ($testMemberOf) { |
||
418 | $this->configuration->hasMemberOfFilterSupport = $this->testMemberOf(); |
||
419 | $this->result->markChange(); |
||
420 | if (!$this->configuration->hasMemberOfFilterSupport) { |
||
421 | throw new \Exception('memberOf is not supported by the server'); |
||
422 | } |
||
423 | } |
||
424 | |||
425 | return $this->result; |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * fetches all groups from LDAP and adds them to the result object |
||
430 | * |
||
431 | * @throws \Exception |
||
432 | */ |
||
433 | public function fetchGroups(string $dbKey, string $confKey): array { |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * @return WizardResult|false |
||
482 | */ |
||
483 | public function determineGroupMemberAssoc() { |
||
484 | $reqs = ['ldapHost', 'ldapGroupFilter']; |
||
485 | if (!$this->configuration->usesLdapi()) { |
||
486 | $reqs[] = 'ldapPort'; |
||
487 | } |
||
488 | if (!$this->checkRequirements($reqs)) { |
||
489 | return false; |
||
490 | } |
||
491 | $attribute = $this->detectGroupMemberAssoc(); |
||
492 | if ($attribute === false) { |
||
493 | return false; |
||
494 | } |
||
495 | $this->configuration->setConfiguration(['ldapGroupMemberAssocAttr' => $attribute]); |
||
496 | $this->result->addChange('ldap_group_member_assoc_attribute', $attribute); |
||
497 | |||
498 | return $this->result; |
||
499 | } |
||
500 | |||
501 | /** |
||
502 | * Detects the available object classes |
||
503 | * @return WizardResult|false the instance's WizardResult instance |
||
504 | * @throws \Exception |
||
505 | */ |
||
506 | public function determineGroupObjectClasses() { |
||
507 | $reqs = ['ldapHost', 'ldapBase']; |
||
508 | if (!$this->configuration->usesLdapi()) { |
||
509 | $reqs[] = 'ldapPort'; |
||
510 | } |
||
511 | if (!$this->checkRequirements($reqs)) { |
||
512 | return false; |
||
513 | } |
||
514 | $cr = $this->getConnection(); |
||
515 | if (!$cr) { |
||
516 | throw new \Exception('Could not connect to LDAP'); |
||
517 | } |
||
518 | |||
519 | $obclasses = ['groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*']; |
||
520 | $this->determineFeature($obclasses, |
||
521 | 'objectclass', |
||
522 | 'ldap_groupfilter_objectclass', |
||
523 | 'ldapGroupFilterObjectclass', |
||
524 | false); |
||
525 | |||
526 | return $this->result; |
||
527 | } |
||
528 | |||
529 | /** |
||
530 | * detects the available object classes |
||
531 | * @return WizardResult|false |
||
532 | * @throws \Exception |
||
533 | */ |
||
534 | public function determineUserObjectClasses() { |
||
535 | $reqs = ['ldapHost', 'ldapBase']; |
||
536 | if (!$this->configuration->usesLdapi()) { |
||
537 | $reqs[] = 'ldapPort'; |
||
538 | } |
||
539 | if (!$this->checkRequirements($reqs)) { |
||
540 | return false; |
||
541 | } |
||
542 | $cr = $this->getConnection(); |
||
543 | if (!$cr) { |
||
544 | throw new \Exception('Could not connect to LDAP'); |
||
545 | } |
||
546 | |||
547 | $obclasses = ['inetOrgPerson', 'person', 'organizationalPerson', |
||
548 | 'user', 'posixAccount', '*']; |
||
549 | $filter = $this->configuration->ldapUserFilter; |
||
550 | //if filter is empty, it is probably the first time the wizard is called |
||
551 | //then, apply suggestions. |
||
552 | $this->determineFeature($obclasses, |
||
553 | 'objectclass', |
||
554 | 'ldap_userfilter_objectclass', |
||
555 | 'ldapUserFilterObjectclass', |
||
556 | empty($filter)); |
||
557 | |||
558 | return $this->result; |
||
559 | } |
||
560 | |||
561 | /** |
||
562 | * @return WizardResult|false |
||
563 | * @throws \Exception |
||
564 | */ |
||
565 | public function getGroupFilter() { |
||
566 | $reqs = ['ldapHost', 'ldapBase']; |
||
567 | if (!$this->configuration->usesLdapi()) { |
||
568 | $reqs[] = 'ldapPort'; |
||
569 | } |
||
570 | if (!$this->checkRequirements($reqs)) { |
||
571 | return false; |
||
572 | } |
||
573 | //make sure the use display name is set |
||
574 | $displayName = $this->configuration->ldapGroupDisplayName; |
||
575 | if ($displayName === '') { |
||
576 | $d = $this->configuration->getDefaults(); |
||
577 | $this->applyFind('ldap_group_display_name', |
||
578 | $d['ldap_group_display_name']); |
||
579 | } |
||
580 | $filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST); |
||
581 | |||
582 | $this->applyFind('ldap_group_filter', $filter); |
||
583 | return $this->result; |
||
584 | } |
||
585 | |||
586 | /** |
||
587 | * @return WizardResult|false |
||
588 | * @throws \Exception |
||
589 | */ |
||
590 | public function getUserListFilter() { |
||
591 | $reqs = ['ldapHost', 'ldapBase']; |
||
592 | if (!$this->configuration->usesLdapi()) { |
||
593 | $reqs[] = 'ldapPort'; |
||
594 | } |
||
595 | if (!$this->checkRequirements($reqs)) { |
||
596 | return false; |
||
597 | } |
||
598 | //make sure the use display name is set |
||
599 | $displayName = $this->configuration->ldapUserDisplayName; |
||
600 | if ($displayName === '') { |
||
601 | $d = $this->configuration->getDefaults(); |
||
602 | $this->applyFind('ldap_display_name', $d['ldap_display_name']); |
||
603 | } |
||
604 | $filter = $this->composeLdapFilter(self::LFILTER_USER_LIST); |
||
605 | if (!$filter) { |
||
606 | throw new \Exception('Cannot create filter'); |
||
607 | } |
||
608 | |||
609 | $this->applyFind('ldap_userlist_filter', $filter); |
||
610 | return $this->result; |
||
611 | } |
||
612 | |||
613 | /** |
||
614 | * @return WizardResult|false |
||
615 | * @throws \Exception |
||
616 | */ |
||
617 | public function getUserLoginFilter() { |
||
618 | $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; |
||
619 | if (!$this->configuration->usesLdapi()) { |
||
620 | $reqs[] = 'ldapPort'; |
||
621 | } |
||
622 | if (!$this->checkRequirements($reqs)) { |
||
623 | return false; |
||
624 | } |
||
625 | |||
626 | $filter = $this->composeLdapFilter(self::LFILTER_LOGIN); |
||
627 | if (!$filter) { |
||
628 | throw new \Exception('Cannot create filter'); |
||
629 | } |
||
630 | |||
631 | $this->applyFind('ldap_login_filter', $filter); |
||
632 | return $this->result; |
||
633 | } |
||
634 | |||
635 | /** |
||
636 | * @return WizardResult|false |
||
637 | * @throws \Exception |
||
638 | */ |
||
639 | public function testLoginName(string $loginName) { |
||
640 | $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; |
||
641 | if (!$this->configuration->usesLdapi()) { |
||
642 | $reqs[] = 'ldapPort'; |
||
643 | } |
||
644 | if (!$this->checkRequirements($reqs)) { |
||
645 | return false; |
||
646 | } |
||
647 | |||
648 | $cr = $this->access->connection->getConnectionResource(); |
||
649 | if (!$this->ldap->isResource($cr)) { |
||
650 | throw new \Exception('connection error'); |
||
651 | } |
||
652 | /** @var resource|\LDAP\Connection $cr */ |
||
653 | |||
654 | if (mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8') |
||
655 | === false) { |
||
656 | throw new \Exception('missing placeholder'); |
||
657 | } |
||
658 | |||
659 | $users = $this->access->countUsersByLoginName($loginName); |
||
660 | if ($this->ldap->errno($cr) !== 0) { |
||
661 | throw new \Exception($this->ldap->error($cr)); |
||
662 | } |
||
663 | $filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter); |
||
664 | $this->result->addChange('ldap_test_loginname', $users); |
||
665 | $this->result->addChange('ldap_test_effective_filter', $filter); |
||
666 | return $this->result; |
||
667 | } |
||
668 | |||
669 | /** |
||
670 | * Tries to determine the port, requires given Host, User DN and Password |
||
671 | * @return WizardResult|false WizardResult on success, false otherwise |
||
672 | * @throws \Exception |
||
673 | */ |
||
674 | public function guessPortAndTLS() { |
||
675 | if (!$this->checkRequirements(['ldapHost', |
||
676 | ])) { |
||
677 | return false; |
||
678 | } |
||
679 | $this->checkHost(); |
||
680 | $portSettings = $this->getPortSettingsToTry(); |
||
681 | |||
682 | //proceed from the best configuration and return on first success |
||
683 | foreach ($portSettings as $setting) { |
||
684 | $p = $setting['port']; |
||
685 | $t = $setting['tls']; |
||
686 | $this->logger->debug( |
||
687 | 'Wiz: trying port '. $p . ', TLS '. $t, |
||
688 | ['app' => 'user_ldap'] |
||
689 | ); |
||
690 | //connectAndBind may throw Exception, it needs to be caught by the |
||
691 | //callee of this method |
||
692 | |||
693 | try { |
||
694 | $settingsFound = $this->connectAndBind($p, $t); |
||
695 | } catch (\Exception $e) { |
||
696 | // any reply other than -1 (= cannot connect) is already okay, |
||
697 | // because then we found the server |
||
698 | // unavailable startTLS returns -11 |
||
699 | if ($e->getCode() > 0) { |
||
700 | $settingsFound = true; |
||
701 | } else { |
||
702 | throw $e; |
||
703 | } |
||
704 | } |
||
705 | |||
706 | if ($settingsFound === true) { |
||
707 | $config = [ |
||
708 | 'ldapPort' => $p, |
||
709 | 'ldapTLS' => (int)$t |
||
710 | ]; |
||
711 | $this->configuration->setConfiguration($config); |
||
712 | $this->logger->debug( |
||
713 | 'Wiz: detected Port ' . $p, |
||
714 | ['app' => 'user_ldap'] |
||
715 | ); |
||
716 | $this->result->addChange('ldap_port', $p); |
||
717 | return $this->result; |
||
718 | } |
||
719 | } |
||
720 | |||
721 | //custom port, undetected (we do not brute force) |
||
722 | return false; |
||
723 | } |
||
724 | |||
725 | /** |
||
726 | * tries to determine a base dn from User DN or LDAP Host |
||
727 | * @return WizardResult|false WizardResult on success, false otherwise |
||
728 | */ |
||
729 | public function guessBaseDN() { |
||
730 | $reqs = ['ldapHost']; |
||
731 | if (!$this->configuration->usesLdapi()) { |
||
732 | $reqs[] = 'ldapPort'; |
||
733 | } |
||
734 | if (!$this->checkRequirements($reqs)) { |
||
735 | return false; |
||
736 | } |
||
737 | |||
738 | //check whether a DN is given in the agent name (99.9% of all cases) |
||
739 | $base = null; |
||
740 | $i = stripos($this->configuration->ldapAgentName, 'dc='); |
||
741 | if ($i !== false) { |
||
742 | $base = substr($this->configuration->ldapAgentName, $i); |
||
743 | if ($this->testBaseDN($base)) { |
||
744 | $this->applyFind('ldap_base', $base); |
||
745 | return $this->result; |
||
746 | } |
||
747 | } |
||
748 | |||
749 | //this did not help :( |
||
750 | //Let's see whether we can parse the Host URL and convert the domain to |
||
751 | //a base DN |
||
752 | $helper = \OC::$server->get(Helper::class); |
||
753 | $domain = $helper->getDomainFromURL($this->configuration->ldapHost); |
||
754 | if (!$domain) { |
||
755 | return false; |
||
756 | } |
||
757 | |||
758 | $dparts = explode('.', $domain); |
||
759 | while (count($dparts) > 0) { |
||
760 | $base2 = 'dc=' . implode(',dc=', $dparts); |
||
761 | if ($base !== $base2 && $this->testBaseDN($base2)) { |
||
762 | $this->applyFind('ldap_base', $base2); |
||
763 | return $this->result; |
||
764 | } |
||
765 | array_shift($dparts); |
||
766 | } |
||
767 | |||
768 | return false; |
||
769 | } |
||
770 | |||
771 | /** |
||
772 | * sets the found value for the configuration key in the WizardResult |
||
773 | * as well as in the Configuration instance |
||
774 | * @param string $key the configuration key |
||
775 | * @param string $value the (detected) value |
||
776 | * |
||
777 | */ |
||
778 | private function applyFind(string $key, string $value): void { |
||
779 | $this->result->addChange($key, $value); |
||
780 | $this->configuration->setConfiguration([$key => $value]); |
||
781 | } |
||
782 | |||
783 | /** |
||
784 | * Checks, whether a port was entered in the Host configuration |
||
785 | * field. In this case the port will be stripped off, but also stored as |
||
786 | * setting. |
||
787 | */ |
||
788 | private function checkHost(): void { |
||
798 | } |
||
799 | } |
||
800 | |||
801 | /** |
||
802 | * tries to detect the group member association attribute which is |
||
803 | * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber' |
||
804 | * @return string|false string with the attribute name, false on error |
||
805 | * @throws \Exception |
||
806 | */ |
||
807 | private function detectGroupMemberAssoc() { |
||
808 | $possibleAttrs = ['uniqueMember', 'memberUid', 'member', 'gidNumber', 'zimbraMailForwardingAddress']; |
||
809 | $filter = $this->configuration->ldapGroupFilter; |
||
810 | if (empty($filter)) { |
||
811 | return false; |
||
812 | } |
||
813 | $cr = $this->getConnection(); |
||
814 | if (!$cr) { |
||
815 | throw new \Exception('Could not connect to LDAP'); |
||
816 | } |
||
817 | $base = $this->configuration->ldapBaseGroups[0] ?: $this->configuration->ldapBase[0]; |
||
818 | $rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000); |
||
819 | if (!$this->ldap->isResource($rr)) { |
||
820 | return false; |
||
821 | } |
||
822 | /** @var resource|\LDAP\Result $rr */ |
||
823 | $er = $this->ldap->firstEntry($cr, $rr); |
||
824 | while ($this->ldap->isResource($er)) { |
||
825 | $this->ldap->getDN($cr, $er); |
||
826 | $attrs = $this->ldap->getAttributes($cr, $er); |
||
827 | $result = []; |
||
828 | $possibleAttrsCount = count($possibleAttrs); |
||
829 | for ($i = 0; $i < $possibleAttrsCount; $i++) { |
||
830 | if (isset($attrs[$possibleAttrs[$i]])) { |
||
831 | $result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count']; |
||
832 | } |
||
833 | } |
||
834 | if (!empty($result)) { |
||
835 | natsort($result); |
||
836 | return key($result); |
||
837 | } |
||
838 | |||
839 | $er = $this->ldap->nextEntry($cr, $er); |
||
840 | } |
||
841 | |||
842 | return false; |
||
843 | } |
||
844 | |||
845 | /** |
||
846 | * Checks whether for a given BaseDN results will be returned |
||
847 | * @param string $base the BaseDN to test |
||
848 | * @return bool true on success, false otherwise |
||
849 | * @throws \Exception |
||
850 | */ |
||
851 | private function testBaseDN(string $base): bool { |
||
852 | $cr = $this->getConnection(); |
||
853 | if (!$cr) { |
||
854 | throw new \Exception('Could not connect to LDAP'); |
||
855 | } |
||
856 | |||
857 | //base is there, let's validate it. If we search for anything, we should |
||
858 | //get a result set > 0 on a proper base |
||
859 | $rr = $this->ldap->search($cr, $base, 'objectClass=*', ['dn'], 0, 1); |
||
860 | if (!$this->ldap->isResource($rr)) { |
||
861 | $errorNo = $this->ldap->errno($cr); |
||
862 | $errorMsg = $this->ldap->error($cr); |
||
863 | $this->logger->info( |
||
864 | 'Wiz: Could not search base '.$base.' Error '.$errorNo.': '.$errorMsg, |
||
865 | ['app' => 'user_ldap'] |
||
866 | ); |
||
867 | return false; |
||
868 | } |
||
869 | /** @var resource|\LDAP\Result $rr */ |
||
870 | $entries = $this->ldap->countEntries($cr, $rr); |
||
871 | return ($entries !== false) && ($entries > 0); |
||
872 | } |
||
873 | |||
874 | /** |
||
875 | * Checks whether the server supports memberOf in LDAP Filter. |
||
876 | * Note: at least in OpenLDAP, availability of memberOf is dependent on |
||
877 | * a configured objectClass. I.e. not necessarily for all available groups |
||
878 | * memberOf does work. |
||
879 | * |
||
880 | * @return bool true if it does, false otherwise |
||
881 | * @throws \Exception |
||
882 | */ |
||
883 | private function testMemberOf(): bool { |
||
884 | $cr = $this->getConnection(); |
||
885 | if (!$cr) { |
||
886 | throw new \Exception('Could not connect to LDAP'); |
||
887 | } |
||
888 | $result = $this->access->countUsers('memberOf=*', ['memberOf'], 1); |
||
889 | if (is_int($result) && $result > 0) { |
||
890 | return true; |
||
891 | } |
||
892 | return false; |
||
893 | } |
||
894 | |||
895 | /** |
||
896 | * creates an LDAP Filter from given configuration |
||
897 | * @param int $filterType int, for which use case the filter shall be created |
||
898 | * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or |
||
899 | * self::LFILTER_GROUP_LIST |
||
900 | * @throws \Exception |
||
901 | */ |
||
902 | private function composeLdapFilter(int $filterType): string { |
||
903 | $filter = ''; |
||
904 | $parts = 0; |
||
905 | switch ($filterType) { |
||
906 | case self::LFILTER_USER_LIST: |
||
907 | $objcs = $this->configuration->ldapUserFilterObjectclass; |
||
908 | //glue objectclasses |
||
909 | if (is_array($objcs) && count($objcs) > 0) { |
||
910 | $filter .= '(|'; |
||
911 | foreach ($objcs as $objc) { |
||
912 | $filter .= '(objectclass=' . $objc . ')'; |
||
913 | } |
||
914 | $filter .= ')'; |
||
915 | $parts++; |
||
916 | } |
||
917 | //glue group memberships |
||
918 | if ($this->configuration->hasMemberOfFilterSupport) { |
||
919 | $cns = $this->configuration->ldapUserFilterGroups; |
||
920 | if (is_array($cns) && count($cns) > 0) { |
||
921 | $filter .= '(|'; |
||
922 | $cr = $this->getConnection(); |
||
923 | if (!$cr) { |
||
924 | throw new \Exception('Could not connect to LDAP'); |
||
925 | } |
||
926 | $base = $this->configuration->ldapBase[0]; |
||
927 | foreach ($cns as $cn) { |
||
928 | $rr = $this->ldap->search($cr, $base, 'cn=' . $cn, ['dn', 'primaryGroupToken']); |
||
929 | if (!$this->ldap->isResource($rr)) { |
||
930 | continue; |
||
931 | } |
||
932 | /** @var resource|\LDAP\Result $rr */ |
||
933 | $er = $this->ldap->firstEntry($cr, $rr); |
||
934 | $attrs = $this->ldap->getAttributes($cr, $er); |
||
935 | $dn = $this->ldap->getDN($cr, $er); |
||
936 | if ($dn === false || $dn === '') { |
||
937 | continue; |
||
938 | } |
||
939 | $filterPart = '(memberof=' . $dn . ')'; |
||
940 | if (isset($attrs['primaryGroupToken'])) { |
||
941 | $pgt = $attrs['primaryGroupToken'][0]; |
||
942 | $primaryFilterPart = '(primaryGroupID=' . $pgt .')'; |
||
943 | $filterPart = '(|' . $filterPart . $primaryFilterPart . ')'; |
||
944 | } |
||
945 | $filter .= $filterPart; |
||
946 | } |
||
947 | $filter .= ')'; |
||
948 | } |
||
949 | $parts++; |
||
950 | } |
||
951 | //wrap parts in AND condition |
||
952 | if ($parts > 1) { |
||
953 | $filter = '(&' . $filter . ')'; |
||
954 | } |
||
955 | if ($filter === '') { |
||
956 | $filter = '(objectclass=*)'; |
||
957 | } |
||
958 | break; |
||
959 | |||
960 | case self::LFILTER_GROUP_LIST: |
||
961 | $objcs = $this->configuration->ldapGroupFilterObjectclass; |
||
962 | //glue objectclasses |
||
963 | if (is_array($objcs) && count($objcs) > 0) { |
||
964 | $filter .= '(|'; |
||
965 | foreach ($objcs as $objc) { |
||
966 | $filter .= '(objectclass=' . $objc . ')'; |
||
967 | } |
||
968 | $filter .= ')'; |
||
969 | $parts++; |
||
970 | } |
||
971 | //glue group memberships |
||
972 | $cns = $this->configuration->ldapGroupFilterGroups; |
||
973 | if (is_array($cns) && count($cns) > 0) { |
||
974 | $filter .= '(|'; |
||
975 | foreach ($cns as $cn) { |
||
976 | $filter .= '(cn=' . $cn . ')'; |
||
977 | } |
||
978 | $filter .= ')'; |
||
979 | } |
||
980 | $parts++; |
||
981 | //wrap parts in AND condition |
||
982 | if ($parts > 1) { |
||
983 | $filter = '(&' . $filter . ')'; |
||
984 | } |
||
985 | break; |
||
986 | |||
987 | case self::LFILTER_LOGIN: |
||
988 | $ulf = $this->configuration->ldapUserFilter; |
||
989 | $loginpart = '=%uid'; |
||
990 | $filterUsername = ''; |
||
991 | $userAttributes = $this->getUserAttributes(); |
||
992 | if ($userAttributes === false) { |
||
993 | throw new \Exception('Failed to get user attributes'); |
||
994 | } |
||
995 | $userAttributes = array_change_key_case(array_flip($userAttributes)); |
||
996 | $parts = 0; |
||
997 | |||
998 | if ($this->configuration->ldapLoginFilterUsername === '1') { |
||
999 | $attr = ''; |
||
1000 | if (isset($userAttributes['uid'])) { |
||
1001 | $attr = 'uid'; |
||
1002 | } elseif (isset($userAttributes['samaccountname'])) { |
||
1003 | $attr = 'samaccountname'; |
||
1004 | } elseif (isset($userAttributes['cn'])) { |
||
1005 | //fallback |
||
1006 | $attr = 'cn'; |
||
1007 | } |
||
1008 | if ($attr !== '') { |
||
1009 | $filterUsername = '(' . $attr . $loginpart . ')'; |
||
1010 | $parts++; |
||
1011 | } |
||
1012 | } |
||
1013 | |||
1014 | $filterEmail = ''; |
||
1015 | if ($this->configuration->ldapLoginFilterEmail === '1') { |
||
1016 | $filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))'; |
||
1017 | $parts++; |
||
1018 | } |
||
1019 | |||
1020 | $filterAttributes = ''; |
||
1021 | $attrsToFilter = $this->configuration->ldapLoginFilterAttributes; |
||
1022 | if (is_array($attrsToFilter) && count($attrsToFilter) > 0) { |
||
1023 | $filterAttributes = '(|'; |
||
1024 | foreach ($attrsToFilter as $attribute) { |
||
1025 | $filterAttributes .= '(' . $attribute . $loginpart . ')'; |
||
1026 | } |
||
1027 | $filterAttributes .= ')'; |
||
1028 | $parts++; |
||
1029 | } |
||
1030 | |||
1031 | $filterLogin = ''; |
||
1032 | if ($parts > 1) { |
||
1033 | $filterLogin = '(|'; |
||
1034 | } |
||
1035 | $filterLogin .= $filterUsername; |
||
1036 | $filterLogin .= $filterEmail; |
||
1037 | $filterLogin .= $filterAttributes; |
||
1038 | if ($parts > 1) { |
||
1039 | $filterLogin .= ')'; |
||
1040 | } |
||
1041 | |||
1042 | $filter = '(&'.$ulf.$filterLogin.')'; |
||
1043 | break; |
||
1044 | } |
||
1045 | |||
1046 | $this->logger->debug( |
||
1047 | 'Wiz: Final filter '.$filter, |
||
1048 | ['app' => 'user_ldap'] |
||
1049 | ); |
||
1050 | |||
1051 | return $filter; |
||
1052 | } |
||
1053 | |||
1054 | /** |
||
1055 | * Connects and Binds to an LDAP Server |
||
1056 | * |
||
1057 | * @param int $port the port to connect with |
||
1058 | * @param bool $tls whether startTLS is to be used |
||
1059 | * @throws \Exception |
||
1060 | */ |
||
1061 | private function connectAndBind(int $port, bool $tls): bool { |
||
1062 | //connect, does not really trigger any server communication |
||
1063 | $host = $this->configuration->ldapHost; |
||
1064 | $hostInfo = parse_url((string)$host); |
||
1065 | if (!is_string($host) || !$hostInfo) { |
||
1066 | throw new \Exception(self::$l->t('Invalid Host')); |
||
1067 | } |
||
1068 | $this->logger->debug( |
||
1069 | 'Wiz: Attempting to connect', |
||
1070 | ['app' => 'user_ldap'] |
||
1071 | ); |
||
1072 | $cr = $this->ldap->connect($host, (string)$port); |
||
1073 | if (!$this->ldap->isResource($cr)) { |
||
1074 | throw new \Exception(self::$l->t('Invalid Host')); |
||
1075 | } |
||
1076 | /** @var resource|\LDAP\Connection $cr */ |
||
1077 | |||
1078 | //set LDAP options |
||
1079 | $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); |
||
1080 | $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0); |
||
1081 | $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT); |
||
1082 | |||
1083 | try { |
||
1084 | if ($tls) { |
||
1085 | $isTlsWorking = @$this->ldap->startTls($cr); |
||
1086 | if (!$isTlsWorking) { |
||
1087 | return false; |
||
1088 | } |
||
1089 | } |
||
1090 | |||
1091 | $this->logger->debug( |
||
1092 | 'Wiz: Attempting to Bind', |
||
1093 | ['app' => 'user_ldap'] |
||
1094 | ); |
||
1095 | //interesting part: do the bind! |
||
1096 | $login = $this->ldap->bind($cr, |
||
1097 | $this->configuration->ldapAgentName, |
||
1098 | $this->configuration->ldapAgentPassword |
||
1099 | ); |
||
1100 | $errNo = $this->ldap->errno($cr); |
||
1101 | $error = $this->ldap->error($cr); |
||
1102 | $this->ldap->unbind($cr); |
||
1103 | } catch (ServerNotAvailableException $e) { |
||
1104 | return false; |
||
1105 | } |
||
1106 | |||
1107 | if ($login === true) { |
||
1108 | $this->logger->debug( |
||
1109 | 'Wiz: Bind successful to Port '. $port . ' TLS ' . (int)$tls, |
||
1110 | ['app' => 'user_ldap'] |
||
1111 | ); |
||
1112 | return true; |
||
1113 | } |
||
1114 | |||
1115 | if ($errNo === -1) { |
||
1116 | //host, port or TLS wrong |
||
1117 | return false; |
||
1118 | } |
||
1119 | throw new \Exception($error, $errNo); |
||
1120 | } |
||
1121 | |||
1122 | /** |
||
1123 | * checks whether a valid combination of agent and password has been |
||
1124 | * provided (either two values or nothing for anonymous connect) |
||
1125 | * @return bool true if everything is fine, false otherwise |
||
1126 | */ |
||
1127 | private function checkAgentRequirements(): bool { |
||
1128 | $agent = $this->configuration->ldapAgentName; |
||
1129 | $pwd = $this->configuration->ldapAgentPassword; |
||
1130 | |||
1131 | return |
||
1132 | ($agent !== '' && $pwd !== '') |
||
1133 | || ($agent === '' && $pwd === '') |
||
1134 | ; |
||
1135 | } |
||
1136 | |||
1137 | private function checkRequirements(array $reqs): bool { |
||
1146 | } |
||
1147 | |||
1148 | /** |
||
1149 | * does a cumulativeSearch on LDAP to get different values of a |
||
1150 | * specified attribute |
||
1151 | * @param string[] $filters array, the filters that shall be used in the search |
||
1152 | * @param string $attr the attribute of which a list of values shall be returned |
||
1153 | * @param int $dnReadLimit the amount of how many DNs should be analyzed. |
||
1154 | * The lower, the faster |
||
1155 | * @param string $maxF string. if not null, this variable will have the filter that |
||
1156 | * yields most result entries |
||
1157 | * @return array|false an array with the values on success, false otherwise |
||
1158 | */ |
||
1159 | public function cumulativeSearchOnAttribute(array $filters, string $attr, int $dnReadLimit = 3, ?string &$maxF = null) { |
||
1160 | $dnRead = []; |
||
1161 | $foundItems = []; |
||
1162 | $maxEntries = 0; |
||
1163 | if (!is_array($this->configuration->ldapBase) |
||
1164 | || !isset($this->configuration->ldapBase[0])) { |
||
1165 | return false; |
||
1166 | } |
||
1167 | $base = $this->configuration->ldapBase[0]; |
||
1168 | $cr = $this->getConnection(); |
||
1169 | if (!$this->ldap->isResource($cr)) { |
||
1170 | return false; |
||
1171 | } |
||
1172 | /** @var resource|\LDAP\Connection $cr */ |
||
1173 | $lastFilter = null; |
||
1174 | if (isset($filters[count($filters) - 1])) { |
||
1175 | $lastFilter = $filters[count($filters) - 1]; |
||
1176 | } |
||
1177 | foreach ($filters as $filter) { |
||
1178 | if ($lastFilter === $filter && count($foundItems) > 0) { |
||
1179 | //skip when the filter is a wildcard and results were found |
||
1180 | continue; |
||
1181 | } |
||
1182 | // 20k limit for performance and reason |
||
1183 | $rr = $this->ldap->search($cr, $base, $filter, [$attr], 0, 20000); |
||
1184 | if (!$this->ldap->isResource($rr)) { |
||
1185 | continue; |
||
1186 | } |
||
1187 | /** @var resource|\LDAP\Result $rr */ |
||
1188 | $entries = $this->ldap->countEntries($cr, $rr); |
||
1189 | $getEntryFunc = 'firstEntry'; |
||
1190 | if (($entries !== false) && ($entries > 0)) { |
||
1191 | if (!is_null($maxF) && $entries > $maxEntries) { |
||
1192 | $maxEntries = $entries; |
||
1193 | $maxF = $filter; |
||
1194 | } |
||
1195 | $dnReadCount = 0; |
||
1196 | do { |
||
1197 | $entry = $this->ldap->$getEntryFunc($cr, $rr); |
||
1198 | $getEntryFunc = 'nextEntry'; |
||
1199 | if (!$this->ldap->isResource($entry)) { |
||
1200 | continue 2; |
||
1201 | } |
||
1202 | $rr = $entry; //will be expected by nextEntry next round |
||
1203 | $attributes = $this->ldap->getAttributes($cr, $entry); |
||
1204 | $dn = $this->ldap->getDN($cr, $entry); |
||
1205 | if ($attributes === false || $dn === false || in_array($dn, $dnRead)) { |
||
1206 | continue; |
||
1207 | } |
||
1208 | $newItems = []; |
||
1209 | $state = $this->getAttributeValuesFromEntry( |
||
1210 | $attributes, |
||
1211 | $attr, |
||
1212 | $newItems |
||
1213 | ); |
||
1214 | $dnReadCount++; |
||
1215 | $foundItems = array_merge($foundItems, $newItems); |
||
1216 | $dnRead[] = $dn; |
||
1217 | } while (($state === self::LRESULT_PROCESSED_SKIP |
||
1218 | || $this->ldap->isResource($entry)) |
||
1219 | && ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit)); |
||
1220 | } |
||
1221 | } |
||
1222 | |||
1223 | return array_unique($foundItems); |
||
1224 | } |
||
1225 | |||
1226 | /** |
||
1227 | * determines if and which $attr are available on the LDAP server |
||
1228 | * @param string[] $objectclasses the objectclasses to use as search filter |
||
1229 | * @param string $attr the attribute to look for |
||
1230 | * @param string $dbkey the dbkey of the setting the feature is connected to |
||
1231 | * @param string $confkey the confkey counterpart for the $dbkey as used in the |
||
1232 | * Configuration class |
||
1233 | * @param bool $po whether the objectClass with most result entries |
||
1234 | * shall be pre-selected via the result |
||
1235 | * @return array list of found items. |
||
1236 | * @throws \Exception |
||
1237 | */ |
||
1238 | private function determineFeature(array $objectclasses, string $attr, string $dbkey, string $confkey, bool $po = false): array { |
||
1239 | $cr = $this->getConnection(); |
||
1240 | if (!$cr) { |
||
1241 | throw new \Exception('Could not connect to LDAP'); |
||
1242 | } |
||
1243 | $p = 'objectclass='; |
||
1244 | foreach ($objectclasses as $key => $value) { |
||
1245 | $objectclasses[$key] = $p.$value; |
||
1246 | } |
||
1247 | $maxEntryObjC = ''; |
||
1248 | |||
1249 | //how deep to dig? |
||
1250 | //When looking for objectclasses, testing few entries is sufficient, |
||
1251 | $dig = 3; |
||
1252 | |||
1253 | $availableFeatures = |
||
1254 | $this->cumulativeSearchOnAttribute($objectclasses, $attr, |
||
1255 | $dig, $maxEntryObjC); |
||
1256 | if (is_array($availableFeatures) |
||
1257 | && count($availableFeatures) > 0) { |
||
1258 | natcasesort($availableFeatures); |
||
1259 | //natcasesort keeps indices, but we must get rid of them for proper |
||
1260 | //sorting in the web UI. Therefore: array_values |
||
1261 | $this->result->addOptions($dbkey, array_values($availableFeatures)); |
||
1262 | } else { |
||
1263 | throw new \Exception(self::$l->t('Could not find the desired feature')); |
||
1264 | } |
||
1265 | |||
1266 | $setFeatures = $this->configuration->$confkey; |
||
1267 | if (is_array($setFeatures) && !empty($setFeatures)) { |
||
1268 | //something is already configured? pre-select it. |
||
1269 | $this->result->addChange($dbkey, $setFeatures); |
||
1270 | } elseif ($po && $maxEntryObjC !== '') { |
||
1271 | //pre-select objectclass with most result entries |
||
1272 | $maxEntryObjC = str_replace($p, '', $maxEntryObjC); |
||
1273 | $this->applyFind($dbkey, $maxEntryObjC); |
||
1274 | $this->result->addChange($dbkey, $maxEntryObjC); |
||
1275 | } |
||
1276 | |||
1277 | return $availableFeatures; |
||
1278 | } |
||
1279 | |||
1280 | /** |
||
1281 | * appends a list of values fr |
||
1282 | * @param array $result the return value from ldap_get_attributes |
||
1283 | * @param string $attribute the attribute values to look for |
||
1284 | * @param array &$known new values will be appended here |
||
1285 | * @return int state on of the class constants LRESULT_PROCESSED_OK, |
||
1286 | * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP |
||
1287 | */ |
||
1288 | private function getAttributeValuesFromEntry(array $result, string $attribute, array &$known): int { |
||
1309 | } |
||
1310 | } |
||
1311 | |||
1312 | /** |
||
1313 | * @return resource|\LDAP\Connection|false a link resource on success, otherwise false |
||
1314 | */ |
||
1315 | private function getConnection() { |
||
1316 | if (!is_null($this->cr)) { |
||
1345 | } |
||
1346 | |||
1347 | private function getDefaultLdapPortSettings(): array { |
||
1348 | static $settings = [ |
||
1349 | ['port' => 7636, 'tls' => false], |
||
1350 | ['port' => 636, 'tls' => false], |
||
1351 | ['port' => 7389, 'tls' => true], |
||
1352 | ['port' => 389, 'tls' => true], |
||
1353 | ['port' => 7389, 'tls' => false], |
||
1354 | ['port' => 389, 'tls' => false], |
||
1357 | } |
||
1358 | |||
1359 | private function getPortSettingsToTry(): array { |
||
1360 | //389 ← LDAP / Unencrypted or StartTLS |
||
1361 | //636 ← LDAPS / SSL |
||
1362 | //7xxx ← UCS. need to be checked first, because both ports may be open |
||
1363 | $host = $this->configuration->ldapHost; |
||
1364 | $port = (int)$this->configuration->ldapPort; |
||
1365 | $portSettings = []; |
||
1366 | |||
1367 | //In case the port is already provided, we will check this first |
||
1387 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths