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