001/* 002 * Copyright 2010-2023 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2010-2023 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-2023 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 the LDAP-specific arguments should include alternate 340 * versions of all long identifiers that consist of multiple words so that 341 * they are available in both camelCase and dash-separated versions. 342 * 343 * @return {@code true} if this tool should provide multiple versions of 344 * long identifiers for LDAP-specific arguments, or {@code false} if 345 * not. 346 */ 347 @Override() 348 protected boolean includeAlternateLongIdentifiers() 349 { 350 return true; 351 } 352 353 354 355 /** 356 * Indicates whether this tool should provide a command-line argument that 357 * allows for low-level SSL debugging. If this returns {@code true}, then an 358 * "--enableSSLDebugging}" argument will be added that sets the 359 * "javax.net.debug" system property to "all" before attempting any 360 * communication. 361 * 362 * @return {@code true} if this tool should offer an "--enableSSLDebugging" 363 * argument, or {@code false} if not. 364 */ 365 @Override() 366 protected boolean supportsSSLDebugging() 367 { 368 return true; 369 } 370 371 372 373 /** 374 * {@inheritDoc} 375 */ 376 @Override() 377 protected boolean logToolInvocationByDefault() 378 { 379 return true; 380 } 381 382 383 384 /** 385 * Adds the arguments needed by this command-line tool to the provided 386 * argument parser which are not related to connecting or authenticating to 387 * the directory server. 388 * 389 * @param parser The argument parser to which the arguments should be added. 390 * 391 * @throws ArgumentException If a problem occurs while adding the arguments. 392 */ 393 @Override() 394 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 395 throws ArgumentException 396 { 397 set = new BooleanArgument('s', "set", 1, 398 "Indicates that the set of accessibility restrictions should be " + 399 "updated rather than retrieved."); 400 parser.addArgument(set); 401 402 403 baseDN = new DNArgument('b', "baseDN", false, 1, "{dn}", 404 "The base DN of the subtree for which an accessibility restriction " + 405 "is to be updated."); 406 baseDN.addLongIdentifier("base-dn", true); 407 parser.addArgument(baseDN); 408 409 410 accessibilityState = new StringArgument('S', "state", false, 1, "{state}", 411 "The accessibility state to use for the accessibility restriction " + 412 "on the target subtree. Allowed values: " + 413 SubtreeAccessibilityState.ACCESSIBLE.getStateName() + ", " + 414 SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED.getStateName() + 415 ", " + 416 SubtreeAccessibilityState.READ_ONLY_BIND_DENIED.getStateName() + 417 ", " + SubtreeAccessibilityState.HIDDEN.getStateName() + '.', 418 ALLOWED_ACCESSIBILITY_STATES); 419 parser.addArgument(accessibilityState); 420 421 422 bypassUserDN = new DNArgument('B', "bypassUserDN", false, 1, "{dn}", 423 "The DN of a user who is allowed to bypass restrictions on the " + 424 "target subtree."); 425 bypassUserDN.addLongIdentifier("bypass-user-dn", true); 426 parser.addArgument(bypassUserDN); 427 428 429 // The baseDN, accessibilityState, and bypassUserDN arguments can only be 430 // used if the set argument was provided. 431 parser.addDependentArgumentSet(baseDN, set); 432 parser.addDependentArgumentSet(accessibilityState, set); 433 parser.addDependentArgumentSet(bypassUserDN, set); 434 435 436 // If the set argument was provided, then the base DN and accessibilityState 437 // arguments must also be given. 438 parser.addDependentArgumentSet(set, baseDN); 439 parser.addDependentArgumentSet(set, accessibilityState); 440 } 441 442 443 444 /** 445 * Performs the core set of processing for this tool. 446 * 447 * @return A result code that indicates whether the processing completed 448 * successfully. 449 */ 450 @Override() 451 @NotNull() 452 public ResultCode doToolProcessing() 453 { 454 // Get a connection to the target directory server. 455 final LDAPConnection connection; 456 try 457 { 458 connection = getConnection(); 459 } 460 catch (final LDAPException le) 461 { 462 Debug.debugException(le); 463 err("Unable to establish a connection to the target directory server: ", 464 StaticUtils.getExceptionMessage(le)); 465 return le.getResultCode(); 466 } 467 468 try 469 { 470 // See whether to do a get or set operation and call the appropriate 471 // method. 472 if (set.isPresent()) 473 { 474 return doSet(connection); 475 } 476 else 477 { 478 return doGet(connection); 479 } 480 } 481 finally 482 { 483 connection.close(); 484 } 485 } 486 487 488 489 /** 490 * Does the work necessary to retrieve the set of subtree accessibility 491 * restrictions defined in the server. 492 * 493 * @param connection The connection to use to communicate with the server. 494 * 495 * @return A result code with information about the result of operation 496 * processing. 497 */ 498 @NotNull() 499 private ResultCode doGet(@NotNull final LDAPConnection connection) 500 { 501 final GetSubtreeAccessibilityExtendedResult result; 502 try 503 { 504 result = (GetSubtreeAccessibilityExtendedResult) 505 connection.processExtendedOperation( 506 new GetSubtreeAccessibilityExtendedRequest()); 507 } 508 catch (final LDAPException le) 509 { 510 Debug.debugException(le); 511 err("An error occurred while attempting to invoke the get subtree " + 512 "accessibility request: ", StaticUtils.getExceptionMessage(le)); 513 return le.getResultCode(); 514 } 515 516 if (result.getResultCode() != ResultCode.SUCCESS) 517 { 518 err("The server returned an error for the get subtree accessibility " + 519 "request: ", result.getDiagnosticMessage()); 520 return result.getResultCode(); 521 } 522 523 final List<SubtreeAccessibilityRestriction> restrictions = 524 result.getAccessibilityRestrictions(); 525 if ((restrictions == null) || restrictions.isEmpty()) 526 { 527 out("There are no subtree accessibility restrictions defined in the " + 528 "server."); 529 return ResultCode.SUCCESS; 530 } 531 532 if (restrictions.size() == 1) 533 { 534 out("1 subtree accessibility restriction was found in the server:"); 535 } 536 else 537 { 538 out(restrictions.size(), 539 " subtree accessibility restrictions were found in the server:"); 540 } 541 542 for (final SubtreeAccessibilityRestriction r : restrictions) 543 { 544 out("Subtree Base DN: ", r.getSubtreeBaseDN()); 545 out("Accessibility State: ", r.getAccessibilityState().getStateName()); 546 547 final String bypassDN = r.getBypassUserDN(); 548 if (bypassDN != null) 549 { 550 out("Bypass User DN: ", bypassDN); 551 } 552 553 out("Effective Time: ", r.getEffectiveTime()); 554 out(); 555 } 556 557 return ResultCode.SUCCESS; 558 } 559 560 561 562 /** 563 * Does the work necessary to update a subtree accessibility restriction 564 * defined in the server. 565 * 566 * @param connection The connection to use to communicate with the server. 567 * 568 * @return A result code with information about the result of operation 569 * processing. 570 */ 571 @NotNull() 572 private ResultCode doSet(@NotNull final LDAPConnection connection) 573 { 574 final SubtreeAccessibilityState state = 575 SubtreeAccessibilityState.forName(accessibilityState.getValue()); 576 if (state == null) 577 { 578 // This should never happen. 579 err("Unsupported subtree accessibility state ", 580 accessibilityState.getValue()); 581 return ResultCode.PARAM_ERROR; 582 } 583 584 final SetSubtreeAccessibilityExtendedRequest request; 585 switch (state) 586 { 587 case ACCESSIBLE: 588 request = SetSubtreeAccessibilityExtendedRequest. 589 createSetAccessibleRequest(baseDN.getStringValue()); 590 break; 591 case READ_ONLY_BIND_ALLOWED: 592 request = SetSubtreeAccessibilityExtendedRequest. 593 createSetReadOnlyRequest(baseDN.getStringValue(), true, 594 bypassUserDN.getStringValue()); 595 break; 596 case READ_ONLY_BIND_DENIED: 597 request = SetSubtreeAccessibilityExtendedRequest. 598 createSetReadOnlyRequest(baseDN.getStringValue(), false, 599 bypassUserDN.getStringValue()); 600 break; 601 case HIDDEN: 602 request = SetSubtreeAccessibilityExtendedRequest.createSetHiddenRequest( 603 baseDN.getStringValue(), bypassUserDN.getStringValue()); 604 break; 605 default: 606 // This should never happen. 607 err("Unsupported subtree accessibility state ", state.getStateName()); 608 return ResultCode.PARAM_ERROR; 609 } 610 611 final ExtendedResult result; 612 try 613 { 614 result = connection.processExtendedOperation(request); 615 } 616 catch (final LDAPException le) 617 { 618 Debug.debugException(le); 619 err("An error occurred while attempting to invoke the set subtree " + 620 "accessibility request: ", StaticUtils.getExceptionMessage(le)); 621 return le.getResultCode(); 622 } 623 624 if (result.getResultCode() == ResultCode.SUCCESS) 625 { 626 out("Successfully set an accessibility state of ", state.getStateName(), 627 " for subtree ", baseDN.getStringValue()); 628 } 629 else 630 { 631 out("Unable to set an accessibility state of ", state.getStateName(), 632 " for subtree ", baseDN.getStringValue(), ": ", 633 result.getDiagnosticMessage()); 634 } 635 636 return result.getResultCode(); 637 } 638 639 640 641 /** 642 * Retrieves a set of information that may be used to generate example usage 643 * information. Each element in the returned map should consist of a map 644 * between an example set of arguments and a string that describes the 645 * behavior of the tool when invoked with that set of arguments. 646 * 647 * @return A set of information that may be used to generate example usage 648 * information. It may be {@code null} or empty if no example usage 649 * information is available. 650 */ 651 @Override() 652 @NotNull() 653 public LinkedHashMap<String[],String> getExampleUsages() 654 { 655 final LinkedHashMap<String[],String> exampleMap = 656 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 657 658 final String[] getArgs = 659 { 660 "--hostname", "server.example.com", 661 "--port", "389", 662 "--bindDN", "uid=admin,dc=example,dc=com", 663 "--bindPassword", "password", 664 }; 665 exampleMap.put(getArgs, 666 "Retrieve information about all subtree accessibility restrictions " + 667 "defined in the server."); 668 669 final String[] setArgs = 670 { 671 "--hostname", "server.example.com", 672 "--port", "389", 673 "--bindDN", "uid=admin,dc=example,dc=com", 674 "--bindPassword", "password", 675 "--set", 676 "--baseDN", "ou=subtree,dc=example,dc=com", 677 "--state", "read-only-bind-allowed", 678 "--bypassUserDN", "uid=bypass,dc=example,dc=com" 679 }; 680 exampleMap.put(setArgs, 681 "Create or update the subtree accessibility state definition for " + 682 "subtree 'ou=subtree,dc=example,dc=com' so that it is " + 683 "read-only for all users except 'uid=bypass,dc=example,dc=com'."); 684 685 return exampleMap; 686 } 687}