001/* 002 * Copyright 2010-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2010-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2010-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.examples; 037 038 039 040import java.io.OutputStream; 041import java.io.Serializable; 042import java.util.LinkedHashMap; 043import java.util.List; 044import java.util.Set; 045 046import com.unboundid.ldap.sdk.ExtendedResult; 047import com.unboundid.ldap.sdk.LDAPConnection; 048import com.unboundid.ldap.sdk.LDAPException; 049import com.unboundid.ldap.sdk.ResultCode; 050import com.unboundid.ldap.sdk.Version; 051import com.unboundid.ldap.sdk.unboundidds.extensions. 052 GetSubtreeAccessibilityExtendedRequest; 053import com.unboundid.ldap.sdk.unboundidds.extensions. 054 GetSubtreeAccessibilityExtendedResult; 055import com.unboundid.ldap.sdk.unboundidds.extensions. 056 SetSubtreeAccessibilityExtendedRequest; 057import com.unboundid.ldap.sdk.unboundidds.extensions. 058 SubtreeAccessibilityRestriction; 059import com.unboundid.ldap.sdk.unboundidds.extensions.SubtreeAccessibilityState; 060import com.unboundid.util.Debug; 061import com.unboundid.util.LDAPCommandLineTool; 062import com.unboundid.util.NotNull; 063import com.unboundid.util.Nullable; 064import com.unboundid.util.StaticUtils; 065import com.unboundid.util.ThreadSafety; 066import com.unboundid.util.ThreadSafetyLevel; 067import com.unboundid.util.args.ArgumentException; 068import com.unboundid.util.args.ArgumentParser; 069import com.unboundid.util.args.BooleanArgument; 070import com.unboundid.util.args.DNArgument; 071import com.unboundid.util.args.StringArgument; 072 073 074 075/** 076 * This class provides a utility that can be used to query and update the set of 077 * subtree accessibility restrictions defined in the Directory Server. 078 * <BR> 079 * <BLOCKQUOTE> 080 * <B>NOTE:</B> This class, and other classes within the 081 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 082 * supported for use against Ping Identity, UnboundID, and 083 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 084 * for proprietary functionality or for external specifications that are not 085 * considered stable or mature enough to be guaranteed to work in an 086 * interoperable way with other types of LDAP servers. 087 * </BLOCKQUOTE> 088 * <BR> 089 * The APIs demonstrated by this example include: 090 * <UL> 091 * <LI>The use of the get/set subtree accessibility extended operations</LI> 092 * <LI>The LDAP command-line tool API.</LI> 093 * <LI>Argument parsing.</LI> 094 * </UL> 095 */ 096@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 097public final class SubtreeAccessibility 098 extends LDAPCommandLineTool 099 implements Serializable 100{ 101 /** 102 * The set of allowed subtree accessibility state values. 103 */ 104 @NotNull private static final Set<String> ALLOWED_ACCESSIBILITY_STATES = 105 StaticUtils.setOf( 106 SubtreeAccessibilityState.ACCESSIBLE.getStateName(), 107 SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED.getStateName(), 108 SubtreeAccessibilityState.READ_ONLY_BIND_DENIED.getStateName(), 109 SubtreeAccessibilityState.HIDDEN.getStateName(), 110 SubtreeAccessibilityState.TO_BE_DELETED.getStateName()); 111 112 113 114 /** 115 * The serial version UID for this serializable class. 116 */ 117private static final long serialVersionUID = 4482916514411902292L; 118 119 120 121 // Indicates whether the set of subtree restrictions should be updated rather 122 // than queried. 123 @Nullable private BooleanArgument set; 124 125 // The argument used to specify the base DN for the target subtree. 126 @Nullable private DNArgument baseDN; 127 128 // The argument used to specify the DN of a user who can bypass restrictions 129 // on the target subtree. 130 @Nullable private DNArgument bypassUserDN; 131 132 // The argument used to specify the accessibility state for the target 133 // subtree. 134 @Nullable private StringArgument accessibilityState; 135 136 137 138 /** 139 * Parse the provided command line arguments and perform the appropriate 140 * processing. 141 * 142 * @param args The command line arguments provided to this program. 143 */ 144 public static void main(@NotNull final String[] args) 145 { 146 final ResultCode resultCode = main(args, System.out, System.err); 147 if (resultCode != ResultCode.SUCCESS) 148 { 149 System.exit(resultCode.intValue()); 150 } 151 } 152 153 154 155 /** 156 * Parse the provided command line arguments and perform the appropriate 157 * processing. 158 * 159 * @param args The command line arguments provided to this program. 160 * @param outStream The output stream to which standard out should be 161 * written. It may be {@code null} if output should be 162 * suppressed. 163 * @param errStream The output stream to which standard error should be 164 * written. It may be {@code null} if error messages 165 * should be suppressed. 166 * 167 * @return A result code indicating whether the processing was successful. 168 */ 169 @NotNull() 170 public static ResultCode main(@NotNull final String[] args, 171 @Nullable final OutputStream outStream, 172 @Nullable final OutputStream errStream) 173 { 174 final SubtreeAccessibility tool = 175 new SubtreeAccessibility(outStream, errStream); 176 return tool.runTool(args); 177 } 178 179 180 181 /** 182 * Creates a new instance of this tool. 183 * 184 * @param outStream The output stream to which standard out should be 185 * written. It may be {@code null} if output should be 186 * suppressed. 187 * @param errStream The output stream to which standard error should be 188 * written. It may be {@code null} if error messages 189 * should be suppressed. 190 */ 191 public SubtreeAccessibility(@Nullable final OutputStream outStream, 192 @Nullable final OutputStream errStream) 193 { 194 super(outStream, errStream); 195 196 set = null; 197 baseDN = null; 198 bypassUserDN = null; 199 accessibilityState = null; 200 } 201 202 203 204 /** 205 * Retrieves the name of this tool. It should be the name of the command used 206 * to invoke this tool. 207 * 208 * @return The name for this tool. 209 */ 210 @Override() 211 @NotNull() 212 public String getToolName() 213 { 214 return "subtree-accessibility"; 215 } 216 217 218 219 /** 220 * Retrieves a human-readable description for this tool. 221 * 222 * @return A human-readable description for this tool. 223 */ 224 @Override() 225 @NotNull() 226 public String getToolDescription() 227 { 228 return "List or update the set of subtree accessibility restrictions " + 229 "defined in the Directory Server."; 230 } 231 232 233 234 /** 235 * Retrieves the version string for this tool. 236 * 237 * @return The version string for this tool. 238 */ 239 @Override() 240 @NotNull() 241 public String getToolVersion() 242 { 243 return Version.NUMERIC_VERSION_STRING; 244 } 245 246 247 248 /** 249 * Indicates whether this tool should provide support for an interactive mode, 250 * in which the tool offers a mode in which the arguments can be provided in 251 * a text-driven menu rather than requiring them to be given on the command 252 * line. If interactive mode is supported, it may be invoked using the 253 * "--interactive" argument. Alternately, if interactive mode is supported 254 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 255 * interactive mode may be invoked by simply launching the tool without any 256 * arguments. 257 * 258 * @return {@code true} if this tool supports interactive mode, or 259 * {@code false} if not. 260 */ 261 @Override() 262 public boolean supportsInteractiveMode() 263 { 264 return true; 265 } 266 267 268 269 /** 270 * Indicates whether this tool defaults to launching in interactive mode if 271 * the tool is invoked without any command-line arguments. This will only be 272 * used if {@link #supportsInteractiveMode()} returns {@code true}. 273 * 274 * @return {@code true} if this tool defaults to using interactive mode if 275 * launched without any command-line arguments, or {@code false} if 276 * not. 277 */ 278 @Override() 279 public boolean defaultsToInteractiveMode() 280 { 281 return true; 282 } 283 284 285 286 /** 287 * Indicates whether this tool should provide arguments for redirecting output 288 * to a file. If this method returns {@code true}, then the tool will offer 289 * an "--outputFile" argument that will specify the path to a file to which 290 * all standard output and standard error content will be written, and it will 291 * also offer a "--teeToStandardOut" argument that can only be used if the 292 * "--outputFile" argument is present and will cause all output to be written 293 * to both the specified output file and to standard output. 294 * 295 * @return {@code true} if this tool should provide arguments for redirecting 296 * output to a file, or {@code false} if not. 297 */ 298 @Override() 299 protected boolean supportsOutputFile() 300 { 301 return true; 302 } 303 304 305 306 /** 307 * Indicates whether this tool should default to interactively prompting for 308 * the bind password if a password is required but no argument was provided 309 * to indicate how to get the password. 310 * 311 * @return {@code true} if this tool should default to interactively 312 * prompting for the bind password, or {@code false} if not. 313 */ 314 @Override() 315 protected boolean defaultToPromptForBindPassword() 316 { 317 return true; 318 } 319 320 321 322 /** 323 * Indicates whether this tool supports the use of a properties file for 324 * specifying default values for arguments that aren't specified on the 325 * command line. 326 * 327 * @return {@code true} if this tool supports the use of a properties file 328 * for specifying default values for arguments that aren't specified 329 * on the command line, or {@code false} if not. 330 */ 331 @Override() 332 public boolean supportsPropertiesFile() 333 { 334 return true; 335 } 336 337 338 339 /** 340 * Indicates whether this tool supports the ability to generate a debug log 341 * file. If this method returns {@code true}, then the tool will expose 342 * additional arguments that can control debug logging. 343 * 344 * @return {@code true} if this tool supports the ability to generate a debug 345 * log file, or {@code false} if not. 346 */ 347 @Override() 348 protected boolean supportsDebugLogging() 349 { 350 return true; 351 } 352 353 354 355 /** 356 * Indicates whether the LDAP-specific arguments should include alternate 357 * versions of all long identifiers that consist of multiple words so that 358 * they are available in both camelCase and dash-separated versions. 359 * 360 * @return {@code true} if this tool should provide multiple versions of 361 * long identifiers for LDAP-specific arguments, or {@code false} if 362 * not. 363 */ 364 @Override() 365 protected boolean includeAlternateLongIdentifiers() 366 { 367 return true; 368 } 369 370 371 372 /** 373 * Indicates whether this tool should provide a command-line argument that 374 * allows for low-level SSL debugging. If this returns {@code true}, then an 375 * "--enableSSLDebugging}" argument will be added that sets the 376 * "javax.net.debug" system property to "all" before attempting any 377 * communication. 378 * 379 * @return {@code true} if this tool should offer an "--enableSSLDebugging" 380 * argument, or {@code false} if not. 381 */ 382 @Override() 383 protected boolean supportsSSLDebugging() 384 { 385 return true; 386 } 387 388 389 390 /** 391 * {@inheritDoc} 392 */ 393 @Override() 394 protected boolean logToolInvocationByDefault() 395 { 396 return true; 397 } 398 399 400 401 /** 402 * Adds the arguments needed by this command-line tool to the provided 403 * argument parser which are not related to connecting or authenticating to 404 * the directory server. 405 * 406 * @param parser The argument parser to which the arguments should be added. 407 * 408 * @throws ArgumentException If a problem occurs while adding the arguments. 409 */ 410 @Override() 411 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 412 throws ArgumentException 413 { 414 set = new BooleanArgument('s', "set", 1, 415 "Indicates that the set of accessibility restrictions should be " + 416 "updated rather than retrieved."); 417 parser.addArgument(set); 418 419 420 baseDN = new DNArgument('b', "baseDN", false, 1, "{dn}", 421 "The base DN of the subtree for which an accessibility restriction " + 422 "is to be updated."); 423 baseDN.addLongIdentifier("base-dn", true); 424 parser.addArgument(baseDN); 425 426 427 accessibilityState = new StringArgument('S', "state", false, 1, "{state}", 428 "The accessibility state to use for the accessibility restriction " + 429 "on the target subtree. Allowed values: " + 430 SubtreeAccessibilityState.ACCESSIBLE.getStateName() + ", " + 431 SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED.getStateName() + 432 ", " + 433 SubtreeAccessibilityState.READ_ONLY_BIND_DENIED.getStateName() + 434 ", " + SubtreeAccessibilityState.HIDDEN.getStateName() + ", or " + 435 SubtreeAccessibilityState.TO_BE_DELETED.getStateName() + '.', 436 ALLOWED_ACCESSIBILITY_STATES); 437 parser.addArgument(accessibilityState); 438 439 440 bypassUserDN = new DNArgument('B', "bypassUserDN", false, 1, "{dn}", 441 "The DN of a user who is allowed to bypass restrictions on the " + 442 "target subtree."); 443 bypassUserDN.addLongIdentifier("bypass-user-dn", true); 444 parser.addArgument(bypassUserDN); 445 446 447 // The baseDN, accessibilityState, and bypassUserDN arguments can only be 448 // used if the set argument was provided. 449 parser.addDependentArgumentSet(baseDN, set); 450 parser.addDependentArgumentSet(accessibilityState, set); 451 parser.addDependentArgumentSet(bypassUserDN, set); 452 453 454 // If the set argument was provided, then the base DN and accessibilityState 455 // arguments must also be given. 456 parser.addDependentArgumentSet(set, baseDN); 457 parser.addDependentArgumentSet(set, accessibilityState); 458 } 459 460 461 462 /** 463 * Performs the core set of processing for this tool. 464 * 465 * @return A result code that indicates whether the processing completed 466 * successfully. 467 */ 468 @Override() 469 @NotNull() 470 public ResultCode doToolProcessing() 471 { 472 // Get a connection to the target directory server. 473 final LDAPConnection connection; 474 try 475 { 476 connection = getConnection(); 477 } 478 catch (final LDAPException le) 479 { 480 Debug.debugException(le); 481 err("Unable to establish a connection to the target directory server: ", 482 StaticUtils.getExceptionMessage(le)); 483 return le.getResultCode(); 484 } 485 486 try 487 { 488 // See whether to do a get or set operation and call the appropriate 489 // method. 490 if (set.isPresent()) 491 { 492 return doSet(connection); 493 } 494 else 495 { 496 return doGet(connection); 497 } 498 } 499 finally 500 { 501 connection.close(); 502 } 503 } 504 505 506 507 /** 508 * Does the work necessary to retrieve the set of subtree accessibility 509 * restrictions defined in the server. 510 * 511 * @param connection The connection to use to communicate with the server. 512 * 513 * @return A result code with information about the result of operation 514 * processing. 515 */ 516 @NotNull() 517 private ResultCode doGet(@NotNull final LDAPConnection connection) 518 { 519 final GetSubtreeAccessibilityExtendedResult result; 520 try 521 { 522 result = (GetSubtreeAccessibilityExtendedResult) 523 connection.processExtendedOperation( 524 new GetSubtreeAccessibilityExtendedRequest()); 525 } 526 catch (final LDAPException le) 527 { 528 Debug.debugException(le); 529 err("An error occurred while attempting to invoke the get subtree " + 530 "accessibility request: ", StaticUtils.getExceptionMessage(le)); 531 return le.getResultCode(); 532 } 533 534 if (result.getResultCode() != ResultCode.SUCCESS) 535 { 536 err("The server returned an error for the get subtree accessibility " + 537 "request: ", result.getDiagnosticMessage()); 538 return result.getResultCode(); 539 } 540 541 final List<SubtreeAccessibilityRestriction> restrictions = 542 result.getAccessibilityRestrictions(); 543 if ((restrictions == null) || restrictions.isEmpty()) 544 { 545 out("There are no subtree accessibility restrictions defined in the " + 546 "server."); 547 return ResultCode.SUCCESS; 548 } 549 550 if (restrictions.size() == 1) 551 { 552 out("1 subtree accessibility restriction was found in the server:"); 553 } 554 else 555 { 556 out(restrictions.size(), 557 " subtree accessibility restrictions were found in the server:"); 558 } 559 560 for (final SubtreeAccessibilityRestriction r : restrictions) 561 { 562 out("Subtree Base DN: ", r.getSubtreeBaseDN()); 563 out("Accessibility State: ", r.getAccessibilityState().getStateName()); 564 565 final String bypassDN = r.getBypassUserDN(); 566 if (bypassDN != null) 567 { 568 out("Bypass User DN: ", bypassDN); 569 } 570 571 out("Effective Time: ", r.getEffectiveTime()); 572 out(); 573 } 574 575 return ResultCode.SUCCESS; 576 } 577 578 579 580 /** 581 * Does the work necessary to update a subtree accessibility restriction 582 * defined in the server. 583 * 584 * @param connection The connection to use to communicate with the server. 585 * 586 * @return A result code with information about the result of operation 587 * processing. 588 */ 589 @NotNull() 590 private ResultCode doSet(@NotNull final LDAPConnection connection) 591 { 592 final SubtreeAccessibilityState state = 593 SubtreeAccessibilityState.forName(accessibilityState.getValue()); 594 if (state == null) 595 { 596 // This should never happen. 597 err("Unsupported subtree accessibility state ", 598 accessibilityState.getValue()); 599 return ResultCode.PARAM_ERROR; 600 } 601 602 final SetSubtreeAccessibilityExtendedRequest request; 603 switch (state) 604 { 605 case ACCESSIBLE: 606 request = SetSubtreeAccessibilityExtendedRequest. 607 createSetAccessibleRequest(baseDN.getStringValue()); 608 break; 609 case READ_ONLY_BIND_ALLOWED: 610 request = SetSubtreeAccessibilityExtendedRequest. 611 createSetReadOnlyRequest(baseDN.getStringValue(), true, 612 bypassUserDN.getStringValue()); 613 break; 614 case READ_ONLY_BIND_DENIED: 615 request = SetSubtreeAccessibilityExtendedRequest. 616 createSetReadOnlyRequest(baseDN.getStringValue(), false, 617 bypassUserDN.getStringValue()); 618 break; 619 case HIDDEN: 620 request = SetSubtreeAccessibilityExtendedRequest.createSetHiddenRequest( 621 baseDN.getStringValue(), bypassUserDN.getStringValue()); 622 break; 623 case TO_BE_DELETED: 624 if (! bypassUserDN.isPresent()) 625 { 626 err("The to-be-deleted accessibility state requires a bypass user " + 627 "to be specified."); 628 return ResultCode.PARAM_ERROR; 629 } 630 631 request = SetSubtreeAccessibilityExtendedRequest. 632 createSetToBeDeletedRequest(baseDN.getStringValue(), 633 bypassUserDN.getStringValue()); 634 break; 635 default: 636 // This should never happen. 637 err("Unsupported subtree accessibility state ", state.getStateName()); 638 return ResultCode.PARAM_ERROR; 639 } 640 641 final ExtendedResult result; 642 try 643 { 644 result = connection.processExtendedOperation(request); 645 } 646 catch (final LDAPException le) 647 { 648 Debug.debugException(le); 649 err("An error occurred while attempting to invoke the set subtree " + 650 "accessibility request: ", StaticUtils.getExceptionMessage(le)); 651 return le.getResultCode(); 652 } 653 654 if (result.getResultCode() == ResultCode.SUCCESS) 655 { 656 out("Successfully set an accessibility state of ", state.getStateName(), 657 " for subtree ", baseDN.getStringValue()); 658 } 659 else 660 { 661 out("Unable to set an accessibility state of ", state.getStateName(), 662 " for subtree ", baseDN.getStringValue(), ": ", 663 result.getDiagnosticMessage()); 664 } 665 666 return result.getResultCode(); 667 } 668 669 670 671 /** 672 * Retrieves a set of information that may be used to generate example usage 673 * information. Each element in the returned map should consist of a map 674 * between an example set of arguments and a string that describes the 675 * behavior of the tool when invoked with that set of arguments. 676 * 677 * @return A set of information that may be used to generate example usage 678 * information. It may be {@code null} or empty if no example usage 679 * information is available. 680 */ 681 @Override() 682 @NotNull() 683 public LinkedHashMap<String[],String> getExampleUsages() 684 { 685 final LinkedHashMap<String[],String> exampleMap = 686 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 687 688 final String[] getArgs = 689 { 690 "--hostname", "server.example.com", 691 "--port", "389", 692 "--bindDN", "uid=admin,dc=example,dc=com", 693 "--bindPassword", "password", 694 }; 695 exampleMap.put(getArgs, 696 "Retrieve information about all subtree accessibility restrictions " + 697 "defined in the server."); 698 699 final String[] setArgs = 700 { 701 "--hostname", "server.example.com", 702 "--port", "389", 703 "--bindDN", "uid=admin,dc=example,dc=com", 704 "--bindPassword", "password", 705 "--set", 706 "--baseDN", "ou=subtree,dc=example,dc=com", 707 "--state", "read-only-bind-allowed", 708 "--bypassUserDN", "uid=bypass,dc=example,dc=com" 709 }; 710 exampleMap.put(setArgs, 711 "Create or update the subtree accessibility state definition for " + 712 "subtree 'ou=subtree,dc=example,dc=com' so that it is " + 713 "read-only for all users except 'uid=bypass,dc=example,dc=com'."); 714 715 return exampleMap; 716 } 717}