001/* 002 * Copyright 2016 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.user.directory; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.Hashtable; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027 028import javax.naming.AuthenticationException; 029import javax.naming.Name; 030import javax.naming.NameParser; 031import javax.naming.NamingEnumeration; 032import javax.naming.NamingException; 033import javax.naming.PartialResultException; 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.Control; 041import javax.naming.ldap.InitialLdapContext; 042import javax.naming.ldap.LdapContext; 043import javax.naming.ldap.SortControl; 044 045import org.apache.avalon.framework.activity.Disposable; 046import org.apache.avalon.framework.component.Component; 047import org.apache.avalon.framework.service.ServiceException; 048import org.apache.avalon.framework.service.ServiceManager; 049import org.apache.commons.lang3.StringUtils; 050 051import org.ametys.core.cache.AbstractCacheManager; 052import org.ametys.core.cache.Cache; 053import org.ametys.core.user.User; 054import org.ametys.core.user.directory.NotUniqueUserException; 055import org.ametys.core.user.directory.UserDirectory; 056import org.ametys.core.util.Cacheable; 057import org.ametys.core.util.ldap.AbstractLDAPConnector; 058import org.ametys.core.util.ldap.ScopeEnumerator; 059import org.ametys.plugins.core.impl.user.LdapUserIdentity; 060import org.ametys.runtime.i18n.I18nizableTextParameter; 061import org.ametys.runtime.i18n.I18nizableText; 062 063/** 064 * Use an ldap directory for getting the list of users and also authenticating 065 * them.<br> 066 */ 067public class LdapUserDirectory extends AbstractLDAPConnector implements UserDirectory, Component, Cacheable, Disposable 068{ 069 /** Name of the parameter holding the datasource id */ 070 public static final String PARAM_DATASOURCE_ID = "runtime.users.ldap.datasource"; 071 /** Relative DN for users. */ 072 public static final String PARAM_USERS_RELATIVE_DN = "runtime.users.ldap.peopleDN"; 073 /** Filter for limiting the search. */ 074 public static final String PARAM_USERS_OBJECT_FILTER = "runtime.users.ldap.baseFilter"; 075 /** The scope used for search. */ 076 public static final String PARAM_USERS_SEARCH_SCOPE = "runtime.users.ldap.scope"; 077 /** Name of the login attribute. */ 078 public static final String PARAM_USERS_LOGIN_ATTRIBUTE = "runtime.users.ldap.loginAttr"; 079 /** Name of the first name attribute. */ 080 public static final String PARAM_USERS_FIRSTNAME_ATTRIBUTE = "runtime.users.ldap.firstnameAttr"; 081 /** Name of the last name attribute. */ 082 public static final String PARAM_USERS_LASTNAME_ATTRIBUTE = "runtime.users.ldap.lastnameAttr"; 083 /** Name of the email attribute. */ 084 public static final String PARAM_USERS_EMAIL_ATTRIBUTE = "runtime.users.ldap.emailAttr"; 085 /** To know if email is a mandatory attribute */ 086 public static final String PARAM_USERS_EMAIL_IS_MANDATORY = "runtime.users.ldap.emailMandatory"; 087 /** True to sort the results on the server side, false to get the results unsorted. */ 088 public static final String PARAM_SERVER_SIDE_SORTING = "runtime.users.ldap.serverSideSorting"; 089 090 private static final String __LDAP_USERDIRECTORY_USER_BY_LOGIN_CACHE_NAME_PREFIX = LdapUserDirectory.class.getName() + "$by.login$"; 091 private static final String __LDAP_USERDIRECTORY_USER_BY_MAIL_CACHE_NAME_PREFIX = LdapUserDirectory.class.getName() + "$by.mail$"; 092 093 094 /** Relative DN for users. */ 095 protected String _usersRelativeDN; 096 /** Filter for limiting the search. */ 097 protected String _usersObjectFilter; 098 /** The scope used for search. */ 099 protected int _usersSearchScope; 100 /** Name of the login attribute. */ 101 protected String _usersLoginAttribute; 102 /** Name of the first name attribute. */ 103 protected String _usersFirstnameAttribute; 104 /** Name of the last name attribute. */ 105 protected String _usersLastnameAttribute; 106 /** Name of the email attribute. */ 107 protected String _usersEmailAttribute; 108 /** To know if email is a mandatory attribute */ 109 protected boolean _userEmailIsMandatory; 110 /** The LDAP search page size. */ 111 protected int _pageSize; 112 113 private String _udModelId; 114 private Map<String, Object> _paramValues; 115 private String _populationId; 116 private String _label; 117 private String _id; 118 119 // Cannot use _populationId + "#" + _id as two UserDirectories with same id can co-exist during a short amount of time (during UserPopulationDAO#_readPopulations) 120 private final String _uniqueCacheSuffix = org.ametys.core.util.StringUtils.generateKey(); 121 122 private AbstractCacheManager _cacheManager; 123 124 public String getId() 125 { 126 return _id; 127 } 128 129 public String getLabel() 130 { 131 return _label; 132 } 133 134 @Override 135 public void service(ServiceManager serviceManager) throws ServiceException 136 { 137 super.service(serviceManager); 138 _cacheManager = (AbstractCacheManager) serviceManager.lookup(AbstractCacheManager.ROLE); 139 } 140 141 @Override 142 public void dispose() 143 { 144 removeCaches(); 145 } 146 147 @Override 148 public AbstractCacheManager getCacheManager() 149 { 150 return _cacheManager; 151 } 152 153 private String getUniqueCacheIdSuffix() 154 { 155 return _uniqueCacheSuffix; 156 } 157 158 @Override 159 public Collection<SingleCacheConfiguration> getManagedCaches() 160 { 161 return Arrays.asList( 162 SingleCacheConfiguration.of( 163 __LDAP_USERDIRECTORY_USER_BY_LOGIN_CACHE_NAME_PREFIX + getUniqueCacheIdSuffix(), 164 _buildI18n("PLUGINS_CORE_USERS_LDAPUSER_CACHE_BY_LOGIN_LABEL"), 165 _buildI18n("PLUGINS_CORE_USERS_LDAPUSER_CACHE_BY_LOGIN_DESC")), 166 SingleCacheConfiguration.of( 167 __LDAP_USERDIRECTORY_USER_BY_MAIL_CACHE_NAME_PREFIX + getUniqueCacheIdSuffix(), 168 _buildI18n("PLUGINS_CORE_USERS_LDAPUSER_CACHE_BY_MAIL_LABEL"), 169 _buildI18n("PLUGINS_CORE_USERS_LDAPUSER_CACHE_BY_MAIL_DESC")) 170 ); 171 } 172 173 private I18nizableText _buildI18n(String i18nKey) 174 { 175 String catalogue = "plugin.core-impl"; 176 I18nizableText userDirectoryId = new I18nizableText(getPopulationId() + "#" + getId()); 177 Map<String, I18nizableTextParameter> labelParams = Map.of("id", userDirectoryId); 178 return new I18nizableText(catalogue, i18nKey, labelParams); 179 } 180 181 private Cache<String, User> getCacheByLogin() 182 { 183 return getCache(__LDAP_USERDIRECTORY_USER_BY_LOGIN_CACHE_NAME_PREFIX + getUniqueCacheIdSuffix()); 184 } 185 186 private Cache<String, User> getCacheByMail() 187 { 188 return getCache(__LDAP_USERDIRECTORY_USER_BY_MAIL_CACHE_NAME_PREFIX + getUniqueCacheIdSuffix()); 189 } 190 191 @Override 192 public void init(String id, String udModelId, Map<String, Object> paramValues, String label) throws Exception 193 { 194 _id = id; 195 _udModelId = udModelId; 196 _paramValues = paramValues; 197 _label = label; 198 199 _usersRelativeDN = (String) paramValues.get(PARAM_USERS_RELATIVE_DN); 200 _usersObjectFilter = (String) paramValues.get(PARAM_USERS_OBJECT_FILTER); 201 _usersSearchScope = ScopeEnumerator.parseScope((String) paramValues.get(PARAM_USERS_SEARCH_SCOPE)); 202 _usersLoginAttribute = (String) paramValues.get(PARAM_USERS_LOGIN_ATTRIBUTE); 203 204 _usersFirstnameAttribute = (String) paramValues.get(PARAM_USERS_FIRSTNAME_ATTRIBUTE); 205 if (_usersFirstnameAttribute != null && _usersFirstnameAttribute.length() == 0) 206 { 207 _usersFirstnameAttribute = null; 208 } 209 210 _usersLastnameAttribute = (String) paramValues.get(PARAM_USERS_LASTNAME_ATTRIBUTE); 211 _usersEmailAttribute = (String) paramValues.get(PARAM_USERS_EMAIL_ATTRIBUTE); 212 _userEmailIsMandatory = (Boolean) paramValues.get(PARAM_USERS_EMAIL_IS_MANDATORY); 213 214 String dataSourceId = (String) paramValues.get(PARAM_DATASOURCE_ID); 215 _delayedInitialize(dataSourceId); 216 217 _pageSize = __DEFAULT_PAGE_SIZE; 218 219 createCaches(); 220 } 221 222 @Override 223 public void setPopulationId(String populationId) 224 { 225 _populationId = populationId; 226 } 227 228 @Override 229 public String getPopulationId() 230 { 231 return _populationId; 232 } 233 234 @Override 235 public Map<String, Object> getParameterValues() 236 { 237 return _paramValues; 238 } 239 240 @Override 241 public String getUserDirectoryModelId() 242 { 243 return _udModelId; 244 } 245 246 @Override 247 public Collection<User> getUsers() 248 { 249 // Create a users list 250 List<User> users = new ArrayList<>(); 251 try 252 { 253 for (SearchResult result : _search(_pageSize, _usersRelativeDN, _usersObjectFilter, _getSearchConstraint(0), false)) 254 { 255 try 256 { 257 Map<String, Object> attributes = _getAttributes(result); 258 if (attributes != null) 259 { 260 // Create user 261 User user = _createUser(attributes); 262 263 if (isCachingEnabled() && user != null) 264 { 265 getCacheByLogin().put(user.getIdentity().getLogin(), user); 266 } 267 268 users.add(user); 269 } 270 } 271 catch (NamingException e) 272 { 273 getLogger().error("Error of communication with ldap server on one user. Continuing", e); 274 } 275 } 276 } 277 catch (NamingException e) 278 { 279 getLogger().error("Error of communication with ldap server", e); 280 } 281 282 // Return the users list as a users collection (may be empty) 283 return users; 284 } 285 286 @Override 287 public List<User> getUsers(int count, int offset, Map<String, Object> parameters) 288 { 289 String pattern = (String) parameters.get("pattern"); 290 if (StringUtils.isEmpty(pattern)) 291 { 292 pattern = null; 293 } 294 295 if (count != 0) 296 { 297 Map<String, Map<String, Object>> entries = new LinkedHashMap<>(); 298 return _internalGetUsers(entries, count, offset >= 0 ? offset : 0, pattern, 0); 299 } 300 return new ArrayList<>(); 301 } 302 303 @Override 304 public User getUserByEmail(String email) throws NotUniqueUserException 305 { 306 if (isCachingEnabled() && getCacheByMail().hasKey(email)) 307 { 308 User user = getCacheByMail().get(email); 309 return user; 310 } 311 312 User principal = null; 313 314 DirContext context = null; 315 NamingEnumeration<SearchResult> results = null; 316 317 try 318 { 319 // Connection to the LDAP server 320 context = new InitialDirContext(_getContextEnv()); 321 322 // Escape the login and create the search filter 323 String filter = "(&" + _usersObjectFilter + "(" + _usersEmailAttribute + "={0}))"; 324 Object[] params = new Object[] {email}; 325 326 // Execute ldap search 327 results = context.search(_usersRelativeDN, filter, params, _getSearchConstraint(0)); 328 329 // Search the user 330 // Case insensitive search are fine here, so no need to loop on results 331 if (results.hasMore()) 332 { 333 SearchResult result = results.next(); 334 Map<String, Object> attributes = _getAttributes(result); 335 if (attributes != null) 336 { 337 // Add a new user to the list 338 principal = _createUser(attributes); 339 } 340 341 // Test if the enumeration has more results with hasMoreElements to avoid unnecessary logs. 342 if (results.hasMoreElements()) 343 { 344 // Cancel the result because there are several matches for one login 345 throw new NotUniqueUserException("Multiple matches for attribute '" + _usersEmailAttribute + "' and value = '" + email + "'"); 346 } 347 } 348 349 if (isCachingEnabled()) 350 { 351 getCacheByMail().put(email, principal); 352 } 353 } 354 catch (PartialResultException e) 355 { 356 if (_ldapFollowReferrals) 357 { 358 getLogger().warn("Error communicating with ldap server retrieving user with email '{}'", email, e); 359 } 360 else 361 { 362 getLogger().debug("Error communicating with ldap server retrieving user with email '{}'", email, e); 363 } 364 } 365 catch (NamingException e) 366 { 367 throw new IllegalStateException("Error communicating with ldap server retrieving user with email '" + email + "'", e); 368 } 369 370 finally 371 { 372 // Close connections 373 _cleanup(context, results); 374 } 375 376 // Return the users or null 377 return principal; 378 } 379 380 @Override 381 public User getUser(String login) 382 { 383 if (isCachingEnabled() && getCacheByLogin().hasKey(login)) 384 { 385 User user = getCacheByLogin().get(login); 386 return user; 387 } 388 389 User principal = null; 390 391 DirContext context = null; 392 NamingEnumeration<SearchResult> results = null; 393 394 try 395 { 396 // Connection to the LDAP server 397 context = new InitialDirContext(_getContextEnv()); 398 399 // Escape the login and create the search filter 400 String filter = "(&" + _usersObjectFilter + "(" + _usersLoginAttribute + "={0}))"; 401 Object[] params = new Object[] {login}; 402 403 // Execute ldap search 404 results = context.search(_usersRelativeDN, filter, params, _getSearchConstraint(0)); 405 406 // Search the user 407 // Case insensitive search are NOT fine here, so we need to loop on results to find exact match in case of multiple approximative results 408 while (results.hasMore()) 409 { 410 SearchResult result = results.next(); 411 Map<String, Object> attributes = _getAttributes(result); 412 if (attributes != null) 413 { 414 if (!StringUtils.equals(login, (String) attributes.get(_usersLoginAttribute))) 415 { 416 // LDAP search may be insensitive, but this method have to be case sensitive 417 // So we manually check 418 continue; 419 } 420 421 // Test if the enumeration has more results with hasMoreElements to avoid unnecessary logs. 422 if (principal != null) 423 { 424 // Cancel the result because there are several matches for one login 425 principal = null; 426 getLogger().error("Multiple matches for attribute '{}' and value = '{}'", _usersLoginAttribute, login); 427 break; 428 } 429 430 // Add a new user to the list 431 principal = _createUser(attributes); 432 } 433 } 434 435 if (isCachingEnabled()) 436 { 437 getCacheByLogin().put(login, principal); 438 } 439 } 440 catch (PartialResultException e) 441 { 442 if (_ldapFollowReferrals) 443 { 444 getLogger().warn("Error communicating with ldap server retrieving user with login '{}'", login, e); 445 } 446 else 447 { 448 getLogger().debug("Error communicating with ldap server retrieving user with login '{}'", login, e); 449 } 450 } 451 catch (NamingException e) 452 { 453 throw new IllegalStateException("Error communicating with ldap server retrieving user with login '" + login + "'", e); 454 } 455 456 finally 457 { 458 // Close connections 459 _cleanup(context, results); 460 } 461 462 // Return the users or null 463 return principal; 464 } 465 466 @Override 467 public boolean checkCredentials(String login, String password) 468 { 469 boolean authenticated = false; 470 471 // Check password is not empty 472 if (StringUtils.isNotEmpty(password)) 473 { 474 // Retrieve user DN 475 String userDN = getUserDN(login); 476 if (userDN != null) 477 { 478 DirContext context = null; 479 480 // Retrieve connection parameters 481 Hashtable<String, String> env = _getContextEnv(); 482 483 // Edit DN and password for authentication 484 env.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple"); 485 env.put(javax.naming.Context.SECURITY_PRINCIPAL, userDN); 486 env.put(javax.naming.Context.SECURITY_CREDENTIALS, password); 487 488 try 489 { 490 // Connection and authentication to LDAP server 491 context = new InitialDirContext(env); 492 // Authentication succeeded 493 authenticated = true; 494 } 495 catch (AuthenticationException e) 496 { 497 getLogger().info("Authentication failed", e); 498 } 499 catch (NamingException e) 500 { 501 // Error 502 getLogger().error("Error communication with ldap server", e); 503 } 504 finally 505 { 506 // Close connections 507 _cleanup(context, null); 508 } 509 } 510 } 511 else if (getLogger().isDebugEnabled()) 512 { 513 getLogger().debug("LDAP Authentication failed since no password (or an empty one) was given"); 514 } 515 516 // If an error happened, do not authenticate the user 517 return authenticated; 518 } 519 520 /** 521 * Get the distinguished name of an user by his login. 522 * @param login Login of the user. 523 * @return The dn of the user, or null if there is no match or if multiple 524 * matches. 525 */ 526 public String getUserDN(String login) 527 { 528 String userDN = null; 529 DirContext context = null; 530 NamingEnumeration<SearchResult> results = null; 531 532 try 533 { 534 // Connection to the LDAP server 535 context = new InitialDirContext(_getContextEnv()); 536 537 // Create search filter 538 String filter = "(&" + _usersObjectFilter + "(" + _usersLoginAttribute + "={0}))"; 539 Object[] params = new Object[] {login}; 540 541 SearchControls constraints = new SearchControls(); 542 // Choose depth of parameterized search 543 constraints.setSearchScope(_usersSearchScope); 544 // Do not ask attributes, we only want the DN 545 constraints.setReturningAttributes(new String[] {}); 546 547 // Execute ldap search 548 results = context.search(_usersRelativeDN, filter, params, constraints); 549 550 // Fill users list 551 if (results.hasMore()) 552 { 553 SearchResult result = results.next(); 554 555 // Retrieve the DN 556 userDN = result.getName(); 557 if (result.isRelative()) 558 { 559 // Retrieve the absolute DN 560 NameParser parser = context.getNameParser(""); 561 Name topDN = parser.parse(context.getNameInNamespace()); 562 topDN.addAll(parser.parse(_usersRelativeDN)); 563 topDN.addAll(parser.parse(userDN)); 564 userDN = topDN.toString(); 565 } 566 567 if (results.hasMoreElements()) 568 { 569 // Cancel the result because there are several matches for one login 570 userDN = null; 571 getLogger().error("Multiple matches for attribute \"{}\" and value = \"{}\"", _usersLoginAttribute, login); 572 } 573 } 574 } 575 catch (NamingException e) 576 { 577 getLogger().error("Error communicating with ldap server retrieving user with login '" + login + "'", e); 578 } 579 580 finally 581 { 582 // Close connections 583 _cleanup(context, results); 584 } 585 return userDN; 586 } 587 588 /** 589 * Create a new user from LDAP attributes 590 * @param attributes the LDAP attributes 591 * @return the user 592 */ 593 protected User _createUser(Map<String, Object> attributes) 594 { 595 if (attributes == null) 596 { 597 return null; 598 } 599 600 String login = (String) attributes.get(_usersLoginAttribute); 601 String userDn = (String) attributes.get("userDN"); 602 String lastName = (String) attributes.get(_usersLastnameAttribute); 603 604 String firstName = null; 605 if (_usersFirstnameAttribute != null) 606 { 607 firstName = (String) attributes.get(_usersFirstnameAttribute); 608 } 609 610 String email = (String) attributes.get(_usersEmailAttribute); 611 612 return new User(new LdapUserIdentity(login, _populationId, userDn), lastName, firstName, email, this); 613 } 614 615 /** 616 * Get the user list. 617 * @param entries Where to store entries 618 * @param count The maximum number of users to sax. Cannot be 0. Can be -1 to all. 619 * @param offset The results to ignore 620 * @param pattern The pattern to match. 621 * @param possibleErrors This number will be added to count to set the max of the request, but count results will still be returned. The difference stands for errors. 622 * @return the final offset 623 */ 624 protected List<User> _internalGetUsers(Map<String, Map<String, Object>> entries, int count, int offset, String pattern, int possibleErrors) 625 { 626 LdapContext context = null; 627 NamingEnumeration<SearchResult> results = null; 628 629 try 630 { 631 // Connection to the LDAP server 632 context = new InitialLdapContext(_getContextEnv(), null); 633 if (_serverSideSorting) 634 { 635 context.setRequestControls(_getSortControls()); 636 } 637 638 Map filter = _getPatternFilter(pattern); 639 640 // Execute ldap search 641 results = context.search(_usersRelativeDN, 642 (String) filter.get("filter"), 643 (Object[]) filter.get("params"), 644 _getSearchConstraint(count == -1 ? 0 : (count + offset + possibleErrors))); 645 646 // Sax results 647 return _users(entries, count, offset, pattern, results, possibleErrors); 648 } 649 catch (NamingException e) 650 { 651 getLogger().error("Error during the communication with ldap server", e); 652 return new ArrayList<>(); 653 } 654 finally 655 { 656 // Close connections 657 _cleanup(context, results); 658 } 659 } 660 661 private List<User> _users(Map<String, Map<String, Object>> entries, int count, int offset, String pattern, NamingEnumeration<SearchResult> results, int possibleErrors) throws NamingException 662 { 663 int nbResults = 0; 664 665 boolean hasMoreElement = results.hasMoreElements(); 666 667 // First loop on the items to ignore (before the offset) 668 while (nbResults < offset && hasMoreElement) 669 { 670 nbResults++; 671 672 // FIXME we should check that this element has really attributes to count it as an real offset 673 results.nextElement(); 674 675 hasMoreElement = results.hasMoreElements(); 676 } 677 678 // Second loop to work 679 while ((count == -1 || entries.size() < count) && hasMoreElement) 680 { 681 nbResults++; 682 683 // Next element 684 SearchResult result = results.nextElement(); 685 Map<String, Object> attrs = _getAttributes(result); 686 if (attrs != null) 687 { 688 entries.put((String) attrs.get(_usersLoginAttribute), attrs); 689 } 690 691 hasMoreElement = results.hasMoreElements(); 692 } 693 694 695 // If we have less results than expected 696 // can be due to errors (null attributes) 697 // can be due to max results is less than wanted results 698 if (entries.size() < count && nbResults == count + offset + possibleErrors) 699 { 700 double nbErrors = count + possibleErrors - entries.size(); 701 double askedResultsSize = possibleErrors + count; 702 int newPossibleErrors = Math.max(possibleErrors + count - entries.size(), (int) Math.ceil((nbErrors / askedResultsSize + 1) * nbErrors)); 703 return _internalGetUsers(entries, count, offset, pattern, newPossibleErrors); 704 } 705 else 706 { 707 List<User> users = new ArrayList<>(); 708 for (Map<String, Object> attributes : entries.values()) 709 { 710 users.add(_createUser(attributes)); 711 } 712 return users; 713 } 714 } 715 716 /** 717 * Get the sort control. 718 * @return the sort controls. May be empty if a small error occurs 719 */ 720 protected Control[] _getSortControls() 721 { 722 try 723 { 724 SortControl sortControl = new SortControl(getSortByFields(), Control.NONCRITICAL); 725 return new Control[] {sortControl}; 726 } 727 catch (IOException e) 728 { 729 getLogger().warn("Cannot sort request on LDAP", e); 730 return new Control[0]; 731 } 732 } 733 734 /** 735 * Get the filter from a pattern. 736 * @param pattern The pattern to match. 737 * @return The result as a Map containing the filter and the parameters. 738 */ 739 protected Map<String, Object> _getPatternFilter(String pattern) 740 { 741 Map<String, Object> result = new HashMap<>(); 742 743 // Check if pattern 744 if (pattern == null) 745 { 746 result.put("filter", _usersObjectFilter); 747 result.put("params", new Object[0]); 748 } 749 else 750 { 751 // Create search filter escaping variables 752 StringBuffer filter = new StringBuffer("(&" + _usersObjectFilter + "(|("); 753 Object[] params = null; 754 755 if (_usersFirstnameAttribute == null) 756 { 757 filter.append(_usersLoginAttribute); 758 filter.append("=*{0}*)("); 759 filter.append(_usersLastnameAttribute); 760 filter.append("=*{1}*)))"); 761 params = new Object[] {pattern, pattern}; 762 } 763 else 764 { 765 filter.append(_usersLoginAttribute); 766 filter.append("=*{0}*)("); 767 filter.append(_usersFirstnameAttribute); 768 filter.append("=*{1}*)("); 769 filter.append(_usersLastnameAttribute); 770 filter.append("=*{2}*)))"); 771 params = new Object[] {pattern, pattern, pattern}; 772 } 773 774 result.put("filter", filter.toString()); 775 result.put("params", params); 776 } 777 return result; 778 } 779 780 /** 781 * Get constraints for a search. 782 * @param maxResults The maximum number of items that will be retrieve (0 783 * means all) 784 * @return The constraints as a SearchControls. 785 */ 786 protected SearchControls _getSearchConstraint(int maxResults) 787 { 788 // Search parameters 789 SearchControls constraints = new SearchControls(); 790 int attributesCount = 4; 791 int index = 0; 792 793 if (_usersFirstnameAttribute == null) 794 { 795 attributesCount--; 796 } 797 798 // Position the wanted attributes 799 String[] attrs = new String[attributesCount]; 800 801 attrs[index++] = _usersLoginAttribute; 802 if (_usersFirstnameAttribute != null) 803 { 804 attrs[index++] = _usersFirstnameAttribute; 805 } 806 attrs[index++] = _usersLastnameAttribute; 807 attrs[index++] = _usersEmailAttribute; 808 809 constraints.setReturningAttributes(attrs); 810 811 // Choose depth of search 812 constraints.setSearchScope(_usersSearchScope); 813 814 if (maxResults > 0) 815 { 816 constraints.setCountLimit(maxResults); 817 } 818 819 return constraints; 820 } 821 822 /** 823 * Get the User corresponding to an user ldap entry 824 * @param attributes The ldap attributes of the entry to sax. 825 * @return the JSON representation 826 */ 827 @Deprecated 828 protected User _entry2User(Map<String, Object> attributes) 829 { 830 return _createUser(attributes); 831 } 832 833 /** 834 * Get attributes from a ldap entry. 835 * @param entry The ldap entry to get attributes from. 836 * @return The attributes in a map. 837 * @throws NamingException If an error with attributes occurred 838 */ 839 protected Map<String, Object> _getAttributes(SearchResult entry) throws NamingException 840 { 841 Map<String, Object> result = new HashMap<>(); 842 843 // Retrieve the entry attributes 844 Attributes attrs = entry.getAttributes(); 845 846 // Retrieve the DN 847 result.put("userDN", entry.getNameInNamespace()); 848 849 // Retrieve the login 850 Attribute ldapAttr = attrs.get(_usersLoginAttribute); 851 if (ldapAttr == null) 852 { 853 getLogger().warn("Missing login attribute : '{}'", _usersLoginAttribute); 854 return null; 855 } 856 result.put(_usersLoginAttribute, ldapAttr.get()); 857 858 if (_usersFirstnameAttribute != null) 859 { 860 // Retrieve the first name 861 ldapAttr = attrs.get(_usersFirstnameAttribute); 862 if (ldapAttr == null) 863 { 864 getLogger().warn("Missing firstname attribute : '{}', for user '{}'.", _usersFirstnameAttribute, result.get(_usersLoginAttribute)); 865 return null; 866 } 867 result.put(_usersFirstnameAttribute, ldapAttr.get()); 868 } 869 870 // Retrieve the last name 871 ldapAttr = attrs.get(_usersLastnameAttribute); 872 if (ldapAttr == null) 873 { 874 getLogger().warn("Missing lastname attribute : '{}', for user '{}'.", _usersLastnameAttribute, result.get(_usersLoginAttribute)); 875 return null; 876 } 877 result.put(_usersLastnameAttribute, ldapAttr.get()); 878 879 // Retrieve the email 880 ldapAttr = attrs.get(_usersEmailAttribute); 881 if (ldapAttr == null && _userEmailIsMandatory) 882 { 883 getLogger().warn("Missing email attribute : '{}', for user '{}'.", _usersEmailAttribute, result.get(_usersLoginAttribute)); 884 return null; 885 } 886 887 if (ldapAttr == null) 888 { 889 result.put(_usersEmailAttribute, ""); 890 } 891 else 892 { 893 result.put(_usersEmailAttribute, ldapAttr.get()); 894 } 895 896 return result; 897 } 898 899 @Override 900 protected String[] getSortByFields() 901 { 902 return new String[] {_usersLastnameAttribute, _usersFirstnameAttribute}; 903 } 904}