001/* 002 * Copyright 2010-2017 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2017 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 * 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"); 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"); 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<String[],String>(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}