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