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 111 112 113 /** 114 * The serial version UID for this serializable class. 115 */ 116 private static final long serialVersionUID = 3703682568143472108L; 117 118 119 120 // Indicates whether the set of subtree restrictions should be updated rather 121 // than queried. 122 @Nullable private BooleanArgument set; 123 124 // The argument used to specify the base DN for the target subtree. 125 @Nullable private DNArgument baseDN; 126 127 // The argument used to specify the DN of a user who can bypass restrictions 128 // on the target subtree. 129 @Nullable private DNArgument bypassUserDN; 130 131 // The argument used to specify the accessibility state for the target 132 // subtree. 133 @Nullable private StringArgument accessibilityState; 134 135 136 137 /** 138 * Parse the provided command line arguments and perform the appropriate 139 * processing. 140 * 141 * @param args The command line arguments provided to this program. 142 */ 143 public static void main(@NotNull final String[] args) 144 { 145 final ResultCode resultCode = main(args, System.out, System.err); 146 if (resultCode != ResultCode.SUCCESS) 147 { 148 System.exit(resultCode.intValue()); 149 } 150 } 151 152 153 154 /** 155 * Parse the provided command line arguments and perform the appropriate 156 * processing. 157 * 158 * @param args The command line arguments provided to this program. 159 * @param outStream The output stream to which standard out should be 160 * written. It may be {@code null} if output should be 161 * suppressed. 162 * @param errStream The output stream to which standard error should be 163 * written. It may be {@code null} if error messages 164 * should be suppressed. 165 * 166 * @return A result code indicating whether the processing was successful. 167 */ 168 @NotNull() 169 public static ResultCode main(@NotNull final String[] args, 170 @Nullable final OutputStream outStream, 171 @Nullable final OutputStream errStream) 172 { 173 final SubtreeAccessibility tool = 174 new SubtreeAccessibility(outStream, errStream); 175 return tool.runTool(args); 176 } 177 178 179 180 /** 181 * Creates a new instance of this tool. 182 * 183 * @param outStream The output stream to which standard out should be 184 * written. It may be {@code null} if output should be 185 * suppressed. 186 * @param errStream The output stream to which standard error should be 187 * written. It may be {@code null} if error messages 188 * should be suppressed. 189 */ 190 public SubtreeAccessibility(@Nullable final OutputStream outStream, 191 @Nullable final OutputStream errStream) 192 { 193 super(outStream, errStream); 194 195 set = null; 196 baseDN = null; 197 bypassUserDN = null; 198 accessibilityState = null; 199 } 200 201 202 203 /** 204 * Retrieves the name of this tool. It should be the name of the command used 205 * to invoke this tool. 206 * 207 * @return The name for this tool. 208 */ 209 @Override() 210 @NotNull() 211 public String getToolName() 212 { 213 return "subtree-accessibility"; 214 } 215 216 217 218 /** 219 * Retrieves a human-readable description for this tool. 220 * 221 * @return A human-readable description for this tool. 222 */ 223 @Override() 224 @NotNull() 225 public String getToolDescription() 226 { 227 return "List or update the set of subtree accessibility restrictions " + 228 "defined in the Directory Server."; 229 } 230 231 232 233 /** 234 * Retrieves the version string for this tool. 235 * 236 * @return The version string for this tool. 237 */ 238 @Override() 239 @NotNull() 240 public String getToolVersion() 241 { 242 return Version.NUMERIC_VERSION_STRING; 243 } 244 245 246 247 /** 248 * Indicates whether this tool should provide support for an interactive mode, 249 * in which the tool offers a mode in which the arguments can be provided in 250 * a text-driven menu rather than requiring them to be given on the command 251 * line. If interactive mode is supported, it may be invoked using the 252 * "--interactive" argument. Alternately, if interactive mode is supported 253 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 254 * interactive mode may be invoked by simply launching the tool without any 255 * arguments. 256 * 257 * @return {@code true} if this tool supports interactive mode, or 258 * {@code false} if not. 259 */ 260 @Override() 261 public boolean supportsInteractiveMode() 262 { 263 return true; 264 } 265 266 267 268 /** 269 * Indicates whether this tool defaults to launching in interactive mode if 270 * the tool is invoked without any command-line arguments. This will only be 271 * used if {@link #supportsInteractiveMode()} returns {@code true}. 272 * 273 * @return {@code true} if this tool defaults to using interactive mode if 274 * launched without any command-line arguments, or {@code false} if 275 * not. 276 */ 277 @Override() 278 public boolean defaultsToInteractiveMode() 279 { 280 return true; 281 } 282 283 284 285 /** 286 * Indicates whether this tool should provide arguments for redirecting output 287 * to a file. If this method returns {@code true}, then the tool will offer 288 * an "--outputFile" argument that will specify the path to a file to which 289 * all standard output and standard error content will be written, and it will 290 * also offer a "--teeToStandardOut" argument that can only be used if the 291 * "--outputFile" argument is present and will cause all output to be written 292 * to both the specified output file and to standard output. 293 * 294 * @return {@code true} if this tool should provide arguments for redirecting 295 * output to a file, or {@code false} if not. 296 */ 297 @Override() 298 protected boolean supportsOutputFile() 299 { 300 return true; 301 } 302 303 304 305 /** 306 * Indicates whether this tool should default to interactively prompting for 307 * the bind password if a password is required but no argument was provided 308 * to indicate how to get the password. 309 * 310 * @return {@code true} if this tool should default to interactively 311 * prompting for the bind password, or {@code false} if not. 312 */ 313 @Override() 314 protected boolean defaultToPromptForBindPassword() 315 { 316 return true; 317 } 318 319 320 321 /** 322 * Indicates whether this tool supports the use of a properties file for 323 * specifying default values for arguments that aren't specified on the 324 * command line. 325 * 326 * @return {@code true} if this tool supports the use of a properties file 327 * for specifying default values for arguments that aren't specified 328 * on the command line, or {@code false} if not. 329 */ 330 @Override() 331 public boolean supportsPropertiesFile() 332 { 333 return true; 334 } 335 336 337 338 /** 339 * Indicates whether this tool supports the ability to generate a debug log 340 * file. If this method returns {@code true}, then the tool will expose 341 * additional arguments that can control debug logging. 342 * 343 * @return {@code true} if this tool supports the ability to generate a debug 344 * log file, or {@code false} if not. 345 */ 346 @Override() 347 protected boolean supportsDebugLogging() 348 { 349 return true; 350 } 351 352 353 354 /** 355 * Indicates whether the LDAP-specific arguments should include alternate 356 * versions of all long identifiers that consist of multiple words so that 357 * they are available in both camelCase and dash-separated versions. 358 * 359 * @return {@code true} if this tool should provide multiple versions of 360 * long identifiers for LDAP-specific arguments, or {@code false} if 361 * not. 362 */ 363 @Override() 364 protected boolean includeAlternateLongIdentifiers() 365 { 366 return true; 367 } 368 369 370 371 /** 372 * Indicates whether this tool should provide a command-line argument that 373 * allows for low-level SSL debugging. If this returns {@code true}, then an 374 * "--enableSSLDebugging}" argument will be added that sets the 375 * "javax.net.debug" system property to "all" before attempting any 376 * communication. 377 * 378 * @return {@code true} if this tool should offer an "--enableSSLDebugging" 379 * argument, or {@code false} if not. 380 */ 381 @Override() 382 protected boolean supportsSSLDebugging() 383 { 384 return true; 385 } 386 387 388 389 /** 390 * {@inheritDoc} 391 */ 392 @Override() 393 protected boolean logToolInvocationByDefault() 394 { 395 return true; 396 } 397 398 399 400 /** 401 * Adds the arguments needed by this command-line tool to the provided 402 * argument parser which are not related to connecting or authenticating to 403 * the directory server. 404 * 405 * @param parser The argument parser to which the arguments should be added. 406 * 407 * @throws ArgumentException If a problem occurs while adding the arguments. 408 */ 409 @Override() 410 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 411 throws ArgumentException 412 { 413 set = new BooleanArgument('s', "set", 1, 414 "Indicates that the set of accessibility restrictions should be " + 415 "updated rather than retrieved."); 416 parser.addArgument(set); 417 418 419 baseDN = new DNArgument('b', "baseDN", false, 1, "{dn}", 420 "The base DN of the subtree for which an accessibility restriction " + 421 "is to be updated."); 422 baseDN.addLongIdentifier("base-dn", true); 423 parser.addArgument(baseDN); 424 425 426 accessibilityState = new StringArgument('S', "state", false, 1, "{state}", 427 "The accessibility state to use for the accessibility restriction " + 428 "on the target subtree. Allowed values: " + 429 SubtreeAccessibilityState.ACCESSIBLE.getStateName() + ", " + 430 SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED.getStateName() + 431 ", " + 432 SubtreeAccessibilityState.READ_ONLY_BIND_DENIED.getStateName() + 433 ", " + SubtreeAccessibilityState.HIDDEN.getStateName() + '.', 434 ALLOWED_ACCESSIBILITY_STATES); 435 parser.addArgument(accessibilityState); 436 437 438 bypassUserDN = new DNArgument('B', "bypassUserDN", false, 1, "{dn}", 439 "The DN of a user who is allowed to bypass restrictions on the " + 440 "target subtree."); 441 bypassUserDN.addLongIdentifier("bypass-user-dn", true); 442 parser.addArgument(bypassUserDN); 443 444 445 // The baseDN, accessibilityState, and bypassUserDN arguments can only be 446 // used if the set argument was provided. 447 parser.addDependentArgumentSet(baseDN, set); 448 parser.addDependentArgumentSet(accessibilityState, set); 449 parser.addDependentArgumentSet(bypassUserDN, set); 450 451 452 // If the set argument was provided, then the base DN and accessibilityState 453 // arguments must also be given. 454 parser.addDependentArgumentSet(set, baseDN); 455 parser.addDependentArgumentSet(set, accessibilityState); 456 } 457 458 459 460 /** 461 * Performs the core set of processing for this tool. 462 * 463 * @return A result code that indicates whether the processing completed 464 * successfully. 465 */ 466 @Override() 467 @NotNull() 468 public ResultCode doToolProcessing() 469 { 470 // Get a connection to the target directory server. 471 final LDAPConnection connection; 472 try 473 { 474 connection = getConnection(); 475 } 476 catch (final LDAPException le) 477 { 478 Debug.debugException(le); 479 err("Unable to establish a connection to the target directory server: ", 480 StaticUtils.getExceptionMessage(le)); 481 return le.getResultCode(); 482 } 483 484 try 485 { 486 // See whether to do a get or set operation and call the appropriate 487 // method. 488 if (set.isPresent()) 489 { 490 return doSet(connection); 491 } 492 else 493 { 494 return doGet(connection); 495 } 496 } 497 finally 498 { 499 connection.close(); 500 } 501 } 502 503 504 505 /** 506 * Does the work necessary to retrieve the set of subtree accessibility 507 * restrictions defined in the server. 508 * 509 * @param connection The connection to use to communicate with the server. 510 * 511 * @return A result code with information about the result of operation 512 * processing. 513 */ 514 @NotNull() 515 private ResultCode doGet(@NotNull final LDAPConnection connection) 516 { 517 final GetSubtreeAccessibilityExtendedResult result; 518 try 519 { 520 result = (GetSubtreeAccessibilityExtendedResult) 521 connection.processExtendedOperation( 522 new GetSubtreeAccessibilityExtendedRequest()); 523 } 524 catch (final LDAPException le) 525 { 526 Debug.debugException(le); 527 err("An error occurred while attempting to invoke the get subtree " + 528 "accessibility request: ", StaticUtils.getExceptionMessage(le)); 529 return le.getResultCode(); 530 } 531 532 if (result.getResultCode() != ResultCode.SUCCESS) 533 { 534 err("The server returned an error for the get subtree accessibility " + 535 "request: ", result.getDiagnosticMessage()); 536 return result.getResultCode(); 537 } 538 539 final List<SubtreeAccessibilityRestriction> restrictions = 540 result.getAccessibilityRestrictions(); 541 if ((restrictions == null) || restrictions.isEmpty()) 542 { 543 out("There are no subtree accessibility restrictions defined in the " + 544 "server."); 545 return ResultCode.SUCCESS; 546 } 547 548 if (restrictions.size() == 1) 549 { 550 out("1 subtree accessibility restriction was found in the server:"); 551 } 552 else 553 { 554 out(restrictions.size(), 555 " subtree accessibility restrictions were found in the server:"); 556 } 557 558 for (final SubtreeAccessibilityRestriction r : restrictions) 559 { 560 out("Subtree Base DN: ", r.getSubtreeBaseDN()); 561 out("Accessibility State: ", r.getAccessibilityState().getStateName()); 562 563 final String bypassDN = r.getBypassUserDN(); 564 if (bypassDN != null) 565 { 566 out("Bypass User DN: ", bypassDN); 567 } 568 569 out("Effective Time: ", r.getEffectiveTime()); 570 out(); 571 } 572 573 return ResultCode.SUCCESS; 574 } 575 576 577 578 /** 579 * Does the work necessary to update a subtree accessibility restriction 580 * defined in the server. 581 * 582 * @param connection The connection to use to communicate with the server. 583 * 584 * @return A result code with information about the result of operation 585 * processing. 586 */ 587 @NotNull() 588 private ResultCode doSet(@NotNull final LDAPConnection connection) 589 { 590 final SubtreeAccessibilityState state = 591 SubtreeAccessibilityState.forName(accessibilityState.getValue()); 592 if (state == null) 593 { 594 // This should never happen. 595 err("Unsupported subtree accessibility state ", 596 accessibilityState.getValue()); 597 return ResultCode.PARAM_ERROR; 598 } 599 600 final SetSubtreeAccessibilityExtendedRequest request; 601 switch (state) 602 { 603 case ACCESSIBLE: 604 request = SetSubtreeAccessibilityExtendedRequest. 605 createSetAccessibleRequest(baseDN.getStringValue()); 606 break; 607 case READ_ONLY_BIND_ALLOWED: 608 request = SetSubtreeAccessibilityExtendedRequest. 609 createSetReadOnlyRequest(baseDN.getStringValue(), true, 610 bypassUserDN.getStringValue()); 611 break; 612 case READ_ONLY_BIND_DENIED: 613 request = SetSubtreeAccessibilityExtendedRequest. 614 createSetReadOnlyRequest(baseDN.getStringValue(), false, 615 bypassUserDN.getStringValue()); 616 break; 617 case HIDDEN: 618 request = SetSubtreeAccessibilityExtendedRequest.createSetHiddenRequest( 619 baseDN.getStringValue(), bypassUserDN.getStringValue()); 620 break; 621 default: 622 // This should never happen. 623 err("Unsupported subtree accessibility state ", state.getStateName()); 624 return ResultCode.PARAM_ERROR; 625 } 626 627 final ExtendedResult result; 628 try 629 { 630 result = connection.processExtendedOperation(request); 631 } 632 catch (final LDAPException le) 633 { 634 Debug.debugException(le); 635 err("An error occurred while attempting to invoke the set subtree " + 636 "accessibility request: ", StaticUtils.getExceptionMessage(le)); 637 return le.getResultCode(); 638 } 639 640 if (result.getResultCode() == ResultCode.SUCCESS) 641 { 642 out("Successfully set an accessibility state of ", state.getStateName(), 643 " for subtree ", baseDN.getStringValue()); 644 } 645 else 646 { 647 out("Unable to set an accessibility state of ", state.getStateName(), 648 " for subtree ", baseDN.getStringValue(), ": ", 649 result.getDiagnosticMessage()); 650 } 651 652 return result.getResultCode(); 653 } 654 655 656 657 /** 658 * Retrieves a set of information that may be used to generate example usage 659 * information. Each element in the returned map should consist of a map 660 * between an example set of arguments and a string that describes the 661 * behavior of the tool when invoked with that set of arguments. 662 * 663 * @return A set of information that may be used to generate example usage 664 * information. It may be {@code null} or empty if no example usage 665 * information is available. 666 */ 667 @Override() 668 @NotNull() 669 public LinkedHashMap<String[],String> getExampleUsages() 670 { 671 final LinkedHashMap<String[],String> exampleMap = 672 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 673 674 final String[] getArgs = 675 { 676 "--hostname", "server.example.com", 677 "--port", "389", 678 "--bindDN", "uid=admin,dc=example,dc=com", 679 "--bindPassword", "password", 680 }; 681 exampleMap.put(getArgs, 682 "Retrieve information about all subtree accessibility restrictions " + 683 "defined in the server."); 684 685 final String[] setArgs = 686 { 687 "--hostname", "server.example.com", 688 "--port", "389", 689 "--bindDN", "uid=admin,dc=example,dc=com", 690 "--bindPassword", "password", 691 "--set", 692 "--baseDN", "ou=subtree,dc=example,dc=com", 693 "--state", "read-only-bind-allowed", 694 "--bypassUserDN", "uid=bypass,dc=example,dc=com" 695 }; 696 exampleMap.put(setArgs, 697 "Create or update the subtree accessibility state definition for " + 698 "subtree 'ou=subtree,dc=example,dc=com' so that it is " + 699 "read-only for all users except 'uid=bypass,dc=example,dc=com'."); 700 701 return exampleMap; 702 } 703}