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