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 068 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 069 * for proprietary functionality or for external specifications that are not 070 * considered stable or mature enough to be guaranteed to work in an 071 * interoperable way with 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<>(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 * Retrieves the name of this tool. It should be the name of the command used 197 * to invoke this tool. 198 * 199 * @return The name for this tool. 200 */ 201 @Override() 202 public String getToolName() 203 { 204 return "subtree-accessibility"; 205 } 206 207 208 209 /** 210 * Retrieves a human-readable description for this tool. 211 * 212 * @return A human-readable description for this tool. 213 */ 214 @Override() 215 public String getToolDescription() 216 { 217 return "List or update the set of subtree accessibility restrictions " + 218 "defined in the Directory Server."; 219 } 220 221 222 223 /** 224 * Retrieves the version string for this tool. 225 * 226 * @return The version string for this tool. 227 */ 228 @Override() 229 public String getToolVersion() 230 { 231 return Version.NUMERIC_VERSION_STRING; 232 } 233 234 235 236 /** 237 * Indicates whether this tool should provide support for an interactive mode, 238 * in which the tool offers a mode in which the arguments can be provided in 239 * a text-driven menu rather than requiring them to be given on the command 240 * line. If interactive mode is supported, it may be invoked using the 241 * "--interactive" argument. Alternately, if interactive mode is supported 242 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 243 * interactive mode may be invoked by simply launching the tool without any 244 * arguments. 245 * 246 * @return {@code true} if this tool supports interactive mode, or 247 * {@code false} if not. 248 */ 249 @Override() 250 public boolean supportsInteractiveMode() 251 { 252 return true; 253 } 254 255 256 257 /** 258 * Indicates whether this tool defaults to launching in interactive mode if 259 * the tool is invoked without any command-line arguments. This will only be 260 * used if {@link #supportsInteractiveMode()} returns {@code true}. 261 * 262 * @return {@code true} if this tool defaults to using interactive mode if 263 * launched without any command-line arguments, or {@code false} if 264 * not. 265 */ 266 @Override() 267 public boolean defaultsToInteractiveMode() 268 { 269 return true; 270 } 271 272 273 274 /** 275 * Indicates whether this tool should provide arguments for redirecting output 276 * to a file. If this method returns {@code true}, then the tool will offer 277 * an "--outputFile" argument that will specify the path to a file to which 278 * all standard output and standard error content will be written, and it will 279 * also offer a "--teeToStandardOut" argument that can only be used if the 280 * "--outputFile" argument is present and will cause all output to be written 281 * to both the specified output file and to standard output. 282 * 283 * @return {@code true} if this tool should provide arguments for redirecting 284 * output to a file, or {@code false} if not. 285 */ 286 @Override() 287 protected boolean supportsOutputFile() 288 { 289 return true; 290 } 291 292 293 294 /** 295 * Indicates whether this tool should default to interactively prompting for 296 * the bind password if a password is required but no argument was provided 297 * to indicate how to get the password. 298 * 299 * @return {@code true} if this tool should default to interactively 300 * prompting for the bind password, or {@code false} if not. 301 */ 302 @Override() 303 protected boolean defaultToPromptForBindPassword() 304 { 305 return true; 306 } 307 308 309 310 /** 311 * Indicates whether this tool supports the use of a properties file for 312 * specifying default values for arguments that aren't specified on the 313 * command line. 314 * 315 * @return {@code true} if this tool supports the use of a properties file 316 * for specifying default values for arguments that aren't specified 317 * on the command line, or {@code false} if not. 318 */ 319 @Override() 320 public boolean supportsPropertiesFile() 321 { 322 return true; 323 } 324 325 326 327 /** 328 * Indicates whether the LDAP-specific arguments should include alternate 329 * versions of all long identifiers that consist of multiple words so that 330 * they are available in both camelCase and dash-separated versions. 331 * 332 * @return {@code true} if this tool should provide multiple versions of 333 * long identifiers for LDAP-specific arguments, or {@code false} if 334 * not. 335 */ 336 @Override() 337 protected boolean includeAlternateLongIdentifiers() 338 { 339 return true; 340 } 341 342 343 344 /** 345 * {@inheritDoc} 346 */ 347 @Override() 348 protected boolean logToolInvocationByDefault() 349 { 350 return true; 351 } 352 353 354 355 /** 356 * Adds the arguments needed by this command-line tool to the provided 357 * argument parser which are not related to connecting or authenticating to 358 * the directory server. 359 * 360 * @param parser The argument parser to which the arguments should be added. 361 * 362 * @throws ArgumentException If a problem occurs while adding the arguments. 363 */ 364 @Override() 365 public void addNonLDAPArguments(final ArgumentParser parser) 366 throws ArgumentException 367 { 368 set = new BooleanArgument('s', "set", 1, 369 "Indicates that the set of accessibility restrictions should be " + 370 "updated rather than retrieved."); 371 parser.addArgument(set); 372 373 374 baseDN = new DNArgument('b', "baseDN", false, 1, "{dn}", 375 "The base DN of the subtree for which an accessibility restriction " + 376 "is to be updated."); 377 baseDN.addLongIdentifier("base-dn", true); 378 parser.addArgument(baseDN); 379 380 381 accessibilityState = new StringArgument('S', "state", false, 1, "{state}", 382 "The accessibility state to use for the accessibility restriction " + 383 "on the target subtree. Allowed values: " + 384 SubtreeAccessibilityState.ACCESSIBLE.getStateName() + ", " + 385 SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED.getStateName() + 386 ", " + 387 SubtreeAccessibilityState.READ_ONLY_BIND_DENIED.getStateName() + 388 ", " + SubtreeAccessibilityState.HIDDEN.getStateName() + '.', 389 ALLOWED_ACCESSIBILITY_STATES); 390 parser.addArgument(accessibilityState); 391 392 393 bypassUserDN = new DNArgument('B', "bypassUserDN", false, 1, "{dn}", 394 "The DN of a user who is allowed to bypass restrictions on the " + 395 "target subtree."); 396 bypassUserDN.addLongIdentifier("bypass-user-dn", true); 397 parser.addArgument(bypassUserDN); 398 399 400 // The baseDN, accessibilityState, and bypassUserDN arguments can only be 401 // used if the set argument was provided. 402 parser.addDependentArgumentSet(baseDN, set); 403 parser.addDependentArgumentSet(accessibilityState, set); 404 parser.addDependentArgumentSet(bypassUserDN, set); 405 406 407 // If the set argument was provided, then the base DN and accessibilityState 408 // arguments must also be given. 409 parser.addDependentArgumentSet(set, baseDN); 410 parser.addDependentArgumentSet(set, accessibilityState); 411 } 412 413 414 415 /** 416 * Performs the core set of processing for this tool. 417 * 418 * @return A result code that indicates whether the processing completed 419 * successfully. 420 */ 421 @Override() 422 public ResultCode doToolProcessing() 423 { 424 // Get a connection to the target directory server. 425 final LDAPConnection connection; 426 try 427 { 428 connection = getConnection(); 429 } 430 catch (final LDAPException le) 431 { 432 Debug.debugException(le); 433 err("Unable to establish a connection to the target directory server: ", 434 StaticUtils.getExceptionMessage(le)); 435 return le.getResultCode(); 436 } 437 438 try 439 { 440 // See whether to do a get or set operation and call the appropriate 441 // method. 442 if (set.isPresent()) 443 { 444 return doSet(connection); 445 } 446 else 447 { 448 return doGet(connection); 449 } 450 } 451 finally 452 { 453 connection.close(); 454 } 455 } 456 457 458 459 /** 460 * Does the work necessary to retrieve the set of subtree accessibility 461 * restrictions defined in the server. 462 * 463 * @param connection The connection to use to communicate with the server. 464 * 465 * @return A result code with information about the result of operation 466 * processing. 467 */ 468 private ResultCode doGet(final LDAPConnection connection) 469 { 470 final GetSubtreeAccessibilityExtendedResult result; 471 try 472 { 473 result = (GetSubtreeAccessibilityExtendedResult) 474 connection.processExtendedOperation( 475 new GetSubtreeAccessibilityExtendedRequest()); 476 } 477 catch (final LDAPException le) 478 { 479 Debug.debugException(le); 480 err("An error occurred while attempting to invoke the get subtree " + 481 "accessibility request: ", StaticUtils.getExceptionMessage(le)); 482 return le.getResultCode(); 483 } 484 485 if (result.getResultCode() != ResultCode.SUCCESS) 486 { 487 err("The server returned an error for the get subtree accessibility " + 488 "request: ", result.getDiagnosticMessage()); 489 return result.getResultCode(); 490 } 491 492 final List<SubtreeAccessibilityRestriction> restrictions = 493 result.getAccessibilityRestrictions(); 494 if ((restrictions == null) || restrictions.isEmpty()) 495 { 496 out("There are no subtree accessibility restrictions defined in the " + 497 "server."); 498 return ResultCode.SUCCESS; 499 } 500 501 if (restrictions.size() == 1) 502 { 503 out("1 subtree accessibility restriction was found in the server:"); 504 } 505 else 506 { 507 out(restrictions.size(), 508 " subtree accessibility restrictions were found in the server:"); 509 } 510 511 for (final SubtreeAccessibilityRestriction r : restrictions) 512 { 513 out("Subtree Base DN: ", r.getSubtreeBaseDN()); 514 out("Accessibility State: ", r.getAccessibilityState().getStateName()); 515 516 final String bypassDN = r.getBypassUserDN(); 517 if (bypassDN != null) 518 { 519 out("Bypass User DN: ", bypassDN); 520 } 521 522 out("Effective Time: ", r.getEffectiveTime()); 523 out(); 524 } 525 526 return ResultCode.SUCCESS; 527 } 528 529 530 531 /** 532 * Does the work necessary to update a subtree accessibility restriction 533 * defined in the server. 534 * 535 * @param connection The connection to use to communicate with the server. 536 * 537 * @return A result code with information about the result of operation 538 * processing. 539 */ 540 private ResultCode doSet(final LDAPConnection connection) 541 { 542 final SubtreeAccessibilityState state = 543 SubtreeAccessibilityState.forName(accessibilityState.getValue()); 544 if (state == null) 545 { 546 // This should never happen. 547 err("Unsupported subtree accessibility state ", 548 accessibilityState.getValue()); 549 return ResultCode.PARAM_ERROR; 550 } 551 552 final SetSubtreeAccessibilityExtendedRequest request; 553 switch (state) 554 { 555 case ACCESSIBLE: 556 request = SetSubtreeAccessibilityExtendedRequest. 557 createSetAccessibleRequest(baseDN.getStringValue()); 558 break; 559 case READ_ONLY_BIND_ALLOWED: 560 request = SetSubtreeAccessibilityExtendedRequest. 561 createSetReadOnlyRequest(baseDN.getStringValue(), true, 562 bypassUserDN.getStringValue()); 563 break; 564 case READ_ONLY_BIND_DENIED: 565 request = SetSubtreeAccessibilityExtendedRequest. 566 createSetReadOnlyRequest(baseDN.getStringValue(), false, 567 bypassUserDN.getStringValue()); 568 break; 569 case HIDDEN: 570 request = SetSubtreeAccessibilityExtendedRequest.createSetHiddenRequest( 571 baseDN.getStringValue(), bypassUserDN.getStringValue()); 572 break; 573 default: 574 // This should never happen. 575 err("Unsupported subtree accessibility state ", state.getStateName()); 576 return ResultCode.PARAM_ERROR; 577 } 578 579 final ExtendedResult result; 580 try 581 { 582 result = connection.processExtendedOperation(request); 583 } 584 catch (final LDAPException le) 585 { 586 Debug.debugException(le); 587 err("An error occurred while attempting to invoke the set subtree " + 588 "accessibility request: ", StaticUtils.getExceptionMessage(le)); 589 return le.getResultCode(); 590 } 591 592 if (result.getResultCode() == ResultCode.SUCCESS) 593 { 594 out("Successfully set an accessibility state of ", state.getStateName(), 595 " for subtree ", baseDN.getStringValue()); 596 } 597 else 598 { 599 out("Unable to set an accessibility state of ", state.getStateName(), 600 " for subtree ", baseDN.getStringValue(), ": ", 601 result.getDiagnosticMessage()); 602 } 603 604 return result.getResultCode(); 605 } 606 607 608 609 /** 610 * Retrieves a set of information that may be used to generate example usage 611 * information. Each element in the returned map should consist of a map 612 * between an example set of arguments and a string that describes the 613 * behavior of the tool when invoked with that set of arguments. 614 * 615 * @return A set of information that may be used to generate example usage 616 * information. It may be {@code null} or empty if no example usage 617 * information is available. 618 */ 619 @Override() 620 public LinkedHashMap<String[],String> getExampleUsages() 621 { 622 final LinkedHashMap<String[],String> exampleMap = new LinkedHashMap<>(2); 623 624 final String[] getArgs = 625 { 626 "--hostname", "server.example.com", 627 "--port", "389", 628 "--bindDN", "uid=admin,dc=example,dc=com", 629 "--bindPassword", "password", 630 }; 631 exampleMap.put(getArgs, 632 "Retrieve information about all subtree accessibility restrictions " + 633 "defined in the server."); 634 635 final String[] setArgs = 636 { 637 "--hostname", "server.example.com", 638 "--port", "389", 639 "--bindDN", "uid=admin,dc=example,dc=com", 640 "--bindPassword", "password", 641 "--set", 642 "--baseDN", "ou=subtree,dc=example,dc=com", 643 "--state", "read-only-bind-allowed", 644 "--bypassUserDN", "uid=bypass,dc=example,dc=com" 645 }; 646 exampleMap.put(setArgs, 647 "Create or update the subtree accessibility state definition for " + 648 "subtree 'ou=subtree,dc=example,dc=com' so that it is " + 649 "read-only for all users except 'uid=bypass,dc=example,dc=com'."); 650 651 return exampleMap; 652 } 653}