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