001    /*
002     * Copyright 2010-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015 UnboundID Corp.
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     */
021    package com.unboundid.ldap.sdk.unboundidds.examples;
022    
023    
024    
025    import java.io.OutputStream;
026    import java.io.Serializable;
027    import java.util.Collections;
028    import java.util.LinkedHashMap;
029    import java.util.LinkedHashSet;
030    import java.util.List;
031    import java.util.Set;
032    
033    import com.unboundid.ldap.sdk.ExtendedResult;
034    import com.unboundid.ldap.sdk.LDAPConnection;
035    import com.unboundid.ldap.sdk.LDAPException;
036    import com.unboundid.ldap.sdk.ResultCode;
037    import com.unboundid.ldap.sdk.Version;
038    import com.unboundid.ldap.sdk.unboundidds.extensions.
039                GetSubtreeAccessibilityExtendedRequest;
040    import com.unboundid.ldap.sdk.unboundidds.extensions.
041                GetSubtreeAccessibilityExtendedResult;
042    import com.unboundid.ldap.sdk.unboundidds.extensions.
043                SetSubtreeAccessibilityExtendedRequest;
044    import com.unboundid.ldap.sdk.unboundidds.extensions.
045                SubtreeAccessibilityRestriction;
046    import com.unboundid.ldap.sdk.unboundidds.extensions.SubtreeAccessibilityState;
047    import com.unboundid.util.Debug;
048    import com.unboundid.util.LDAPCommandLineTool;
049    import com.unboundid.util.StaticUtils;
050    import com.unboundid.util.ThreadSafety;
051    import com.unboundid.util.ThreadSafetyLevel;
052    import com.unboundid.util.args.ArgumentException;
053    import com.unboundid.util.args.ArgumentParser;
054    import com.unboundid.util.args.BooleanArgument;
055    import com.unboundid.util.args.DNArgument;
056    import com.unboundid.util.args.StringArgument;
057    
058    
059    
060    /**
061     * <BLOCKQUOTE>
062     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
063     *   LDAP SDK for Java.  It is not available for use in applications that
064     *   include only the Standard Edition of the LDAP SDK, and is not supported for
065     *   use in conjunction with non-UnboundID products.
066     * </BLOCKQUOTE>
067     * This class provides a utility that can be used to query and update the set of
068     * subtree accessibility restrictions defined in the Directory Server.
069     * <BR><BR>
070     * The APIs demonstrated by this example include:
071     * <UL>
072     *   <LI>The use of the get/set subtree accessibility extended operations</LI>
073     *   <LI>The LDAP command-line tool API.</LI>
074     *   <LI>Argument parsing.</LI>
075     * </UL>
076     */
077    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
078    public final class SubtreeAccessibility
079           extends LDAPCommandLineTool
080           implements Serializable
081    {
082      /**
083       * The set of allowed subtree accessibility state values.
084       */
085      private static final Set<String> ALLOWED_ACCESSIBILITY_STATES;
086      static
087      {
088        final LinkedHashSet<String> stateValues = new LinkedHashSet<String>(4);
089    
090        stateValues.add(SubtreeAccessibilityState.ACCESSIBLE.getStateName());
091        stateValues.add(
092             SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED.getStateName());
093        stateValues.add(
094             SubtreeAccessibilityState.READ_ONLY_BIND_DENIED.getStateName());
095        stateValues.add(SubtreeAccessibilityState.HIDDEN.getStateName());
096    
097        ALLOWED_ACCESSIBILITY_STATES = Collections.unmodifiableSet(stateValues);
098      }
099    
100    
101    
102      /**
103       * The serial version UID for this serializable class.
104       */
105      private static final long serialVersionUID = 3703682568143472108L;
106    
107    
108    
109      // Indicates whether the set of subtree restrictions should be updated rather
110      // than queried.
111      private BooleanArgument set;
112    
113      // The argument used to specify the base DN for the target subtree.
114      private DNArgument baseDN;
115    
116      // The argument used to specify the DN of a user who can bypass restrictions
117      // on the target subtree.
118      private DNArgument bypassUserDN;
119    
120      // The argument used to specify the accessibility state for the target
121      // subtree.
122      private StringArgument accessibilityState;
123    
124    
125    
126      /**
127       * Parse the provided command line arguments and perform the appropriate
128       * processing.
129       *
130       * @param  args  The command line arguments provided to this program.
131       */
132      public static void main(final String[] args)
133      {
134        final ResultCode resultCode = main(args, System.out, System.err);
135        if (resultCode != ResultCode.SUCCESS)
136        {
137          System.exit(resultCode.intValue());
138        }
139      }
140    
141    
142    
143      /**
144       * Parse the provided command line arguments and perform the appropriate
145       * processing.
146       *
147       * @param  args       The command line arguments provided to this program.
148       * @param  outStream  The output stream to which standard out should be
149       *                    written.  It may be {@code null} if output should be
150       *                    suppressed.
151       * @param  errStream  The output stream to which standard error should be
152       *                    written.  It may be {@code null} if error messages
153       *                    should be suppressed.
154       *
155       * @return  A result code indicating whether the processing was successful.
156       */
157      public static ResultCode main(final String[] args,
158                                    final OutputStream outStream,
159                                    final OutputStream errStream)
160      {
161        final SubtreeAccessibility tool =
162             new SubtreeAccessibility(outStream, errStream);
163        return tool.runTool(args);
164      }
165    
166    
167    
168      /**
169       * Creates a new instance of this tool.
170       *
171       * @param  outStream  The output stream to which standard out should be
172       *                    written.  It may be {@code null} if output should be
173       *                    suppressed.
174       * @param  errStream  The output stream to which standard error should be
175       *                    written.  It may be {@code null} if error messages
176       *                    should be suppressed.
177       */
178      public SubtreeAccessibility(final OutputStream outStream,
179                                  final OutputStream errStream)
180      {
181        super(outStream, errStream);
182    
183        set                = null;
184        baseDN             = null;
185        bypassUserDN       = null;
186        accessibilityState = null;
187      }
188    
189    
190    
191    
192      /**
193       * Retrieves the name of this tool.  It should be the name of the command used
194       * to invoke this tool.
195       *
196       * @return  The name for this tool.
197       */
198      @Override()
199      public String getToolName()
200      {
201        return "subtree-accessibility";
202      }
203    
204    
205    
206      /**
207       * Retrieves a human-readable description for this tool.
208       *
209       * @return  A human-readable description for this tool.
210       */
211      @Override()
212      public String getToolDescription()
213      {
214        return "List or update the a set of subtree accessibility restrictions " +
215             "defined in the Directory Server.";
216      }
217    
218    
219    
220      /**
221       * Retrieves the version string for this tool.
222       *
223       * @return  The version string for this tool.
224       */
225      @Override()
226      public String getToolVersion()
227      {
228        return Version.NUMERIC_VERSION_STRING;
229      }
230    
231    
232    
233      /**
234       * Adds the arguments needed by this command-line tool to the provided
235       * argument parser which are not related to connecting or authenticating to
236       * the directory server.
237       *
238       * @param  parser  The argument parser to which the arguments should be added.
239       *
240       * @throws  ArgumentException  If a problem occurs while adding the arguments.
241       */
242      @Override()
243      public void addNonLDAPArguments(final ArgumentParser parser)
244             throws ArgumentException
245      {
246        set = new BooleanArgument('s', "set", 1,
247             "Indicates that the set of accessibility restrictions should be " +
248                  "updated rather than retrieved.");
249        parser.addArgument(set);
250    
251    
252        baseDN = new DNArgument('b', "baseDN", false, 1, "{dn}",
253             "The base DN of the subtree for which an accessibility restriction " +
254                  "is to be updated.");
255        parser.addArgument(baseDN);
256    
257    
258        accessibilityState = new StringArgument('S', "state", false, 1, "{state}",
259             "The accessibility state to use for the accessibility restriction " +
260                  "on the target subtree.  Allowed values:  " +
261                  SubtreeAccessibilityState.ACCESSIBLE.getStateName() + ", " +
262                  SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED.getStateName() +
263                  ", " +
264                  SubtreeAccessibilityState.READ_ONLY_BIND_DENIED.getStateName() +
265                  ", " + SubtreeAccessibilityState.HIDDEN.getStateName() + '.',
266             ALLOWED_ACCESSIBILITY_STATES);
267        parser.addArgument(accessibilityState);
268    
269    
270        bypassUserDN = new DNArgument('B', "bypassUserDN", false, 1, "{dn}",
271             "The DN of a user who is allowed to bypass restrictions on the " +
272                  "target subtree.");
273        parser.addArgument(bypassUserDN);
274    
275    
276        // The baseDN, accessibilityState, and bypassUserDN arguments can only be
277        // used if the set argument was provided.
278        parser.addDependentArgumentSet(baseDN, set);
279        parser.addDependentArgumentSet(accessibilityState, set);
280        parser.addDependentArgumentSet(bypassUserDN, set);
281    
282    
283        // If the set argument was provided, then the base DN and accessibilityState
284        // arguments must also be given.
285        parser.addDependentArgumentSet(set, baseDN);
286        parser.addDependentArgumentSet(set, accessibilityState);
287      }
288    
289    
290    
291      /**
292       * Performs the core set of processing for this tool.
293       *
294       * @return  A result code that indicates whether the processing completed
295       *          successfully.
296       */
297      @Override()
298      public ResultCode doToolProcessing()
299      {
300        // Get a connection to the target directory server.
301        final LDAPConnection connection;
302        try
303        {
304          connection = getConnection();
305        }
306        catch (final LDAPException le)
307        {
308          Debug.debugException(le);
309          err("Unable to establish a connection to the target directory server:  ",
310               StaticUtils.getExceptionMessage(le));
311          return le.getResultCode();
312        }
313    
314        try
315        {
316          // See whether to do a get or set operation and call the appropriate
317          // method.
318          if (set.isPresent())
319          {
320            return doSet(connection);
321          }
322          else
323          {
324            return doGet(connection);
325          }
326        }
327        finally
328        {
329          connection.close();
330        }
331      }
332    
333    
334    
335      /**
336       * Does the work necessary to retrieve the set of subtree accessibility
337       * restrictions defined in the server.
338       *
339       * @param  connection  The connection to use to communicate with the server.
340       *
341       * @return  A result code with information about the result of operation
342       *          processing.
343       */
344      private ResultCode doGet(final LDAPConnection connection)
345      {
346        final GetSubtreeAccessibilityExtendedResult result;
347        try
348        {
349          result = (GetSubtreeAccessibilityExtendedResult)
350               connection.processExtendedOperation(
351                    new GetSubtreeAccessibilityExtendedRequest());
352        }
353        catch (final LDAPException le)
354        {
355          Debug.debugException(le);
356          err("An error occurred while attempting to invoke the get subtree " +
357               "accessibility request:  ", StaticUtils.getExceptionMessage(le));
358          return le.getResultCode();
359        }
360    
361        if (result.getResultCode() != ResultCode.SUCCESS)
362        {
363          err("The server returned an error for the get subtree accessibility " +
364               "request:  ", result.getDiagnosticMessage());
365          return result.getResultCode();
366        }
367    
368        final List<SubtreeAccessibilityRestriction> restrictions =
369             result.getAccessibilityRestrictions();
370        if ((restrictions == null) || restrictions.isEmpty())
371        {
372          out("There are no subtree accessibility restrictions defined in the " +
373               "server.");
374          return ResultCode.SUCCESS;
375        }
376    
377        if (restrictions.size() == 1)
378        {
379          out("1 subtree accessibility restriction was found in the server:");
380        }
381        else
382        {
383          out(restrictions.size(),
384               " subtree accessibility restrictions were found in the server:");
385        }
386    
387        for (final SubtreeAccessibilityRestriction r : restrictions)
388        {
389          out("Subtree Base DN:      ", r.getSubtreeBaseDN());
390          out("Accessibility State:  ", r.getAccessibilityState().getStateName());
391    
392          final String bypassDN = r.getBypassUserDN();
393          if (bypassDN != null)
394          {
395            out("Bypass User DN:       ", bypassDN);
396          }
397    
398          out("Effective Time:       ", r.getEffectiveTime());
399          out();
400        }
401    
402        return ResultCode.SUCCESS;
403      }
404    
405    
406    
407      /**
408       * Does the work necessary to update a subtree accessibility restriction
409       * defined in the server.
410       *
411       * @param  connection  The connection to use to communicate with the server.
412       *
413       * @return  A result code with information about the result of operation
414       *          processing.
415       */
416      private ResultCode doSet(final LDAPConnection connection)
417      {
418        final SubtreeAccessibilityState state =
419             SubtreeAccessibilityState.forName(accessibilityState.getValue());
420        if (state == null)
421        {
422          // This should never happen.
423          err("Unsupported subtree accessibility state ",
424               accessibilityState.getValue());
425          return ResultCode.PARAM_ERROR;
426        }
427    
428        final SetSubtreeAccessibilityExtendedRequest request;
429        switch (state)
430        {
431          case ACCESSIBLE:
432            request = SetSubtreeAccessibilityExtendedRequest.
433                 createSetAccessibleRequest(baseDN.getStringValue());
434            break;
435          case READ_ONLY_BIND_ALLOWED:
436            request = SetSubtreeAccessibilityExtendedRequest.
437                 createSetReadOnlyRequest(baseDN.getStringValue(), true,
438                      bypassUserDN.getStringValue());
439            break;
440          case READ_ONLY_BIND_DENIED:
441            request = SetSubtreeAccessibilityExtendedRequest.
442                 createSetReadOnlyRequest(baseDN.getStringValue(), false,
443                      bypassUserDN.getStringValue());
444            break;
445          case HIDDEN:
446            request = SetSubtreeAccessibilityExtendedRequest.createSetHiddenRequest(
447                 baseDN.getStringValue(), bypassUserDN.getStringValue());
448            break;
449          default:
450            // This should never happen.
451            err("Unsupported subtree accessibility state ", state.getStateName());
452            return ResultCode.PARAM_ERROR;
453        }
454    
455        final ExtendedResult result;
456        try
457        {
458          result = connection.processExtendedOperation(request);
459        }
460        catch (final LDAPException le)
461        {
462          Debug.debugException(le);
463          err("An error occurred while attempting to invoke the set subtree " +
464               "accessibility request:  ", StaticUtils.getExceptionMessage(le));
465          return le.getResultCode();
466        }
467    
468        if (result.getResultCode() == ResultCode.SUCCESS)
469        {
470          out("Successfully set an accessibility state of ", state.getStateName(),
471               " for subtree ", baseDN.getStringValue());
472        }
473        else
474        {
475          out("Unable to set an accessibility state of ", state.getStateName(),
476               " for subtree ", baseDN.getStringValue(), ":  ",
477               result.getDiagnosticMessage());
478        }
479    
480        return result.getResultCode();
481      }
482    
483    
484    
485      /**
486       * Retrieves a set of information that may be used to generate example usage
487       * information.  Each element in the returned map should consist of a map
488       * between an example set of arguments and a string that describes the
489       * behavior of the tool when invoked with that set of arguments.
490       *
491       * @return  A set of information that may be used to generate example usage
492       *          information.  It may be {@code null} or empty if no example usage
493       *          information is available.
494       */
495      @Override()
496      public LinkedHashMap<String[],String> getExampleUsages()
497      {
498        final LinkedHashMap<String[],String> exampleMap =
499             new LinkedHashMap<String[],String>(2);
500    
501        final String[] getArgs =
502        {
503          "--hostname", "server.example.com",
504          "--port", "389",
505          "--bindDN", "uid=admin,dc=example,dc=com",
506          "--bindPassword", "password",
507        };
508        exampleMap.put(getArgs,
509             "Retrieve information about all subtree accessibility restrictions " +
510                  "defined in the server.");
511    
512        final String[] setArgs =
513        {
514          "--hostname", "server.example.com",
515          "--port", "389",
516          "--bindDN", "uid=admin,dc=example,dc=com",
517          "--bindPassword", "password",
518          "--set",
519          "--baseDN", "ou=subtree,dc=example,dc=com",
520          "--state", "read-only-bind-allowed",
521          "--bypassUserDN", "uid=bypass,dc=example,dc=com"
522        };
523        exampleMap.put(setArgs,
524             "Create or update the subtree accessibility state definition for " +
525                  "subtree 'ou=subtree,dc=example,dc=com' so that it is " +
526                  "read-only for all users except 'uid=bypass,dc=example,dc=com'.");
527    
528        return exampleMap;
529      }
530    }