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