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