001/* 002 * Copyright 2017 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.core.impl.group.directory.ldap; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.Comparator; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.Iterator; 024import java.util.LinkedHashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.NoSuchElementException; 028import java.util.Set; 029import java.util.TreeSet; 030 031import javax.naming.Context; 032import javax.naming.NamingEnumeration; 033import javax.naming.NamingException; 034import javax.naming.directory.Attribute; 035import javax.naming.directory.Attributes; 036import javax.naming.directory.DirContext; 037import javax.naming.directory.InitialDirContext; 038import javax.naming.directory.SearchControls; 039import javax.naming.directory.SearchResult; 040import javax.naming.ldap.InitialLdapContext; 041import javax.naming.ldap.LdapContext; 042 043import org.apache.avalon.framework.service.ServiceException; 044import org.apache.avalon.framework.service.ServiceManager; 045import org.apache.commons.lang3.StringUtils; 046import org.slf4j.Logger; 047 048import org.ametys.core.group.Group; 049import org.ametys.core.group.GroupIdentity; 050import org.ametys.core.group.directory.GroupDirectory; 051import org.ametys.core.group.directory.GroupDirectoryModel; 052import org.ametys.core.user.UserIdentity; 053import org.ametys.core.user.UserManager; 054import org.ametys.core.user.directory.UserDirectory; 055import org.ametys.core.user.population.UserPopulationDAO; 056import org.ametys.core.util.ldap.AbstractLDAPConnector; 057import org.ametys.core.util.ldap.ScopeEnumerator; 058import org.ametys.plugins.core.impl.user.LdapUserIdentity; 059import org.ametys.plugins.core.impl.user.directory.LdapUserDirectory; 060import org.ametys.runtime.i18n.I18nizableText; 061 062/** 063 * Use a LDAP server for getting the groups of users 064 */ 065public class LdapGroupDirectory extends AbstractLDAPConnector<Object, Object> implements GroupDirectory 066{ 067 /** Name of the parameter holding the datasource id */ 068 protected static final String __PARAM_DATASOURCE_ID = "runtime.groups.ldap.datasource"; 069 /** Name of the parameter holding the id of the associated user directory */ 070 protected static final String __PARAM_ASSOCIATED_USERDIRECTORY_ID = "runtime.groups.ldap.userdirectory"; 071 /** Relative DN for groups. */ 072 protected static final String __PARAM_GROUPS_RELATIVE_DN = "runtime.groups.ldap.groupDN"; 073 /** Filter for limiting the search. */ 074 protected static final String __PARAM_GROUPS_OBJECT_FILTER = "runtime.groups.ldap.filter"; 075 /** The scope used for search. */ 076 protected static final String __PARAM_GROUPS_SEARCH_SCOPE = "runtime.groups.ldap.scope"; 077 /** Name of the id attribute. */ 078 protected static final String __PARAM_GROUPS_ID_ATTRIBUTE = "runtime.groups.ldap.id"; 079 /** Name of the decription attribute. */ 080 protected static final String __PARAM_GROUPS_DESCRIPTION_ATTRIBUTE = "runtime.groups.ldap.description"; 081 082 /** Name of the user uid attribute. */ 083 protected static final String __PARAM_USERS_UID_ATTRIBUTE = "runtime.users.ldap.uidAttr"; 084 /** Name of the member DN attribute. */ 085 protected static final String __PARAM_GROUPS_MEMBER_ATTRIBUTE = "runtime.groups.ldap.member"; 086 /** Name of the member DN attribute. */ 087 protected static final String __PARAM_GROUPS_MEMBEROF_ATTRIBUTE = "runtime.groups.ldap.memberof"; 088 089 private static final GroupComparator __GROUP_COMPARATOR = new GroupComparator(); 090 091 /** The user manager */ 092 protected UserManager _userManager; 093 /** The DAO for user populations */ 094 protected UserPopulationDAO _userPopulationDAO; 095 096 /** The group DN relative to baseDN */ 097 protected String _groupsRelativeDN; 098 /** The filter to find groups */ 099 protected String _groupsObjectFilter; 100 /** The scope used for search. */ 101 protected int _groupsSearchScope; 102 /** The group id attribute */ 103 protected String _groupsIdAttribute; 104 /** The group description attribute */ 105 protected String _groupsDescriptionAttribute; 106 /** The LDAP search page size. */ 107 protected int _pageSize; 108 109 /** The attribute which contains the member DN */ 110 protected String _groupsMemberAttribute; 111 /** The id of the associated user directory where the LDAP group will retrieve the users */ 112 protected String _associatedUserDirectoryId; 113 /** The id of the associated user population where the LDAP group will retrieve the users */ 114 protected String _associatedPopulationId; 115 /** The user id in 'memberUid' attribute (on groups for retrieving the users of a group). */ 116 protected String _userUidAttribute; 117 118 /** The attribute which contains the groups of a user */ 119 protected String _usersMemberOfAttribute; 120 121 /** The id */ 122 protected String _id; 123 /** The label */ 124 protected I18nizableText _label; 125 /** The id of the {@link GroupDirectoryModel} */ 126 private String _groupDirectoryModelId; 127 /** The map of the values of the parameters */ 128 private Map<String, Object> _paramValues; 129 130 @Override 131 public String getId() 132 { 133 return _id; 134 } 135 136 @Override 137 public I18nizableText getLabel() 138 { 139 return _label; 140 } 141 142 @Override 143 public void setId(String id) 144 { 145 _id = id; 146 } 147 148 @Override 149 public void setLabel(I18nizableText label) 150 { 151 _label = label; 152 } 153 154 @Override 155 public String getGroupDirectoryModelId() 156 { 157 return _groupDirectoryModelId; 158 } 159 160 @Override 161 public Map<String, Object> getParameterValues() 162 { 163 return _paramValues; 164 } 165 166 @Override 167 public void service(ServiceManager serviceManager) throws ServiceException 168 { 169 super.service(serviceManager); 170 _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE); 171 _userPopulationDAO = (UserPopulationDAO) serviceManager.lookup(UserPopulationDAO.ROLE); 172 } 173 174 @Override 175 public void init(String groupDirectoryModelId, Map<String, Object> paramValues) throws Exception 176 { 177 _groupDirectoryModelId = groupDirectoryModelId; 178 _paramValues = paramValues; 179 180 String populationAndUserDirectory = (String) paramValues.get(__PARAM_ASSOCIATED_USERDIRECTORY_ID); 181 String[] split = populationAndUserDirectory.split("#"); 182 _associatedPopulationId = split[0]; 183 _associatedUserDirectoryId = split[1]; 184 185 // FIXME https://issues.ametys.org/browse/RUNTIME-2392 (avoid this check to prevent circular dependency) 186// UserDirectory associatedUserDirectory = _userPopulationDAO.getUserPopulation(_associatedPopulationId).getUserDirectory(_associatedUserDirectoryId); 187// if (!(associatedUserDirectory instanceof LdapUserDirectory)) 188// { 189// throw new IllegalArgumentException("The parameter '" + __PARAM_ASSOCIATED_USERDIRECTORY_ID + "' must reference a LDAP user directory"); 190// } 191 192 _groupsRelativeDN = (String) paramValues.get(__PARAM_GROUPS_RELATIVE_DN); 193 _groupsObjectFilter = (String) paramValues.get(__PARAM_GROUPS_OBJECT_FILTER); 194 _groupsSearchScope = ScopeEnumerator.parseScope((String) paramValues.get(__PARAM_GROUPS_SEARCH_SCOPE)); 195 _groupsIdAttribute = (String) paramValues.get(__PARAM_GROUPS_ID_ATTRIBUTE); 196 _groupsDescriptionAttribute = (String) paramValues.get(__PARAM_GROUPS_DESCRIPTION_ATTRIBUTE); 197 198 _userUidAttribute = (String) paramValues.get(__PARAM_USERS_UID_ATTRIBUTE); 199 200 _groupsMemberAttribute = (String) paramValues.get(__PARAM_GROUPS_MEMBER_ATTRIBUTE); 201 202 _usersMemberOfAttribute = (String) paramValues.get(__PARAM_GROUPS_MEMBEROF_ATTRIBUTE); 203 204 String dataSourceId = (String) paramValues.get(__PARAM_DATASOURCE_ID); 205 try 206 { 207 _delayedInitialize(dataSourceId); 208 } 209 catch (Exception e) 210 { 211 getLogger().error("An error occured during the initialization of LDAPUserDirectory", e); 212 } 213 214 _pageSize = __DEFAULT_PAGE_SIZE; 215 } 216 217 @Override 218 public Group getGroup(String groupID) 219 { 220 Group group = null; 221 222 DirContext context = null; 223 NamingEnumeration results = null; 224 225 try 226 { 227 // Connect to ldap server 228 context = new InitialDirContext(_getContextEnv()); 229 230 // Create search filter 231 StringBuffer filter = new StringBuffer("(&"); 232 filter.append(_groupsObjectFilter); 233 filter.append("("); 234 filter.append(_groupsIdAttribute); 235 filter.append("={0}))"); 236 237 // Run search 238 results = context.search(_groupsRelativeDN, filter.toString(), 239 new Object[] {groupID}, _getSearchConstraint()); 240 241 // Check if a group matches 242 if (results.hasMoreElements()) 243 { 244 // Retrieve the found group 245 group = _getUserGroup((SearchResult) results.nextElement()); 246 } 247 } 248 catch (IllegalArgumentException e) 249 { 250 getLogger().error("Error missing at least one attribute or attribute value", e); 251 } 252 catch (NamingException e) 253 { 254 getLogger().error("Error communication with ldap server", e); 255 } 256 finally 257 { 258 // Close connection resources 259 _cleanup(context, results); 260 } 261 262 // Return group or null 263 return group; 264 } 265 266 @Override 267 public Set<Group> getGroups() 268 { 269 Set<Group> groups = new TreeSet<>(__GROUP_COMPARATOR); 270 271 try 272 { 273 for (SearchResult searchResult : _search(_pageSize, _groupsRelativeDN, _groupsObjectFilter, _getSearchConstraint())) 274 { 275 // Add a new group to the set 276 Group userGroup = _getUserGroup(searchResult); 277 if (userGroup != null) 278 { 279 groups.add(userGroup); 280 } 281 } 282 } 283 catch (NamingException e) 284 { 285 getLogger().error("Error of communication with ldap server", e); 286 } 287 catch (IllegalArgumentException e) 288 { 289 getLogger().error("Error missing at least one attribute or attribute value", e); 290 } 291 292 // Return the list of users as a collection of UserGroup, possibly empty 293 return groups; 294 } 295 296 @Override 297 public Set<String> getUserGroups(UserIdentity userIdentity) 298 { 299 String populationId = userIdentity.getPopulationId(); 300 301 if (!populationId.equals(_associatedPopulationId)) 302 { 303 return Collections.emptySet(); 304 } 305 306 // Cache hit, return the results. 307 if (isCacheEnabled()) 308 { 309 @SuppressWarnings("unchecked") 310 Set<String> userGroups = (Set<String>) getObjectFromCache(userIdentity); 311 if (userGroups != null) 312 { 313 return userGroups; 314 } 315 } 316 317 Set<String> groups; 318 319 UserDirectory associatedUserDirectory = _userPopulationDAO.getUserPopulation(_associatedPopulationId).getUserDirectory(_associatedUserDirectoryId); 320 321 if (!(associatedUserDirectory instanceof LdapUserDirectory)) 322 { 323 throw new IllegalArgumentException("A Ldap group directory must be associated with a Ldap user directory."); 324 } 325 326 LdapUserDirectory associatedLdapUserDirectory = (LdapUserDirectory) associatedUserDirectory; 327 328 String usersRelativeDN = (String) associatedLdapUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_RELATIVE_DN); 329 330 // If param 'runtime.groups.ldap.memberof' is present, try to read the property from the user entries 331 if (StringUtils.isNotEmpty(_usersMemberOfAttribute)) 332 { 333 groups = _getUserGroupsFromMemberofAttr(userIdentity, usersRelativeDN, associatedLdapUserDirectory); 334 } 335 // If param 'runtime.groups.ldap.memberof' is not present, 'runtime.groups.ldap.member' must be present, try to read the property from the group entries 336 else 337 { 338 groups = _getUserGroupsFromMemberAttr(userIdentity, associatedLdapUserDirectory); 339 } 340 341 // Cache the results. 342 if (isCacheEnabled()) 343 { 344 addObjectInCache(userIdentity, groups); 345 } 346 347 // Return the groups, posssibly empty 348 return groups; 349 } 350 351 private Set<String> _getUserGroupsFromMemberofAttr(UserIdentity userIdentity, String usersRelativeDN, LdapUserDirectory associatedUserDirectory) 352 { 353 Set<String> groups = new HashSet<>(); 354 355 String login = userIdentity.getLogin(); 356 DirContext context = null; 357 NamingEnumeration userResults = null; 358 359 try 360 { 361 // Connect to ldap server 362 context = new InitialDirContext(_getContextEnv()); 363 Attributes userAttrs = null; 364 365 if (userIdentity instanceof LdapUserIdentity) 366 { 367 // Lookup the user and get the attribute of the groups 368 String dn = ((LdapUserIdentity) userIdentity).getDn(); 369 String relativeDn = _getRelativeDn(dn); 370 371 userAttrs = context.getAttributes(relativeDn, new String[] {_usersMemberOfAttribute}); 372 } 373 else 374 { 375 // Search user with given login attribute 376 String userLoginAttribute = (String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_LOGIN_ATTRIBUTE); 377 String usersObjectFilter = (String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_OBJECT_FILTER); 378 379 // Create search filter 380 StringBuffer userFilter = new StringBuffer("(&"); 381 userFilter.append(usersObjectFilter); 382 userFilter.append("("); 383 userFilter.append(userLoginAttribute); 384 userFilter.append("={0}))"); 385 386 getLogger().debug("Searching groups of user '{}' on user itself: '{}'.", login, userFilter); 387 388 // Run search 389 userResults = context.search(usersRelativeDN, userFilter.toString(), new Object[] {login}, _getUserSearchConstraint(new String[] {userLoginAttribute, _usersMemberOfAttribute})); 390 391 // Fill the set of groups 392 if (userResults.hasMoreElements()) 393 { 394 // The search result should not send more than one result as it is an id 395 SearchResult userResult = (SearchResult) userResults.nextElement(); 396 userAttrs = userResult.getAttributes(); 397 } 398 userResults.close(); 399 } 400 401 // The user may come from the good population (the associated population), but from a user diretory which is not the LDAP one 402 if (userAttrs != null) 403 { 404 groups.addAll(_getGroupIdsOfUser(userAttrs, context)); 405 } 406 } 407 catch (NamingException e) 408 { 409 getLogger().error("Error communication with ldap server", e); 410 } 411 finally 412 { 413 // Close connection resources 414 _cleanup(context, userResults); 415 } 416 417 getLogger().debug("{} groups found for user '{}' from '{}' attribute on users", groups.size(), login, _usersMemberOfAttribute); 418 419 return groups; 420 } 421 422 private Set<String> _getUserGroupsFromMemberAttr(UserIdentity userIdentity, LdapUserDirectory userDirectory) 423 { 424 Set<String> groups = new HashSet<>(); 425 String login = userIdentity.getLogin(); 426 427 DirContext context = null; 428 NamingEnumeration userResults = null; 429 430 // Create search filter 431 StringBuffer groupFilter = new StringBuffer("(&"); 432 groupFilter.append(_groupsObjectFilter); 433 434 groupFilter.append("(|"); 435 436 String dn = userDirectory.getUserDN(login); 437 438 // If 'runtime.groups.ldap.member' references a DN 439 groupFilter.append("("); 440 groupFilter.append(_groupsMemberAttribute); 441 groupFilter.append("={0}"); 442 groupFilter.append(")"); 443 444 // If 'runtime.groups.ldap.member' references a UID 445 groupFilter.append("("); 446 groupFilter.append(_groupsMemberAttribute); 447 groupFilter.append("={1})"); 448 449 groupFilter.append("))"); 450 451 getLogger().debug("Searching groups of user '{}' with base DN '{}': '{}'.", login, _groupsRelativeDN, groupFilter); 452 453 // Run search 454 int groupCount = 0; 455 try 456 { 457 // Connect to ldap server 458 context = new InitialDirContext(_getContextEnv()); 459 460 userResults = context.search(_groupsRelativeDN, groupFilter.toString(), 461 new Object[] {dn, login}, _getSearchConstraint()); 462 463 // Fill the set of groups 464 while (userResults.hasMoreElements()) 465 { 466 // Retrieve the found group 467 String groupId = _getGroupId((SearchResult) userResults.nextElement()); 468 if (groupId != null) 469 { 470 groups.add(groupId); 471 groupCount++; 472 } 473 } 474 } 475 catch (NamingException e) 476 { 477 getLogger().error("Error communication with ldap server", e); 478 } 479 finally 480 { 481 // Close connection resources 482 _cleanup(context, userResults); 483 } 484 485 getLogger().debug("{} groups found for user '{}' from '{}' attribute on groups", groupCount, login, _groupsMemberAttribute); 486 487 return groups; 488 } 489 490 /** 491 * Get a group id from attributes of a ldap group entry. 492 * @param groupEntry The ldap group entry to get attributes from. 493 * @return The group id as a String. 494 * @throws IllegalArgumentException If a needed attribute is missing. 495 */ 496 protected String _getGroupId(SearchResult groupEntry) 497 { 498 // Retrieve the attributes of the entry 499 Attributes attrs = groupEntry.getAttributes(); 500 501 try 502 { 503 // Retrieve the identifier of a group 504 Attribute groupIDAttr = attrs.get(_groupsIdAttribute); 505 if (groupIDAttr == null) 506 { 507 getLogger().warn("Missing group id attribute : \"{}\". Group will be ignored.", _groupsIdAttribute); 508 return null; 509 } 510 511 return (String) groupIDAttr.get(); 512 } 513 catch (NamingException e) 514 { 515 getLogger().warn("Missing at least one value for an attribute in an ldap entry. Group will be ignored.", e); 516 return null; 517 } 518 } 519 520 /** 521 * Get group ids from attributes of a ldap user entry. 522 * @param userAttrs The attributes of a ldap user entry 523 * @param context The context 524 * @return The group ids as a Set of String. 525 * @throws NamingException If a naming exception was encountered while retrieving the group DNs 526 * @throws IllegalArgumentException If a needed attribute is missing. 527 */ 528 @SuppressWarnings("unchecked") 529 protected Set<String> _getGroupIdsOfUser(Attributes userAttrs, DirContext context) throws NamingException 530 { 531 Set<String> groups = new HashSet<>(); 532 533 // Retrieve the identifier of the groups 534 Attribute userGroups = userAttrs.get(_usersMemberOfAttribute); 535 if (userGroups != null) 536 { 537 NamingEnumeration<String> groupDns = null; 538 try 539 { 540 // Retrieve the members of the group 541 groupDns = (NamingEnumeration<String>) userGroups.getAll(); 542 while (groupDns.hasMoreElements()) 543 { 544 String groupDn = groupDns.nextElement(); 545 try 546 { 547 String relativeGroupDn = _getRelativeDn(groupDn); 548 Attributes groupAttrs = context.getAttributes(relativeGroupDn, new String[] {_groupsIdAttribute}); 549 Attribute groupIdAttr = groupAttrs.get(_groupsIdAttribute); 550 if (groupIdAttr != null) 551 { 552 groups.add((String) groupIdAttr.get()); 553 } 554 } 555 catch (NamingException e) 556 { 557 getLogger().warn(String.format("Unable to get the group from the LDAP DN entry: %s", groupDn), e); 558 } 559 } 560 } 561 finally 562 { 563 _cleanup(null, groupDns); 564 } 565 } 566 567 return groups; 568 } 569 570 @Override 571 public List<Map<String, Object>> groups2JSON(int count, int offset, Map parameters, boolean withUsers) 572 { 573 List<Map<String, Object>> groups = new ArrayList<>(); 574 575 String pattern = (String) parameters.get("pattern"); 576 577 Iterator iterator = getGroups().iterator(); 578 579 int currentOffset = offset; 580 581 while (currentOffset > 0 && iterator.hasNext()) 582 { 583 Group group = (Group) iterator.next(); 584 if (StringUtils.isEmpty(pattern) || group.getLabel().toLowerCase().indexOf(pattern.toLowerCase()) != -1 || (group.getIdentity() != null && group.getIdentity().getId().toLowerCase().indexOf(pattern.toLowerCase()) != -1)) 585 { 586 currentOffset--; 587 } 588 } 589 590 int currentCount = count; 591 while ((count == -1 || currentCount > 0) && iterator.hasNext()) 592 { 593 Group group = (Group) iterator.next(); 594 595 if (StringUtils.isEmpty(pattern) || group.getLabel().toLowerCase().indexOf(pattern.toLowerCase()) != -1 || (group.getIdentity() != null && group.getIdentity().getId().toLowerCase().indexOf(pattern.toLowerCase()) != -1)) 596 { 597 groups.add(_group2JSON(group, withUsers)); 598 599 currentCount--; 600 } 601 } 602 603 return groups; 604 } 605 606 @Override 607 public Map<String, Object> group2JSON(String id, boolean withUsers) 608 { 609 Group group = getGroup(id); 610 return _group2JSON(group, withUsers); 611 } 612 613 /** 614 * Get an UserGroup from attributes of a ldap entry. 615 * @param entry The ldap entry to get attributes from. 616 * @return The group as an UserGroup. 617 * @throws IllegalArgumentException If a needed attribute is missing. 618 */ 619 protected Group _getUserGroup(SearchResult entry) 620 { 621 LdapGroup group = null; 622 // Retrieve the attributes of the entry 623 Attributes attrs = entry.getAttributes(); 624 625 try 626 { 627 // Retrieve the identifier of a group 628 Attribute groupIDAttr = attrs.get(_groupsIdAttribute); 629 if (groupIDAttr == null) 630 { 631 getLogger().warn("Missing group id attribute : \"" + _groupsIdAttribute + "\". Group will be ignored."); 632 return null; 633 } 634 String groupID = (String) groupIDAttr.get(); 635 636 // Retrieve the description of a group 637 Attribute groupDESCAttr = attrs.get(_groupsDescriptionAttribute); 638 if (groupDESCAttr == null) 639 { 640 getLogger().warn("Missing group description attribute : \"" + _groupsDescriptionAttribute + "\". Group will be ignored."); 641 return null; 642 } 643 String groupDesc = (String) groupDESCAttr.get(); 644 645 Attribute membersAttr = null; 646 if (StringUtils.isNotEmpty(_groupsMemberAttribute)) 647 { 648 membersAttr = attrs.get(_groupsMemberAttribute); 649 } 650 group = new LdapGroup(new GroupIdentity(groupID, getId()), groupDesc, this, membersAttr, getLogger()); 651 } 652 catch (NamingException e) 653 { 654 getLogger().warn("Missing at least one value for an attribute in an ldap entry. Group will be ignored.", e); 655 return null; 656 } 657 658 return group; 659 } 660 661 /** 662 * If the given DN is absolute, return the relative DN. Otherwise, return the given DN. 663 * @param dn The absolute or relative DN 664 * @return The relative DN 665 */ 666 protected String _getRelativeDn(String dn) 667 { 668 String relativeDn = dn; 669 String suffix = "," + _ldapBaseDN; 670 if (dn.endsWith(suffix)) 671 { 672 relativeDn = StringUtils.substring(dn, 0, -suffix.length()); 673 } 674 675 return relativeDn; 676 } 677 678 /** 679 * Gets a user according to its DN 680 * @param ldapDn The DN of the user in the LDAP 681 * @return A user 682 */ 683 protected UserIdentity _getUserInLdapFromDn(String ldapDn) 684 { 685 UserDirectory associatedUserDirectory = _userPopulationDAO.getUserPopulation(_associatedPopulationId).getUserDirectory(_associatedUserDirectoryId); 686 String userLoginAttribute = (String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_LOGIN_ATTRIBUTE); 687 688 String relativeDn = _getRelativeDn(ldapDn); 689 690 LdapContext ldapContext = null; 691 try 692 { 693 ldapContext = new InitialLdapContext(_getContextEnv(), null); 694 Attributes userAttrs = ldapContext.getAttributes(relativeDn, new String[] {userLoginAttribute}); 695 Attribute userLogin = userAttrs.get(userLoginAttribute); 696 if (userLogin == null) 697 { 698 getLogger().warn("User '{}' was found in LDAP but is missing the attribute {}", relativeDn, userLoginAttribute); 699 return null; 700 } 701 UserIdentity identity = new UserIdentity((String) userLogin.get(), _associatedPopulationId); 702 if (_userManager.getUser(identity) != null) 703 { 704 return identity; 705 } 706 else 707 { 708 getLogger().warn("User with login '{}' was found in LDAP but is not a user of the population {}", userLogin.get(), _associatedPopulationId); 709 } 710 } 711 catch (NamingException e) 712 { 713 getLogger().warn(String.format("Unable to get the user from the LDAP DN entry: %s", ldapDn), e); 714 } 715 finally 716 { 717 _cleanup(ldapContext, null); 718 } 719 720 return null; 721 } 722 723 /** 724 * Gets a user according to its UID 725 * @param ldapUid The UID of the user in the LDAP 726 * @return A user 727 */ 728 protected UserIdentity _getUserInLdapFromUid(String ldapUid) 729 { 730 UserDirectory associatedUserDirectory = _userPopulationDAO.getUserPopulation(_associatedPopulationId).getUserDirectory(_associatedUserDirectoryId); 731 String userLoginAttribute = (String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_LOGIN_ATTRIBUTE); 732 String usersRelativeDN = (String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_RELATIVE_DN); 733 734 try 735 { 736 String filter = _userUidAttribute + "=" + ldapUid; 737 SearchControls constraints = _getUserSearchConstraint(new String[] {userLoginAttribute}); 738 739 List<SearchResult> results = _search(_pageSize, usersRelativeDN, filter, constraints); 740 if (results.size() > 0) 741 { 742 SearchResult searchResult = results.get(0); 743 Attribute userLogin = searchResult.getAttributes().get(userLoginAttribute); 744 if (userLogin == null) 745 { 746 getLogger().warn("User '{}' was found in LDAP but is missing the attribute {}", searchResult, userLoginAttribute); 747 return null; 748 } 749 UserIdentity identity = new UserIdentity((String) userLogin.get(), _associatedPopulationId); 750 if (_userManager.getUser(identity) != null) 751 { 752 return identity; 753 } 754 else 755 { 756 getLogger().warn("User with login '{}' was found in LDAP but is not a user of the population {}", userLogin.get(), _associatedPopulationId); 757 } 758 } 759 getLogger().warn("Unable to get the user from the LDAP UID: {}", ldapUid); 760 return null; 761 } 762 catch (NamingException | NoSuchElementException e) 763 { 764 getLogger().warn(String.format("Unable to get the user from the LDAP UID: %s", ldapUid), e); 765 return null; 766 } 767 } 768 769 /** 770 * Gets all users of a group from the 'runtime.groups.ldap.memberof' attribute on the users 771 * @param groupId The id of the group 772 * @return The users of the given group, only by looking at the 'runtime.groups.ldap.memberof' attribute on the users 773 */ 774 protected Set<UserIdentity> _getUsersFromMembersOfAttr(String groupId) 775 { 776 Set<UserIdentity> identities = new LinkedHashSet<>(); 777 if (_usersMemberOfAttribute == null) 778 { 779 return identities; 780 } 781 782 UserDirectory associatedUserDirectory = _userPopulationDAO.getUserPopulation(_associatedPopulationId).getUserDirectory(_associatedUserDirectoryId); 783 String userLoginAttribute = (String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_LOGIN_ATTRIBUTE); 784 String usersRelativeDN = (String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_RELATIVE_DN); 785 String usersObjectFilter = (String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_OBJECT_FILTER); 786 787 try 788 { 789 // FIXME work only if the group id attribute in Ametys is the same as the id in the LDAP ? Would be better if a GroupIdentity owned the DN of the group 790 String memberOfValue = _groupsIdAttribute + "=" + groupId + "," + _groupsRelativeDN + "," + _ldapBaseDN; 791 String filter = "(&" + usersObjectFilter + "(" + _usersMemberOfAttribute + "=" + memberOfValue + "))"; 792 793 List<SearchResult> searchResults = _search(_pageSize, usersRelativeDN, filter, _getUserSearchConstraint(new String[] {userLoginAttribute})); 794 for (SearchResult searchResult : searchResults) 795 { 796 Attributes attrs = searchResult.getAttributes(); 797 Attribute userLogin = attrs.get(userLoginAttribute); 798 if (userLogin == null) 799 { 800 getLogger().warn("User '{}' was found in LDAP but is missing the attribute {}", searchResult, userLoginAttribute); 801 break; 802 } 803 UserIdentity identity = new UserIdentity((String) userLogin.get(), _associatedPopulationId); 804 if (_userManager.getUser(identity) != null) 805 { 806 identities.add(identity); 807 } 808 else 809 { 810 getLogger().warn("User with login '{}' was found in LDAP but is not a user of the population {}", userLogin.get(), _associatedPopulationId); 811 } 812 } 813 } 814 catch (NamingException e) 815 { 816 getLogger().error("Error of communication with ldap server", e); 817 } 818 819 return identities; 820 } 821 822 private SearchControls _getUserSearchConstraint(String[] returningAttributes) 823 { 824 // Search parameters 825 SearchControls constraints = new SearchControls(); 826 827 // Position the wanted attributes 828 constraints.setReturningAttributes(returningAttributes); 829 830 // Choose depth of search 831 UserDirectory associatedUserDirectory = _userPopulationDAO.getUserPopulation(_associatedPopulationId).getUserDirectory(_associatedUserDirectoryId); 832 int usersSearchScope = ScopeEnumerator.parseScope((String) associatedUserDirectory.getParameterValues().get(LdapUserDirectory.PARAM_USERS_SEARCH_SCOPE)); 833 constraints.setSearchScope(usersSearchScope); 834 835 return constraints; 836 } 837 838 /** 839 * Get constraints for a search. 840 * @return The constraints as a SearchControls. 841 */ 842 protected SearchControls _getSearchConstraint() 843 { 844 // Search parameters 845 SearchControls constraints = new SearchControls(); 846 847 // Only one attribute to retrieve 848 constraints.setReturningAttributes(new String [] {_groupsIdAttribute, _groupsDescriptionAttribute, _groupsMemberAttribute}); 849 // Choose the depth of search 850 constraints.setSearchScope(_groupsSearchScope); 851 return constraints; 852 } 853 854 /** 855 * Get group as JSON object 856 * @param group the group 857 * @param users true to get users' group 858 * @return the group as JSON object 859 */ 860 protected Map<String, Object> _group2JSON(Group group, boolean users) 861 { 862 Map<String, Object> group2json = new HashMap<>(); 863 group2json.put("id", group.getIdentity().getId()); 864 group2json.put("groupDirectory", group.getIdentity().getDirectoryId()); 865 group2json.put("groupDirectoryLabel", group.getGroupDirectory().getLabel()); 866 group2json.put("label", group.getLabel()); 867 if (users) 868 { 869 group2json.put("users", group.getUsers()); 870 } 871 return group2json; 872 } 873 874 /** 875 * Group comparator. 876 */ 877 private static class GroupComparator implements Comparator<Group> 878 { 879 /** 880 * Constructor. 881 */ 882 public GroupComparator() 883 { 884 // Nothing to do. 885 } 886 887 @Override 888 public int compare(Group g1, Group g2) 889 { 890 if (g1.getIdentity().getId().equals(g2.getIdentity().getId())) 891 { 892 return 0; 893 } 894 895 // Case insensitive sort 896 int compareTo = g1.getLabel().toLowerCase().compareTo(g2.getLabel().toLowerCase()); 897 if (compareTo == 0) 898 { 899 return g1.getIdentity().getId().compareTo(g2.getIdentity().getId()); 900 } 901 return compareTo; 902 } 903 } 904 905 /** 906 * Implementation of {@link Group} for Ldap group directory 907 */ 908 private static final class LdapGroup implements Group 909 { 910 private boolean _userInitialized; 911 private Set<UserIdentity> _users; 912 private GroupIdentity _identity; 913 private String _groupLabel; 914 private LdapGroupDirectory _groupDirectory; 915 private Attribute _membersAttr; 916 private Logger _logger; 917 918 LdapGroup(GroupIdentity identity, String label, LdapGroupDirectory groupDirectory, Attribute membersAttr, Logger logger) 919 { 920 _identity = identity; 921 _groupLabel = label; 922 _groupDirectory = groupDirectory; 923 _membersAttr = membersAttr; 924 _logger = logger; 925 _userInitialized = false; 926 _users = new HashSet<>(); 927 } 928 929 @Override 930 public GroupIdentity getIdentity() 931 { 932 return _identity; 933 } 934 935 @Override 936 public String getLabel() 937 { 938 return _groupLabel; 939 } 940 941 @Override 942 public GroupDirectory getGroupDirectory() 943 { 944 return _groupDirectory; 945 } 946 947 @Override 948 public Set<UserIdentity> getUsers() 949 { 950 if (!_userInitialized) 951 { 952 if (_hasUsersFromCache(_identity)) 953 { 954 _users.addAll(_getUsersFromCache(_identity)); 955 } 956 else 957 { 958 _users.addAll(_membersAttr != null ? _getUsersFromMembersAttr() 959 : _groupDirectory._getUsersFromMembersOfAttr(_identity.getId())); 960 _loadUsersInCache(_identity, _users); 961 } 962 _userInitialized = true; 963 } 964 return _users; 965 } 966 967 @SuppressWarnings("synthetic-access") 968 private boolean _hasUsersFromCache(GroupIdentity groupIdentity) 969 { 970 return _groupDirectory.isCacheEnabled() && _groupDirectory.getObjectFromCache(groupIdentity) != null; 971 } 972 973 @SuppressWarnings({"synthetic-access", "unchecked"}) 974 private Set<UserIdentity> _getUsersFromCache(GroupIdentity groupIdentity) // only call if _hasUsersFromCache returned true before 975 { 976 _logger.debug("Users found in cache for group '{}", groupIdentity); 977 return (Set<UserIdentity>) _groupDirectory.getObjectFromCache(groupIdentity); 978 } 979 980 @SuppressWarnings("synthetic-access") 981 private void _loadUsersInCache(GroupIdentity groupIdentity, Set<UserIdentity> users) 982 { 983 if (_groupDirectory.isCacheEnabled()) 984 { 985 _groupDirectory.addObjectInCache(groupIdentity, users); 986 _logger.debug("Users loaded in cache for group '{}", groupIdentity); 987 } 988 } 989 990 private Set<UserIdentity> _getUsersFromMembersAttr() 991 { 992 Set<UserIdentity> users = new HashSet<>(); 993 994 // First fill an intermediate list of members as strings 995 // Do not resolve yet in order to close enumeration ASAP 996 NamingEnumeration members = null; 997 List<String> userDNs = new ArrayList<>(); 998 try 999 { 1000 // Retrieve the members of the group 1001 members = _membersAttr.getAll(); 1002 while (members.hasMore()) 1003 { 1004 String userDN = (String) members.next(); 1005 userDNs.add(userDN); 1006 } 1007 } 1008 catch (NamingException e) 1009 { 1010 _logger.warn("Missing at least one value for an attribute in an ldap entry. Group will be ignored.", e); 1011 } 1012 finally 1013 { 1014 _cleanup(null, members); 1015 } 1016 1017 // Then resolve the users 1018 for (String userDN : userDNs) 1019 { 1020 // Retrieve the identity 1021 UserIdentity identity = _isDn(userDN) ? _groupDirectory._getUserInLdapFromDn(userDN) 1022 : _groupDirectory._getUserInLdapFromUid(userDN); 1023 1024 if (identity != null) 1025 { 1026 // Add the curent user 1027 users.add(identity); 1028 } 1029 } 1030 1031 return users; 1032 } 1033 1034 private boolean _isDn(String userDN) 1035 { 1036 // Let's say that if it contains the '=' character, it is a DN, otherwise it is a UID 1037 return userDN.contains("="); 1038 } 1039 1040 @SuppressWarnings("synthetic-access") 1041 private void _cleanup(Context context, NamingEnumeration members) 1042 { 1043 _groupDirectory._cleanup(context, members); 1044 } 1045 1046 @Override 1047 public String toString() 1048 { 1049 StringBuffer sb = new StringBuffer("UserGroup["); 1050 sb.append(_identity); 1051 sb.append(" ("); 1052 sb.append(_groupLabel); 1053 sb.append(") => "); 1054 if (_userInitialized) 1055 { 1056 sb.append(_users.toString()); 1057 } 1058 else 1059 { 1060 sb.append("\"Users are not loaded yet\""); 1061 } 1062 sb.append("]"); 1063 return sb.toString(); 1064 } 1065 1066 @Override 1067 public boolean equals(Object another) 1068 { 1069 if (another == null || !(another instanceof LdapGroup)) 1070 { 1071 return false; 1072 } 1073 1074 LdapGroup otherGroup = (LdapGroup) another; 1075 1076 return _identity != null && _identity.equals(otherGroup.getIdentity()); 1077 } 1078 1079 @Override 1080 public int hashCode() 1081 { 1082 return _identity.hashCode(); 1083 } 1084 } 1085}