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 the LDAP-specific arguments should include alternate
340   * versions of all long identifiers that consist of multiple words so that
341   * they are available in both camelCase and dash-separated versions.
342   *
343   * @return  {@code true} if this tool should provide multiple versions of
344   *          long identifiers for LDAP-specific arguments, or {@code false} if
345   *          not.
346   */
347  @Override()
348  protected boolean includeAlternateLongIdentifiers()
349  {
350    return true;
351  }
352
353
354
355  /**
356   * Indicates whether this tool should provide a command-line argument that
357   * allows for low-level SSL debugging.  If this returns {@code true}, then an
358   * "--enableSSLDebugging}" argument will be added that sets the
359   * "javax.net.debug" system property to "all" before attempting any
360   * communication.
361   *
362   * @return  {@code true} if this tool should offer an "--enableSSLDebugging"
363   *          argument, or {@code false} if not.
364   */
365  @Override()
366  protected boolean supportsSSLDebugging()
367  {
368    return true;
369  }
370
371
372
373  /**
374   * {@inheritDoc}
375   */
376  @Override()
377  protected boolean logToolInvocationByDefault()
378  {
379    return true;
380  }
381
382
383
384  /**
385   * Adds the arguments needed by this command-line tool to the provided
386   * argument parser which are not related to connecting or authenticating to
387   * the directory server.
388   *
389   * @param  parser  The argument parser to which the arguments should be added.
390   *
391   * @throws  ArgumentException  If a problem occurs while adding the arguments.
392   */
393  @Override()
394  public void addNonLDAPArguments(@NotNull final ArgumentParser parser)
395         throws ArgumentException
396  {
397    set = new BooleanArgument('s', "set", 1,
398         "Indicates that the set of accessibility restrictions should be " +
399              "updated rather than retrieved.");
400    parser.addArgument(set);
401
402
403    baseDN = new DNArgument('b', "baseDN", false, 1, "{dn}",
404         "The base DN of the subtree for which an accessibility restriction " +
405              "is to be updated.");
406    baseDN.addLongIdentifier("base-dn", true);
407    parser.addArgument(baseDN);
408
409
410    accessibilityState = new StringArgument('S', "state", false, 1, "{state}",
411         "The accessibility state to use for the accessibility restriction " +
412              "on the target subtree.  Allowed values:  " +
413              SubtreeAccessibilityState.ACCESSIBLE.getStateName() + ", " +
414              SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED.getStateName() +
415              ", " +
416              SubtreeAccessibilityState.READ_ONLY_BIND_DENIED.getStateName() +
417              ", " + SubtreeAccessibilityState.HIDDEN.getStateName() + '.',
418         ALLOWED_ACCESSIBILITY_STATES);
419    parser.addArgument(accessibilityState);
420
421
422    bypassUserDN = new DNArgument('B', "bypassUserDN", false, 1, "{dn}",
423         "The DN of a user who is allowed to bypass restrictions on the " +
424              "target subtree.");
425    bypassUserDN.addLongIdentifier("bypass-user-dn", true);
426    parser.addArgument(bypassUserDN);
427
428
429    // The baseDN, accessibilityState, and bypassUserDN arguments can only be
430    // used if the set argument was provided.
431    parser.addDependentArgumentSet(baseDN, set);
432    parser.addDependentArgumentSet(accessibilityState, set);
433    parser.addDependentArgumentSet(bypassUserDN, set);
434
435
436    // If the set argument was provided, then the base DN and accessibilityState
437    // arguments must also be given.
438    parser.addDependentArgumentSet(set, baseDN);
439    parser.addDependentArgumentSet(set, accessibilityState);
440  }
441
442
443
444  /**
445   * Performs the core set of processing for this tool.
446   *
447   * @return  A result code that indicates whether the processing completed
448   *          successfully.
449   */
450  @Override()
451  @NotNull()
452  public ResultCode doToolProcessing()
453  {
454    // Get a connection to the target directory server.
455    final LDAPConnection connection;
456    try
457    {
458      connection = getConnection();
459    }
460    catch (final LDAPException le)
461    {
462      Debug.debugException(le);
463      err("Unable to establish a connection to the target directory server:  ",
464           StaticUtils.getExceptionMessage(le));
465      return le.getResultCode();
466    }
467
468    try
469    {
470      // See whether to do a get or set operation and call the appropriate
471      // method.
472      if (set.isPresent())
473      {
474        return doSet(connection);
475      }
476      else
477      {
478        return doGet(connection);
479      }
480    }
481    finally
482    {
483      connection.close();
484    }
485  }
486
487
488
489  /**
490   * Does the work necessary to retrieve the set of subtree accessibility
491   * restrictions defined in the server.
492   *
493   * @param  connection  The connection to use to communicate with the server.
494   *
495   * @return  A result code with information about the result of operation
496   *          processing.
497   */
498  @NotNull()
499  private ResultCode doGet(@NotNull final LDAPConnection connection)
500  {
501    final GetSubtreeAccessibilityExtendedResult result;
502    try
503    {
504      result = (GetSubtreeAccessibilityExtendedResult)
505           connection.processExtendedOperation(
506                new GetSubtreeAccessibilityExtendedRequest());
507    }
508    catch (final LDAPException le)
509    {
510      Debug.debugException(le);
511      err("An error occurred while attempting to invoke the get subtree " +
512           "accessibility request:  ", StaticUtils.getExceptionMessage(le));
513      return le.getResultCode();
514    }
515
516    if (result.getResultCode() != ResultCode.SUCCESS)
517    {
518      err("The server returned an error for the get subtree accessibility " +
519           "request:  ", result.getDiagnosticMessage());
520      return result.getResultCode();
521    }
522
523    final List<SubtreeAccessibilityRestriction> restrictions =
524         result.getAccessibilityRestrictions();
525    if ((restrictions == null) || restrictions.isEmpty())
526    {
527      out("There are no subtree accessibility restrictions defined in the " +
528           "server.");
529      return ResultCode.SUCCESS;
530    }
531
532    if (restrictions.size() == 1)
533    {
534      out("1 subtree accessibility restriction was found in the server:");
535    }
536    else
537    {
538      out(restrictions.size(),
539           " subtree accessibility restrictions were found in the server:");
540    }
541
542    for (final SubtreeAccessibilityRestriction r : restrictions)
543    {
544      out("Subtree Base DN:      ", r.getSubtreeBaseDN());
545      out("Accessibility State:  ", r.getAccessibilityState().getStateName());
546
547      final String bypassDN = r.getBypassUserDN();
548      if (bypassDN != null)
549      {
550        out("Bypass User DN:       ", bypassDN);
551      }
552
553      out("Effective Time:       ", r.getEffectiveTime());
554      out();
555    }
556
557    return ResultCode.SUCCESS;
558  }
559
560
561
562  /**
563   * Does the work necessary to update a subtree accessibility restriction
564   * defined in the server.
565   *
566   * @param  connection  The connection to use to communicate with the server.
567   *
568   * @return  A result code with information about the result of operation
569   *          processing.
570   */
571  @NotNull()
572  private ResultCode doSet(@NotNull final LDAPConnection connection)
573  {
574    final SubtreeAccessibilityState state =
575         SubtreeAccessibilityState.forName(accessibilityState.getValue());
576    if (state == null)
577    {
578      // This should never happen.
579      err("Unsupported subtree accessibility state ",
580           accessibilityState.getValue());
581      return ResultCode.PARAM_ERROR;
582    }
583
584    final SetSubtreeAccessibilityExtendedRequest request;
585    switch (state)
586    {
587      case ACCESSIBLE:
588        request = SetSubtreeAccessibilityExtendedRequest.
589             createSetAccessibleRequest(baseDN.getStringValue());
590        break;
591      case READ_ONLY_BIND_ALLOWED:
592        request = SetSubtreeAccessibilityExtendedRequest.
593             createSetReadOnlyRequest(baseDN.getStringValue(), true,
594                  bypassUserDN.getStringValue());
595        break;
596      case READ_ONLY_BIND_DENIED:
597        request = SetSubtreeAccessibilityExtendedRequest.
598             createSetReadOnlyRequest(baseDN.getStringValue(), false,
599                  bypassUserDN.getStringValue());
600        break;
601      case HIDDEN:
602        request = SetSubtreeAccessibilityExtendedRequest.createSetHiddenRequest(
603             baseDN.getStringValue(), bypassUserDN.getStringValue());
604        break;
605      default:
606        // This should never happen.
607        err("Unsupported subtree accessibility state ", state.getStateName());
608        return ResultCode.PARAM_ERROR;
609    }
610
611    final ExtendedResult result;
612    try
613    {
614      result = connection.processExtendedOperation(request);
615    }
616    catch (final LDAPException le)
617    {
618      Debug.debugException(le);
619      err("An error occurred while attempting to invoke the set subtree " +
620           "accessibility request:  ", StaticUtils.getExceptionMessage(le));
621      return le.getResultCode();
622    }
623
624    if (result.getResultCode() == ResultCode.SUCCESS)
625    {
626      out("Successfully set an accessibility state of ", state.getStateName(),
627           " for subtree ", baseDN.getStringValue());
628    }
629    else
630    {
631      out("Unable to set an accessibility state of ", state.getStateName(),
632           " for subtree ", baseDN.getStringValue(), ":  ",
633           result.getDiagnosticMessage());
634    }
635
636    return result.getResultCode();
637  }
638
639
640
641  /**
642   * Retrieves a set of information that may be used to generate example usage
643   * information.  Each element in the returned map should consist of a map
644   * between an example set of arguments and a string that describes the
645   * behavior of the tool when invoked with that set of arguments.
646   *
647   * @return  A set of information that may be used to generate example usage
648   *          information.  It may be {@code null} or empty if no example usage
649   *          information is available.
650   */
651  @Override()
652  @NotNull()
653  public LinkedHashMap<String[],String> getExampleUsages()
654  {
655    final LinkedHashMap<String[],String> exampleMap =
656         new LinkedHashMap<>(StaticUtils.computeMapCapacity(2));
657
658    final String[] getArgs =
659    {
660      "--hostname", "server.example.com",
661      "--port", "389",
662      "--bindDN", "uid=admin,dc=example,dc=com",
663      "--bindPassword", "password",
664    };
665    exampleMap.put(getArgs,
666         "Retrieve information about all subtree accessibility restrictions " +
667              "defined in the server.");
668
669    final String[] setArgs =
670    {
671      "--hostname", "server.example.com",
672      "--port", "389",
673      "--bindDN", "uid=admin,dc=example,dc=com",
674      "--bindPassword", "password",
675      "--set",
676      "--baseDN", "ou=subtree,dc=example,dc=com",
677      "--state", "read-only-bind-allowed",
678      "--bypassUserDN", "uid=bypass,dc=example,dc=com"
679    };
680    exampleMap.put(setArgs,
681         "Create or update the subtree accessibility state definition for " +
682              "subtree 'ou=subtree,dc=example,dc=com' so that it is " +
683              "read-only for all users except 'uid=bypass,dc=example,dc=com'.");
684
685    return exampleMap;
686  }
687}