|
38 | 38 | import javax.naming.PartialResultException; |
39 | 39 | import javax.naming.SizeLimitExceededException; |
40 | 40 | import javax.naming.directory.Attribute; |
| 41 | +import javax.naming.directory.Attributes; |
41 | 42 | import javax.naming.directory.SearchControls; |
42 | 43 | import javax.naming.directory.SearchResult; |
43 | 44 | import javax.naming.ldap.Control; |
@@ -165,6 +166,9 @@ public class LdapRealm extends DefaultLdapRealm { |
165 | 166 | private String userSearchScope = "subtree"; |
166 | 167 | private String groupSearchScope = "subtree"; |
167 | 168 | private boolean groupSearchEnableMatchingRuleInChain; |
| 169 | + // Support for FreeIPA nested groups using memberOf attribute |
| 170 | + private boolean useMemberOfForNestedGroups; |
| 171 | + private String memberOfAttribute = "memberOf"; |
168 | 172 |
|
169 | 173 | private String groupSearchBase; |
170 | 174 |
|
@@ -343,80 +347,146 @@ protected Set<String> rolesFor(PrincipalCollection principals, String userNameIn |
343 | 347 |
|
344 | 348 | String userDn = getUserDnForSearch(userName); |
345 | 349 |
|
346 | | - // Activate paged results |
347 | | - int pageSize = getPagingSize(); |
348 | | - LOGGER.debug("Ldap PagingSize: {}", pageSize); |
349 | | - int numResults = 0; |
350 | | - try { |
351 | | - ldapCtx.addToEnvironment(Context.REFERRAL, "ignore"); |
352 | | - |
353 | | - ldapCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize, |
354 | | - Control.NONCRITICAL)}); |
355 | | - |
356 | | - // ldapsearch -h localhost -p 33389 -D |
357 | | - // uid=guest,ou=people,dc=hadoop,dc=apache,dc=org -w guest-password |
358 | | - // -b dc=hadoop,dc=apache,dc=org -s sub '(objectclass=*)' |
| 350 | + // Check if we should use memberOf attribute for nested groups (FreeIPA support) |
| 351 | + if (useMemberOfForNestedGroups) { |
| 352 | + // Search for the user to get memberOf attribute values |
| 353 | + SearchControls searchControls = getUserSearchControls(); |
| 354 | + searchControls.setReturningAttributes(new String[]{memberOfAttribute}); |
| 355 | + |
359 | 356 | NamingEnumeration<SearchResult> searchResultEnum = null; |
360 | | - SearchControls searchControls = getGroupSearchControls(); |
361 | 357 | try { |
362 | | - if (groupSearchEnableMatchingRuleInChain) { |
363 | | - searchResultEnum = ldapCtx.search( |
364 | | - getGroupSearchBase(), |
365 | | - String.format( |
366 | | - MATCHING_RULE_IN_CHAIN_FORMAT, groupObjectClass, memberAttribute, userDn), |
367 | | - searchControls); |
368 | | - while (searchResultEnum != null && searchResultEnum.hasMore()) { |
369 | | - // searchResults contains all the groups in search scope |
370 | | - numResults++; |
371 | | - final SearchResult group = searchResultEnum.next(); |
372 | | - |
373 | | - Attribute attribute = group.getAttributes().get(getGroupIdAttribute()); |
374 | | - String groupName = attribute.get().toString(); |
375 | | - |
376 | | - String roleName = roleNameFor(groupName); |
377 | | - if (roleName != null) { |
378 | | - roleNames.add(roleName); |
379 | | - } else { |
380 | | - roleNames.add(groupName); |
381 | | - } |
| 358 | + // Search for the user |
| 359 | + String searchFilter; |
| 360 | + if (userSearchFilter == null) { |
| 361 | + if (userSearchAttributeName == null) { |
| 362 | + searchFilter = String.format("(objectclass=%1$s)", getUserObjectClass()); |
| 363 | + } else { |
| 364 | + searchFilter = String.format("(&(objectclass=%1$s)(%2$s=%3$s))", getUserObjectClass(), |
| 365 | + userSearchAttributeName, expandTemplate(getUserSearchAttributeTemplate(), userName)); |
382 | 366 | } |
383 | 367 | } else { |
384 | | - // Default group search filter |
385 | | - String searchFilter = String.format("(objectclass=%1$s)", groupObjectClass); |
386 | | - |
387 | | - // If group search filter is defined in Shiro config, then use it |
388 | | - if (groupSearchFilter != null) { |
389 | | - searchFilter = expandTemplate(groupSearchFilter, userName); |
390 | | - //searchFilter = String.format("%1$s", groupSearchFilter); |
391 | | - } |
392 | | - LOGGER.debug("Group SearchBase|SearchFilter|GroupSearchScope: " + "{}|{}|{}", |
393 | | - getGroupSearchBase(), searchFilter, groupSearchScope); |
394 | | - searchResultEnum = ldapCtx.search( |
395 | | - getGroupSearchBase(), |
396 | | - searchFilter, |
397 | | - searchControls); |
398 | | - while (searchResultEnum != null && searchResultEnum.hasMore()) { |
399 | | - // searchResults contains all the groups in search scope |
400 | | - numResults++; |
401 | | - final SearchResult group = searchResultEnum.next(); |
402 | | - addRoleIfMember(userDn, group, roleNames, groupNames, ldapContextFactory); |
| 368 | + searchFilter = expandTemplate(userSearchFilter, userName); |
| 369 | + } |
| 370 | + |
| 371 | + LOGGER.debug("MemberOf Attribute Search - SearchBase|SearchFilter: {}|{}", |
| 372 | + getUserSearchBase(), searchFilter); |
| 373 | + searchResultEnum = ldapCtx.search(getUserSearchBase(), searchFilter, searchControls); |
| 374 | + |
| 375 | + if (searchResultEnum.hasMore()) { |
| 376 | + SearchResult searchResult = searchResultEnum.next(); |
| 377 | + Attributes attrs = searchResult.getAttributes(); |
| 378 | + |
| 379 | + if (attrs != null && attrs.get(memberOfAttribute) != null) { |
| 380 | + Attribute memberOfAttr = attrs.get(memberOfAttribute); |
| 381 | + NamingEnumeration<?> values = memberOfAttr.getAll(); |
| 382 | + |
| 383 | + while (values.hasMore()) { |
| 384 | + String groupDn = (String) values.next(); |
| 385 | + // Extract the group name (cn) from the group DN |
| 386 | + LdapName ldapName = new LdapName(groupDn); |
| 387 | + for (int i = 0; i < ldapName.size(); i++) { |
| 388 | + String rdn = ldapName.get(i); |
| 389 | + if (rdn.startsWith(getGroupIdAttribute() + "=")) { |
| 390 | + String groupName = rdn.substring(getGroupIdAttribute().length() + 1); |
| 391 | + LOGGER.debug("Found group via memberOf: {}", groupName); |
| 392 | + groupNames.add(groupName); |
| 393 | + |
| 394 | + String roleName = roleNameFor(groupName); |
| 395 | + if (roleName != null) { |
| 396 | + roleNames.add(roleName); |
| 397 | + } else { |
| 398 | + roleNames.add(groupName); |
| 399 | + } |
| 400 | + break; |
| 401 | + } |
| 402 | + } |
| 403 | + } |
403 | 404 | } |
404 | 405 | } |
405 | 406 | } catch (PartialResultException e) { |
406 | | - LOGGER.debug("Ignoring PartitalResultException"); |
| 407 | + LOGGER.debug("Ignoring PartialResultException for memberOf search"); |
407 | 408 | } finally { |
408 | 409 | if (searchResultEnum != null) { |
409 | 410 | searchResultEnum.close(); |
410 | 411 | } |
411 | 412 | } |
412 | | - // Re-activate paged results |
413 | | - ldapCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize, |
414 | | - null, Control.CRITICAL)}); |
415 | | - } catch (SizeLimitExceededException e) { |
416 | | - LOGGER.info("Only retrieved first {} groups due to SizeLimitExceededException.", numResults); |
417 | | - } catch (IOException e) { |
418 | | - LOGGER.error("Unabled to setup paged results"); |
| 413 | + } else { |
| 414 | + // Activate paged results |
| 415 | + int pageSize = getPagingSize(); |
| 416 | + LOGGER.debug("Ldap PagingSize: {}", pageSize); |
| 417 | + int numResults = 0; |
| 418 | + try { |
| 419 | + ldapCtx.addToEnvironment(Context.REFERRAL, "ignore"); |
| 420 | + |
| 421 | + ldapCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize, |
| 422 | + Control.NONCRITICAL)}); |
| 423 | + |
| 424 | + // ldapsearch -h localhost -p 33389 -D |
| 425 | + // uid=guest,ou=people,dc=hadoop,dc=apache,dc=org -w guest-password |
| 426 | + // -b dc=hadoop,dc=apache,dc=org -s sub '(objectclass=*)' |
| 427 | + NamingEnumeration<SearchResult> searchResultEnum = null; |
| 428 | + SearchControls searchControls = getGroupSearchControls(); |
| 429 | + try { |
| 430 | + if (groupSearchEnableMatchingRuleInChain) { |
| 431 | + searchResultEnum = ldapCtx.search( |
| 432 | + getGroupSearchBase(), |
| 433 | + String.format( |
| 434 | + MATCHING_RULE_IN_CHAIN_FORMAT, groupObjectClass, memberAttribute, userDn), |
| 435 | + searchControls); |
| 436 | + while (searchResultEnum != null && searchResultEnum.hasMore()) { |
| 437 | + // searchResults contains all the groups in search scope |
| 438 | + numResults++; |
| 439 | + final SearchResult group = searchResultEnum.next(); |
| 440 | + |
| 441 | + Attribute attribute = group.getAttributes().get(getGroupIdAttribute()); |
| 442 | + String groupName = attribute.get().toString(); |
| 443 | + |
| 444 | + String roleName = roleNameFor(groupName); |
| 445 | + if (roleName != null) { |
| 446 | + roleNames.add(roleName); |
| 447 | + } else { |
| 448 | + roleNames.add(groupName); |
| 449 | + } |
| 450 | + } |
| 451 | + } else { |
| 452 | + // Default group search filter |
| 453 | + String searchFilter = String.format("(objectclass=%1$s)", groupObjectClass); |
| 454 | + |
| 455 | + // If group search filter is defined in Shiro config, then use it |
| 456 | + if (groupSearchFilter != null) { |
| 457 | + searchFilter = expandTemplate(groupSearchFilter, userName); |
| 458 | + //searchFilter = String.format("%1$s", groupSearchFilter); |
| 459 | + } |
| 460 | + LOGGER.debug("Group SearchBase|SearchFilter|GroupSearchScope: " + "{}|{}|{}", |
| 461 | + getGroupSearchBase(), searchFilter, groupSearchScope); |
| 462 | + searchResultEnum = ldapCtx.search( |
| 463 | + getGroupSearchBase(), |
| 464 | + searchFilter, |
| 465 | + searchControls); |
| 466 | + while (searchResultEnum != null && searchResultEnum.hasMore()) { |
| 467 | + // searchResults contains all the groups in search scope |
| 468 | + numResults++; |
| 469 | + final SearchResult group = searchResultEnum.next(); |
| 470 | + addRoleIfMember(userDn, group, roleNames, groupNames, ldapContextFactory); |
| 471 | + } |
| 472 | + } |
| 473 | + } catch (PartialResultException e) { |
| 474 | + LOGGER.debug("Ignoring PartitalResultException"); |
| 475 | + } finally { |
| 476 | + if (searchResultEnum != null) { |
| 477 | + searchResultEnum.close(); |
| 478 | + } |
| 479 | + } |
| 480 | + // Re-activate paged results |
| 481 | + ldapCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize, |
| 482 | + null, Control.CRITICAL)}); |
| 483 | + } catch (SizeLimitExceededException e) { |
| 484 | + LOGGER.info("Only retrieved first {} groups due to SizeLimitExceededException.", numResults); |
| 485 | + } catch (IOException e) { |
| 486 | + LOGGER.error("Unabled to setup paged results"); |
| 487 | + } |
419 | 488 | } |
| 489 | + |
420 | 490 | // save role names and group names in session so that they can be |
421 | 491 | // easily looked up outside of this object |
422 | 492 | session.setAttribute(SUBJECT_USER_ROLES, roleNames); |
@@ -817,6 +887,22 @@ public void setGroupSearchEnableMatchingRuleInChain( |
817 | 887 | this.groupSearchEnableMatchingRuleInChain = groupSearchEnableMatchingRuleInChain; |
818 | 888 | } |
819 | 889 |
|
| 890 | + public boolean isUseMemberOfForNestedGroups() { |
| 891 | + return useMemberOfForNestedGroups; |
| 892 | + } |
| 893 | + |
| 894 | + public void setUseMemberOfForNestedGroups(boolean useMemberOfForNestedGroups) { |
| 895 | + this.useMemberOfForNestedGroups = useMemberOfForNestedGroups; |
| 896 | + } |
| 897 | + |
| 898 | + public String getMemberOfAttribute() { |
| 899 | + return memberOfAttribute; |
| 900 | + } |
| 901 | + |
| 902 | + public void setMemberOfAttribute(String memberOfAttribute) { |
| 903 | + this.memberOfAttribute = memberOfAttribute; |
| 904 | + } |
| 905 | + |
820 | 906 | private SearchControls getUserSearchControls() { |
821 | 907 | SearchControls searchControls = SUBTREE_SCOPE; |
822 | 908 | if ("onelevel".equalsIgnoreCase(userSearchScope)) { |
|
0 commit comments