001/* 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved 005 * 006 * The contents of this file are subject to the terms 007 * of the Common Development and Distribution License 008 * (the License). You may not use this file except in 009 * compliance with the License. 010 * 011 * You can obtain a copy of the License at 012 * https://opensso.dev.java.net/public/CDDLv1.0.html or 013 * opensso/legal/CDDLv1.0.txt 014 * See the License for the specific language governing 015 * permission and limitations under the License. 016 * 017 * When distributing Covered Code, include this CDDL 018 * Header Notice in each file and include the License file 019 * at opensso/legal/CDDLv1.0.txt. 020 * If applicable, add the following below the CDDL Header, 021 * with the fields enclosed by brackets [] replaced by 022 * your own identifying information: 023 * "Portions Copyrighted [year] [name of copyright owner]" 024 * 025 * $Id: DataLayer.java,v 1.19 2009/11/20 23:52:52 ww203982 Exp $ 026 * 027 * Portions Copyrighted 2011-2016 ForgeRock AS. 028 */ 029 030package com.iplanet.ums; 031 032import static org.forgerock.opendj.ldap.LDAPConnectionFactory.AUTHN_BIND_REQUEST; 033 034import com.iplanet.am.util.SystemProperties; 035import com.iplanet.services.ldap.Attr; 036import com.iplanet.services.ldap.AttrSet; 037import com.iplanet.services.ldap.DSConfigMgr; 038import com.iplanet.services.ldap.LDAPServiceException; 039import com.iplanet.services.ldap.LDAPUser; 040import com.iplanet.services.ldap.ServerInstance; 041import com.iplanet.services.ldap.event.EventService; 042import com.iplanet.services.util.I18n; 043import com.sun.identity.common.configuration.ConfigurationListener; 044import com.sun.identity.common.configuration.ConfigurationObserver; 045import com.sun.identity.security.ServerInstanceAction; 046import com.sun.identity.shared.Constants; 047import com.sun.identity.shared.debug.Debug; 048 049import java.io.IOException; 050import java.security.AccessController; 051import java.security.Principal; 052import java.util.ArrayList; 053import java.util.Collection; 054import java.util.Collections; 055import java.util.HashSet; 056import java.util.List; 057import java.util.Set; 058import java.util.StringTokenizer; 059import java.util.concurrent.TimeUnit; 060 061import org.forgerock.openam.ldap.LDAPRequests; 062import org.forgerock.opendj.ldap.Attribute; 063import org.forgerock.opendj.ldap.Attributes; 064import org.forgerock.opendj.ldap.ByteString; 065import org.forgerock.opendj.ldap.Connection; 066import org.forgerock.opendj.ldap.ConnectionFactory; 067import org.forgerock.opendj.ldap.ConnectionPool; 068import org.forgerock.opendj.ldap.Connections; 069import org.forgerock.opendj.ldap.DN; 070import org.forgerock.opendj.ldap.Entry; 071import org.forgerock.opendj.ldap.LDAPConnectionFactory; 072import org.forgerock.opendj.ldap.LdapException; 073import org.forgerock.opendj.ldap.Modification; 074import org.forgerock.opendj.ldap.ModificationType; 075import org.forgerock.opendj.ldap.ResultCode; 076import org.forgerock.opendj.ldap.SearchScope; 077import org.forgerock.opendj.ldap.controls.Control; 078import org.forgerock.opendj.ldap.controls.ProxiedAuthV1RequestControl; 079import org.forgerock.opendj.ldap.controls.ServerSideSortRequestControl; 080import org.forgerock.opendj.ldap.controls.VirtualListViewRequestControl; 081import org.forgerock.opendj.ldap.requests.AddRequest; 082import org.forgerock.opendj.ldap.requests.DeleteRequest; 083import org.forgerock.opendj.ldap.requests.ModifyDNRequest; 084import org.forgerock.opendj.ldap.requests.ModifyRequest; 085import org.forgerock.opendj.ldap.requests.SearchRequest; 086import org.forgerock.opendj.ldap.requests.SimpleBindRequest; 087import org.forgerock.opendj.ldap.responses.SearchResultEntry; 088import org.forgerock.opendj.ldap.schema.Schema; 089import org.forgerock.opendj.ldif.ConnectionEntryReader; 090import org.forgerock.util.Options; 091import org.forgerock.util.thread.listener.ShutdownListener; 092import org.forgerock.util.thread.listener.ShutdownManager; 093 094/** 095 * DataLayer (A PACKAGE SCOPE CLASS) to access LDAP or other database 096 * 097 * TODO: 1. Needs to subclass and isolate the current implementation of 098 * DataLayer as DSLayer for ldap specific operations 2. Improvements needed for 099 * _ldapPool: destroy(), initial bind user, tunning for MIN and MAX initial 100 * settings etc 3. May choose to extend implementation of _ldapPool from 101 * LDAPConnectionPool so that there is load balance between connections. Also 102 * _ldapPool may be implemented with a HashTable of (host,port) for mulitple 103 * pools of connections for mulitple (host,port) to DS servers instead of single 104 * host and port. 105 * 106 * @supported.api 107 */ 108public class DataLayer implements java.io.Serializable { 109 110 private static final String RETRIES_KEY = "com.iplanet.am.replica.num.retries"; 111 private static final String RETRIES_DELAY_KEY = "com.iplanet.am.replica.delay.between.retries"; 112 113 /** 114 * Static section to retrieve the debug object. 115 */ 116 private static Debug debug; 117 118 private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG); 119 120 private static DataLayerConfigListener configListener; 121 122 /** 123 * Default minimal connections if none is defined in configuration 124 */ 125 126 /** 127 * Default maximum connections if none is defined in configuration 128 */ 129 static final int MAX_CONN = 20; 130 131 /** 132 * Default maximum backlog queue size 133 */ 134 static final int MAX_BACKLOG = 100; 135 136 static final String LDAP_MAXBACKLOG = "maxbacklog"; 137 138 static final String LDAP_RELEASECONNBEFORESEARCH = 139 "releaseconnectionbeforesearchcompletes"; 140 141 static final String LDAP_REFERRAL = "referral"; 142 143 private static int replicaRetryNum = 1; 144 145 private static long replicaRetryInterval = 1000; 146 147 private static final String LDAP_CONNECTION_NUM_RETRIES = 148 "com.iplanet.am.ldap.connection.num.retries"; 149 150 private static final String LDAP_CONNECTION_RETRY_INTERVAL = 151 "com.iplanet.am.ldap.connection.delay.between.retries"; 152 153 private static final String LDAP_CONNECTION_ERROR_CODES = 154 "com.iplanet.am.ldap.connection.ldap.error.codes.retries"; 155 156 private static int connNumRetry = 3; 157 158 private static int connRetryInterval = 1000; 159 160 private static Set<ResultCode> retryErrorCodes = new HashSet<>(); 161 162 static { 163 debug = Debug.getInstance(IUMSConstants.UMS_DEBUG); 164 initConnectionParams(); 165 } 166 167 public static void initConnectionParams() { 168 String numRetryStr = SystemProperties.get(LDAP_CONNECTION_NUM_RETRIES); 169 if (numRetryStr != null) { 170 try { 171 connNumRetry = Integer.parseInt(numRetryStr); 172 } catch (NumberFormatException e) { 173 if (debug.warningEnabled()) { 174 debug.warning("Invalid value for " 175 + LDAP_CONNECTION_NUM_RETRIES); 176 } 177 } 178 } 179 180 String retryIntervalStr = SystemProperties 181 .get(LDAP_CONNECTION_RETRY_INTERVAL); 182 if (retryIntervalStr != null) { 183 try { 184 connRetryInterval = Integer.parseInt(retryIntervalStr); 185 } catch (NumberFormatException e) { 186 if (debug.warningEnabled()) { 187 debug.warning("Invalid value for " 188 + LDAP_CONNECTION_RETRY_INTERVAL); 189 } 190 } 191 } 192 193 String retryErrs = SystemProperties.get(LDAP_CONNECTION_ERROR_CODES); 194 if (retryErrs != null) { 195 StringTokenizer stz = new StringTokenizer(retryErrs, ","); 196 while (stz.hasMoreTokens()) { 197 retryErrorCodes.add(ResultCode.valueOf(Integer.parseInt(stz.nextToken().trim()))); 198 } 199 } 200 201 if (debug.messageEnabled()) { 202 debug.message("DataLayer: number of retry = " + connNumRetry); 203 debug.message("DataLayer: retry interval = " + connRetryInterval); 204 debug.message("DataLayer: retry error codes = " + retryErrorCodes); 205 } 206 } 207 208 /** 209 * DataLayer constructor 210 */ 211 private DataLayer() { 212 } 213 214 /** 215 * Constructor given the extra parameter of guid and pwd identifying an 216 * authenticated principal 217 * 218 * @param host 219 * LDAP host 220 * @param port 221 * LDAP port 222 * @param pwd 223 * Password for the user 224 */ 225 private DataLayer(String id, String pwd, String host, int port) 226 throws UMSException { 227 m_proxyUser = id; 228 m_proxyPassword = pwd; 229 m_host = host; 230 m_port = port; 231 configListener = new DataLayerConfigListener(); 232 233 initReplicaProperties(); 234 initLdapPool(); 235 } 236 237 /** 238 * Create the singleton DataLayer object if it doesn't exist already. 239 * 240 * @supported.api 241 */ 242 public synchronized static DataLayer getInstance(ServerInstance serverCfg) 243 throws UMSException { 244 // Make sure only one instance of this class is created. 245 if (m_instance == null) { 246 String host = "localhost"; 247 int port = 389; 248 String pUser = ""; 249 String pPwd = ""; 250 251 if (serverCfg != null) { 252 host = serverCfg.getServerName(); 253 port = serverCfg.getPort(); 254 pUser = serverCfg.getAuthID(); 255 pPwd = (String) AccessController 256 .doPrivileged(new ServerInstanceAction(serverCfg)); 257 } 258 m_instance = new DataLayer(pUser, pPwd, host, port); 259 260 ConfigurationObserver.getInstance().addListener(configListener); 261 262 // Start the EventService thread if it has not already started. 263 initializeEventService(); 264 } 265 return m_instance; 266 } 267 268 /** 269 * Create the singleton DataLayer object if it doesn't exist already. 270 * Assumes the server instance for "LDAPUser.Type.AUTH_PROXY". 271 * 272 * @supported.api 273 */ 274 public static DataLayer getInstance() throws UMSException { 275 // Make sure only one instance of this class is created. 276 if (m_instance == null) { 277 try { 278 DSConfigMgr cfgMgr = DSConfigMgr.getDSConfigMgr(); 279 ServerInstance serverCfg = cfgMgr.getServerInstance(LDAPUser.Type.AUTH_PROXY); 280 m_instance = getInstance(serverCfg); 281 } catch (LDAPServiceException ex) { 282 debug.error("Error: Unable to get server config instance " 283 + ex.getMessage()); 284 } 285 } 286 return m_instance; 287 } 288 289 /** 290 * Get connection from pool. Reauthenticate if necessary 291 * 292 * @return connection that is available to use. 293 * 294 * @supported.api 295 */ 296 public Connection getConnection(java.security.Principal principal) throws LdapException { 297 if (_ldapPool == null) 298 return null; 299 300 if (debug.messageEnabled()) { 301 debug.message("Invoking _ldapPool.getConnection()"); 302 } 303 304 // proxy as given principal 305 ProxiedAuthV1RequestControl.newControl(principal.getName()); 306 Connection conn = _ldapPool.getConnection(); 307 if (debug.messageEnabled()) { 308 debug.message("Got Connection : " + conn); 309 } 310 311 return conn; 312 } 313 314 /** 315 * Returns String values of the attribute. 316 * 317 * @param principal Authentication Principal. 318 * @param guid distinguished name. 319 * @param attrName attribute name. 320 * 321 * @supported.api 322 */ 323 public String[] getAttributeString(Principal principal, Guid guid, String attrName) { 324 String id = guid.getDn(); 325 SearchRequest request = LDAPRequests.newSearchRequest(id, SearchScope.BASE_OBJECT, "(objectclass=*)"); 326 try { 327 try (ConnectionEntryReader reader = readLDAPEntry(principal, request)) { 328 Attribute attribute = reader.readEntry().getAttribute(attrName); 329 Collection<String> values = new ArrayList<>(); 330 for (ByteString byteString : attribute) { 331 values.add(byteString.toString()); 332 } 333 return values.toArray(new String[0]); 334 } 335 } catch (Exception e) { 336 if (debug.warningEnabled()) { 337 debug.warning( 338 "Exception in DataLayer.getAttributeString for DN: " 339 + id, e); 340 } 341 return null; 342 } 343 } 344 345 /** 346 * Returns <code>Attr</code> from the given attribute name. 347 * 348 * @param principal Authentication Principal. 349 * @param guid Distinguished name. 350 * @param attrName Attribute name. 351 * 352 * @supported.api 353 */ 354 public Attr getAttribute(Principal principal, Guid guid, String attrName) { 355 String id = guid.getDn(); 356 try { 357 SearchRequest request = LDAPRequests.newSearchRequest(id, SearchScope.BASE_OBJECT, "(objectclass=*)", 358 attrName); 359 try (ConnectionEntryReader reader = readLDAPEntry(principal, request)) { 360 Attribute attribute = reader.readEntry().getAttribute(attrName); 361 if (attribute == null) { 362 return null; 363 } else { 364 return new Attr(attribute); 365 } 366 } 367 } catch (Exception e) { 368 if (debug.warningEnabled()) { 369 debug.warning("Exception in DataLayer.getAttribute for DN: " 370 + id, e); 371 } 372 return null; 373 } 374 } 375 376 /** 377 * Returns attributes for the given attribute names. 378 * 379 * @param principal Authentication Principal. 380 * @param guid Distinguished name. 381 * @param attrNames Attribute names. 382 * @return collection of Attr. 383 * 384 * @supported.api 385 */ 386 public Collection<Attr> getAttributes(Principal principal, Guid guid, Collection<String> attrNames) { 387 String id = guid.getDn(); 388 SearchRequest request = LDAPRequests.newSearchRequest(id, SearchScope.BASE_OBJECT, "(objectclass=*)", 389 attrNames.toArray(EMPTY_STRING_ARRAY)); 390 ConnectionEntryReader ldapEntry; 391 try { 392 ldapEntry = readLDAPEntry(principal, request); 393 394 if (ldapEntry == null) { 395 debug.warning("No attributes returned may not have permission to read"); 396 return Collections.emptySet(); 397 } 398 399 Collection<Attr> attributes = new ArrayList<>(); 400 while (ldapEntry.hasNext()) { 401 if (ldapEntry.isEntry()) { 402 SearchResultEntry entry = ldapEntry.readEntry(); 403 for (Attribute attr : entry.getAllAttributes()) { 404 attributes.add(new Attr(attr)); 405 } 406 } 407 } 408 return attributes; 409 } catch(Exception e) { 410 debug.warning("Exception in DataLayer.getAttributes for DN: {}", id, e); 411 return null; 412 } 413 } 414 415 /** 416 * Adds entry to the server. 417 * 418 * @param principal Authenticated Principal. 419 * @param guid Distinguished name. 420 * @param attrSet attribute set containing name/value pairs. 421 * @exception AccessRightsException if insufficient access> 422 * @exception EntryAlreadyExistsException if the entry already exists. 423 * @exception UMSException if fail to add entry. 424 * 425 * @supported.api 426 */ 427 public void addEntry( 428 java.security.Principal principal, 429 Guid guid, 430 AttrSet attrSet 431 ) throws UMSException { 432 String id = guid.getDn(); 433 ResultCode errorCode; 434 435 try { 436 AddRequest request = LDAPRequests.newAddRequest(id); 437 for (Attribute attribute : attrSet.toLDAPAttributeSet()) { 438 request.addAttribute(attribute); 439 } 440 441 int retry = 0; 442 while (retry <= connNumRetry) { 443 if (debug.messageEnabled()) { 444 debug.message("DataLayer.addEntry retry: " + retry); 445 } 446 447 try (Connection conn = getConnection(principal)) { 448 conn.add(request); 449 return; 450 } catch (LdapException e) { 451 errorCode = e.getResult().getResultCode(); 452 if (!retryErrorCodes.contains(errorCode) || retry == connNumRetry) { 453 throw e; 454 } 455 retry++; 456 try { 457 Thread.sleep(connRetryInterval); 458 } catch (InterruptedException ex) { 459 } 460 } 461 } 462 } catch (LdapException e) { 463 if (debug.warningEnabled()) { 464 debug.warning("Exception in DataLayer.addEntry for DN: " + id, 465 e); 466 } 467 errorCode = e.getResult().getResultCode(); 468 String[] args = {id}; 469 if (ResultCode.ENTRY_ALREADY_EXISTS.equals(errorCode)) { 470 throw new EntryAlreadyExistsException(i18n.getString(IUMSConstants.ENTRY_ALREADY_EXISTS, args), e); 471 } else if (ResultCode.INSUFFICIENT_ACCESS_RIGHTS.equals(errorCode)) { 472 throw new AccessRightsException(i18n.getString(IUMSConstants.INSUFFICIENT_ACCESS_ADD, args), e); 473 } else { 474 throw new UMSException(i18n.getString(IUMSConstants.UNABLE_TO_ADD_ENTRY, args), e); 475 } 476 } 477 } 478 479 /** 480 * Delete entry from the server 481 * 482 * @param guid 483 * globally unique identifier for the entry 484 * @exception AccessRightsException 485 * insufficient access 486 * @exception EntryNotFoundException 487 * if the entry is not found 488 * @exception UMSException 489 * Fail to delete the entry 490 * 491 * @supported.api 492 */ 493 public void deleteEntry(java.security.Principal principal, Guid guid) 494 throws UMSException { 495 if (guid == null) { 496 String msg = i18n.getString(IUMSConstants.BAD_ID); 497 throw new IllegalArgumentException(msg); 498 } 499 String id = guid.getDn(); 500 ResultCode errorCode; 501 502 try { 503 DeleteRequest request = LDAPRequests.newDeleteRequest(id); 504 int retry = 0; 505 while (retry <= connNumRetry) { 506 if (debug.messageEnabled()) { 507 debug.message("DataLayer.deleteEntry retry: " + retry); 508 } 509 510 try (Connection conn = getConnection(principal)) { 511 conn.delete(request); 512 return; 513 } catch (LdapException e) { 514 if (!retryErrorCodes.contains(e.getResult().getResultCode()) 515 || retry == connNumRetry) { 516 throw e; 517 } 518 retry++; 519 try { 520 Thread.sleep(connRetryInterval); 521 } catch (InterruptedException ex) { 522 } 523 } 524 } 525 } catch (LdapException e) { 526 debug.error("Exception in DataLayer.deleteEntry for DN: " + id, e); 527 errorCode = e.getResult().getResultCode(); 528 String[] args = { id }; 529 if (ResultCode.NO_SUCH_OBJECT.equals(errorCode)) { 530 throw new EntryNotFoundException(i18n.getString(IUMSConstants.ENTRY_NOT_FOUND, args), e); 531 } else if (ResultCode.INSUFFICIENT_ACCESS_RIGHTS.equals(errorCode)) { 532 throw new AccessRightsException(i18n.getString(IUMSConstants.INSUFFICIENT_ACCESS_DELETE, args), e); 533 } else { 534 throw new UMSException(i18n.getString(IUMSConstants.UNABLE_TO_DELETE_ENTRY, args), e); 535 } 536 } 537 } 538 539 /** 540 * Read an ldap entry 541 * 542 * @param guid 543 * globally unique identifier for the entry 544 * @return an attribute set representing the entry in ldap, all non 545 * operational attributes are read 546 * @exception EntryNotFoundException 547 * if the entry is not found 548 * @exception UMSException 549 * Fail to read the entry 550 * 551 * @supported.api 552 */ 553 public AttrSet read(java.security.Principal principal, Guid guid) 554 throws UMSException { 555 return read(principal, guid, null); 556 } 557 558 /** 559 * Reads an ldap entry. 560 * 561 * @param principal Authentication Principal. 562 * @param guid Globally unique identifier for the entry. 563 * @param attrNames Attributes to read. 564 * @return an attribute set representing the entry in LDAP. 565 * @exception EntryNotFoundException if the entry is not found. 566 * @exception UMSException if fail to read the entry. 567 * 568 * @supported.api 569 */ 570 public AttrSet read( 571 java.security.Principal principal, 572 Guid guid, 573 String attrNames[] 574 ) throws UMSException { 575 String id = guid.getDn(); 576 ConnectionEntryReader entryReader; 577 SearchRequest request = LDAPRequests.newSearchRequest(id, SearchScope.BASE_OBJECT, "(objectclass=*)", 578 attrNames); 579 580 entryReader = readLDAPEntry(principal, request); 581 582 if (entryReader == null) { 583 throw new AccessRightsException(id); 584 } 585 586 Collection<Attribute> attrs = new ArrayList<>(); 587 try (ConnectionEntryReader reader = entryReader) { 588 while (reader.hasNext()) { 589 if (reader.isReference()) { 590 reader.readReference(); 591 //TODO AME-7017 592 } 593 SearchResultEntry entry = entryReader.readEntry(); 594 for (Attribute attr : entry.getAllAttributes()) { 595 attrs.add(attr); 596 } 597 } 598 if (attrs.isEmpty()) { 599 throw new EntryNotFoundException(i18n.getString(IUMSConstants.ENTRY_NOT_FOUND, new String[]{id})); 600 } 601 return new AttrSet(attrs); 602 } catch (IOException e) { 603 throw new UMSException(i18n.getString(IUMSConstants.UNABLE_TO_READ_ENTRY, new String[]{id}), e); 604 } 605 } 606 607 public void rename(java.security.Principal principal, Guid guid, 608 String newName, boolean deleteOldName) 609 throws UMSException { 610 String id = guid.getDn(); 611 ResultCode errorCode; 612 613 try { 614 ModifyDNRequest request = LDAPRequests.newModifyDNRequest(id, newName); 615 int retry = 0; 616 while (retry <= connNumRetry) { 617 if (debug.messageEnabled()) { 618 debug.message("DataLayer.rename retry: " + retry); 619 } 620 621 try (Connection conn = getConnection(principal)) { 622 conn.applyChange(request); 623 return; 624 } catch (LdapException e) { 625 errorCode = e.getResult().getResultCode(); 626 if (!retryErrorCodes.contains(errorCode) || retry == connNumRetry) { 627 throw e; 628 } 629 retry++; 630 try { 631 Thread.sleep(connRetryInterval); 632 } catch (InterruptedException ex) { 633 } 634 } 635 } 636 } catch (LdapException e) { 637 if (debug.warningEnabled()) { 638 debug.warning("Exception in DataLayer.rename for DN: " + id, e); 639 } 640 errorCode = e.getResult().getResultCode(); 641 if (ResultCode.NO_SUCH_OBJECT.equals(errorCode)) { 642 throw new EntryNotFoundException(id, e); 643 } else if (ResultCode.INSUFFICIENT_ACCESS_RIGHTS.equals(errorCode)) { 644 throw new AccessRightsException(id, e); 645 } else { 646 throw new UMSException(id, e); 647 } 648 } 649 } 650 651 /** 652 * Modifies an ldap entry. 653 * 654 * @param principal Authentication Principal. 655 * @param guid globally unique identifier for the entry. 656 * @param modifications Set of modifications for the entry. 657 * @exception AccessRightsException if insufficient access 658 * @exception EntryNotFoundException if the entry is not found. 659 * @exception UMSException if failure 660 * 661 * @supported.api 662 */ 663 public void modify(Principal principal, Guid guid, Collection<Modification> modifications) 664 throws UMSException { 665 String id = guid.getDn(); 666 ResultCode errorCode; 667 668 try { 669 ModifyRequest request = LDAPRequests.newModifyRequest(id); 670 for (Modification modification : modifications) { 671 request.addModification(modification); 672 } 673 int retry = 0; 674 while (retry <= connNumRetry) { 675 if (debug.messageEnabled()) { 676 debug.message("DataLayer.modify retry: " + retry); 677 } 678 679 try (Connection conn = getConnection(principal)) { 680 conn.modify(request); 681 return; 682 } catch (LdapException e) { 683 if (!retryErrorCodes.contains("" + e.getResult().getResultCode().toString()) 684 || retry == connNumRetry) { 685 throw e; 686 } 687 retry++; 688 try { 689 Thread.sleep(connRetryInterval); 690 } catch (InterruptedException ex) { 691 } 692 } 693 } 694 } catch (LdapException e) { 695 if (debug.warningEnabled()) { 696 debug.warning("Exception in DataLayer.modify for DN: " + id, e); 697 } 698 errorCode = e.getResult().getResultCode(); 699 if (ResultCode.NO_SUCH_OBJECT.equals(errorCode)) { 700 throw new EntryNotFoundException(id, e); 701 } else if (ResultCode.INSUFFICIENT_ACCESS_RIGHTS.equals(errorCode)) { 702 throw new AccessRightsException(id, e); 703 } else { 704 throw new UMSException(id, e); 705 } 706 } 707 } 708 709 /** 710 * Changes user password. 711 * 712 * @param guid globally unique identifier for the entry. 713 * @param attrName password attribute name 714 * @param oldPassword old password 715 * @param newPassword new password 716 * @exception AccessRightsException if insufficient access 717 * @exception EntryNotFoundException if the entry is not found. 718 * @exception UMSException if failure 719 * 720 * @supported.api 721 */ 722 public void changePassword(Guid guid, String attrName, String oldPassword, String newPassword) 723 throws UMSException { 724 725 Modification modification = new Modification(ModificationType.REPLACE, 726 Attributes.singletonAttribute(attrName, newPassword)); 727 728 String id = guid.getDn(); 729 730 try { 731 DSConfigMgr dsCfg = DSConfigMgr.getDSConfigMgr(); 732 String hostAndPort = dsCfg.getHostName("default"); 733 734 // All connections will use authentication 735 SimpleBindRequest bindRequest = LDAPRequests.newSimpleBindRequest(id, oldPassword.toCharArray()); 736 Options options = Options.defaultOptions() 737 .set(AUTHN_BIND_REQUEST, bindRequest); 738 739 try (ConnectionFactory factory = new LDAPConnectionFactory(hostAndPort, 389, options)) { 740 Connection ldc = factory.getConnection(); 741 ldc.modify(LDAPRequests.newModifyRequest(id).addModification(modification)); 742 } catch (LdapException ldex) { 743 if (debug.warningEnabled()) { 744 debug.warning("DataLayer.changePassword:", ldex); 745 } 746 ResultCode errorCode = ldex.getResult().getResultCode(); 747 if (ResultCode.NO_SUCH_OBJECT.equals(errorCode)) { 748 throw new EntryNotFoundException(id, ldex); 749 } else if (ResultCode.INSUFFICIENT_ACCESS_RIGHTS.equals(errorCode)) { 750 throw new AccessRightsException(id, ldex); 751 } else { 752 throw new UMSException(id, ldex); 753 } 754 } 755 } catch (LDAPServiceException ex) { 756 debug.error("DataLayer.changePassword:", ex); 757 throw new UMSException(id, ex); 758 } 759 } 760 761 /** 762 * Adds value for an attribute and saves the change in the database. 763 * 764 * @param principal Authenticated Principal. 765 * @param guid ID of the entry to which to add the attribute value. 766 * @param name name of the attribute to which value is being added. 767 * @param value Value to be added to the attribute. 768 * @throws UMSException if there is any error while adding the value. 769 * 770 * @supported.api 771 */ 772 public void addAttributeValue(Principal principal, Guid guid, String name, String value) throws UMSException { 773 // Delegate to the other modify() method. 774 modifyAttributeValue(ModificationType.ADD, principal, guid, name, value); 775 } 776 777 /** 778 * Removes value for an attribute and saves the change in the database. 779 * 780 * @param principal Authenticated Principal. 781 * @param guid the id of the entry from which to remove the attribute value. 782 * @param name Name of the attribute from which value is being removed 783 * @param value Value to be removed from the attribute. 784 * @throws UMSException if there is any error while removing the value. 785 * 786 * @supported.api 787 */ 788 public void removeAttributeValue(Principal principal, Guid guid, String name, String value) throws UMSException { 789 // Delegate to the other modify() method. 790 modifyAttributeValue(ModificationType.DELETE, principal, guid, name, value); 791 } 792 793 private void modifyAttributeValue(ModificationType modType, Principal principal, Guid guid, String name, 794 String value) throws UMSException { 795 // Delegate to the other modify() method. 796 modify(principal, guid, Collections.singleton( 797 new Modification(modType, Attributes.singletonAttribute(name, value)))); 798 } 799 800 private List<Control> getSearchControls(SearchControl searchControl) throws LdapException { 801 if (searchControl != null) { 802 int[] vlvRange = searchControl.getVLVRange(); 803 SortKey[] sortKeys = searchControl.getSortKeys(); 804 Collection<org.forgerock.opendj.ldap.SortKey> ldapSortKeys; 805 List<Control> ctrls = new ArrayList<>(); // will hold all server controls 806 807 if (sortKeys != null) { 808 ldapSortKeys = new ArrayList<>(sortKeys.length); 809 for (SortKey sortKey : sortKeys) { 810 ldapSortKeys.add(new org.forgerock.opendj.ldap.SortKey(sortKey.attributeName, sortKey.reverse)); 811 } 812 813 ctrls.add(ServerSideSortRequestControl.newControl(false, ldapSortKeys)); 814 815 if (vlvRange != null) { 816 if (searchControl.getVLVJumpTo() == null) { 817 ctrls.add(VirtualListViewRequestControl.newOffsetControl(false, vlvRange[0], 0, vlvRange[1], 818 vlvRange[2], null)); 819 } else { 820 ctrls.add(VirtualListViewRequestControl.newAssertionControl(false, 821 ByteString.valueOfUtf8(searchControl.getVLVJumpTo()), vlvRange[1], vlvRange[2], null)); 822 } 823 } 824 } 825 return ctrls; 826 } 827 return null; 828 } 829 830 /** 831 * Performs synchronous search based on specified ldap filter. This is low 832 * level API which assumes caller knows how to construct a data store filer. 833 * 834 * @param principal Authenticated Principal. 835 * @param guid Unique identifier for the entry. 836 * @param scope Scope can be either <code>SCOPE_ONE</code>, 837 * <code>SCOPE_SUB</code> or <code>SCOPE_BASE</code>. 838 * @param searchFilter Search filter for this search. 839 * @param attrNames Attribute name for retrieving. 840 * @param attrOnly if true, returns the names but not the values of the 841 * attributes found. 842 * @param searchControl Search Control. 843 * @exception UMSException if failure. 844 * @exception InvalidSearchFilterException if failure 845 * 846 * @supported.api 847 */ 848 public SearchResults search( 849 java.security.Principal principal, 850 Guid guid, 851 int scope, 852 String searchFilter, 853 String attrNames[], 854 boolean attrOnly, 855 SearchControl searchControl 856 ) throws UMSException { 857 String id = guid.getDn(); 858 859 // always add "objectclass" to attributes to get, to find the right java 860 // class 861 String[] attrNames1 = null; 862 if (attrNames != null) { 863 attrNames1 = new String[attrNames.length + 1]; 864 System.arraycopy(attrNames, 0, attrNames1, 0, attrNames.length); 865 attrNames1[attrNames1.length - 1] = "objectclass"; 866 } else { 867 attrNames1 = new String[] { "objectclass" }; 868 } 869 870 ConnectionEntryReader ldapResults = null; 871 872 // if searchFilter is null, search for everything under the base 873 if (searchFilter == null) { 874 searchFilter = "(objectclass=*)"; 875 } 876 ResultCode errorCode; 877 878 try { 879 Connection conn = getConnection(principal); 880 List<Control> controls = getSearchControls(searchControl); 881 // call readLDAPEntry() only in replica case, save one LDAP search 882 // assume replica case when replicaRetryNum is not 0 883 if (replicaRetryNum != 0) { 884 readLDAPEntry(conn, id, null); 885 } 886 887 SearchRequest request = null; 888 int retry = 0; 889 while (retry <= connNumRetry) { 890 if (debug.messageEnabled()) { 891 debug.message("DataLayer.search retry: " + retry); 892 } 893 894 if (searchControl != null && searchControl.isGetAllReturnAttributesEnabled()) { 895 /* 896 * The array {"*"} is used, because LDAPv3 defines 897 * "*" as a special string indicating all 898 * attributes. This gets all the attributes. 899 */ 900 attrNames1 = new String[] { "*" }; 901 } 902 request = LDAPRequests.newSearchRequest(id, SearchScope.valueOf(scope), searchFilter, attrNames1); 903 break; 904 } 905 for (Control control : controls) { 906 request.addControl(control); 907 } 908 909 ldapResults = conn.search(request); 910 911 // TODO: need review and see if conn is recorded properly for 912 // subsequent use 913 // 914 SearchResults result = new SearchResults(conn, ldapResults, conn, this); 915 result.set(SearchResults.BASE_ID, id); 916 result.set(SearchResults.SEARCH_FILTER, searchFilter); 917 result.set(SearchResults.SEARCH_SCOPE, scope); 918 919 if ((searchControl != null) 920 && (searchControl.contains(SearchControl.KeyVlvRange) 921 || searchControl.contains(SearchControl.KeyVlvJumpTo))) { 922 result.set(SearchResults.EXPECT_VLV_RESPONSE, Boolean.TRUE); 923 924 } 925 926 if (searchControl != null 927 && searchControl.contains(SearchControl.KeySortKeys)) { 928 SortKey[] sortKeys = searchControl.getSortKeys(); 929 if (sortKeys != null && sortKeys.length > 0) { 930 result.set(SearchResults.SORT_KEYS, sortKeys); 931 } 932 } 933 934 return result; 935 936 } catch (LdapException e) { 937 errorCode = e.getResult().getResultCode(); 938 if (debug.warningEnabled()) { 939 debug.warning("Exception in DataLayer.search: ", e); 940 } 941 String msg = i18n.getString(IUMSConstants.SEARCH_FAILED); 942 if (ResultCode.TIME_LIMIT_EXCEEDED.equals(errorCode)) { 943 int timeLimit = searchControl != null ? searchControl.getTimeOut() : 0; 944 throw new TimeLimitExceededException(String.valueOf(timeLimit), e); 945 } else if (ResultCode.SIZE_LIMIT_EXCEEDED.equals(errorCode)) { 946 int sizeLimit = searchControl != null ? searchControl.getMaxResults() : 0; 947 throw new SizeLimitExceededException(String.valueOf(sizeLimit), e); 948 } else if (ResultCode.CLIENT_SIDE_PARAM_ERROR.equals(errorCode) 949 || ResultCode.PROTOCOL_ERROR.equals(errorCode)) { 950 throw new InvalidSearchFilterException(searchFilter, e); 951 } else { 952 throw new UMSException(msg, e); 953 } 954 } 955 } 956 957 /** 958 * Perform synchronous search based on specified ldap filter. This is low 959 * level API which assumes caller knows how to construct a data store filer. 960 * 961 * @param principal Authenticated Principal. 962 * @param guid Unique identifier for the entry 963 * @param scope Scope can be either <code>SCOPE_ONE</code>, 964 * <code>SCOPE_SUB</code>, <code>SCOBE_BASE</code> 965 * @param searchFilter Search filter for this search. 966 * @param searchControl Search Control. 967 * @exception UMSException if failure. 968 * @exception InvalidSearchFilterException if failure. 969 * 970 * @supported.api 971 */ 972 public SearchResults searchIDs( 973 java.security.Principal principal, 974 Guid guid, 975 int scope, 976 String searchFilter, 977 SearchControl searchControl 978 ) throws InvalidSearchFilterException, UMSException { 979 // TODO: support LDAP referral 980 String attrNames[] = { "objectclass" }; 981 return search(principal, guid, scope, searchFilter, attrNames, false, 982 searchControl); 983 } 984 985 /** 986 * Fetches the schema from the LDAP directory server. Retrieve the entire 987 * schema from the root of a Directory Server. 988 * 989 * @return the schema in the LDAP directory server 990 * @exception AccessRightsException 991 * insufficient access 992 * @exception UMSException 993 * Fail to fetch the schema. 994 * @exception LdapException 995 * Error with LDAP connection. 996 * 997 * @supported.api 998 */ 999 public Schema getSchema(java.security.Principal principal) throws UMSException { 1000 ResultCode errorCode; 1001 1002 try (Connection conn = getConnection(principal)) { 1003 int retry = 0; 1004 while (retry <= connNumRetry) { 1005 if (debug.messageEnabled()) { 1006 debug.message("DataLayer.getSchema retry: " + retry); 1007 } 1008 1009 try { 1010 return Schema.readSchemaForEntry(conn, DN.valueOf("cn=schema")); 1011 } catch (LdapException e) { 1012 if (!retryErrorCodes.contains(e.getResult().getResultCode()) || retry == connNumRetry) { 1013 throw e; 1014 } 1015 retry++; 1016 try { 1017 Thread.sleep(connRetryInterval); 1018 } catch (InterruptedException ex) { 1019 } 1020 } 1021 } 1022 } catch (LdapException e) { 1023 debug.error("Exception in DataLayer.getSchema: ", e); 1024 errorCode = e.getResult().getResultCode(); 1025 if (ResultCode.INSUFFICIENT_ACCESS_RIGHTS.equals(errorCode)) { 1026 throw new AccessRightsException(m_host, e); 1027 } else { 1028 throw new UMSException(m_host, e); 1029 } 1030 } 1031 1032 return null; 1033 } 1034 1035 private synchronized void initReplicaProperties() { 1036 int retries = SystemProperties.getAsInt(RETRIES_KEY, 0); 1037 if (retries < 0) { 1038 retries = 0; 1039 debug.warning("Invalid value for replica retry num, set to 0"); 1040 } 1041 1042 replicaRetryNum = retries; 1043 1044 long interval = SystemProperties.getAsLong(RETRIES_DELAY_KEY, 0); 1045 if (interval < 0) { 1046 interval = 0; 1047 debug.warning("Invalid value for replica interval, set to 0"); 1048 } 1049 1050 replicaRetryInterval = interval; 1051 } 1052 1053 public Entry readLDAPEntry(Connection ld, String dn, 1054 String[] attrnames) throws LdapException { 1055 1056 LdapException ldapEx = null; 1057 int retry = 0; 1058 int connRetry = 0; 1059 while (retry <= replicaRetryNum && connRetry <= connNumRetry) { 1060 if (debug.messageEnabled()) { 1061 debug.message("DataLayer.readLDAPEntry: connRetry: " 1062 + connRetry); 1063 debug.message("DataLayer.readLDAPEntry: retry: " + retry); 1064 } 1065 try { 1066 if (attrnames == null) { 1067 return ld.searchSingleEntry(LDAPRequests.newSingleEntrySearchRequest(dn)); 1068 } else { 1069 return ld.searchSingleEntry(LDAPRequests.newSingleEntrySearchRequest(dn, attrnames)); 1070 } 1071 } catch (LdapException e) { 1072 ResultCode errorCode = e.getResult().getResultCode(); 1073 if (ResultCode.NO_SUCH_OBJECT.equals(errorCode)) { 1074 if (debug.messageEnabled()) { 1075 debug.message("Replica: entry not found: " + dn 1076 + " retry: " + retry); 1077 } 1078 if (retry == replicaRetryNum) { 1079 ldapEx = e; 1080 } else { 1081 try { 1082 Thread.sleep(replicaRetryInterval); 1083 } catch (Exception ignored) { 1084 } 1085 } 1086 retry++; 1087 } else if (retryErrorCodes.contains("" + errorCode)) { 1088 if (connRetry == connNumRetry) { 1089 ldapEx = e; 1090 } else { 1091 try { 1092 Thread.sleep(connRetryInterval); 1093 } catch (Exception ignored) { 1094 } 1095 } 1096 connRetry++; 1097 } else { 1098 throw e; 1099 } 1100 } 1101 } 1102 1103 throw ldapEx; 1104 } 1105 1106 public ConnectionEntryReader readLDAPEntry(Principal principal, SearchRequest request) throws UMSException { 1107 1108 LdapException ldapEx = null; 1109 int retry = 0; 1110 int connRetry = 0; 1111 while (retry <= replicaRetryNum && connRetry <= connNumRetry) { 1112 if (debug.messageEnabled()) { 1113 debug.message("DataLayer.readLDAPEntry: connRetry: " 1114 + connRetry); 1115 debug.message("DataLayer.readLDAPEntry: retry: " + retry); 1116 } 1117 try (Connection conn = getConnection(principal)) { 1118 return conn.search(request); 1119 } catch (LdapException e) { 1120 ResultCode errorCode = e.getResult().getResultCode(); 1121 if (ResultCode.NO_SUCH_OBJECT.equals(errorCode)) { 1122 if (debug.messageEnabled()) { 1123 debug.message("Replica: entry not found: " + 1124 request.getName().toString() + " retry: " + retry); 1125 } 1126 if (retry == replicaRetryNum) { 1127 ldapEx = e; 1128 } else { 1129 try { 1130 Thread.sleep(replicaRetryInterval); 1131 } catch (Exception ex) { 1132 } 1133 } 1134 retry++; 1135 } else if (retryErrorCodes.contains("" + errorCode)) { 1136 if (connRetry == connNumRetry) { 1137 ldapEx = e; 1138 } else { 1139 try { 1140 Thread.sleep(connRetryInterval); 1141 } catch (Exception ex) { 1142 } 1143 } 1144 connRetry++; 1145 } else { 1146 throw new UMSException(e.getMessage(), e); 1147 } 1148 } 1149 } 1150 1151 throw new UMSException(ldapEx.getMessage(), ldapEx); 1152 } 1153 1154 1155 /** 1156 * Initialize the pool shared by all DataLayer object(s). 1157 */ 1158 private synchronized void initLdapPool() throws UMSException { 1159 // Don't do anything if pool is already initialized 1160 if (_ldapPool != null) 1161 return; 1162 1163 /* 1164 * Initialize the pool with minimum and maximum connections settings 1165 * retrieved from configuration 1166 */ 1167 ServerInstance svrCfg = null; 1168 String hostName = null; 1169 1170 try { 1171 DSConfigMgr dsCfg = DSConfigMgr.getDSConfigMgr(); 1172 hostName = dsCfg.getHostName("default"); 1173 baseFactory = dsCfg.getNewProxyConnectionFactory(); 1174 1175 svrCfg = dsCfg.getServerInstance(LDAPUser.Type.AUTH_PROXY); 1176 } catch (LDAPServiceException ex) { 1177 debug.error("Error initializing connection pool " + ex.getMessage()); 1178 } 1179 1180 // Check if svrCfg was successfully obtained 1181 if (svrCfg == null) { 1182 debug.error("Error getting server config."); 1183 String args[] = new String[1]; 1184 args[0] = hostName == null ? "default" : hostName; 1185 throw new UMSException(i18n.getString(IUMSConstants.NEW_INSTANCE_FAILED, args)); 1186 } 1187 1188 int poolMin = svrCfg.getMinConnections(); 1189 int poolMax = svrCfg.getMaxConnections(); 1190 m_releaseConnectionBeforeSearchCompletes = svrCfg.getBooleanValue(LDAP_RELEASECONNBEFORESEARCH, false); 1191 1192 if (debug.messageEnabled()) { 1193 debug.message("Creating ldap connection pool with: poolMin {}, poolMax {}", poolMin, poolMax); 1194 } 1195 1196 int idleTimeout = SystemProperties.getAsInt(Constants.LDAP_CONN_IDLE_TIME_IN_SECS, 0); 1197 if (idleTimeout == 0) { 1198 debug.warning("Idle timeout not set. Defaulting to 0."); 1199 } 1200 1201 _ldapPool = Connections.newCachedConnectionPool( 1202 Connections.newNamedConnectionFactory(baseFactory, "DataLayer"), poolMin, poolMax, idleTimeout, 1203 TimeUnit.SECONDS); 1204 1205 ShutdownManager shutdownMan = com.sun.identity.common.ShutdownManager.getInstance(); 1206 shutdownMan.addShutdownListener( 1207 new ShutdownListener() { 1208 public void shutdown() { 1209 if (_ldapPool != null) { 1210 _ldapPool.close(); 1211 } 1212 } 1213 } 1214 ); 1215 } 1216 1217 public static int getConnNumRetry() { 1218 return connNumRetry; 1219 } 1220 1221 public static int getConnRetryInterval() { 1222 return connRetryInterval; 1223 } 1224 1225 public static Set<ResultCode> getRetryErrorCodes() { 1226 return retryErrorCodes; 1227 } 1228 1229 private static void initializeEventService() { 1230 // Initialize event service. This is to make sure that EventService 1231 // thread is started. The other place where it is also tried to start 1232 // is: com.iplanet.am.sdk.ldap.AMEventManager which is 1233 // initialized in com.iplanet.am.sdk.ldap.DirectoryManager 1234 if (!EventService.isStarted()) { 1235 // Use a separate thread to start the EventService thread. 1236 // This will prevent deadlocks associated in the system because 1237 // of EventService related dependencies. 1238 InitEventServiceThread th = new InitEventServiceThread(); 1239 Thread initEventServiceThread = new Thread(th, 1240 "InitEventServiceThread"); 1241 initEventServiceThread.setDaemon(true); 1242 initEventServiceThread.start(); 1243 } 1244 } 1245 1246 private static class InitEventServiceThread implements Runnable { 1247 public void run() { 1248 debug.message("InitEventServiceThread:initializeEventService() - " 1249 + "EventService thread getting initialized "); 1250 try { 1251 EventService es = EventService.getEventService(); 1252 synchronized (es) { 1253 if (!EventService.isStarted()) { 1254 es.restartPSearches(); 1255 } 1256 } 1257 } catch (Exception e) { 1258 // An Error occurred while initializing EventService 1259 debug.error("InitEventServiceThread:run() Unable to start EventService!!", e); 1260 } 1261 } 1262 } 1263 1264 static private ConnectionPool _ldapPool = null; 1265 1266 static private ConnectionFactory baseFactory = null; 1267 1268 static private DataLayer m_instance = null; 1269 1270 private String m_host = null; 1271 1272 private int m_port; 1273 1274 private String m_proxyUser = ""; 1275 1276 private String m_proxyPassword = ""; 1277 1278 private boolean m_releaseConnectionBeforeSearchCompletes = false; 1279 1280 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 1281 1282 private class DataLayerConfigListener implements ConfigurationListener { 1283 1284 @Override 1285 public synchronized void notifyChanges() { 1286 final int retries = SystemProperties.getAsInt(RETRIES_KEY, 0); 1287 final long delay = SystemProperties.getAsLong(RETRIES_DELAY_KEY, 0); 1288 1289 if (retries != replicaRetryNum || delay != replicaRetryInterval) { 1290 initReplicaProperties(); 1291 } 1292 } 1293 } 1294}