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