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