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