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