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