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