001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS 026 */ 027 028package org.opends.admin.ads.util; 029 030import java.io.IOException; 031import java.net.ConnectException; 032import java.net.URI; 033import java.util.HashSet; 034import java.util.Hashtable; 035import java.util.Set; 036 037import javax.naming.CommunicationException; 038import javax.naming.Context; 039import javax.naming.NamingEnumeration; 040import javax.naming.NamingException; 041import javax.naming.directory.Attribute; 042import javax.naming.directory.Attributes; 043import javax.naming.directory.SearchControls; 044import javax.naming.directory.SearchResult; 045import javax.naming.ldap.Control; 046import javax.naming.ldap.InitialLdapContext; 047import javax.naming.ldap.StartTlsRequest; 048import javax.naming.ldap.StartTlsResponse; 049import javax.net.ssl.HostnameVerifier; 050import javax.net.ssl.KeyManager; 051import javax.net.ssl.TrustManager; 052 053import org.forgerock.i18n.LocalizableMessage; 054import org.forgerock.i18n.slf4j.LocalizedLogger; 055import org.opends.server.replication.plugin.EntryHistorical; 056import org.opends.server.schema.SchemaConstants; 057 058import com.forgerock.opendj.cli.Utils; 059 060/** 061 * Class providing some utilities to create LDAP connections using JNDI and 062 * to manage entries retrieved using JNDI. 063 * 064 */ 065public class ConnectionUtils 066{ 067 private static final String STARTTLS_PROPERTY = 068 "org.opends.connectionutils.isstarttls"; 069 070 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 071 072 /** 073 * Private constructor: this class cannot be instantiated. 074 */ 075 private ConnectionUtils() 076 { 077 } 078 079 /** 080 * Creates a clear LDAP connection and returns the corresponding LdapContext. 081 * This methods uses the specified parameters to create a JNDI environment 082 * hashtable and creates an InitialLdapContext instance. 083 * 084 * @param ldapURL 085 * the target LDAP URL 086 * @param dn 087 * passed as Context.SECURITY_PRINCIPAL if not null 088 * @param pwd 089 * passed as Context.SECURITY_CREDENTIALS if not null 090 * @param timeout 091 * passed as com.sun.jndi.ldap.connect.timeout if > 0 092 * @param env 093 * null or additional environment properties 094 * 095 * @throws NamingException 096 * the exception thrown when instantiating InitialLdapContext 097 * 098 * @return the created InitialLdapContext. 099 * @see javax.naming.Context 100 * @see javax.naming.ldap.InitialLdapContext 101 */ 102 public static InitialLdapContext createLdapContext(String ldapURL, String dn, 103 String pwd, int timeout, Hashtable<String, String> env) 104 throws NamingException 105 { 106 env = copy(env); 107 env.put(Context.INITIAL_CONTEXT_FACTORY, 108 "com.sun.jndi.ldap.LdapCtxFactory"); 109 env.put("java.naming.ldap.attributes.binary", 110 EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); 111 env.put(Context.PROVIDER_URL, ldapURL); 112 if (timeout >= 1) 113 { 114 env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(timeout)); 115 } 116 if (dn != null) 117 { 118 env.put(Context.SECURITY_PRINCIPAL, dn); 119 } 120 if (pwd != null) 121 { 122 env.put(Context.SECURITY_CREDENTIALS, pwd); 123 } 124 125 /* Contains the DirContext and the Exception if any */ 126 final Object[] pair = new Object[] 127 { null, null }; 128 final Hashtable<String, String> fEnv = env; 129 Thread t = new Thread(new Runnable() 130 { 131 @Override 132 public void run() 133 { 134 try 135 { 136 pair[0] = new InitialLdapContext(fEnv, null); 137 138 } catch (NamingException ne) 139 { 140 pair[1] = ne; 141 142 } catch (Throwable t) 143 { 144 t.printStackTrace(); 145 pair[1] = t; 146 } 147 } 148 }); 149 t.setDaemon(true); 150 return getInitialLdapContext(t, pair, timeout); 151 } 152 153 /** 154 * Creates an LDAPS connection and returns the corresponding LdapContext. 155 * This method uses the TrusteSocketFactory class so that the specified 156 * trust manager gets called during the SSL handshake. If trust manager is 157 * null, certificates are not verified during SSL handshake. 158 * 159 * @param ldapsURL the target *LDAPS* URL. 160 * @param dn passed as Context.SECURITY_PRINCIPAL if not null. 161 * @param pwd passed as Context.SECURITY_CREDENTIALS if not null. 162 * @param timeout passed as com.sun.jndi.ldap.connect.timeout if > 0. 163 * @param env null or additional environment properties. 164 * @param trustManager null or the trust manager to be invoked during SSL 165 * negotiation. 166 * @param keyManager null or the key manager to be invoked during SSL 167 * negotiation. 168 * @return the established connection with the given parameters. 169 * 170 * @throws NamingException the exception thrown when instantiating 171 * InitialLdapContext. 172 * 173 * @see javax.naming.Context 174 * @see javax.naming.ldap.InitialLdapContext 175 * @see TrustedSocketFactory 176 */ 177 public static InitialLdapContext createLdapsContext(String ldapsURL, 178 String dn, String pwd, int timeout, Hashtable<String, String> env, 179 TrustManager trustManager, KeyManager keyManager) throws NamingException { 180 env = copy(env); 181 env.put(Context.INITIAL_CONTEXT_FACTORY, 182 "com.sun.jndi.ldap.LdapCtxFactory"); 183 env.put("java.naming.ldap.attributes.binary", 184 EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); 185 env.put(Context.PROVIDER_URL, ldapsURL); 186 env.put("java.naming.ldap.factory.socket", 187 org.opends.admin.ads.util.TrustedSocketFactory.class.getName()); 188 189 if (dn != null) 190 { 191 env.put(Context.SECURITY_PRINCIPAL, dn); 192 } 193 194 if (pwd != null) 195 { 196 env.put(Context.SECURITY_CREDENTIALS, pwd); 197 } 198 199 if (trustManager == null) 200 { 201 trustManager = new BlindTrustManager(); 202 } 203 204 /* Contains the DirContext and the Exception if any */ 205 final Object[] pair = new Object[] {null, null}; 206 final Hashtable<String, String> fEnv = env; 207 final TrustManager fTrustManager = trustManager; 208 final KeyManager fKeyManager = keyManager; 209 210 Thread t = new Thread(new Runnable() { 211 @Override 212 public void run() { 213 try { 214 TrustedSocketFactory.setCurrentThreadTrustManager(fTrustManager, 215 fKeyManager); 216 pair[0] = new InitialLdapContext(fEnv, null); 217 } catch (NamingException | RuntimeException ne) { 218 pair[1] = ne; 219 } 220 } 221 }); 222 t.setDaemon(true); 223 return getInitialLdapContext(t, pair, timeout); 224 } 225 226 /** 227 * Clones the provided InitialLdapContext and returns a connection using 228 * the same parameters. 229 * @param ctx the connection to be cloned. 230 * @param timeout the timeout to establish the connection in milliseconds. 231 * Use {@code 0} to express no timeout. 232 * @param trustManager the trust manager to be used to connect. 233 * @param keyManager the key manager to be used to connect. 234 * @return the new InitialLdapContext connected to the server. 235 * @throws NamingException if there was an error creating the new connection. 236 */ 237 public static InitialLdapContext cloneInitialLdapContext( 238 final InitialLdapContext ctx, int timeout, TrustManager trustManager, 239 KeyManager keyManager) throws NamingException 240 { 241 Hashtable<?, ?> env = ctx.getEnvironment(); 242 Control[] ctls = ctx.getConnectControls(); 243 Control[] newCtls = null; 244 if (ctls != null) 245 { 246 newCtls = new Control[ctls.length]; 247 System.arraycopy(ctls, 0, newCtls, 0, ctls.length); 248 } 249 /* Contains the DirContext and the Exception if any */ 250 final Object[] pair = new Object[] {null, null}; 251 final Hashtable<?, ?> fEnv = env; 252 final TrustManager fTrustManager = trustManager; 253 final KeyManager fKeyManager = keyManager; 254 final Control[] fNewCtls = newCtls; 255 256 Thread t = new Thread(new Runnable() { 257 @Override 258 public void run() { 259 try { 260 if (isSSL(ctx) || isStartTLS(ctx)) 261 { 262 TrustedSocketFactory.setCurrentThreadTrustManager(fTrustManager, 263 fKeyManager); 264 } 265 pair[0] = new InitialLdapContext(fEnv, fNewCtls); 266 } catch (NamingException | RuntimeException ne) { 267 pair[1] = ne; 268 } 269 } 270 }); 271 return getInitialLdapContext(t, pair, timeout); 272 } 273 274 /** 275 * Creates an LDAP+StartTLS connection and returns the corresponding 276 * LdapContext. 277 * This method first creates an LdapContext with anonymous bind. Then it 278 * requests a StartTlsRequest extended operation. The StartTlsResponse is 279 * setup with the specified hostname verifier. Negotiation is done using a 280 * TrustSocketFactory so that the specified TrustManager gets called during 281 * the SSL handshake. 282 * If trust manager is null, certificates are not checked during SSL 283 * handshake. 284 * 285 * @param ldapURL the target *LDAP* URL. 286 * @param dn passed as Context.SECURITY_PRINCIPAL if not null. 287 * @param pwd passed as Context.SECURITY_CREDENTIALS if not null. 288 * @param timeout passed as com.sun.jndi.ldap.connect.timeout if > 0. 289 * @param env null or additional environment properties. 290 * @param trustManager null or the trust manager to be invoked during SSL 291 * negotiation. 292 * @param keyManager null or the key manager to be invoked during SSL 293 * negotiation. 294 * @param verifier null or the hostname verifier to be setup in the 295 * StartTlsResponse. 296 * @return the established connection with the given parameters. 297 * 298 * @throws NamingException the exception thrown when instantiating 299 * InitialLdapContext. 300 * 301 * @see javax.naming.Context 302 * @see javax.naming.ldap.InitialLdapContext 303 * @see javax.naming.ldap.StartTlsRequest 304 * @see javax.naming.ldap.StartTlsResponse 305 * @see TrustedSocketFactory 306 */ 307 308 public static InitialLdapContext createStartTLSContext(String ldapURL, 309 String dn, String pwd, int timeout, Hashtable<String, String> env, 310 TrustManager trustManager, KeyManager keyManager, 311 HostnameVerifier verifier) 312 throws NamingException 313 { 314 if (trustManager == null) 315 { 316 trustManager = new BlindTrustManager(); 317 } 318 if (verifier == null) { 319 verifier = new BlindHostnameVerifier(); 320 } 321 322 env = copy(env); 323 env.put(Context.INITIAL_CONTEXT_FACTORY, 324 "com.sun.jndi.ldap.LdapCtxFactory"); 325 env.put("java.naming.ldap.attributes.binary", 326 EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); 327 env.put(Context.PROVIDER_URL, ldapURL); 328 env.put(Context.SECURITY_AUTHENTICATION , "none"); 329 330 /* Contains the DirContext and the Exception if any */ 331 final Object[] pair = new Object[] {null, null}; 332 final Hashtable<?, ?> fEnv = env; 333 final String fDn = dn; 334 final String fPwd = pwd; 335 final TrustManager fTrustManager = trustManager; 336 final KeyManager fKeyManager = keyManager; 337 final HostnameVerifier fVerifier = verifier; 338 339 Thread t = new Thread(new Runnable() { 340 @Override 341 public void run() { 342 try { 343 StartTlsResponse tls; 344 345 InitialLdapContext result = new InitialLdapContext(fEnv, null); 346 347 tls = (StartTlsResponse) result.extendedOperation( 348 new StartTlsRequest()); 349 tls.setHostnameVerifier(fVerifier); 350 try 351 { 352 tls.negotiate(new TrustedSocketFactory(fTrustManager,fKeyManager)); 353 } 354 catch(IOException x) { 355 NamingException xx; 356 xx = new CommunicationException( 357 "Failed to negotiate Start TLS operation"); 358 xx.initCause(x); 359 result.close(); 360 throw xx; 361 } 362 363 result.addToEnvironment(STARTTLS_PROPERTY, "true"); 364 if (fDn != null) 365 { 366 result.addToEnvironment(Context.SECURITY_AUTHENTICATION , "simple"); 367 result.addToEnvironment(Context.SECURITY_PRINCIPAL, fDn); 368 if (fPwd != null) 369 { 370 result.addToEnvironment(Context.SECURITY_CREDENTIALS, fPwd); 371 } 372 result.reconnect(null); 373 } 374 pair[0] = result; 375 } catch (NamingException | RuntimeException ne) 376 { 377 pair[1] = ne; 378 } 379 } 380 }); 381 t.setDaemon(true); 382 return getInitialLdapContext(t, pair, timeout); 383 } 384 385 private static Hashtable<String, String> copy(Hashtable<String, String> env) { 386 return env != null ? new Hashtable<>(env) : new Hashtable<String, String>(); 387 } 388 389 /** 390 * Returns the LDAP URL used in the provided InitialLdapContext. 391 * @param ctx the context to analyze. 392 * @return the LDAP URL used in the provided InitialLdapContext. 393 */ 394 public static String getLdapUrl(InitialLdapContext ctx) 395 { 396 String s = null; 397 try 398 { 399 s = (String)ctx.getEnvironment().get(Context.PROVIDER_URL); 400 } 401 catch (NamingException ne) 402 { 403 // This is really strange. Seems like a bug somewhere. 404 logger.warn(LocalizableMessage.raw("Naming exception getting environment of "+ctx, 405 ne)); 406 } 407 return s; 408 } 409 410 /** 411 * Returns the host name used in the provided InitialLdapContext. 412 * @param ctx the context to analyze. 413 * @return the host name used in the provided InitialLdapContext. 414 */ 415 public static String getHostName(InitialLdapContext ctx) 416 { 417 String s = null; 418 try 419 { 420 URI ldapURL = new URI(getLdapUrl(ctx)); 421 s = ldapURL.getHost(); 422 } 423 catch (Throwable t) 424 { 425 // This is really strange. Seems like a bug somewhere. 426 logger.warn(LocalizableMessage.raw("Error getting host: "+t, t)); 427 } 428 return s; 429 } 430 431 /** 432 * Returns the port number used in the provided InitialLdapContext. 433 * @param ctx the context to analyze. 434 * @return the port number used in the provided InitialLdapContext. 435 */ 436 public static int getPort(InitialLdapContext ctx) 437 { 438 int port = -1; 439 try 440 { 441 URI ldapURL = new URI(getLdapUrl(ctx)); 442 port = ldapURL.getPort(); 443 } 444 catch (Throwable t) 445 { 446 // This is really strange. Seems like a bug somewhere. 447 logger.warn(LocalizableMessage.raw("Error getting port: "+t, t)); 448 } 449 return port; 450 } 451 452 /** 453 * Returns the host port representation of the server to which this 454 * context is connected. 455 * @param ctx the context to analyze. 456 * @return the host port representation of the server to which this 457 * context is connected. 458 */ 459 public static String getHostPort(InitialLdapContext ctx) 460 { 461 return getHostName(ctx)+":"+getPort(ctx); 462 } 463 464 /** 465 * Returns the bind DN used in the provided InitialLdapContext. 466 * @param ctx the context to analyze. 467 * @return the bind DN used in the provided InitialLdapContext. 468 */ 469 public static String getBindDN(InitialLdapContext ctx) 470 { 471 String bindDN = null; 472 try 473 { 474 bindDN = (String)ctx.getEnvironment().get(Context.SECURITY_PRINCIPAL); 475 } 476 catch (NamingException ne) 477 { 478 // This is really strange. Seems like a bug somewhere. 479 logger.warn(LocalizableMessage.raw("Naming exception getting environment of "+ctx, 480 ne)); 481 } 482 return bindDN; 483 } 484 485 /** 486 * Returns the password used in the provided InitialLdapContext. 487 * @param ctx the context to analyze. 488 * @return the password used in the provided InitialLdapContext. 489 */ 490 public static String getBindPassword(InitialLdapContext ctx) 491 { 492 String bindPwd = null; 493 try 494 { 495 bindPwd = (String)ctx.getEnvironment().get(Context.SECURITY_CREDENTIALS); 496 } 497 catch (NamingException ne) 498 { 499 // This is really strange. Seems like a bug somewhere. 500 logger.warn(LocalizableMessage.raw("Naming exception getting environment of "+ctx, 501 ne)); 502 } 503 return bindPwd; 504 } 505 506 /** 507 * Tells whether we are using SSL in the provided InitialLdapContext. 508 * @param ctx the context to analyze. 509 * @return <CODE>true</CODE> if we are using SSL and <CODE>false</CODE> 510 * otherwise. 511 */ 512 public static boolean isSSL(InitialLdapContext ctx) 513 { 514 boolean isSSL = false; 515 try 516 { 517 isSSL = getLdapUrl(ctx).toLowerCase().startsWith("ldaps"); 518 } 519 catch (Throwable t) 520 { 521 // This is really strange. Seems like a bug somewhere. 522 logger.warn(LocalizableMessage.raw("Error getting if is SSL "+t, t)); 523 } 524 return isSSL; 525 } 526 527 /** 528 * Tells whether we are using StartTLS in the provided InitialLdapContext. 529 * @param ctx the context to analyze. 530 * @return <CODE>true</CODE> if we are using StartTLS and <CODE>false</CODE> 531 * otherwise. 532 */ 533 public static boolean isStartTLS(InitialLdapContext ctx) 534 { 535 boolean isStartTLS = false; 536 try 537 { 538 isStartTLS = "true".equalsIgnoreCase((String)ctx.getEnvironment().get( 539 STARTTLS_PROPERTY)); 540 } 541 catch (NamingException ne) 542 { 543 // This is really strange. Seems like a bug somewhere. 544 logger.warn(LocalizableMessage.raw("Naming exception getting environment of "+ctx, 545 ne)); 546 } 547 return isStartTLS; 548 } 549 550 /** 551 * Method used to know if we can connect as administrator in a server with a 552 * given password and dn. 553 * @param ldapUrl the LDAP URL of the server. 554 * @param dn the dn to be used. 555 * @param pwd the password to be used. 556 * @param timeout the timeout to establish the connection in milliseconds. 557 * Use {@code 0} to express no timeout. 558 * @return <CODE>true</CODE> if we can connect and read the configuration and 559 * <CODE>false</CODE> otherwise. 560 */ 561 public static boolean canConnectAsAdministrativeUser(String ldapUrl, 562 String dn, String pwd, int timeout) 563 { 564 boolean canConnectAsAdministrativeUser = false; 565 try 566 { 567 InitialLdapContext ctx; 568 if (ldapUrl.toLowerCase().startsWith("ldap:")) 569 { 570 ctx = createLdapContext(ldapUrl, dn, pwd, timeout, 571 null); 572 } 573 else 574 { 575 ctx = createLdapsContext(ldapUrl, dn, pwd, timeout, 576 null, null, null); 577 } 578 579 canConnectAsAdministrativeUser = connectedAsAdministrativeUser(ctx); 580 } catch (NamingException ne) 581 { 582 // Nothing to do. 583 } catch (Throwable t) 584 { 585 throw new IllegalStateException("Unexpected throwable.", t); 586 } 587 return canConnectAsAdministrativeUser; 588 } 589 590 /** 591 * Method used to know if we are connected as administrator in a server with a 592 * given InitialLdapContext. 593 * @param ctx the context. 594 * @return <CODE>true</CODE> if we are connected and read the configuration 595 * and <CODE>false</CODE> otherwise. 596 */ 597 public static boolean connectedAsAdministrativeUser(InitialLdapContext ctx) 598 { 599 boolean connectedAsAdministrativeUser = false; 600 try 601 { 602 /* 603 * Search for the config to check that it is the directory manager. 604 */ 605 SearchControls searchControls = new SearchControls(); 606 searchControls.setSearchScope( 607 SearchControls. OBJECT_SCOPE); 608 searchControls.setReturningAttributes( 609 new String[] { SchemaConstants.NO_ATTRIBUTES }); 610 NamingEnumeration<SearchResult> sr = 611 ctx.search("cn=config", "objectclass=*", searchControls); 612 try 613 { 614 while (sr.hasMore()) 615 { 616 sr.next(); 617 } 618 } 619 finally 620 { 621 try 622 { 623 sr.close(); 624 } 625 catch(Exception ex) 626 { 627 logger.warn(LocalizableMessage.raw( 628 "Unexpected error closing enumeration on cn=Config entry", ex)); 629 } 630 } 631 connectedAsAdministrativeUser = true; 632 } catch (NamingException ne) 633 { 634 // Nothing to do. 635 } catch (Throwable t) 636 { 637 throw new IllegalStateException("Unexpected throwable.", t); 638 } 639 return connectedAsAdministrativeUser; 640 } 641 642 /** 643 * This is just a commodity method used to try to get an InitialLdapContext. 644 * @param t the Thread to be used to create the InitialLdapContext. 645 * @param pair an Object[] array that contains the InitialLdapContext and the 646 * Throwable if any occurred. 647 * @param timeout the timeout in milliseconds. If we do not get to create the 648 * connection before the timeout a CommunicationException will be thrown. 649 * @return the created InitialLdapContext 650 * @throws NamingException if something goes wrong during the creation. 651 */ 652 private static InitialLdapContext getInitialLdapContext(Thread t, 653 Object[] pair, int timeout) throws NamingException 654 { 655 try 656 { 657 if (timeout > 0) 658 { 659 t.start(); 660 t.join(timeout); 661 } else 662 { 663 t.run(); 664 } 665 666 } catch (InterruptedException x) 667 { 668 // This might happen for problems in sockets 669 // so it does not necessarily imply a bug 670 } 671 672 boolean throwException = false; 673 674 if (timeout > 0 && t.isAlive()) 675 { 676 t.interrupt(); 677 try 678 { 679 t.join(2000); 680 } catch (InterruptedException x) 681 { 682 // This might happen for problems in sockets 683 // so it does not necessarily imply a bug 684 } 685 throwException = true; 686 } 687 688 if (pair[0] == null && pair[1] == null) 689 { 690 throwException = true; 691 } 692 693 if (throwException) 694 { 695 NamingException xx; 696 ConnectException x = new ConnectException("Connection timed out"); 697 xx = new CommunicationException("Connection timed out"); 698 xx.initCause(x); 699 throw xx; 700 } 701 702 if (pair[1] != null) 703 { 704 if (pair[1] instanceof NamingException) 705 { 706 throw (NamingException) pair[1]; 707 708 } else if (pair[1] instanceof RuntimeException) 709 { 710 throw (RuntimeException) pair[1]; 711 712 } else if (pair[1] instanceof Throwable) 713 { 714 throw new IllegalStateException("Unexpected throwable occurred", 715 (Throwable) pair[1]); 716 } 717 } 718 return (InitialLdapContext) pair[0]; 719 } 720 721 /** 722 * Returns the LDAP URL for the provided parameters. 723 * @param host the host name. 724 * @param port the LDAP port. 725 * @param useSSL whether to use SSL or not. 726 * @return the LDAP URL for the provided parameters. 727 */ 728 public static String getLDAPUrl(String host, int port, boolean useSSL) 729 { 730 host = Utils.getHostNameForLdapUrl(host); 731 return (useSSL ? "ldaps://" : "ldap://") + host + ":" + port; 732 } 733 734 /** 735 * Returns the String representation of the first value of an attribute in a 736 * LDAP entry. 737 * @param entry the entry. 738 * @param attrName the attribute name. 739 * @return the String representation of the first value of an attribute in a 740 * LDAP entry. 741 * @throws NamingException if there is an error processing the entry. 742 */ 743 public static String getFirstValue(SearchResult entry, String attrName) 744 throws NamingException 745 { 746 String v = null; 747 Attributes attrs = entry.getAttributes(); 748 if (attrs != null) 749 { 750 Attribute attr = attrs.get(attrName); 751 if (attr != null && attr.size() > 0) 752 { 753 Object o = attr.get(); 754 if (o instanceof String) 755 { 756 v = (String)o; 757 } 758 else 759 { 760 v = String.valueOf(o); 761 } 762 } 763 } 764 return v; 765 } 766 767 /** 768 * Returns a Set with the String representation of the values of an attribute 769 * in a LDAP entry. The returned Set will never be null. 770 * @param entry the entry. 771 * @param attrName the attribute name. 772 * @return a Set with the String representation of the values of an attribute 773 * in a LDAP entry. 774 * @throws NamingException if there is an error processing the entry. 775 */ 776 public static Set<String> getValues(SearchResult entry, String attrName) 777 throws NamingException 778 { 779 Set<String> values = new HashSet<>(); 780 Attributes attrs = entry.getAttributes(); 781 if (attrs != null) 782 { 783 Attribute attr = attrs.get(attrName); 784 if (attr != null) 785 { 786 for (int i=0; i<attr.size(); i++) 787 { 788 values.add((String)attr.get(i)); 789 } 790 } 791 } 792 return values; 793 } 794}